KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sf > saxon > value > DateValue


1 package net.sf.saxon.value;
2 import net.sf.saxon.Err;
3 import net.sf.saxon.ConversionContext;
4 import net.sf.saxon.expr.XPathContext;
5 import net.sf.saxon.functions.Component;
6 import net.sf.saxon.om.FastStringBuffer;
7 import net.sf.saxon.trans.DynamicError;
8 import net.sf.saxon.trans.XPathException;
9 import net.sf.saxon.type.BuiltInAtomicType;
10 import net.sf.saxon.type.ItemType;
11 import net.sf.saxon.type.Type;
12 import net.sf.saxon.type.ValidationException;
13
14 import java.util.*;
15
16 /**
17 * A value of type Date. Note that a Date may include a TimeZone, and unlike the situation
18 * with dateTime, the timezone is part of the value space.
19 */

20
21 public class DateValue extends CalendarValue {
22
23     protected int tzOffset = 0; // maintained in minutes
24

25
26     // UTCDate is held as a redundant representation of the information
27
private Date UTCDate = null; // Always read this using getUTCDate
28

29     /**
30      * Default constructor needed for subtyping
31      */

32
33     protected DateValue() {}
34
35     /**
36     * Constructor: create a dateTime value from a supplied string, in
37     * ISO 8601 format
38     */

39     public DateValue(CharSequence JavaDoc s) throws XPathException {
40         setLexicalValue(s);
41     }
42
43     /**
44      * Create a DateValue
45      * @param calendar the absolute date/time value
46      * @param timeZoneSpecified true if there is a timezone
47      * @param timeZoneOffset the timezone offset from UTC in minutes
48      */

49     public DateValue(GregorianCalendar calendar, boolean timeZoneSpecified, int timeZoneOffset) {
50         this.calendar = calendar;
51         this.zoneSpecified = timeZoneSpecified;
52         this.tzOffset = timeZoneOffset;
53     }
54
55     /**
56      * Initialize the DateValue using a character string in the format yyyy-mm-dd and an optional time zone.
57      * Input must have format [+|-]yyyy-mm-dd[([+|-]hh:mm | Z)]
58      * @param s the supplied string value
59      * @throws XPathException
60      */

61     public void setLexicalValue(CharSequence JavaDoc s) throws XPathException {
62         zoneSpecified = false;
63         StringTokenizer tok = new StringTokenizer(trimWhitespace(s).toString(), "-:+Z", true);
64         try {
65             if (!tok.hasMoreElements()) badDate("Too short", s);
66             String JavaDoc part = (String JavaDoc)tok.nextElement();
67             int era = +1;
68             if ("+".equals(part)) {
69                 part = (String JavaDoc)tok.nextElement();
70             } else if ("-".equals(part)) {
71                 era = -1;
72                 part = (String JavaDoc)tok.nextElement();
73             }
74             int year = Integer.parseInt(part) * era;
75             if (part.length() < 4) badDate("Year is less than four digits", s);
76             if (year==0) badDate("Year zero is not allowed", s);
77             if (!tok.hasMoreElements()) badDate("Too short", s);
78             if (!"-".equals(tok.nextElement())) badDate("Wrong delimiter after year", s);
79
80             if (!tok.hasMoreElements()) badDate("Too short", s);
81             part = (String JavaDoc)tok.nextElement();
82             int month = Integer.parseInt(part);
83             if (part.length() != 2) badDate("Month must be two digits", s);
84             if (month < 1 || month > 12) badDate("Month is out of range", s);
85             if (!tok.hasMoreElements()) badDate("Too short", s);
86             if (!"-".equals(tok.nextElement())) badDate("Wrong delimiter after month", s);
87
88             if (!tok.hasMoreElements()) badDate("Too short", s);
89             part = (String JavaDoc)tok.nextElement();
90             int day = Integer.parseInt(part);
91             if (part.length() != 2) badDate("Day must be two digits", s);
92             if (day < 1 || day > 31) badDate("Day is out of range", s);
93
94             if (tok.hasMoreElements()) {
95
96                 String JavaDoc delim = (String JavaDoc)tok.nextElement();
97
98                 if ("Z".equals(delim)) {
99                     zoneSpecified = true;
100                     tzOffset = 0;
101                     if (tok.hasMoreElements()) badDate("Continues after 'Z'", s);
102                 } else if (!(!"+".equals(delim) && !"-".equals(delim))) {
103                     zoneSpecified = true;
104                     if (!tok.hasMoreElements()) badDate("Missing timezone", s);
105                     part = (String JavaDoc)tok.nextElement();
106                     int tzhour = Integer.parseInt(part);
107                     if (part.length() != 2) badDate("Timezone hour must be two digits", s);
108                     if (tzhour > 14) badDate("Timezone hour is out of range", s);
109                     if (tzhour > 12) badDate("Because of Java limitations, Saxon currently limits the timezone to +/- 12 hours", s);
110                     if (!tok.hasMoreElements()) badDate("No minutes in timezone", s);
111                     if (!":".equals(tok.nextElement())) badDate("Wrong delimiter after timezone hour", s);
112
113                     if (!tok.hasMoreElements()) badDate("No minutes in timezone", s);
114                     part = (String JavaDoc)tok.nextElement();
115                     int tzminute = Integer.parseInt(part);
116                     if (part.length() != 2) badDate("Timezone minute must be two digits", s);
117                     if (tzminute > 59) badDate("Timezone minute is out of range", s);
118                     if (tok.hasMoreElements()) badDate("Continues after timezone", s);
119
120                     tzOffset = (tzhour*60 + tzminute);
121                     if ("-".equals(delim)) tzOffset = -tzOffset;
122                 } else {
123                     badDate("Timezone format is incorrect", s);
124                 }
125             }
126
127             TimeZone zone = new SimpleTimeZone(tzOffset*60000, "LLL");
128             calendar = new GregorianCalendar(zone);
129             calendar.clear();
130             calendar.setLenient(false);
131             calendar.set(Math.abs(year), month-1, day);
132             calendar.set(Calendar.ZONE_OFFSET, tzOffset*60000);
133             calendar.set(Calendar.DST_OFFSET, 0);
134             if (year < 0) {
135                 calendar.set(Calendar.ERA, GregorianCalendar.BC);
136             }
137             try {
138                 calendar.getTime();
139             } catch (IllegalArgumentException JavaDoc err) {
140                 badDate("Non-existent date", s);
141             }
142
143
144         } catch (NumberFormatException JavaDoc err) {
145             badDate("Non-numeric component", s);
146         }
147     }
148
149     private void badDate(String JavaDoc msg, CharSequence JavaDoc value) throws ValidationException {
150         ValidationException err = new ValidationException("Invalid date " + Err.wrap(value, Err.VALUE) + ". " + msg);
151         err.setErrorCode("FORG0001");
152         throw err;
153     }
154
155     /**
156     * Get the UTC date/time corresponding to this dateTime. This normalizes
157     * the value to incorporate the timezone information, for example
158     * 2002-01-01T07:00:00-05:00 gives the same answer as 2002-01-01T12:00:00Z
159     */

160
161     public Date getUTCDate() {
162         // implement this as a memo function
163
if (UTCDate==null) {
164             UTCDate = calendar.getTime();
165         }
166         return UTCDate;
167     }
168
169     /**
170     * Convert to target data type
171     * @param requiredType an integer identifying the required atomic type
172     * @param conversion
173      * @return an AtomicValue, a value of the required type; or an ErrorValue
174     */

175
176     public AtomicValue convertPrimitive(BuiltInAtomicType requiredType, boolean validate, ConversionContext conversion) {
177         switch(requiredType.getPrimitiveType()) {
178         case Type.DATE:
179         case Type.ATOMIC:
180         case Type.ITEM:
181             return this;
182         case Type.DATE_TIME:
183             return new DateTimeValue(calendar, zoneSpecified);
184
185         case Type.STRING:
186             return new StringValue(getStringValueCS());
187
188         case Type.UNTYPED_ATOMIC:
189             return new UntypedAtomicValue(getStringValueCS());
190
191         case Type.G_YEAR: {
192             GregorianCalendar cal2 = new GregorianCalendar(calendar.getTimeZone());
193             cal2.clear();
194             cal2.set(Calendar.ERA, calendar.get(Calendar.ERA));
195             cal2.set(calendar.get(Calendar.YEAR), 0, 1);
196             cal2.set(Calendar.ZONE_OFFSET, tzOffset*60000);
197             cal2.set(Calendar.DST_OFFSET, 0);
198             calendar.getTime();
199             return new GYearValue(cal2, zoneSpecified, tzOffset);
200         }
201         case Type.G_YEAR_MONTH: {
202             GregorianCalendar cal2 = new GregorianCalendar(calendar.getTimeZone());
203             cal2.clear();
204             cal2.set(Calendar.ERA, calendar.get(Calendar.ERA));
205             cal2.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), 1);
206             cal2.set(Calendar.ZONE_OFFSET, tzOffset*60000);
207             cal2.set(Calendar.DST_OFFSET, 0);
208             calendar.getTime();
209             return new GYearMonthValue(cal2, zoneSpecified, tzOffset);
210         }
211         case Type.G_MONTH: {
212             GregorianCalendar cal2 = new GregorianCalendar(calendar.getTimeZone());
213             cal2.clear();
214             cal2.set(2000, calendar.get(Calendar.MONTH), 1);
215             cal2.set(Calendar.ZONE_OFFSET, tzOffset*60000);
216             cal2.set(Calendar.DST_OFFSET, 0);
217             calendar.getTime();
218             return new GMonthValue(cal2, zoneSpecified, tzOffset);
219         }
220         case Type.G_MONTH_DAY: {
221             GregorianCalendar cal2 = new GregorianCalendar(calendar.getTimeZone());
222             cal2.clear();
223             cal2.set(2000, calendar.get(Calendar.MONTH), calendar.get(Calendar.DATE));
224             cal2.set(Calendar.ZONE_OFFSET, tzOffset*60000);
225             cal2.set(Calendar.DST_OFFSET, 0);
226             calendar.getTime();
227             return new GMonthDayValue(cal2, zoneSpecified, tzOffset);
228         }
229         case Type.G_DAY:{
230             GregorianCalendar cal2 = new GregorianCalendar(calendar.getTimeZone());
231             cal2.clear();
232             cal2.set(2000, 0, calendar.get(Calendar.DATE));
233             cal2.set(Calendar.ZONE_OFFSET, tzOffset*60000);
234             cal2.set(Calendar.DST_OFFSET, 0);
235             calendar.getTime();
236             return new GDayValue(cal2, zoneSpecified, tzOffset);
237         }
238
239         default:
240             ValidationException err = new ValidationException("Cannot convert date to " +
241                                      requiredType.getDisplayName());
242             //err.setXPathContext(context);
243
err.setErrorCode("FORG0001");
244             return new ValidationErrorValue(err);
245         }
246     }
247
248     /**
249     * Convert to string
250     * @return ISO 8601 representation.
251     */

252
253     public String JavaDoc getStringValue() {
254
255         FastStringBuffer sb = new FastStringBuffer(16);
256         int era = calendar.get(GregorianCalendar.ERA);
257         int year = calendar.get(Calendar.YEAR);
258         if (era == GregorianCalendar.BC) {
259             sb.append('-');
260         }
261         DateTimeValue.appendString(sb, year, (year>9999 ? (calendar.get(Calendar.YEAR)+"").length() : 4));
262         sb.append('-');
263         DateTimeValue.appendString(sb, calendar.get(Calendar.MONTH)+1, 2);
264         sb.append('-');
265         DateTimeValue.appendString(sb, calendar.get(Calendar.DATE), 2);
266
267         if (zoneSpecified) {
268             DateTimeValue.appendTimezone(tzOffset, sb);
269         }
270
271         return sb.toString();
272
273     }
274
275     /**
276     * Determine the data type of the expression
277     * @return Type.DATE,
278     */

279
280     public ItemType getItemType() {
281         return Type.DATE_TYPE;
282     }
283
284     /**
285      * Return a dateTime with the same localized value, but
286      * without the timezone component
287      * @return the result of removing the timezone
288      */

289
290     public CalendarValue removeTimezone() throws XPathException {
291         return (CalendarValue)
292                     ((DateTimeValue)convert(Type.DATE_TIME, null))
293                         .removeTimezone()
294                         .convert(Type.DATE, null);
295     }
296
297     /**
298      * Return a date, time, or dateTime with the same normalized value, but
299      * in a different timezone
300      * @return the date/time in the new timezone
301      * @throws XPathException
302      */

303
304     public CalendarValue setTimezone(SecondsDurationValue tz) throws XPathException {
305        return (CalendarValue)
306                     ((DateTimeValue)convert(Type.DATE_TIME, null))
307                         .setTimezone(tz)
308                         .convert(Type.DATE, null);
309     }
310
311     /**
312     * Convert to Java object (for passing to external functions)
313     */

314
315     public Object JavaDoc convertToJava(Class JavaDoc target, XPathContext context) throws XPathException {
316         if (target.isAssignableFrom(Date.class)) {
317             return getUTCDate();
318         } else if (target.isAssignableFrom(DateTimeValue.class)) {
319             return this;
320         } else if (target==String JavaDoc.class || target==CharSequence JavaDoc.class) {
321             return getStringValue();
322         } else if (target==Object JavaDoc.class) {
323             return getStringValue();
324         } else {
325             Object JavaDoc o = super.convertToJava(target, context);
326             if (o == null) {
327                 throw new DynamicError("Conversion of date to " + target.getName() +
328                         " is not supported");
329             }
330             return o;
331         }
332     }
333
334     /**
335     * Get a component of the value. Returns null if the timezone component is
336     * requested and is not present.
337     */

338
339     public AtomicValue getComponent(int component) throws XPathException {
340         switch (component) {
341         case Component.YEAR:
342             return new IntegerValue(calendar.get(Calendar.YEAR));
343         case Component.MONTH:
344             return new IntegerValue(calendar.get(Calendar.MONTH) + 1);
345         case Component.DAY:
346             return new IntegerValue(calendar.get(Calendar.DATE));
347         case Component.TIMEZONE:
348             if (zoneSpecified) {
349                 return SecondsDurationValue.fromMilliseconds(tzOffset*60000);
350             } else {
351                 return null;
352             }
353         default:
354             throw new IllegalArgumentException JavaDoc("Unknown component for date: " + component);
355         }
356     }
357
358     /**
359     * Compare the value to another date value
360     * @param other The other date value. Must be an object of class DateValue.
361     * @return negative value if this one is the earlier, 0 if they are chronologically equal,
362     * positive value if this one is the later. For this purpose, dateTime values with an unknown
363     * timezone are considered to be UTC values (the Comparable interface requires
364     * a total ordering).
365     * @throws ClassCastException if the other value is not a DateValue (the parameter
366     * is declared as Object to satisfy the Comparable interface)
367     */

368
369     public int compareTo(Object JavaDoc other) {
370         if (!(other instanceof DateValue)) {
371             throw new ClassCastException JavaDoc("Date values are not comparable to " + other.getClass());
372         }
373         int primaryDiff = getUTCDate().compareTo(((DateValue)other).getUTCDate());
374         if (primaryDiff==0) {
375             // 1 Jan 2002 in New York is later than 1 Jan 2002 in London...
376
return ((DateValue)other).tzOffset - tzOffset;
377         } else {
378             return primaryDiff;
379         }
380
381     }
382
383     /**
384      * Compare this value to another value of the same type, using the supplied ConversionContext
385      * to get the implicit timezone if required.
386      */

387
388     public int compareTo(CalendarValue other, ConversionContext conversion) {
389         // TODO: the role of implicit timezone in date comparisons is unclear
390
return compareTo(other);
391     }
392
393     public boolean equals(Object JavaDoc other) {
394         return compareTo(other) == 0;
395     }
396
397     public int hashCode() {
398         // Equality must imply same hashcode, but not vice-versa
399
return getUTCDate().hashCode() + new Integer JavaDoc(tzOffset).hashCode();
400     }
401
402     /**
403      * Add a duration to a date
404      * @param duration the duration to be added (may be negative)
405      * @return the new date
406      * @throws XPathException if the duration is an xs:duration, as distinct from
407      * a subclass thereof
408      */

409
410     public CalendarValue add(DurationValue duration) throws XPathException {
411         if (duration instanceof SecondsDurationValue) {
412             double seconds = duration.getLengthInSeconds();
413             int days = (int)Math.floor(seconds / (60*60*24));
414             GregorianCalendar cal2 = (GregorianCalendar)calendar.clone();
415             cal2.add(Calendar.DATE, days);
416             return new DateValue(cal2, zoneSpecified, tzOffset);
417         } else if (duration instanceof MonthDurationValue) {
418             int months = ((MonthDurationValue)duration).getLengthInMonths();
419             GregorianCalendar cal2 = (GregorianCalendar)calendar.clone();
420             cal2.add(Calendar.MONTH, months);
421             return new DateValue(cal2, zoneSpecified, tzOffset);
422         } else {
423             DynamicError err = new DynamicError(
424                     "Date arithmetic is not supported on xs:duration, only on its subtypes");
425             err.setIsTypeError(true);
426             throw err;
427         }
428     }
429
430   /**
431      * Determine the difference between two points in time, as a duration
432      * @param other the other point in time
433      * @param context
434      * @return the duration as an xdt:dayTimeDuration
435      * @throws XPathException for example if one value is a date and the other is a time
436      */

437
438     public SecondsDurationValue subtract(CalendarValue other, ConversionContext context) throws XPathException {
439         if (!(other instanceof DateValue)) {
440             DynamicError err = new DynamicError(
441                     "First operand of '-' is a date, but the second is not");
442             err.setIsTypeError(true);
443             throw err;
444         }
445         return super.subtract(other, context);
446     }
447
448  }
449
450 //
451
// The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
452
// you may not use this file except in compliance with the License. You may obtain a copy of the
453
// License at http://www.mozilla.org/MPL/
454
//
455
// Software distributed under the License is distributed on an "AS IS" basis,
456
// WITHOUT WARRANTY OF ANY KIND, either express or implied.
457
// See the License for the specific language governing rights and limitations under the License.
458
//
459
// The Original Code is: all this file.
460
//
461
// The Initial Developer of the Original Code is Michael H. Kay
462
//
463
// Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
464
//
465
// Contributor(s): none.
466
//
467

468
Popular Tags