/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jena.datatypes.xsd.impl; import org.apache.jena.datatypes.RDFDatatype ; import org.apache.jena.datatypes.xsd.XSDDatatype ; import org.apache.jena.datatypes.xsd.XSDDateTime ; import org.apache.jena.graph.impl.LiteralLabel ; /** * Base class for all date/time/duration type representations. * Includes support functions for parsing and comparing dates. */ public class XSDAbstractDateTimeType extends XSDDatatype { /** * Constructor */ public XSDAbstractDateTimeType(String typename) { super(typename); } /** * Compares two instances of values of the given datatype. * This ignores lang tags and just uses the java.lang.Number * equality. */ @Override public boolean isEqual(LiteralLabel value1, LiteralLabel value2) { return value1.getValue().equals(value2.getValue()); } /** Mask to indicate whether year is present */ public static final short YEAR_MASK = 0x1; /** Mask to indicate whether month is present */ public static final short MONTH_MASK = 0x2; /** Mask to indicate whether day is present */ public static final short DAY_MASK = 0x4; /** Mask to indicate whether time is present */ public static final short TIME_MASK = 0x8; /** Mask to indicate all date/time are present */ public static final short FULL_MASK = 0xf; // -------------------------------------------------------------------- // This code is adapated from Xerces 2.6.0 AbstractDateTimeDV. // Copyright (c) 1999-2003 The Apache Software Foundation. All rights // reserved. // -------------------------------------------------------------------- //define constants protected final static int CY = 0, M = 1, D = 2, h = 3, m = 4, s = 5, ms = 6, msscale=8, utc=7, hh=0, mm=1; //size for all objects must have the same fields: //CCYY, MM, DD, h, m, s, ms + timeZone protected final static int TOTAL_SIZE = 9; //define constants to be used in assigning default values for //all date/time excluding duration protected final static int YEAR=2000; protected final static int MONTH=01; protected final static int DAY = 15; /** * Parses time hh:mm:ss.sss and time zone if any * * @param start * @param end * @param data * @exception RuntimeException */ protected void getTime (String buffer, int start, int end, int[] data, int[] timeZone) throws RuntimeException{ int stop = start+2; //get hours (hh) data[h]=parseInt(buffer, start,stop); //get minutes (mm) if (buffer.charAt(stop++)!=':') { throw new RuntimeException("Error in parsing time zone" ); } start = stop; stop = stop+2; data[m]=parseInt(buffer, start,stop); //get seconds (ss) if (buffer.charAt(stop++)!=':') { throw new RuntimeException("Error in parsing time zone" ); } start = stop; stop = stop+2; data[s]=parseInt(buffer, start,stop); if (stop == end) return; //get miliseconds (ms) start = stop; int millisec = buffer.charAt(start) == '.' ? start : -1; //find UTC sign if any int sign = findUTCSign(buffer, start, end); //parse miliseconds if ( millisec != -1 ) { // The end of millisecond part is between . and // either the end of the UTC sign start = sign < 0 ? end : sign; int msEnd = start; while (buffer.charAt(msEnd-1) == '0') msEnd--; data[ms]=parseInt(buffer, millisec+1, msEnd); data[msscale] = msEnd - millisec - 1; } //parse UTC time zone (hh:mm) if ( sign>0 ) { if (start != sign) throw new RuntimeException("Error in parsing time zone" ); getTimeZone(buffer, data, sign, end, timeZone); } else if (start != end) { throw new RuntimeException("Error in parsing time zone" ); } } /** * Parses date CCYY-MM-DD * * @param start * @param end * @param data * @exception RuntimeException */ protected int getDate (String buffer, int start, int end, int[] date) throws RuntimeException{ start = getYearMonth(buffer, start, end, date); if (buffer.charAt(start++) !='-') { throw new RuntimeException("CCYY-MM must be followed by '-' sign"); } int stop = start + 2; date[D]=parseInt(buffer, start, stop); return stop; } /** * Parses date CCYY-MM * * @param start * @param end * @param data * @exception RuntimeException */ protected int getYearMonth (String buffer, int start, int end, int[] date) throws RuntimeException{ if ( buffer.charAt(0)=='-' ) { // REVISIT: date starts with preceding '-' sign // do we have to do anything with it? // start++; } int i = indexOf(buffer, start, end, '-'); if ( i==-1 ) throw new RuntimeException("Year separator is missing or misplaced"); int length = i-start; if (length<4) { throw new RuntimeException("Year must have 'CCYY' format"); } else if (length > 4 && buffer.charAt(start)=='0'){ throw new RuntimeException("Leading zeros are required if the year value would otherwise have fewer than four digits; otherwise they are forbidden"); } date[CY]= parseIntYear(buffer, i); if (buffer.charAt(i)!='-') { throw new RuntimeException("CCYY must be followed by '-' sign"); } start = ++i; i = start +2; date[M]=parseInt(buffer, start, i); return i; //fStart points right after the MONTH } /** * Shared code from Date and YearMonth datatypes. * Finds if time zone sign is present * * @param end * @param date * @exception RuntimeException */ protected void parseTimeZone (String buffer, int start, int end, int[] date, int[] timeZone) throws RuntimeException{ //fStart points right after the date if ( start<end ) { int sign = findUTCSign(buffer, start, end); if ( sign<0 ) { throw new RuntimeException ("Error in month parsing"); } else { getTimeZone(buffer, date, sign, end, timeZone); } } } /** * Parses time zone: 'Z' or {+,-} followed by hh:mm * * @param data * @param sign * @exception RuntimeException */ protected void getTimeZone (String buffer, int[] data, int sign, int end, int[] timeZone) throws RuntimeException{ data[utc]=buffer.charAt(sign); if ( buffer.charAt(sign) == 'Z' ) { if (end>(++sign)) { throw new RuntimeException("Error in parsing time zone"); } return; } if ( sign<=(end-6) ) { //parse [hh] int stop = ++sign+2; timeZone[hh]=parseInt(buffer, sign, stop); if (buffer.charAt(stop++)!=':') { throw new RuntimeException("Error in parsing time zone" ); } //parse [ss] timeZone[mm]=parseInt(buffer, stop, stop+2); if ( stop+2!=end ) { throw new RuntimeException("Error in parsing time zone"); } } else { throw new RuntimeException("Error in parsing time zone"); } } /** * Computes index of given char within StringBuffer * * @param start * @param end * @param ch character to look for in StringBuffer * @return index of ch within StringBuffer */ protected int indexOf (String buffer, int start, int end, char ch) { for ( int i=start;i<end;i++ ) { if ( buffer.charAt(i) == ch ) { return i; } } return -1; } // check whether the character is in the range 0x30 ~ 0x39 public static final boolean isDigit(char ch) { return ch >= '0' && ch <= '9'; } // if the character is in the range 0x30 ~ 0x39, return its int value (0~9), // otherwise, return -1 public static final int getDigit(char ch) { return isDigit(ch) ? ch - '0' : -1; } /** * Return index of UTC char: 'Z', '+', '-' * * @param start * @param end * @return index of the UTC character that was found */ protected int findUTCSign (String buffer, int start, int end) { int c; for ( int i=start;i<end;i++ ) { c=buffer.charAt(i); if ( c == 'Z' || c=='+' || c=='-' ) { return i; } } return -1; } /** * Given start and end position, parses string value * * @param value string to parse * @param start Start position * @param end end position * @return return integer representation of characters */ protected int parseInt (String buffer, int start, int end) throws NumberFormatException{ //REVISIT: more testing on this parsing needs to be done. int radix=10; int result = 0; int digit=0; int limit = -Integer.MAX_VALUE; int multmin = limit / radix; int i = start; do { digit = getDigit(buffer.charAt(i)); if ( digit < 0 ) throw new NumberFormatException("'"+buffer.toString()+"' has wrong format"); if ( result < multmin ) throw new NumberFormatException("'"+buffer.toString()+"' has wrong format"); result *= radix; if ( result < limit + digit ) throw new NumberFormatException("'"+buffer.toString()+"' has wrong format"); result -= digit; }while ( ++i < end ); return -result; } // parse Year differently to support negative value. protected int parseIntYear (String buffer, int end){ int radix=10; int result = 0; boolean negative = false; int i=0; int limit; int multmin; int digit=0; if (buffer.charAt(0) == '-'){ negative = true; limit = Integer.MIN_VALUE; i++; } else{ limit = -Integer.MAX_VALUE; } multmin = limit / radix; while (i < end) { digit = getDigit(buffer.charAt(i++)); if (digit < 0) throw new NumberFormatException("'"+buffer.toString()+"' has wrong format"); if (result < multmin) throw new NumberFormatException("'"+buffer.toString()+"' has wrong format"); result *= radix; if (result < limit + digit) throw new NumberFormatException("'"+buffer.toString()+"' has wrong format"); result -= digit; } if (negative) { if (i > 1) return result; else throw new NumberFormatException("'"+buffer.toString()+"' has wrong format"); } return -result; } public String dateToString(int[] date) { StringBuffer message = new StringBuffer(25); append(message, date[CY], 4); message.append('-'); append(message, date[M], 2); message.append('-'); append(message, date[D], 2); message.append('T'); append(message, date[h], 2); message.append(':'); append(message, date[m], 2); message.append(':'); append(message, date[s], 2); message.append('.'); appendFractionalTime(message, date[ms], date[msscale]); append(message, (char)date[utc], 0); return message.toString(); } /** Append the fraction time part of a date/time vector to * a string buffer. */ public static void appendFractionalTime(StringBuffer buff, int fsec, int scale) { String msString = Integer.toString(fsec); int pad = scale - msString.length(); while (pad > 0) { buff.append('0'); pad--; } int trunc = msString.length(); while (trunc > 0 && msString.charAt(trunc-1) == '0') trunc --; buff.append(msString.substring(0, trunc)); } protected void append(StringBuffer message, int value, int nch) { if (value < 0) { message.append('-'); value = -value; } if (nch == 4) { if (value < 10) message.append("000"); else if (value < 100) message.append("00"); else if (value < 1000) message.append("0"); message.append(value); } else if (nch == 2) { if (value < 10) message.append('0'); message.append(value); } else { if (value != 0) message.append((char)value); } } // -------------------------------------------------------------------- // End of code is adapated from Xerces 2.6.0 AbstractDateTimeDV. // -------------------------------------------------------------------- /** * Normalization. If the value is narrower than the current data type * (e.g. value is xsd:date but the time is xsd:datetime) returns * the narrower type for the literal. * If the type is narrower than the value then it may normalize * the value (e.g. set the mask of an XSDDateTime) * Currently only used to narrow gener XSDDateTime objects * to the minimal XSD date/time type. * @param value the current object value * @param dt the currently set data type * @return a narrower version of the datatype based on the actual value range */ @Override public RDFDatatype normalizeSubType(Object value, RDFDatatype dt) { if (value instanceof XSDDateTime) { if (dt.equals(XSDDatatype.XSDdateTime)) { return ((XSDDateTime)value).getNarrowedDatatype(); } else if (dt instanceof XSDDatatype){ // We've externally narrowed the type, push this down to the date time ((XSDDateTime)value).narrowType((XSDDatatype)dt); } } return this; } }