KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > gnu > math > DateTime


1 // Copyright (c) 2006 Per M.A. Bothner.
2
// This is free software; for terms and warranty disclaimer see ../../COPYING.
3

4 package gnu.math;
5 import java.util.Date JavaDoc;
6 import java.util.Calendar JavaDoc;
7 import java.util.TimeZone JavaDoc;
8 import java.util.GregorianCalendar JavaDoc;
9 import gnu.math.IntNum;
10
11 /**
12  * Represents a date and/or time.
13  * Similar functionality as java.util.Calendar (and uses GregorianCalendar
14  * internally) but supports arithmetic.
15  * Can be for XML Schema date/time types, specifically as used in XPath/Xquery..
16  */

17
18 public class DateTime extends Quantity implements Cloneable JavaDoc
19 {
20   Unit unit = Unit.date;
21
22   /** Fractional seconds, in units of nanoseconds. */
23   int nanoSeconds;
24   GregorianCalendar JavaDoc calendar;
25   int mask;
26
27   /*
28   static final int REFERENCE_YEAR = 1972;
29   static final int REFERENCE_MONTH = 0; // January
30   static final int REFERENCE_DAY = 0; // January 1
31   */

32
33   static final int YEAR_COMPONENT = 1;
34   static final int MONTH_COMPONENT = 2;
35   static final int DAY_COMPONENT = 3;
36   static final int HOURS_COMPONENT = 4;
37   static final int MINUTES_COMPONENT = 5;
38   static final int SECONDS_COMPONENT = 6;
39   static final int TIMEZONE_COMPONENT = 7;
40
41   public static final int YEAR_MASK = 1 << YEAR_COMPONENT;
42   public static final int MONTH_MASK = 1 << MONTH_COMPONENT;
43   public static final int DAY_MASK = 1 << DAY_COMPONENT;
44   public static final int HOURS_MASK = 1 << HOURS_COMPONENT;
45   public static final int MINUTES_MASK = 1 << MINUTES_COMPONENT;
46   public static final int SECONDS_MASK = 1 << SECONDS_COMPONENT;
47   public static final int TIMEZONE_MASK = 1 << TIMEZONE_COMPONENT;
48   public static final int DATE_MASK = YEAR_MASK|MONTH_MASK|DAY_MASK;
49   public static final int TIME_MASK = HOURS_MASK|MINUTES_MASK|SECONDS_MASK;
50
51   public int components() { return mask & ~TIMEZONE_MASK; }
52
53   public DateTime cast (int newComponents)
54   {
55     int oldComponents = mask & ~TIMEZONE_MASK;
56     if (newComponents == oldComponents)
57       return this;
58     DateTime copy
59       = new DateTime(newComponents, (GregorianCalendar JavaDoc) calendar.clone());
60     if ((newComponents & ~oldComponents) != 0
61         // Special case: Casting xs:date to xs:dateTime *is* allowed.
62
&& ! (oldComponents == DATE_MASK
63               && newComponents == (DATE_MASK|TIME_MASK)))
64       throw new ClassCastException JavaDoc("cannot cast DateTime - missing conponents");
65     if (isZoneUnspecified())
66       copy.mask &= ~TIMEZONE_MASK;
67     else
68       copy.mask |= TIMEZONE_MASK;
69     int extraComponents = oldComponents & ~newComponents;
70     if ((extraComponents & TIME_MASK) != 0)
71       {
72         copy.calendar.clear(Calendar.HOUR_OF_DAY);
73         copy.calendar.clear(Calendar.MINUTE);
74         copy.calendar.clear(Calendar.SECOND);
75       }
76     else
77       copy.nanoSeconds = nanoSeconds;
78     if ((extraComponents & YEAR_MASK) != 0)
79       {
80         copy.calendar.clear(Calendar.YEAR);
81         copy.calendar.clear(Calendar.ERA);
82       }
83     if ((extraComponents & MONTH_MASK) != 0)
84       copy.calendar.clear(Calendar.MONTH);
85     if ((extraComponents & DAY_MASK) != 0)
86       copy.calendar.clear(Calendar.DATE);
87     return copy;
88   }
89
90   private static final Date JavaDoc minDate = new Date JavaDoc(Long.MIN_VALUE);
91
92   public DateTime (int mask)
93   {
94     calendar = new GregorianCalendar JavaDoc();
95     // Never use Julian calendar.
96
calendar.setGregorianChange(minDate);
97     calendar.clear();
98     this.mask = mask;
99   }
100
101   public DateTime (int mask, GregorianCalendar JavaDoc calendar)
102   {
103     this.calendar = calendar;
104     this.mask = mask;
105   }
106
107   public static DateTime parse (String JavaDoc value, int mask)
108   {
109     DateTime result = new DateTime(mask);
110     value = value.trim();
111     int len = value.length();
112     int pos = 0;
113     boolean wantDate = (mask & DATE_MASK) != 0;
114     boolean wantTime = (mask & TIME_MASK) != 0;
115     if (wantDate)
116       {
117         pos = result.parseDate(value, pos, mask);
118         if (wantTime)
119           {
120             if (pos < 0 || pos >= len || value.charAt(pos) != 'T')
121               pos = -1;
122             else
123               pos++;
124           }
125       }
126     if (wantTime)
127       pos = result.parseTime(value, pos);
128     pos = result.parseZone(value, pos);
129     if (pos != len)
130       throw new NumberFormatException JavaDoc("Unrecognized date/time '"+value+'\'');
131     return result;
132   }
133
134   int parseDate(String JavaDoc str, int start, int mask)
135   {
136     if (start < 0)
137       return start;
138     int len = str.length();
139     boolean negYear = false;
140     if (start < len && str.charAt(start) == '-')
141       {
142         start++;
143         negYear = true;
144       }
145     int pos = start;
146     int part, year, month;
147     if ((mask & YEAR_MASK) == 0)
148       {
149         if (! negYear)
150           return -1;
151         year = -1;
152       }
153     else
154       {
155         part = parseDigits(str, pos);
156         year = part >> 16;
157         pos = part & 0xffff;
158         if (pos != start+4 && (pos <=start+4 || str.charAt(start) == '0'))
159           return -1;
160         if (negYear || year == 0)
161           {
162             calendar.set(Calendar.ERA, GregorianCalendar.BC);
163             calendar.set(Calendar.YEAR, year+1);
164           }
165         else
166           calendar.set(Calendar.YEAR, year);
167       }
168     if ((mask & (MONTH_MASK|DAY_MASK)) == 0)
169       return pos;
170     if (pos >= len || str.charAt(pos) != '-')
171       return -1;
172     start = ++pos;
173     if ((mask & MONTH_MASK) != 0)
174       {
175         part = parseDigits(str, start);
176         month = part >> 16;
177         pos = part & 0xffff;
178         if (month <= 0 || month > 12 || pos != start + 2)
179           return -1;
180         calendar.set(Calendar.MONTH, month-1);
181         if ((mask & DAY_MASK) == 0)
182           return pos;
183       }
184     else
185       month = -1;
186     if (pos >= len || str.charAt(pos) != '-')
187       return -1;
188     start = pos+1;
189     part = parseDigits(str, start);
190     int day = part >> 16;
191     pos = part & 0xffff;
192     if (day > 0 && pos == start+2)
193       {
194         int maxDay;
195         if ((mask & MONTH_MASK) == 0)
196           maxDay = 31;
197         else
198           maxDay = daysInMonth(month-1, (mask & YEAR_MASK) != 0 ? year : 2000);
199         if (day <= maxDay)
200           {
201             calendar.set(Calendar.DATE, day);
202             return pos;
203           }
204       }
205     return -1;
206   }
207
208   public static boolean isLeapYear (int year)
209   {
210     return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0);
211   }
212
213   public static int daysInMonth (int month, int year)
214   {
215     switch (month)
216       {
217       case Calendar.APRIL:
218       case Calendar.JUNE:
219       case Calendar.SEPTEMBER:
220       case Calendar.NOVEMBER:
221         return 30;
222       case Calendar.FEBRUARY:
223         return isLeapYear(year) ? 29 : 28;
224       default:
225         return 31;
226       }
227   }
228
229   public static TimeZone JavaDoc GMT = TimeZone.getTimeZone("GMT");
230
231   int parseZone(String JavaDoc str, int start)
232   {
233     if (start < 0)
234       return start;
235     int part = parseZoneMinutes(str, start);
236     if (part == 0)
237       return -1;
238     if (part == start)
239       return start;
240     int minutes = part >> 16;
241     TimeZone JavaDoc zone;
242     int pos = part & 0xffff;
243     if (minutes == 0)
244       zone = GMT;
245     else
246       zone = TimeZone.getTimeZone("GMT"+ str.substring(start, pos));
247     calendar.setTimeZone(zone);
248     mask |= TIMEZONE_MASK;
249     return pos;
250   }
251
252   /** Return (MINUTES<<16)|END_POS if time-zone indicator was seen.
253    * Returns START otherwise, or 0 on an error. */

254   int parseZoneMinutes(String JavaDoc str, int start)
255   {
256     int len = str.length();
257     if (start == len || start < 0)
258       return start;
259     char ch = str.charAt(start);
260     if (ch == 'Z')
261       return start+1;
262     if (ch != '+' && ch != '-')
263       return start;
264     start++;
265     int part = parseDigits(str, start);
266     int hour = part >> 16;
267     if (hour > 14)
268       return 0;
269     int minute = 60 * hour;
270     int pos = part & 0xffff;
271     if (pos != start+2)
272       return 0;
273     if (pos < len)
274       {
275         if (str.charAt(pos) == ':')
276           {
277             start = pos+1;
278             part = parseDigits(str, start);
279             pos = part & 0xffff;
280             part >>= 16;
281             if (part > 0 && (part >= 60 || hour == 14))
282               return 0;
283             minute += part;
284             if (pos!=start+2)
285               return 0;
286           }
287       }
288     else // The minutes part is not optional.
289
return 0;
290     if (minute > 840)
291       return 0;
292     if (ch == '-')
293       minute = -minute;
294     return (minute << 16)|pos;
295   }
296
297   int parseTime(String JavaDoc str, int start)
298   {
299     if (start < 0)
300       return start;
301     int len = str.length();
302     int pos = start;
303     int part = parseDigits(str, start);
304     int hour = part >> 16;
305     pos = part & 0xffff;
306     if (hour <= 24 && pos == start+2 && pos != len && str.charAt(pos) == ':')
307       {
308         start = pos + 1;
309         part = parseDigits(str, start);
310         int minute = part >> 16;
311         pos = part & 0xffff;
312         if (minute < 60 && pos == start+2
313             && pos != len && str.charAt(pos) == ':')
314           {
315             start = pos + 1;
316             part = parseDigits(str, start);
317             int second = part >> 16;
318             pos = part & 0xffff;
319             // We don't allow/handle leap seconds.
320
if (second < 60 && pos == start+2)
321               {
322                 if (pos + 1 < len && str.charAt(pos) == '.'
323                     && Character.digit(str.charAt(pos+1), 10) >= 0)
324                   {
325                     start = pos + 1;
326                     pos = start;
327                     int nanos = 0;
328                     int nfrac = 0;
329                     for (; pos < len; nfrac++, pos++)
330                       {
331                         int dig = Character.digit(str.charAt(pos), 10);
332                         if (dig < 0)
333                           break;
334                         if (nfrac < 9)
335                           nanos = 10 * nanos + dig;
336                         else if (nfrac == 9 && dig >= 5)
337                           nanos++;
338                       }
339                     while (nfrac++ < 9)
340                       nanos = 10 * nanos;
341                     nanoSeconds = nanos;
342                   }
343                 if (hour == 24
344                     && (minute != 0 || second != 0 || nanoSeconds != 0))
345                   return -1;
346                 calendar.set(Calendar.HOUR_OF_DAY, hour);
347                 calendar.set(Calendar.MINUTE, minute);
348                 calendar.set(Calendar.SECOND, second);
349                 return pos;
350               }
351           }
352       }
353     return -1;
354   }
355
356   /** Return (VALUE << 16)|END. */
357   private static int parseDigits(String JavaDoc str, int start)
358   {
359     int i = start;
360     int val = -1;
361     int len = str.length();
362     while (i < len)
363       {
364         char ch = str.charAt(i);
365         int dig = Character.digit(ch, 10);
366         if (dig < 0)
367           break;
368         if (val > 20000)
369           return 0; // possible overflow
370
val = val < 0 ? dig : 10 * val + dig;
371         i++;
372       }
373     return val < 0 ? i : (val << 16) | i;
374   }
375
376   public int getYear()
377   {
378     int year = calendar.get(Calendar.YEAR);
379     if (calendar.get(Calendar.ERA) == GregorianCalendar.BC)
380       year = 1 - year;
381     return year;
382   }
383
384   public int getMonth()
385   {
386     return calendar.get(Calendar.MONTH) + 1;
387   }
388
389   public int getDay()
390   {
391     return calendar.get(Calendar.DATE);
392   }
393
394   public int getHours()
395   {
396     return calendar.get(Calendar.HOUR_OF_DAY);
397   }
398
399   public int getMinutes()
400   {
401     return calendar.get(Calendar.MINUTE);
402   }
403
404   public int getSecondsOnly ()
405   {
406     return calendar.get(Calendar.SECOND);
407   }
408
409   public int getWholeSeconds () // deprecated
410
{
411     return calendar.get(Calendar.SECOND);
412   }
413
414   public int getNanoSecondsOnly ()
415   {
416     return nanoSeconds;
417   }
418
419   /*
420   public Object getSecondsObject ()
421   {
422     return IntNum.make(getWholeSeconds());
423   }
424   */

425
426   /** Return -1, 0, or 1, depending on which value is greater. */
427   public static int compare (DateTime date1, DateTime date2)
428   {
429     long millis1 = date1.calendar.getTimeInMillis();
430     long millis2 = date2.calendar.getTimeInMillis();
431     if (((date1.mask | date2.mask) & DATE_MASK) == 0)
432       {
433         if (millis1 < 0) millis1 += 24 * 60 * 60 * 1000;
434         if (millis2 < 0) millis2 += 24 * 60 * 60 * 1000;
435       }
436     int nanos1 = date1.nanoSeconds;
437     int nanos2 = date2.nanoSeconds;
438     millis1 += nanos1 / 1000000;
439     millis2 += nanos2 / 1000000;
440     nanos1 = nanos1 % 1000000;
441     nanos2 = nanos2 % 1000000;
442     return millis1 < millis2 ? -1 : millis1 > millis2 ? 1
443       : nanos1 < nanos2 ? -1 : nanos1 > nanos2 ? 1 : 0;
444   }
445
446   public int compare (Object JavaDoc obj)
447   {
448     if (obj instanceof DateTime)
449       return compare (this, (DateTime) obj);
450     return ((Numeric) obj).compareReversed (this);
451   }
452
453   public static Duration sub (DateTime date1, DateTime date2)
454   {
455     long millis1 = date1.calendar.getTimeInMillis();
456     long millis2 = date2.calendar.getTimeInMillis();
457     int nanos1 = date1.nanoSeconds;
458     int nanos2 = date2.nanoSeconds;
459     millis1 += nanos1 / 1000000;
460     millis2 += nanos2 / 1000000;
461     nanos1 = nanos1 % 1000000;
462     nanos2 = nanos2 % 1000000;
463     long millis = millis1 - millis2;
464     long seconds = millis / 1000;
465     int nanos = (int) ((millis % 1000) * 1000000 + nanos2 - nanos2);
466     seconds += nanos / 1000000000;
467     nanos = nanos % 1000000000;
468     return Duration.make(0, seconds, nanos, Unit.second);
469   }
470
471   public DateTime withZoneUnspecified ()
472   {
473     if (isZoneUnspecified())
474       return this;
475     DateTime r = new DateTime(mask, (GregorianCalendar JavaDoc) calendar.clone());
476     r.calendar.setTimeZone(TimeZone.getDefault());
477     r.mask &= ~TIMEZONE_MASK;
478     return r;
479   }
480
481   public DateTime adjustTimezone (int newOffset)
482   {
483     DateTime r = new DateTime(mask, (GregorianCalendar JavaDoc) calendar.clone());
484     TimeZone JavaDoc zone;
485     if (newOffset == 0)
486       zone = GMT;
487     else
488       {
489         StringBuffer JavaDoc sbuf = new StringBuffer JavaDoc("GMT");
490         toStringZone(newOffset, sbuf);
491         zone = TimeZone.getTimeZone(sbuf.toString());
492       }
493     r.calendar.setTimeZone(zone);
494     if ((r.mask & TIMEZONE_MASK) != 0)
495       {
496         long millis = calendar.getTimeInMillis();
497         r.calendar.setTimeInMillis(millis);
498         if ((mask & TIME_MASK) == 0)
499           {
500             r.calendar.set(Calendar.HOUR_OF_DAY, 0);
501             r.calendar.set(Calendar.MINUTE, 0);
502             r.calendar.set(Calendar.SECOND, 0);
503             r.nanoSeconds = 0;
504           }
505       }
506     else
507       r.mask |= TIMEZONE_MASK;
508     return r;
509   }
510
511   public static DateTime add (DateTime x, Duration y, int k)
512   {
513     if (y.unit == Unit.duration
514         || (y.unit == Unit.month && (x.mask & DATE_MASK) != DATE_MASK))
515       throw new IllegalArgumentException JavaDoc("invalid date/time +/- duration combinatuion");
516     DateTime r = new DateTime(x.mask, (GregorianCalendar JavaDoc) x.calendar.clone());
517     if (y.months != 0)
518       {
519         int month = 12 * r.getYear() + r.calendar.get(Calendar.MONTH);
520         month += k * y.months;
521         int day = r.calendar.get(Calendar.DATE);
522         int year, daysInMonth;
523         if (month >= 12)
524           {
525             year = month / 12;
526             month = month % 12;
527             r.calendar.set(Calendar.ERA, GregorianCalendar.AD);
528             daysInMonth = daysInMonth(month, year);
529           }
530         else
531           {
532             month = 11 - month;
533             r.calendar.set(Calendar.ERA, GregorianCalendar.BC);
534             year = (month / 12) + 1;
535             month = 11 - (month % 12);
536             daysInMonth = daysInMonth(month, 1);
537           }
538         
539         if (day > daysInMonth)
540           day = daysInMonth;
541         r.calendar.set(year, month, day);
542       }
543     long nanos = x.nanoSeconds + k * (y.seconds * 1000000000L + y.nanos);
544     if (nanos != 0)
545       {
546         if ((x.mask & TIME_MASK) == 0)
547           { // Truncate to 00:00:00
548
long nanosPerDay = 1000000000L * 24 * 60 * 60;
549             long mod = nanos % nanosPerDay;
550             if (mod < 0)
551               mod += nanosPerDay;
552             nanos -= mod;
553           }
554         long millis = r.calendar.getTimeInMillis();
555         millis += (nanos / 1000000000L) * 1000;
556         r.calendar.setTimeInMillis(millis);
557         r.nanoSeconds = (int) (nanos % 1000000000L);
558       }
559     return r;
560   }
561
562   public static DateTime addMinutes (DateTime x, int y)
563   {
564     return addSeconds (x, 60 * y);
565   }
566
567   public static DateTime addSeconds (DateTime x, int y)
568   {
569     DateTime r = new DateTime(x.mask, (GregorianCalendar JavaDoc) x.calendar.clone());
570     long nanos = y * 1000000000L;
571     if (nanos != 0)
572       {
573         nanos = x.nanoSeconds + nanos;
574         long millis = x.calendar.getTimeInMillis();
575         millis += (nanos / 1000000L);
576         r.calendar.setTimeInMillis(millis);
577         r.nanoSeconds = (int) (nanos % 1000000L);
578       }
579     return r;
580   }
581
582   public Numeric add (Object JavaDoc y, int k)
583   {
584     if (y instanceof Duration)
585       return DateTime.add(this, (Duration) y, k);
586     if (y instanceof DateTime && k == -1)
587       return DateTime.sub(this, (DateTime) y);
588     throw new IllegalArgumentException JavaDoc ();
589   }
590
591   public Numeric addReversed (Numeric x, int k)
592   {
593     if (x instanceof Duration && k == 1)
594       return DateTime.add(this, (Duration) x, k);
595     throw new IllegalArgumentException JavaDoc ();
596   }
597
598   private static void append (int value, StringBuffer JavaDoc sbuf, int minWidth)
599   {
600     int start = sbuf.length();
601     sbuf.append(value);
602     int padding = start + minWidth - sbuf.length();
603     while (--padding >= 0)
604       sbuf.insert(start, '0');
605   }
606
607   public void toStringDate(StringBuffer JavaDoc sbuf)
608   {
609     int mask = components();
610     if ((mask & YEAR_MASK) != 0)
611       {
612         int year = calendar.get(Calendar.YEAR);
613         if (calendar.get(Calendar.ERA) == GregorianCalendar.BC)
614           {
615             year--;
616             if (year != 0)
617               sbuf.append('-');
618           }
619         append(year, sbuf, 4);
620       }
621     else
622       sbuf.append('-');
623     if ((mask & (MONTH_MASK|DAY_MASK)) != 0)
624       {
625         sbuf.append('-');
626         if ((mask & MONTH_MASK) != 0)
627           append(getMonth(), sbuf, 2);
628         if ((mask & DAY_MASK) != 0)
629           {
630             sbuf.append('-');
631             append(getDay(), sbuf, 2);
632           }
633       }
634   }
635
636   public void toStringTime(StringBuffer JavaDoc sbuf)
637   {
638     append(getHours(), sbuf, 2);
639     sbuf.append(':');
640     append(getMinutes(), sbuf, 2);
641     sbuf.append(':');
642     append(getWholeSeconds(), sbuf, 2);
643     Duration.appendNanoSeconds(nanoSeconds, sbuf);
644   }
645
646   public boolean isZoneUnspecified ()
647   {
648     //TimeZone zone = calendar.getTimeZone();
649
//return zone.equals(TimeZone.getDefault()); // FIXME?
650
return (mask & TIMEZONE_MASK) == 0;
651   }
652
653   public int getZoneMinutes ()
654   {
655     return calendar.getTimeZone().getRawOffset() / 60000;
656   }
657
658   /** Get a TimeZone object for a given offset.
659    * @param minutes timezone offset in minutes.
660    */

661   public static TimeZone JavaDoc minutesToTimeZone (int minutes)
662   {
663     if (minutes == 0)
664       return DateTime.GMT;
665     StringBuffer JavaDoc sbuf = new StringBuffer JavaDoc("GMT");
666     toStringZone(minutes, sbuf);
667     return TimeZone.getTimeZone(sbuf.toString());
668   }
669
670   public void setTimeZone (TimeZone JavaDoc timeZone)
671   {
672     calendar.setTimeZone(timeZone);
673   }
674
675   public void toStringZone(StringBuffer JavaDoc sbuf)
676   {
677     if (isZoneUnspecified())
678       return;
679     toStringZone(getZoneMinutes(), sbuf);
680   }
681   public static void toStringZone(int minutes, StringBuffer JavaDoc sbuf)
682   {
683     if (minutes == 0)
684       sbuf.append('Z');
685     else
686       {
687         if (minutes < 0)
688           {
689             sbuf.append('-');
690             minutes = -minutes;
691           }
692         else
693           sbuf.append('+');
694         append(minutes/60, sbuf, 2);
695         sbuf.append(':');
696         append(minutes%60, sbuf, 2);
697       }
698   }
699
700   public void toString (StringBuffer JavaDoc sbuf)
701   {
702     int mask = components();
703     boolean hasDate = (mask & DATE_MASK) != 0;
704     boolean hasTime = (mask & TIME_MASK) != 0;
705     if (hasDate)
706       {
707         toStringDate(sbuf);
708         if (hasTime)
709           sbuf.append('T');
710       }
711     if (hasTime)
712       toStringTime(sbuf);
713     toStringZone(sbuf);
714   }
715
716   public String JavaDoc toString ()
717   {
718     StringBuffer JavaDoc sbuf = new StringBuffer JavaDoc();
719     toString(sbuf);
720     return sbuf.toString();
721   }
722
723   public boolean isExact ()
724   {
725     return (mask & TIME_MASK) == 0;
726   }
727
728   public boolean isZero ()
729   {
730     throw new Error JavaDoc("DateTime.isZero not meaningful!");
731   }
732
733   public Unit unit() { return unit; }
734   public Complex number ()
735   {
736     throw new Error JavaDoc("number needs to be implemented!");
737   }
738 }
739
Popular Tags