001: /******************************************************************
002: * File: XSDAbstractDateTimeType.java
003: * Created by: Dave Reynolds
004: * Created on: 04-Dec-2003
005: *
006: * (c) Copyright 2003, 2004, 2005, 2006, 2007, 2008 Hewlett-Packard Development Company, LP
007: * All rights reserved.
008: * [See end of file]
009: * $Id: XSDAbstractDateTimeType.java,v 1.10 2008/01/02 12:04:04 andy_seaborne Exp $
010: *****************************************************************/package com.hp.hpl.jena.datatypes.xsd.impl;
011:
012: import com.hp.hpl.jena.datatypes.RDFDatatype;
013: import com.hp.hpl.jena.datatypes.xsd.XSDDatatype;
014: import com.hp.hpl.jena.datatypes.xsd.XSDDateTime;
015: import com.hp.hpl.jena.graph.impl.LiteralLabel;
016:
017: /**
018: * Base class for all date/time/duration type representations.
019: * Includes support functions for parsing and comparing dates.
020: *
021: * @author <a href="mailto:der@hplb.hpl.hp.com">Dave Reynolds</a>
022: * @version $Revision: 1.10 $ on $Date: 2008/01/02 12:04:04 $
023: */
024: public class XSDAbstractDateTimeType extends XSDDatatype {
025:
026: /**
027: * Constructor
028: */
029: public XSDAbstractDateTimeType(String typename) {
030: super (typename);
031: }
032:
033: /**
034: * Compares two instances of values of the given datatype.
035: * This ignores lang tags and just uses the java.lang.Number
036: * equality.
037: */
038: public boolean isEqual(LiteralLabel value1, LiteralLabel value2) {
039: return value1.getValue().equals(value2.getValue());
040: }
041:
042: /** Mask to indicate whether year is present */
043: public static final short YEAR_MASK = 0x1;
044:
045: /** Mask to indicate whether month is present */
046: public static final short MONTH_MASK = 0x2;
047:
048: /** Mask to indicate whether day is present */
049: public static final short DAY_MASK = 0x4;
050:
051: /** Mask to indicate whether time is present */
052: public static final short TIME_MASK = 0x8;
053:
054: /** Mask to indicate all date/time are present */
055: public static final short FULL_MASK = 0xf;
056:
057: // --------------------------------------------------------------------
058: // This code is adapated from Xerces 2.6.0 AbstractDateTimeDV.
059: // Copyright (c) 1999-2003 The Apache Software Foundation. All rights
060: // reserved.
061: // --------------------------------------------------------------------
062:
063: //define constants
064: protected final static int CY = 0, M = 1, D = 2, h = 3, m = 4,
065: s = 5, ms = 6, msscale = 8, utc = 7, hh = 0, mm = 1;
066:
067: //size for all objects must have the same fields:
068: //CCYY, MM, DD, h, m, s, ms + timeZone
069: protected final static int TOTAL_SIZE = 9;
070:
071: //define constants to be used in assigning default values for
072: //all date/time excluding duration
073: protected final static int YEAR = 2000;
074: protected final static int MONTH = 01;
075: protected final static int DAY = 15;
076:
077: /**
078: * Parses time hh:mm:ss.sss and time zone if any
079: *
080: * @param start
081: * @param end
082: * @param data
083: * @exception RuntimeException
084: */
085: protected void getTime(String buffer, int start, int end,
086: int[] data, int[] timeZone) throws RuntimeException {
087:
088: int stop = start + 2;
089:
090: //get hours (hh)
091: data[h] = parseInt(buffer, start, stop);
092:
093: //get minutes (mm)
094:
095: if (buffer.charAt(stop++) != ':') {
096: throw new RuntimeException("Error in parsing time zone");
097: }
098: start = stop;
099: stop = stop + 2;
100: data[m] = parseInt(buffer, start, stop);
101:
102: //get seconds (ss)
103: if (buffer.charAt(stop++) != ':') {
104: throw new RuntimeException("Error in parsing time zone");
105: }
106: start = stop;
107: stop = stop + 2;
108: data[s] = parseInt(buffer, start, stop);
109:
110: if (stop == end)
111: return;
112:
113: //get miliseconds (ms)
114: start = stop;
115: int milisec = buffer.charAt(start) == '.' ? start : -1;
116:
117: //find UTC sign if any
118: int sign = findUTCSign(buffer, start, end);
119:
120: //parse miliseconds
121: if (milisec != -1) {
122: // The end of millisecond part is between . and
123: // either the end of the UTC sign
124: start = sign < 0 ? end : sign;
125: int msEnd = start;
126: while (buffer.charAt(msEnd - 1) == '0')
127: msEnd--;
128: data[ms] = parseInt(buffer, milisec + 1, msEnd);
129: data[msscale] = msEnd - milisec - 1;
130: }
131:
132: //parse UTC time zone (hh:mm)
133: if (sign > 0) {
134: if (start != sign)
135: throw new RuntimeException("Error in parsing time zone");
136: getTimeZone(buffer, data, sign, end, timeZone);
137: } else if (start != end) {
138: throw new RuntimeException("Error in parsing time zone");
139: }
140: }
141:
142: /**
143: * Parses date CCYY-MM-DD
144: *
145: * @param start
146: * @param end
147: * @param data
148: * @exception RuntimeException
149: */
150: protected int getDate(String buffer, int start, int end, int[] date)
151: throws RuntimeException {
152:
153: start = getYearMonth(buffer, start, end, date);
154:
155: if (buffer.charAt(start++) != '-') {
156: throw new RuntimeException(
157: "CCYY-MM must be followed by '-' sign");
158: }
159: int stop = start + 2;
160: date[D] = parseInt(buffer, start, stop);
161: return stop;
162: }
163:
164: /**
165: * Parses date CCYY-MM
166: *
167: * @param start
168: * @param end
169: * @param data
170: * @exception RuntimeException
171: */
172: protected int getYearMonth(String buffer, int start, int end,
173: int[] date) throws RuntimeException {
174:
175: if (buffer.charAt(0) == '-') {
176: // REVISIT: date starts with preceding '-' sign
177: // do we have to do anything with it?
178: //
179: start++;
180: }
181: int i = indexOf(buffer, start, end, '-');
182: if (i == -1)
183: throw new RuntimeException(
184: "Year separator is missing or misplaced");
185: int length = i - start;
186: if (length < 4) {
187: throw new RuntimeException("Year must have 'CCYY' format");
188: } else if (length > 4 && buffer.charAt(start) == '0') {
189: throw new RuntimeException(
190: "Leading zeros are required if the year value would otherwise have fewer than four digits; otherwise they are forbidden");
191: }
192: date[CY] = parseIntYear(buffer, i);
193: if (buffer.charAt(i) != '-') {
194: throw new RuntimeException(
195: "CCYY must be followed by '-' sign");
196: }
197: start = ++i;
198: i = start + 2;
199: date[M] = parseInt(buffer, start, i);
200: return i; //fStart points right after the MONTH
201: }
202:
203: /**
204: * Shared code from Date and YearMonth datatypes.
205: * Finds if time zone sign is present
206: *
207: * @param end
208: * @param date
209: * @exception RuntimeException
210: */
211: protected void parseTimeZone(String buffer, int start, int end,
212: int[] date, int[] timeZone) throws RuntimeException {
213:
214: //fStart points right after the date
215:
216: if (start < end) {
217: int sign = findUTCSign(buffer, start, end);
218: if (sign < 0) {
219: throw new RuntimeException("Error in month parsing");
220: } else {
221: getTimeZone(buffer, date, sign, end, timeZone);
222: }
223: }
224: }
225:
226: /**
227: * Parses time zone: 'Z' or {+,-} followed by hh:mm
228: *
229: * @param data
230: * @param sign
231: * @exception RuntimeException
232: */
233: protected void getTimeZone(String buffer, int[] data, int sign,
234: int end, int[] timeZone) throws RuntimeException {
235: data[utc] = buffer.charAt(sign);
236:
237: if (buffer.charAt(sign) == 'Z') {
238: if (end > (++sign)) {
239: throw new RuntimeException("Error in parsing time zone");
240: }
241: return;
242: }
243: if (sign <= (end - 6)) {
244:
245: //parse [hh]
246: int stop = ++sign + 2;
247: timeZone[hh] = parseInt(buffer, sign, stop);
248: if (buffer.charAt(stop++) != ':') {
249: throw new RuntimeException("Error in parsing time zone");
250: }
251:
252: //parse [ss]
253: timeZone[mm] = parseInt(buffer, stop, stop + 2);
254:
255: if (stop + 2 != end) {
256: throw new RuntimeException("Error in parsing time zone");
257: }
258:
259: } else {
260: throw new RuntimeException("Error in parsing time zone");
261: }
262: }
263:
264: /**
265: * Computes index of given char within StringBuffer
266: *
267: * @param start
268: * @param end
269: * @param ch character to look for in StringBuffer
270: * @return index of ch within StringBuffer
271: */
272: protected int indexOf(String buffer, int start, int end, char ch) {
273: for (int i = start; i < end; i++) {
274: if (buffer.charAt(i) == ch) {
275: return i;
276: }
277: }
278: return -1;
279: }
280:
281: // check whether the character is in the range 0x30 ~ 0x39
282: public static final boolean isDigit(char ch) {
283: return ch >= '0' && ch <= '9';
284: }
285:
286: // if the character is in the range 0x30 ~ 0x39, return its int value (0~9),
287: // otherwise, return -1
288: public static final int getDigit(char ch) {
289: return isDigit(ch) ? ch - '0' : -1;
290: }
291:
292: /**
293: * Return index of UTC char: 'Z', '+', '-'
294: *
295: * @param start
296: * @param end
297: * @return index of the UTC character that was found
298: */
299: protected int findUTCSign(String buffer, int start, int end) {
300: int c;
301: for (int i = start; i < end; i++) {
302: c = buffer.charAt(i);
303: if (c == 'Z' || c == '+' || c == '-') {
304: return i;
305: }
306:
307: }
308: return -1;
309: }
310:
311: /**
312: * Given start and end position, parses string value
313: *
314: * @param value string to parse
315: * @param start Start position
316: * @param end end position
317: * @return return integer representation of characters
318: */
319: protected int parseInt(String buffer, int start, int end)
320: throws NumberFormatException {
321: //REVISIT: more testing on this parsing needs to be done.
322: int radix = 10;
323: int result = 0;
324: int digit = 0;
325: int limit = -Integer.MAX_VALUE;
326: int multmin = limit / radix;
327: int i = start;
328: do {
329: digit = getDigit(buffer.charAt(i));
330: if (digit < 0)
331: throw new NumberFormatException("'" + buffer.toString()
332: + "' has wrong format");
333: if (result < multmin)
334: throw new NumberFormatException("'" + buffer.toString()
335: + "' has wrong format");
336: result *= radix;
337: if (result < limit + digit)
338: throw new NumberFormatException("'" + buffer.toString()
339: + "' has wrong format");
340: result -= digit;
341:
342: } while (++i < end);
343: return -result;
344: }
345:
346: // parse Year differently to support negative value.
347: protected int parseIntYear(String buffer, int end) {
348: int radix = 10;
349: int result = 0;
350: boolean negative = false;
351: int i = 0;
352: int limit;
353: int multmin;
354: int digit = 0;
355:
356: if (buffer.charAt(0) == '-') {
357: negative = true;
358: limit = Integer.MIN_VALUE;
359: i++;
360:
361: } else {
362: limit = -Integer.MAX_VALUE;
363: }
364: multmin = limit / radix;
365: while (i < end) {
366: digit = getDigit(buffer.charAt(i++));
367: if (digit < 0)
368: throw new NumberFormatException("'" + buffer.toString()
369: + "' has wrong format");
370: if (result < multmin)
371: throw new NumberFormatException("'" + buffer.toString()
372: + "' has wrong format");
373: result *= radix;
374: if (result < limit + digit)
375: throw new NumberFormatException("'" + buffer.toString()
376: + "' has wrong format");
377: result -= digit;
378: }
379:
380: if (negative) {
381: if (i > 1)
382: return result;
383: else
384: throw new NumberFormatException("'" + buffer.toString()
385: + "' has wrong format");
386: }
387: return -result;
388:
389: }
390:
391: public String dateToString(int[] date) {
392: StringBuffer message = new StringBuffer(25);
393: append(message, date[CY], 4);
394: message.append('-');
395: append(message, date[M], 2);
396: message.append('-');
397: append(message, date[D], 2);
398: message.append('T');
399: append(message, date[h], 2);
400: message.append(':');
401: append(message, date[m], 2);
402: message.append(':');
403: append(message, date[s], 2);
404: message.append('.');
405: appendFractionalTime(message, date[ms], date[msscale]);
406: append(message, (char) date[utc], 0);
407: return message.toString();
408: }
409:
410: /** Append the fraction time part of a date/time vector to
411: * a string buffer.
412: */
413: public static void appendFractionalTime(StringBuffer buff,
414: int fsec, int scale) {
415: String msString = Integer.toString(fsec);
416: int pad = scale - msString.length();
417: while (pad > 0) {
418: buff.append('0');
419: pad--;
420: }
421: int trunc = msString.length();
422: while (trunc > 0 && msString.charAt(trunc - 1) == '0')
423: trunc--;
424: buff.append(msString.substring(0, trunc));
425: }
426:
427: protected void append(StringBuffer message, int value, int nch) {
428: if (value < 0) {
429: message.append('-');
430: value = -value;
431: }
432: if (nch == 4) {
433: if (value < 10)
434: message.append("000");
435: else if (value < 100)
436: message.append("00");
437: else if (value < 1000)
438: message.append("0");
439: message.append(value);
440: } else if (nch == 2) {
441: if (value < 10)
442: message.append('0');
443: message.append(value);
444: } else {
445: if (value != 0)
446: message.append((char) value);
447: }
448: }
449:
450: // --------------------------------------------------------------------
451: // End of code is adapated from Xerces 2.6.0 AbstractDateTimeDV.
452: // --------------------------------------------------------------------
453:
454: /**
455: * Normalization. If the value is narrower than the current data type
456: * (e.g. value is xsd:date but the time is xsd:datetime) returns
457: * the narrower type for the literal.
458: * If the type is narrower than the value then it may normalize
459: * the value (e.g. set the mask of an XSDDateTime)
460: * Currently only used to narrow gener XSDDateTime objects
461: * to the minimal XSD date/time type.
462: * @param value the current object value
463: * @param dt the currently set data type
464: * @return a narrower version of the datatype based on the actual value range
465: */
466: public RDFDatatype normalizeSubType(Object value, RDFDatatype dt) {
467: if (value instanceof XSDDateTime) {
468: if (dt.equals(XSDDatatype.XSDdateTime)) {
469: return ((XSDDateTime) value).getNarrowedDatatype();
470: } else if (dt instanceof XSDDatatype) {
471: // We've externally narrowed the type, push this down to the date time
472: ((XSDDateTime) value).narrowType((XSDDatatype) dt);
473: }
474: }
475: return this ;
476: }
477:
478: }
479:
480: /*
481: (c) Copyright 2003, 2004, 2005, 2006, 2007, 2008 Hewlett-Packard Development Company, LP
482: All rights reserved.
483:
484: Redistribution and use in source and binary forms, with or without
485: modification, are permitted provided that the following conditions
486: are met:
487:
488: 1. Redistributions of source code must retain the above copyright
489: notice, this list of conditions and the following disclaimer.
490:
491: 2. Redistributions in binary form must reproduce the above copyright
492: notice, this list of conditions and the following disclaimer in the
493: documentation and/or other materials provided with the distribution.
494:
495: 3. The name of the author may not be used to endorse or promote products
496: derived from this software without specific prior written permission.
497:
498: THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
499: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
500: OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
501: IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
502: INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
503: NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
504: DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
505: THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
506: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
507: THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
508: */
|