KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > thaiopensource > datatype > xsd > DateTimeDatatype


1 package com.thaiopensource.datatype.xsd;
2
3 import org.relaxng.datatype.ValidationContext;
4
5 import java.util.Calendar JavaDoc;
6 import java.util.Date JavaDoc;
7 import java.util.GregorianCalendar JavaDoc;
8
9 class DateTimeDatatype extends RegexDatatype implements OrderRelation {
10   static private final String JavaDoc YEAR_PATTERN = "-?([1-9][0-9]*)?[0-9]{4}";
11   static private final String JavaDoc MONTH_PATTERN = "[0-9]{2}";
12   static private final String JavaDoc DAY_OF_MONTH_PATTERN = "[0-9]{2}";
13   static private final String JavaDoc TIME_PATTERN = "[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]*)?";
14   static private final String JavaDoc TZ_PATTERN = "(Z|[+\\-][0-9][0-9]:[0-5][0-9])?";
15
16   private final String JavaDoc template;
17
18   /**
19    * The argument specifies the lexical representation accepted:
20    * Y specifies a year with optional preceding minus
21    * M specifies a two digit month
22    * D specifies a two digit day of month
23    * t specifies a time (hh:mm:ss.sss)
24    * any other character stands for itself.
25    * All lexical representations are implicitly followed by an optional time zone.
26    */

27   DateTimeDatatype(String JavaDoc template) {
28     super(makePattern(template));
29     this.template = template;
30   }
31
32   static private String JavaDoc makePattern(String JavaDoc template) {
33     StringBuffer JavaDoc pattern = new StringBuffer JavaDoc();
34     for (int i = 0, len = template.length(); i < len; i++) {
35       char c = template.charAt(i);
36       switch (c) {
37       case 'Y':
38         pattern.append(YEAR_PATTERN);
39         break;
40       case 'M':
41         pattern.append(MONTH_PATTERN);
42         break;
43       case 'D':
44         pattern.append(DAY_OF_MONTH_PATTERN);
45         break;
46       case 't':
47         pattern.append(TIME_PATTERN);
48         break;
49       default:
50         pattern.append(c);
51         break;
52       }
53     }
54     pattern.append(TZ_PATTERN);
55     return pattern.toString();
56   }
57
58   boolean allowsValue(String JavaDoc str, ValidationContext vc) {
59     return getValue(str, vc) != null;
60   }
61
62   static private class DateTime {
63     private final Date JavaDoc date;
64     private final int leapMilliseconds;
65     private final boolean hasTimeZone;
66
67     DateTime(Date JavaDoc date, int leapMilliseconds, boolean hasTimeZone) {
68       this.date = date;
69       this.leapMilliseconds = leapMilliseconds;
70       this.hasTimeZone = hasTimeZone;
71     }
72
73     public boolean equals(Object JavaDoc obj) {
74       if (!(obj instanceof DateTime))
75         return false;
76       DateTime other = (DateTime)obj;
77       return (this.date.equals(other.date)
78               && this.leapMilliseconds == other.leapMilliseconds
79               && this.hasTimeZone == other.hasTimeZone);
80     }
81
82     public int hashCode() {
83       return date.hashCode();
84     }
85
86     Date JavaDoc getDate() {
87       return date;
88     }
89
90     int getLeapMilliseconds() {
91       return leapMilliseconds;
92     }
93
94     boolean getHasTimeZone() {
95       return hasTimeZone;
96     }
97   }
98
99   // XXX Check leap second validity?
100
// XXX Allow 24:00:00?
101
Object JavaDoc getValue(String JavaDoc str, ValidationContext vc) {
102     boolean negative = false;
103     int year = 2000; // any leap year will do
104
int month = 1;
105     int day = 1;
106     int hours = 0;
107     int minutes = 0;
108     int seconds = 0;
109     int milliseconds = 0;
110     int pos = 0;
111     int len = str.length();
112     for (int templateIndex = 0, templateLength = template.length();
113          templateIndex < templateLength;
114          templateIndex++) {
115       char templateChar = template.charAt(templateIndex);
116       switch (templateChar) {
117       case 'Y':
118         negative = str.charAt(pos) == '-';
119         int yearStartIndex = negative ? pos + 1 : pos;
120         pos = skipDigits(str, yearStartIndex);
121         try {
122           year = Integer.parseInt(str.substring(yearStartIndex, pos));
123         }
124         catch (NumberFormatException JavaDoc e) {
125           return null;
126         }
127         break;
128       case 'M':
129         month = parse2Digits(str, pos);
130         pos += 2;
131         break;
132       case 'D':
133         day = parse2Digits(str, pos);
134         pos += 2;
135         break;
136       case 't':
137         hours = parse2Digits(str, pos);
138         pos += 3;
139         minutes = parse2Digits(str, pos);
140         pos += 3;
141         seconds = parse2Digits(str, pos);
142         pos += 2;
143         if (pos < len && str.charAt(pos) == '.') {
144           int end = skipDigits(str, ++pos);
145           for (int j = 0; j < 3; j++) {
146             milliseconds *= 10;
147             if (pos < end)
148               milliseconds += str.charAt(pos++) - '0';
149           }
150           pos = end;
151         }
152         break;
153       default:
154         pos++;
155         break;
156       }
157     }
158     boolean hasTimeZone = pos < len;
159     int tzOffset;
160     if (hasTimeZone && str.charAt(pos) != 'Z')
161       tzOffset = parseTimeZone(str, pos);
162     else
163       tzOffset = 0;
164     int leapMilliseconds;
165     if (seconds == 60) {
166       leapMilliseconds = milliseconds + 1;
167       milliseconds = 999;
168       seconds = 59;
169     }
170     else
171       leapMilliseconds = 0;
172     try {
173       GregorianCalendar JavaDoc cal = CalendarFactory.getCalendar();
174       Date JavaDoc date;
175       if (cal == CalendarFactory.cal) {
176         synchronized (cal) {
177           date = createDate(cal, tzOffset, negative, year, month, day, hours, minutes, seconds, milliseconds);
178         }
179       }
180       else
181         date = createDate(cal, tzOffset, negative, year, month, day, hours, minutes, seconds, milliseconds);
182       return new DateTime(date, leapMilliseconds, hasTimeZone);
183     }
184     catch (IllegalArgumentException JavaDoc e) {
185       return null;
186     }
187   }
188
189   // The GregorianCalendar constructor is incredibly slow with some
190
// versions of GCJ (specifically the version shipped with RedHat 9).
191
// This code attempts to detect when construction is slow.
192
// When it is, we synchronize access to a single
193
// object; otherwise, we create a new object each time we need it
194
// so as to avoid thread lock contention.
195

196   static class CalendarFactory {
197     static private final int UNKNOWN = -1;
198     static private final int SLOW = 0;
199     static private final int FAST = 1;
200     static private final int LIMIT = 10;
201     static private int speed = UNKNOWN;
202     static GregorianCalendar JavaDoc cal = new GregorianCalendar JavaDoc();
203
204     static GregorianCalendar JavaDoc getCalendar() {
205       // Don't need to synchronize this because speed is atomic.
206
switch (speed) {
207       case SLOW:
208         return cal;
209       case FAST:
210         return new GregorianCalendar JavaDoc();
211       }
212       // Note that we are not timing the first construction (which happens
213
// at class initialization), since that may involve one-time cache
214
// initialization.
215
long start = System.currentTimeMillis();
216       GregorianCalendar JavaDoc tem = new GregorianCalendar JavaDoc();
217       long time = System.currentTimeMillis() - start;
218       speed = time > LIMIT ? SLOW : FAST;
219       return tem;
220     }
221   }
222
223   private static Date JavaDoc createDate(GregorianCalendar JavaDoc cal, int tzOffset, boolean negative,
224                                  int year, int month, int day,
225                                  int hours, int minutes, int seconds, int milliseconds) {
226     cal.setLenient(false);
227     cal.setGregorianChange(new Date JavaDoc(Long.MIN_VALUE));
228     cal.clear();
229     // Using a time zone of "GMT+XX:YY" doesn't work with JDK 1.1, so we have to do it like this.
230
cal.set(Calendar.ZONE_OFFSET, tzOffset);
231     cal.set(Calendar.DST_OFFSET, 0);
232     cal.set(Calendar.ERA, negative ? GregorianCalendar.BC : GregorianCalendar.AD);
233     // months in ISO8601 start with 1; months in Java start with 0
234
month -= 1;
235     cal.set(year, month, day, hours, minutes, seconds);
236     cal.set(Calendar.MILLISECOND, milliseconds);
237     checkDate(cal.isLeapYear(year), month, day); // for GCJ
238
return cal.getTime();
239   }
240
241   static private void checkDate(boolean isLeapYear, int month, int day) {
242     if (month < 0 || month > 11 || day < 1)
243       throw new IllegalArgumentException JavaDoc();
244     int dayMax;
245     switch (month) {
246     // Thirty days have September, April, June and November...
247
case Calendar.SEPTEMBER:
248     case Calendar.APRIL:
249     case Calendar.JUNE:
250     case Calendar.NOVEMBER:
251       dayMax = 30;
252       break;
253     case Calendar.FEBRUARY:
254       dayMax = isLeapYear ? 29 : 28;
255       break;
256     default:
257       dayMax = 31;
258       break;
259     }
260     if (day > dayMax)
261       throw new IllegalArgumentException JavaDoc();
262   }
263
264   static private int parseTimeZone(String JavaDoc str, int i) {
265     int sign = str.charAt(i) == '-' ? -1 : 1;
266     return (Integer.parseInt(str.substring(i + 1, i + 3))*60 + Integer.parseInt(str.substring(i + 4)))*60*1000*sign;
267   }
268
269   static private int parse2Digits(String JavaDoc str, int i) {
270     return (str.charAt(i) - '0')*10 + (str.charAt(i + 1) - '0');
271   }
272
273   static private int skipDigits(String JavaDoc str, int i) {
274     for (int len = str.length(); i < len; i++) {
275       if ("0123456789".indexOf(str.charAt(i)) < 0)
276         break;
277     }
278     return i;
279   }
280
281   OrderRelation getOrderRelation() {
282     return this;
283   }
284
285   static private final int TIME_ZONE_MAX = 14*60*60*1000;
286
287   public boolean isLessThan(Object JavaDoc obj1, Object JavaDoc obj2) {
288     DateTime dt1 = (DateTime)obj1;
289     DateTime dt2 = (DateTime)obj2;
290     long t1 = dt1.getDate().getTime();
291     long t2 = dt2.getDate().getTime();
292     if (dt1.getHasTimeZone() == dt2.getHasTimeZone())
293       return isLessThan(t1,
294                         dt1.getLeapMilliseconds(),
295                         t2,
296                         dt2.getLeapMilliseconds());
297     else if (!dt2.getHasTimeZone())
298       return isLessThan(t1, dt1.getLeapMilliseconds(), t2 - TIME_ZONE_MAX, dt2.getLeapMilliseconds());
299     else
300       return isLessThan(t1 + TIME_ZONE_MAX, dt1.getLeapMilliseconds(), t2, dt2.getLeapMilliseconds());
301   }
302
303   static private boolean isLessThan(long t1, int leapMillis1, long t2, int leapMillis2) {
304     if (t1 < t2)
305       return true;
306     if (t1 > t2)
307       return false;
308     if (leapMillis1 < leapMillis2)
309       return true;
310     return false;
311   }
312 }
313
Popular Tags