~ubuntu-branches/debian/wheezy/jing-trang/wheezy

« back to all changes in this revision

Viewing changes to mod/xsd-datatype/src/main/com/thaiopensource/datatype/xsd/DateTimeDatatype.java

  • Committer: Bazaar Package Importer
  • Author(s): Samuel Thibault
  • Date: 2009-09-01 15:53:03 UTC
  • Revision ID: james.westby@ubuntu.com-20090901155303-2kweef05h5v9j3ni
Tags: upstream-20090818
ImportĀ upstreamĀ versionĀ 20090818

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
package com.thaiopensource.datatype.xsd;
 
2
 
 
3
import org.relaxng.datatype.DatatypeException;
 
4
import org.relaxng.datatype.ValidationContext;
 
5
 
 
6
import java.util.Calendar;
 
7
import java.util.Date;
 
8
import java.util.GregorianCalendar;
 
9
 
 
10
class DateTimeDatatype extends RegexDatatype implements OrderRelation {
 
11
  static private final String YEAR_PATTERN = "-?([1-9][0-9]*)?[0-9]{4}";
 
12
  static private final String MONTH_PATTERN = "[0-9]{2}";
 
13
  static private final String DAY_OF_MONTH_PATTERN = "[0-9]{2}";
 
14
  static private final String TIME_PATTERN = "[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]*)?";
 
15
  static private final String TZ_PATTERN = "(Z|[+\\-][0-9][0-9]:[0-5][0-9])?";
 
16
 
 
17
  private final String template;
 
18
  private final String lexicalSpaceKey;
 
19
 
 
20
  /**
 
21
   * The argument specifies the lexical representation accepted:
 
22
   * Y specifies a year with optional preceding minus
 
23
   * M specifies a two digit month
 
24
   * D specifies a two digit day of month
 
25
   * t specifies a time (hh:mm:ss.sss)
 
26
   * any other character stands for itself.
 
27
   * All lexical representations are implicitly followed by an optional time zone.
 
28
   */
 
29
  DateTimeDatatype(String template) {
 
30
    super(makePattern(template));
 
31
    this.template = template;
 
32
    this.lexicalSpaceKey = makeLexicalSpaceKey(template);
 
33
  }
 
34
 
 
35
  String getLexicalSpaceKey() {
 
36
    return lexicalSpaceKey;
 
37
  }
 
38
 
 
39
  static private String makeLexicalSpaceKey(String template) {
 
40
    String key = "";
 
41
    if (template.indexOf('Y') >= 0)
 
42
      key += "_y";
 
43
    if (template.indexOf('M') >= 0)
 
44
      key += "_m";
 
45
    if (template.indexOf('D') >= 0)
 
46
      key += "_d";
 
47
    if (key.length() > 0)
 
48
      key = "date" + key;
 
49
    if (template.indexOf('t') >= 0)
 
50
      key = key.length() > 0 ? key + "_time" : "time";
 
51
    return key;
 
52
  }
 
53
 
 
54
  static private String makePattern(String template) {
 
55
    StringBuffer pattern = new StringBuffer();
 
56
    for (int i = 0, len = template.length(); i < len; i++) {
 
57
      char c = template.charAt(i);
 
58
      switch (c) {
 
59
      case 'Y':
 
60
        pattern.append(YEAR_PATTERN);
 
61
        break;
 
62
      case 'M':
 
63
        pattern.append(MONTH_PATTERN);
 
64
        break;
 
65
      case 'D':
 
66
        pattern.append(DAY_OF_MONTH_PATTERN);
 
67
        break;
 
68
      case 't':
 
69
        pattern.append(TIME_PATTERN);
 
70
        break;
 
71
      default:
 
72
        pattern.append(c);
 
73
        break;
 
74
      }
 
75
    }
 
76
    pattern.append(TZ_PATTERN);
 
77
    return pattern.toString();
 
78
  }
 
79
 
 
80
  static private class DateTime {
 
81
    private final Date date;
 
82
    private final int leapMilliseconds;
 
83
    private final boolean hasTimeZone;
 
84
 
 
85
    DateTime(Date date, int leapMilliseconds, boolean hasTimeZone) {
 
86
      this.date = date;
 
87
      this.leapMilliseconds = leapMilliseconds;
 
88
      this.hasTimeZone = hasTimeZone;
 
89
    }
 
90
 
 
91
    public boolean equals(Object obj) {
 
92
      if (!(obj instanceof DateTime))
 
93
        return false;
 
94
      DateTime other = (DateTime)obj;
 
95
      return (this.date.equals(other.date)
 
96
              && this.leapMilliseconds == other.leapMilliseconds
 
97
              && this.hasTimeZone == other.hasTimeZone);
 
98
    }
 
99
 
 
100
    public int hashCode() {
 
101
      return date.hashCode();
 
102
    }
 
103
 
 
104
    Date getDate() {
 
105
      return date;
 
106
    }
 
107
 
 
108
    int getLeapMilliseconds() {
 
109
      return leapMilliseconds;
 
110
    }
 
111
 
 
112
    boolean getHasTimeZone() {
 
113
      return hasTimeZone;
 
114
    }
 
115
  }
 
116
 
 
117
  // XXX Check leap second validity?
 
118
  // XXX Allow 24:00:00?
 
119
  Object getValue(String str, ValidationContext vc) throws DatatypeException {
 
120
    boolean negative = false;
 
121
    int year = 2000; // any leap year will do
 
122
    int month = 1;
 
123
    int day = 1;
 
124
    int hours = 0;
 
125
    int minutes = 0;
 
126
    int seconds = 0;
 
127
    int milliseconds = 0;
 
128
    int pos = 0;
 
129
    int len = str.length();
 
130
    for (int templateIndex = 0, templateLength = template.length();
 
131
         templateIndex < templateLength;
 
132
         templateIndex++) {
 
133
      char templateChar = template.charAt(templateIndex);
 
134
      switch (templateChar) {
 
135
      case 'Y':
 
136
        negative = str.charAt(pos) == '-';
 
137
        int yearStartIndex = negative ? pos + 1 : pos;
 
138
        pos = skipDigits(str, yearStartIndex);
 
139
        try {
 
140
          year = Integer.parseInt(str.substring(yearStartIndex, pos));
 
141
        }
 
142
        catch (NumberFormatException e) {
 
143
          throw createLexicallyInvalidException();
 
144
        }
 
145
        break;
 
146
      case 'M':
 
147
        month = parse2Digits(str, pos);
 
148
        pos += 2;
 
149
        break;
 
150
      case 'D':
 
151
        day = parse2Digits(str, pos);
 
152
        pos += 2;
 
153
        break;
 
154
      case 't':
 
155
        hours = parse2Digits(str, pos);
 
156
        pos += 3;
 
157
        minutes = parse2Digits(str, pos);
 
158
        pos += 3;
 
159
        seconds = parse2Digits(str, pos);
 
160
        pos += 2;
 
161
        if (pos < len && str.charAt(pos) == '.') {
 
162
          int end = skipDigits(str, ++pos);
 
163
          for (int j = 0; j < 3; j++) {
 
164
            milliseconds *= 10;
 
165
            if (pos < end)
 
166
              milliseconds += str.charAt(pos++) - '0';
 
167
          }
 
168
          pos = end;
 
169
        }
 
170
        break;
 
171
      default:
 
172
        pos++;
 
173
        break;
 
174
      }
 
175
    }
 
176
    boolean hasTimeZone = pos < len;
 
177
    int tzOffset;
 
178
    if (hasTimeZone && str.charAt(pos) != 'Z')
 
179
      tzOffset = parseTimeZone(str, pos);
 
180
    else
 
181
      tzOffset = 0;
 
182
    int leapMilliseconds;
 
183
    if (seconds == 60) {
 
184
      leapMilliseconds = milliseconds + 1;
 
185
      milliseconds = 999;
 
186
      seconds = 59;
 
187
    }
 
188
    else
 
189
      leapMilliseconds = 0;
 
190
    try {
 
191
      GregorianCalendar cal = CalendarFactory.getCalendar();
 
192
      Date date;
 
193
      if (cal == CalendarFactory.cal) {
 
194
        synchronized (cal) {
 
195
          date = createDate(cal, tzOffset, negative, year, month, day, hours, minutes, seconds, milliseconds);
 
196
        }
 
197
      }
 
198
      else
 
199
        date = createDate(cal, tzOffset, negative, year, month, day, hours, minutes, seconds, milliseconds);
 
200
      return new DateTime(date, leapMilliseconds, hasTimeZone);
 
201
    }
 
202
    catch (IllegalArgumentException e) {
 
203
      throw createLexicallyInvalidException();
 
204
    }
 
205
  }
 
206
 
 
207
  // The GregorianCalendar constructor is incredibly slow with some
 
208
  // versions of GCJ (specifically the version shipped with RedHat 9).
 
209
  // This code attempts to detect when construction is slow.
 
210
  // When it is, we synchronize access to a single
 
211
  // object; otherwise, we create a new object each time we need it
 
212
  // so as to avoid thread lock contention.
 
213
 
 
214
  static class CalendarFactory {
 
215
    static private final int UNKNOWN = -1;
 
216
    static private final int SLOW = 0;
 
217
    static private final int FAST = 1;
 
218
    static private final int LIMIT = 10;
 
219
    static private int speed = UNKNOWN;
 
220
    static GregorianCalendar cal = new GregorianCalendar();
 
221
 
 
222
    static GregorianCalendar getCalendar() {
 
223
      // Don't need to synchronize this because speed is atomic.
 
224
      switch (speed) {
 
225
      case SLOW:
 
226
        return cal;
 
227
      case FAST:
 
228
        return new GregorianCalendar();
 
229
      }
 
230
      // Note that we are not timing the first construction (which happens
 
231
      // at class initialization), since that may involve one-time cache
 
232
      // initialization.
 
233
      long start = System.currentTimeMillis();
 
234
      GregorianCalendar tem = new GregorianCalendar();
 
235
      long time = System.currentTimeMillis() - start;
 
236
      speed =  time > LIMIT ? SLOW : FAST;
 
237
      return tem;
 
238
    }
 
239
  }
 
240
 
 
241
  private static Date createDate(GregorianCalendar cal, int tzOffset, boolean negative,
 
242
                                 int year, int month, int day,
 
243
                                 int hours, int minutes, int seconds, int milliseconds) {
 
244
    cal.setLenient(false);
 
245
    cal.setGregorianChange(new Date(Long.MIN_VALUE));
 
246
    cal.clear();
 
247
    // Using a time zone of "GMT+XX:YY" doesn't work with JDK 1.1, so we have to do it like this.
 
248
    cal.set(Calendar.ZONE_OFFSET, tzOffset);
 
249
    cal.set(Calendar.DST_OFFSET, 0);
 
250
    cal.set(Calendar.ERA, negative ? GregorianCalendar.BC : GregorianCalendar.AD);
 
251
    // months in ISO8601 start with 1; months in Java start with 0
 
252
    month -= 1;
 
253
    cal.set(year, month, day, hours, minutes, seconds);
 
254
    cal.set(Calendar.MILLISECOND, milliseconds);
 
255
    checkDate(cal.isLeapYear(year), month, day); // for GCJ
 
256
    return cal.getTime();
 
257
  }
 
258
 
 
259
  static private void checkDate(boolean isLeapYear, int month, int day) {
 
260
    if (month < 0 || month > 11 || day < 1)
 
261
      throw new IllegalArgumentException();
 
262
    int dayMax;
 
263
    switch (month) {
 
264
    // Thirty days have September, April, June and November...
 
265
    case Calendar.SEPTEMBER:
 
266
    case Calendar.APRIL:
 
267
    case Calendar.JUNE:
 
268
    case Calendar.NOVEMBER:
 
269
      dayMax = 30;
 
270
      break;
 
271
    case Calendar.FEBRUARY:
 
272
      dayMax = isLeapYear ? 29 : 28;
 
273
      break;
 
274
    default:
 
275
      dayMax = 31;
 
276
      break;
 
277
    }
 
278
    if (day > dayMax)
 
279
      throw new IllegalArgumentException();
 
280
  }
 
281
 
 
282
  static private int parseTimeZone(String str, int i) {
 
283
    int sign = str.charAt(i) == '-' ? -1 : 1;
 
284
    return (Integer.parseInt(str.substring(i + 1, i + 3))*60 + Integer.parseInt(str.substring(i + 4)))*60*1000*sign;
 
285
  }
 
286
 
 
287
  static private int parse2Digits(String str, int i) {
 
288
    return (str.charAt(i) - '0')*10 + (str.charAt(i + 1) - '0');
 
289
  }
 
290
 
 
291
  static private int skipDigits(String str, int i) {
 
292
    for (int len = str.length(); i < len; i++) {
 
293
      if ("0123456789".indexOf(str.charAt(i)) < 0)
 
294
        break;
 
295
    }
 
296
    return i;
 
297
  }
 
298
 
 
299
  OrderRelation getOrderRelation() {
 
300
    return this;
 
301
  }
 
302
 
 
303
  static private final int TIME_ZONE_MAX = 14*60*60*1000;
 
304
 
 
305
  public boolean isLessThan(Object obj1, Object obj2) {
 
306
    DateTime dt1 = (DateTime)obj1;
 
307
    DateTime dt2 = (DateTime)obj2;
 
308
    long t1 = dt1.getDate().getTime();
 
309
    long t2 = dt2.getDate().getTime();
 
310
    if (dt1.getHasTimeZone() == dt2.getHasTimeZone())
 
311
      return isLessThan(t1,
 
312
                        dt1.getLeapMilliseconds(),
 
313
                        t2,
 
314
                        dt2.getLeapMilliseconds());
 
315
    else if (!dt2.getHasTimeZone())
 
316
      return isLessThan(t1, dt1.getLeapMilliseconds(), t2 - TIME_ZONE_MAX, dt2.getLeapMilliseconds());
 
317
    else
 
318
      return isLessThan(t1 + TIME_ZONE_MAX, dt1.getLeapMilliseconds(), t2, dt2.getLeapMilliseconds());
 
319
  }
 
320
 
 
321
  static private boolean isLessThan(long t1, int leapMillis1, long t2, int leapMillis2) {
 
322
    if (t1 < t2)
 
323
      return true;
 
324
    if (t1 > t2)
 
325
      return false;
 
326
    if (leapMillis1 < leapMillis2)
 
327
      return true;
 
328
    return false;
 
329
  }
 
330
}