49 * |
49 * |
50 * @version $Id: AbstractDateTimeDV.java,v 1.7 2010-11-01 04:39:46 joehw Exp $ |
50 * @version $Id: AbstractDateTimeDV.java,v 1.7 2010-11-01 04:39:46 joehw Exp $ |
51 */ |
51 */ |
52 public abstract class AbstractDateTimeDV extends TypeValidator { |
52 public abstract class AbstractDateTimeDV extends TypeValidator { |
53 |
53 |
54 //debugging |
54 //debugging |
55 private static final boolean DEBUG=false; |
55 private static final boolean DEBUG = false; |
56 |
56 //define shared variables for date/time |
57 //define shared variables for date/time |
57 //define constants to be used in assigning default values for |
58 |
58 //all date/time excluding duration |
59 |
59 protected final static int YEAR = 2000; |
60 //define constants to be used in assigning default values for |
60 protected final static int MONTH = 01; |
61 //all date/time excluding duration |
61 protected final static int DAY = 01; |
62 protected final static int YEAR=2000; |
|
63 protected final static int MONTH=01; |
|
64 protected final static int DAY = 01; |
|
65 |
|
66 protected static final DatatypeFactory datatypeFactory = new DatatypeFactoryImpl(); |
62 protected static final DatatypeFactory datatypeFactory = new DatatypeFactoryImpl(); |
67 |
63 |
68 public short getAllowedFacets(){ |
64 @Override |
69 return ( XSSimpleTypeDecl.FACET_PATTERN | XSSimpleTypeDecl.FACET_WHITESPACE | XSSimpleTypeDecl.FACET_ENUMERATION |XSSimpleTypeDecl.FACET_MAXINCLUSIVE |XSSimpleTypeDecl.FACET_MININCLUSIVE | XSSimpleTypeDecl.FACET_MAXEXCLUSIVE | XSSimpleTypeDecl.FACET_MINEXCLUSIVE ); |
65 public short getAllowedFacets() { |
70 }//getAllowedFacets() |
66 return (XSSimpleTypeDecl.FACET_PATTERN | XSSimpleTypeDecl.FACET_WHITESPACE | XSSimpleTypeDecl.FACET_ENUMERATION | XSSimpleTypeDecl.FACET_MAXINCLUSIVE | XSSimpleTypeDecl.FACET_MININCLUSIVE | XSSimpleTypeDecl.FACET_MAXEXCLUSIVE | XSSimpleTypeDecl.FACET_MINEXCLUSIVE); |
71 |
67 }//getAllowedFacets() |
72 |
68 |
73 // distinguishes between identity and equality for date/time values |
69 // distinguishes between identity and equality for date/time values |
74 // ie: two values representing the same "moment in time" but with different |
70 // ie: two values representing the same "moment in time" but with different |
75 // remembered timezones are now equal but not identical. |
71 // remembered timezones are now equal but not identical. |
76 public boolean isIdentical (Object value1, Object value2) { |
72 @Override |
77 if (!(value1 instanceof DateTimeData) || !(value2 instanceof DateTimeData)) { |
73 public boolean isIdentical(Object value1, Object value2) { |
78 return false; |
74 if (!(value1 instanceof DateTimeData) || !(value2 instanceof DateTimeData)) { |
|
75 return false; |
|
76 } |
|
77 |
|
78 DateTimeData v1 = (DateTimeData) value1; |
|
79 DateTimeData v2 = (DateTimeData) value2; |
|
80 |
|
81 // original timezones must be the same in addition to date/time values |
|
82 // being 'equal' |
|
83 if ((v1.timezoneHr == v2.timezoneHr) && (v1.timezoneMin == v2.timezoneMin)) { |
|
84 return v1.equals(v2); |
|
85 } |
|
86 |
|
87 return false; |
|
88 }//isIdentical() |
|
89 |
|
90 // the parameters are in compiled form (from getActualValue) |
|
91 @Override |
|
92 public int compare(Object value1, Object value2) { |
|
93 return compareDates(((DateTimeData) value1), |
|
94 ((DateTimeData) value2), true); |
|
95 }//compare() |
|
96 |
|
97 /** |
|
98 * Compare algorithm described in dateDime (3.2.7). Duration datatype |
|
99 * overwrites this method |
|
100 * |
|
101 * @param date1 normalized date representation of the first value |
|
102 * @param date2 normalized date representation of the second value |
|
103 * @param strict |
|
104 * @return less, greater, less_equal, greater_equal, equal |
|
105 */ |
|
106 protected short compareDates(DateTimeData date1, DateTimeData date2, boolean strict) { |
|
107 if (date1.utc == date2.utc) { |
|
108 return compareOrder(date1, date2); |
|
109 } |
|
110 short c1, c2; |
|
111 |
|
112 DateTimeData tempDate = new DateTimeData(null, this); |
|
113 |
|
114 if (date1.utc == 'Z') { |
|
115 |
|
116 //compare date1<=date1<=(date2 with time zone -14) |
|
117 // |
|
118 cloneDate(date2, tempDate); //clones date1 value to global temporary storage: fTempDate |
|
119 tempDate.timezoneHr = 14; |
|
120 tempDate.timezoneMin = 0; |
|
121 tempDate.utc = '+'; |
|
122 normalize(tempDate); |
|
123 c1 = compareOrder(date1, tempDate); |
|
124 if (c1 == LESS_THAN) { |
|
125 return c1; |
|
126 } |
|
127 |
|
128 //compare date1>=(date2 with time zone +14) |
|
129 // |
|
130 cloneDate(date2, tempDate); //clones date1 value to global temporary storage: tempDate |
|
131 tempDate.timezoneHr = -14; |
|
132 tempDate.timezoneMin = 0; |
|
133 tempDate.utc = '-'; |
|
134 normalize(tempDate); |
|
135 c2 = compareOrder(date1, tempDate); |
|
136 if (c2 == GREATER_THAN) { |
|
137 return c2; |
|
138 } |
|
139 |
|
140 return INDETERMINATE; |
|
141 } else if (date2.utc == 'Z') { |
|
142 |
|
143 //compare (date1 with time zone -14)<=date2 |
|
144 // |
|
145 cloneDate(date1, tempDate); //clones date1 value to global temporary storage: tempDate |
|
146 tempDate.timezoneHr = -14; |
|
147 tempDate.timezoneMin = 0; |
|
148 tempDate.utc = '-'; |
|
149 if (DEBUG) { |
|
150 System.out.println("tempDate=" + dateToString(tempDate)); |
|
151 } |
|
152 normalize(tempDate); |
|
153 c1 = compareOrder(tempDate, date2); |
|
154 if (DEBUG) { |
|
155 System.out.println("date=" + dateToString(date2)); |
|
156 System.out.println("tempDate=" + dateToString(tempDate)); |
|
157 } |
|
158 if (c1 == LESS_THAN) { |
|
159 return c1; |
|
160 } |
|
161 |
|
162 //compare (date1 with time zone +14)<=date2 |
|
163 // |
|
164 cloneDate(date1, tempDate); //clones date1 value to global temporary storage: tempDate |
|
165 tempDate.timezoneHr = 14; |
|
166 tempDate.timezoneMin = 0; |
|
167 tempDate.utc = '+'; |
|
168 normalize(tempDate); |
|
169 c2 = compareOrder(tempDate, date2); |
|
170 if (DEBUG) { |
|
171 System.out.println("tempDate=" + dateToString(tempDate)); |
|
172 } |
|
173 if (c2 == GREATER_THAN) { |
|
174 return c2; |
|
175 } |
|
176 |
|
177 return INDETERMINATE; |
|
178 } |
|
179 return INDETERMINATE; |
|
180 |
|
181 } |
|
182 |
|
183 /** |
|
184 * Given normalized values, determines order-relation between give date/time |
|
185 * objects. |
|
186 * |
|
187 * @param date1 date/time object |
|
188 * @param date2 date/time object |
|
189 * @return 0 if date1 and date2 are equal, a value less than 0 if date1 is |
|
190 * less than date2, a value greater than 0 if date1 is greater than date2 |
|
191 */ |
|
192 protected short compareOrder(DateTimeData date1, DateTimeData date2) { |
|
193 if (date1.position < 1) { |
|
194 if (date1.year < date2.year) { |
|
195 return -1; |
|
196 } |
|
197 if (date1.year > date2.year) { |
|
198 return 1; |
|
199 } |
|
200 } |
|
201 if (date1.position < 2) { |
|
202 if (date1.month < date2.month) { |
|
203 return -1; |
|
204 } |
|
205 if (date1.month > date2.month) { |
|
206 return 1; |
|
207 } |
|
208 } |
|
209 if (date1.day < date2.day) { |
|
210 return -1; |
|
211 } |
|
212 if (date1.day > date2.day) { |
|
213 return 1; |
|
214 } |
|
215 if (date1.hour < date2.hour) { |
|
216 return -1; |
|
217 } |
|
218 if (date1.hour > date2.hour) { |
|
219 return 1; |
|
220 } |
|
221 if (date1.minute < date2.minute) { |
|
222 return -1; |
|
223 } |
|
224 if (date1.minute > date2.minute) { |
|
225 return 1; |
|
226 } |
|
227 if (date1.second < date2.second) { |
|
228 return -1; |
|
229 } |
|
230 if (date1.second > date2.second) { |
|
231 return 1; |
|
232 } |
|
233 if (date1.utc < date2.utc) { |
|
234 return -1; |
|
235 } |
|
236 if (date1.utc > date2.utc) { |
|
237 return 1; |
|
238 } |
|
239 return 0; |
|
240 } |
|
241 |
|
242 /** |
|
243 * Parses time hh:mm:ss.sss and time zone if any |
|
244 * |
|
245 * @param start |
|
246 * @param end |
|
247 * @param data |
|
248 * @exception RuntimeException |
|
249 */ |
|
250 protected void getTime(String buffer, int start, int end, DateTimeData data) throws RuntimeException { |
|
251 |
|
252 int stop = start + 2; |
|
253 |
|
254 //get hours (hh) |
|
255 data.hour = parseInt(buffer, start, stop); |
|
256 |
|
257 //get minutes (mm) |
|
258 |
|
259 if (buffer.charAt(stop++) != ':') { |
|
260 throw new RuntimeException("Error in parsing time zone"); |
|
261 } |
|
262 start = stop; |
|
263 stop = stop + 2; |
|
264 data.minute = parseInt(buffer, start, stop); |
|
265 |
|
266 //get seconds (ss) |
|
267 if (buffer.charAt(stop++) != ':') { |
|
268 throw new RuntimeException("Error in parsing time zone"); |
|
269 } |
|
270 |
|
271 //find UTC sign if any |
|
272 int sign = findUTCSign(buffer, start, end); |
|
273 |
|
274 //get seconds (ms) |
|
275 start = stop; |
|
276 stop = sign < 0 ? end : sign; |
|
277 data.second = parseSecond(buffer, start, stop); |
|
278 |
|
279 //parse UTC time zone (hh:mm) |
|
280 if (sign > 0) { |
|
281 getTimeZone(buffer, data, sign, end); |
|
282 } |
|
283 } |
|
284 |
|
285 /** |
|
286 * Parses date CCYY-MM-DD |
|
287 * |
|
288 * @param buffer |
|
289 * @param start start position |
|
290 * @param end end position |
|
291 * @param date |
|
292 * @exception RuntimeException |
|
293 */ |
|
294 protected int getDate(String buffer, int start, int end, DateTimeData date) throws RuntimeException { |
|
295 |
|
296 start = getYearMonth(buffer, start, end, date); |
|
297 |
|
298 if (buffer.charAt(start++) != '-') { |
|
299 throw new RuntimeException("CCYY-MM must be followed by '-' sign"); |
|
300 } |
|
301 int stop = start + 2; |
|
302 date.day = parseInt(buffer, start, stop); |
|
303 return stop; |
|
304 } |
|
305 |
|
306 /** |
|
307 * Parses date CCYY-MM |
|
308 * |
|
309 * @param buffer |
|
310 * @param start start position |
|
311 * @param end end position |
|
312 * @param date |
|
313 * @exception RuntimeException |
|
314 */ |
|
315 protected int getYearMonth(String buffer, int start, int end, DateTimeData date) throws RuntimeException { |
|
316 |
|
317 if (buffer.charAt(0) == '-') { |
|
318 // REVISIT: date starts with preceding '-' sign |
|
319 // do we have to do anything with it? |
|
320 // |
|
321 start++; |
|
322 } |
|
323 int i = indexOf(buffer, start, end, '-'); |
|
324 if (i == -1) { |
|
325 throw new RuntimeException("Year separator is missing or misplaced"); |
|
326 } |
|
327 int length = i - start; |
|
328 if (length < 4) { |
|
329 throw new RuntimeException("Year must have 'CCYY' format"); |
|
330 } else if (length > 4 && buffer.charAt(start) == '0') { |
|
331 throw new RuntimeException("Leading zeros are required if the year value would otherwise have fewer than four digits; otherwise they are forbidden"); |
|
332 } |
|
333 date.year = parseIntYear(buffer, i); |
|
334 if (buffer.charAt(i) != '-') { |
|
335 throw new RuntimeException("CCYY must be followed by '-' sign"); |
|
336 } |
|
337 start = ++i; |
|
338 i = start + 2; |
|
339 date.month = parseInt(buffer, start, i); |
|
340 return i; //fStart points right after the MONTH |
|
341 } |
|
342 |
|
343 /** |
|
344 * Shared code from Date and YearMonth datatypes. Finds if time zone sign is |
|
345 * present |
|
346 * |
|
347 * @param end |
|
348 * @param date |
|
349 * @exception RuntimeException |
|
350 */ |
|
351 protected void parseTimeZone(String buffer, int start, int end, DateTimeData date) throws RuntimeException { |
|
352 |
|
353 //fStart points right after the date |
|
354 |
|
355 if (start < end) { |
|
356 if (!isNextCharUTCSign(buffer, start, end)) { |
|
357 throw new RuntimeException("Error in month parsing"); |
|
358 } else { |
|
359 getTimeZone(buffer, date, start, end); |
|
360 } |
|
361 } |
|
362 } |
|
363 |
|
364 /** |
|
365 * Parses time zone: 'Z' or {+,-} followed by hh:mm |
|
366 * |
|
367 * @param data |
|
368 * @param sign |
|
369 * @exception RuntimeException |
|
370 */ |
|
371 protected void getTimeZone(String buffer, DateTimeData data, int sign, int end) throws RuntimeException { |
|
372 data.utc = buffer.charAt(sign); |
|
373 |
|
374 if (buffer.charAt(sign) == 'Z') { |
|
375 if (end > (++sign)) { |
|
376 throw new RuntimeException("Error in parsing time zone"); |
|
377 } |
|
378 return; |
|
379 } |
|
380 if (sign <= (end - 6)) { |
|
381 |
|
382 int negate = buffer.charAt(sign) == '-' ? -1 : 1; |
|
383 //parse hr |
|
384 int stop = ++sign + 2; |
|
385 data.timezoneHr = negate * parseInt(buffer, sign, stop); |
|
386 if (buffer.charAt(stop++) != ':') { |
|
387 throw new RuntimeException("Error in parsing time zone"); |
|
388 } |
|
389 |
|
390 //parse min |
|
391 data.timezoneMin = negate * parseInt(buffer, stop, stop + 2); |
|
392 |
|
393 if (stop + 2 != end) { |
|
394 throw new RuntimeException("Error in parsing time zone"); |
|
395 } |
|
396 if (data.timezoneHr != 0 || data.timezoneMin != 0) { |
|
397 data.normalized = false; |
|
398 } |
|
399 } else { |
|
400 throw new RuntimeException("Error in parsing time zone"); |
|
401 } |
|
402 if (DEBUG) { |
|
403 System.out.println("time[hh]=" + data.timezoneHr + " time[mm]=" + data.timezoneMin); |
|
404 } |
|
405 } |
|
406 |
|
407 /** |
|
408 * Computes index of given char within StringBuffer |
|
409 * |
|
410 * @param start |
|
411 * @param end |
|
412 * @param ch character to look for in StringBuffer |
|
413 * @return index of ch within StringBuffer |
|
414 */ |
|
415 protected int indexOf(String buffer, int start, int end, char ch) { |
|
416 for (int i = start; i < end; i++) { |
|
417 if (buffer.charAt(i) == ch) { |
|
418 return i; |
|
419 } |
|
420 } |
|
421 return -1; |
|
422 } |
|
423 |
|
424 /** |
|
425 * Validates given date/time object accoring to W3C PR Schema [D.1 ISO 8601 |
|
426 * Conventions] |
|
427 * |
|
428 * @param data |
|
429 */ |
|
430 protected void validateDateTime(DateTimeData data) { |
|
431 |
|
432 //REVISIT: should we throw an exception for not valid dates |
|
433 // or reporting an error message should be sufficient? |
|
434 |
|
435 /** |
|
436 * XML Schema 1.1 - RQ-123: Allow year 0000 in date related types. |
|
437 */ |
|
438 if (!Constants.SCHEMA_1_1_SUPPORT && data.year == 0) { |
|
439 throw new RuntimeException("The year \"0000\" is an illegal year value"); |
|
440 |
|
441 } |
|
442 |
|
443 if (data.month < 1 || data.month > 12) { |
|
444 throw new RuntimeException("The month must have values 1 to 12"); |
|
445 |
|
446 } |
|
447 |
|
448 //validate days |
|
449 if (data.day > maxDayInMonthFor(data.year, data.month) || data.day < 1) { |
|
450 throw new RuntimeException("The day must have values 1 to 31"); |
|
451 } |
|
452 |
|
453 //validate hours |
|
454 if (data.hour > 23 || data.hour < 0) { |
|
455 if (data.hour == 24 && data.minute == 0 && data.second == 0) { |
|
456 data.hour = 0; |
|
457 if (++data.day > maxDayInMonthFor(data.year, data.month)) { |
|
458 data.day = 1; |
|
459 if (++data.month > 12) { |
|
460 data.month = 1; |
|
461 if (Constants.SCHEMA_1_1_SUPPORT) { |
|
462 ++data.year; |
|
463 } else if (++data.year == 0) { |
|
464 data.year = 1; |
|
465 } |
|
466 } |
79 } |
467 } |
80 |
468 } else { |
81 DateTimeData v1 = (DateTimeData)value1; |
469 throw new RuntimeException("Hour must have values 0-23, unless 24:00:00"); |
82 DateTimeData v2 = (DateTimeData)value2; |
470 } |
83 |
471 } |
84 // original timezones must be the same in addition to date/time values |
472 |
85 // being 'equal' |
473 //validate |
86 if ((v1.timezoneHr == v2.timezoneHr) && (v1.timezoneMin == v2.timezoneMin)) { |
474 if (data.minute > 59 || data.minute < 0) { |
87 return v1.equals(v2); |
475 throw new RuntimeException("Minute must have values 0-59"); |
88 } |
476 } |
89 |
477 |
90 return false; |
478 //validate |
91 }//isIdentical() |
479 if (data.second >= 60 || data.second < 0) { |
92 |
480 throw new RuntimeException("Second must have values 0-59"); |
93 // the parameters are in compiled form (from getActualValue) |
481 |
94 public int compare (Object value1, Object value2) { |
482 } |
95 return compareDates(((DateTimeData)value1), |
483 |
96 ((DateTimeData)value2), true); |
484 //validate |
97 }//compare() |
485 if (data.timezoneHr > 14 || data.timezoneHr < -14) { |
98 |
486 throw new RuntimeException("Time zone should have range -14:00 to +14:00"); |
99 /** |
487 } else { |
100 * Compare algorithm described in dateDime (3.2.7). |
488 if ((data.timezoneHr == 14 || data.timezoneHr == -14) && data.timezoneMin != 0) { |
101 * Duration datatype overwrites this method |
489 throw new RuntimeException("Time zone should have range -14:00 to +14:00"); |
102 * |
490 } else if (data.timezoneMin > 59 || data.timezoneMin < -59) { |
103 * @param date1 normalized date representation of the first value |
491 throw new RuntimeException("Minute must have values 0-59"); |
104 * @param date2 normalized date representation of the second value |
492 } |
105 * @param strict |
493 } |
106 * @return less, greater, less_equal, greater_equal, equal |
494 |
107 */ |
495 } |
108 protected short compareDates(DateTimeData date1, DateTimeData date2, boolean strict) { |
496 |
109 if (date1.utc == date2.utc) { |
497 /** |
110 return compareOrder(date1, date2); |
498 * Return index of UTC char: 'Z', '+', '-' |
111 } |
499 * |
112 short c1, c2; |
500 * @param start |
113 |
501 * @param end |
114 DateTimeData tempDate = new DateTimeData(null, this); |
502 * @return index of the UTC character that was found |
115 |
503 */ |
116 if ( date1.utc=='Z' ) { |
504 protected int findUTCSign(String buffer, int start, int end) { |
117 |
505 int c; |
118 //compare date1<=date1<=(date2 with time zone -14) |
506 for (int i = start; i < end; i++) { |
119 // |
507 c = buffer.charAt(i); |
120 cloneDate(date2, tempDate); //clones date1 value to global temporary storage: fTempDate |
508 if (c == 'Z' || c == '+' || c == '-') { |
121 tempDate.timezoneHr=14; |
509 return i; |
122 tempDate.timezoneMin = 0; |
510 } |
123 tempDate.utc='+'; |
511 |
124 normalize(tempDate); |
512 } |
125 c1 = compareOrder(date1, tempDate); |
513 return -1; |
126 if (c1 == LESS_THAN) |
514 } |
127 return c1; |
515 |
128 |
516 /** |
129 //compare date1>=(date2 with time zone +14) |
517 * Returns |
130 // |
518 * <code>true</code> if the character at start is 'Z', '+' or '-'. |
131 cloneDate(date2, tempDate); //clones date1 value to global temporary storage: tempDate |
|
132 tempDate.timezoneHr = -14; |
|
133 tempDate.timezoneMin = 0; |
|
134 tempDate.utc='-'; |
|
135 normalize(tempDate); |
|
136 c2 = compareOrder(date1, tempDate); |
|
137 if (c2 == GREATER_THAN) |
|
138 return c2; |
|
139 |
|
140 return INDETERMINATE; |
|
141 } |
|
142 else if ( date2.utc=='Z' ) { |
|
143 |
|
144 //compare (date1 with time zone -14)<=date2 |
|
145 // |
|
146 cloneDate(date1, tempDate); //clones date1 value to global temporary storage: tempDate |
|
147 tempDate.timezoneHr = -14; |
|
148 tempDate.timezoneMin = 0; |
|
149 tempDate.utc='-'; |
|
150 if (DEBUG) { |
|
151 System.out.println("tempDate=" + dateToString(tempDate)); |
|
152 } |
|
153 normalize(tempDate); |
|
154 c1 = compareOrder(tempDate, date2); |
|
155 if (DEBUG) { |
|
156 System.out.println("date=" + dateToString(date2)); |
|
157 System.out.println("tempDate=" + dateToString(tempDate)); |
|
158 } |
|
159 if (c1 == LESS_THAN) |
|
160 return c1; |
|
161 |
|
162 //compare (date1 with time zone +14)<=date2 |
|
163 // |
|
164 cloneDate(date1, tempDate); //clones date1 value to global temporary storage: tempDate |
|
165 tempDate.timezoneHr = 14; |
|
166 tempDate.timezoneMin = 0; |
|
167 tempDate.utc='+'; |
|
168 normalize(tempDate); |
|
169 c2 = compareOrder(tempDate, date2); |
|
170 if (DEBUG) { |
|
171 System.out.println("tempDate=" + dateToString(tempDate)); |
|
172 } |
|
173 if (c2 == GREATER_THAN) |
|
174 return c2; |
|
175 |
|
176 return INDETERMINATE; |
|
177 } |
|
178 return INDETERMINATE; |
|
179 |
|
180 } |
|
181 |
|
182 /** |
|
183 * Given normalized values, determines order-relation |
|
184 * between give date/time objects. |
|
185 * |
|
186 * @param date1 date/time object |
|
187 * @param date2 date/time object |
|
188 * @return 0 if date1 and date2 are equal, a value less than 0 if date1 is less than date2, a value greater than 0 if date1 is greater than date2 |
|
189 */ |
|
190 protected short compareOrder(DateTimeData date1, DateTimeData date2) { |
|
191 if(date1.position < 1) { |
|
192 if (date1.year < date2.year) |
|
193 return -1; |
|
194 if (date1.year > date2.year) |
|
195 return 1; |
|
196 } |
|
197 if(date1.position < 2) { |
|
198 if (date1.month < date2.month) |
|
199 return -1; |
|
200 if (date1.month > date2.month) |
|
201 return 1; |
|
202 } |
|
203 if (date1.day < date2.day) |
|
204 return -1; |
|
205 if (date1.day > date2.day) |
|
206 return 1; |
|
207 if (date1.hour < date2.hour) |
|
208 return -1; |
|
209 if (date1.hour > date2.hour) |
|
210 return 1; |
|
211 if (date1.minute < date2.minute) |
|
212 return -1; |
|
213 if (date1.minute > date2.minute) |
|
214 return 1; |
|
215 if (date1.second < date2.second) |
|
216 return -1; |
|
217 if (date1.second > date2.second) |
|
218 return 1; |
|
219 if (date1.utc < date2.utc) |
|
220 return -1; |
|
221 if (date1.utc > date2.utc) |
|
222 return 1; |
|
223 return 0; |
|
224 } |
|
225 |
|
226 /** |
|
227 * Parses time hh:mm:ss.sss and time zone if any |
|
228 * |
|
229 * @param start |
|
230 * @param end |
|
231 * @param data |
|
232 * @exception RuntimeException |
|
233 */ |
|
234 protected void getTime (String buffer, int start, int end, DateTimeData data) throws RuntimeException{ |
|
235 |
|
236 int stop = start+2; |
|
237 |
|
238 //get hours (hh) |
|
239 data.hour=parseInt(buffer, start,stop); |
|
240 |
|
241 //get minutes (mm) |
|
242 |
|
243 if (buffer.charAt(stop++)!=':') { |
|
244 throw new RuntimeException("Error in parsing time zone" ); |
|
245 } |
|
246 start = stop; |
|
247 stop = stop+2; |
|
248 data.minute=parseInt(buffer, start,stop); |
|
249 |
|
250 //get seconds (ss) |
|
251 if (buffer.charAt(stop++)!=':') { |
|
252 throw new RuntimeException("Error in parsing time zone" ); |
|
253 } |
|
254 |
|
255 //find UTC sign if any |
|
256 int sign = findUTCSign(buffer, start, end); |
|
257 |
|
258 //get seconds (ms) |
|
259 start = stop; |
|
260 stop = sign < 0 ? end : sign; |
|
261 data.second = parseSecond(buffer, start, stop); |
|
262 |
|
263 //parse UTC time zone (hh:mm) |
|
264 if (sign > 0) { |
|
265 getTimeZone(buffer, data, sign, end); |
|
266 } |
|
267 } |
|
268 |
|
269 /** |
|
270 * Parses date CCYY-MM-DD |
|
271 * |
|
272 * @param buffer |
|
273 * @param start start position |
|
274 * @param end end position |
|
275 * @param date |
|
276 * @exception RuntimeException |
|
277 */ |
|
278 protected int getDate (String buffer, int start, int end, DateTimeData date) throws RuntimeException{ |
|
279 |
|
280 start = getYearMonth(buffer, start, end, date); |
|
281 |
|
282 if (buffer.charAt(start++) !='-') { |
|
283 throw new RuntimeException("CCYY-MM must be followed by '-' sign"); |
|
284 } |
|
285 int stop = start + 2; |
|
286 date.day=parseInt(buffer, start, stop); |
|
287 return stop; |
|
288 } |
|
289 |
|
290 /** |
|
291 * Parses date CCYY-MM |
|
292 * |
|
293 * @param buffer |
|
294 * @param start start position |
|
295 * @param end end position |
|
296 * @param date |
|
297 * @exception RuntimeException |
|
298 */ |
|
299 protected int getYearMonth (String buffer, int start, int end, DateTimeData date) throws RuntimeException{ |
|
300 |
|
301 if ( buffer.charAt(0)=='-' ) { |
|
302 // REVISIT: date starts with preceding '-' sign |
|
303 // do we have to do anything with it? |
|
304 // |
|
305 start++; |
|
306 } |
|
307 int i = indexOf(buffer, start, end, '-'); |
|
308 if ( i==-1 ) throw new RuntimeException("Year separator is missing or misplaced"); |
|
309 int length = i-start; |
|
310 if (length<4) { |
|
311 throw new RuntimeException("Year must have 'CCYY' format"); |
|
312 } |
|
313 else if (length > 4 && buffer.charAt(start)=='0'){ |
|
314 throw new RuntimeException("Leading zeros are required if the year value would otherwise have fewer than four digits; otherwise they are forbidden"); |
|
315 } |
|
316 date.year= parseIntYear(buffer, i); |
|
317 if (buffer.charAt(i)!='-') { |
|
318 throw new RuntimeException("CCYY must be followed by '-' sign"); |
|
319 } |
|
320 start = ++i; |
|
321 i = start +2; |
|
322 date.month=parseInt(buffer, start, i); |
|
323 return i; //fStart points right after the MONTH |
|
324 } |
|
325 |
|
326 /** |
|
327 * Shared code from Date and YearMonth datatypes. |
|
328 * Finds if time zone sign is present |
|
329 * |
|
330 * @param end |
|
331 * @param date |
|
332 * @exception RuntimeException |
|
333 */ |
|
334 protected void parseTimeZone (String buffer, int start, int end, DateTimeData date) throws RuntimeException{ |
|
335 |
|
336 //fStart points right after the date |
|
337 |
|
338 if ( start < end ) { |
|
339 if (!isNextCharUTCSign(buffer, start, end)) { |
|
340 throw new RuntimeException ("Error in month parsing"); |
|
341 } |
|
342 else { |
|
343 getTimeZone(buffer, date, start, end); |
|
344 } |
|
345 } |
|
346 } |
|
347 |
|
348 /** |
|
349 * Parses time zone: 'Z' or {+,-} followed by hh:mm |
|
350 * |
|
351 * @param data |
|
352 * @param sign |
|
353 * @exception RuntimeException |
|
354 */ |
|
355 protected void getTimeZone (String buffer, DateTimeData data, int sign, int end) throws RuntimeException{ |
|
356 data.utc=buffer.charAt(sign); |
|
357 |
|
358 if ( buffer.charAt(sign) == 'Z' ) { |
|
359 if (end>(++sign)) { |
|
360 throw new RuntimeException("Error in parsing time zone"); |
|
361 } |
|
362 return; |
|
363 } |
|
364 if ( sign<=(end-6) ) { |
|
365 |
|
366 int negate = buffer.charAt(sign) == '-'?-1:1; |
|
367 //parse hr |
|
368 int stop = ++sign+2; |
|
369 data.timezoneHr = negate*parseInt(buffer, sign, stop); |
|
370 if (buffer.charAt(stop++)!=':') { |
|
371 throw new RuntimeException("Error in parsing time zone" ); |
|
372 } |
|
373 |
|
374 //parse min |
|
375 data.timezoneMin = negate*parseInt(buffer, stop, stop+2); |
|
376 |
|
377 if ( stop+2!=end ) { |
|
378 throw new RuntimeException("Error in parsing time zone"); |
|
379 } |
|
380 if(data.timezoneHr != 0 || data.timezoneMin != 0) |
|
381 data.normalized = false; |
|
382 } |
|
383 else { |
|
384 throw new RuntimeException("Error in parsing time zone"); |
|
385 } |
|
386 if ( DEBUG ) { |
|
387 System.out.println("time[hh]="+data.timezoneHr + " time[mm]=" +data.timezoneMin); |
|
388 } |
|
389 } |
|
390 |
|
391 /** |
|
392 * Computes index of given char within StringBuffer |
|
393 * |
|
394 * @param start |
|
395 * @param end |
|
396 * @param ch character to look for in StringBuffer |
|
397 * @return index of ch within StringBuffer |
|
398 */ |
|
399 protected int indexOf (String buffer, int start, int end, char ch) { |
|
400 for ( int i=start;i<end;i++ ) { |
|
401 if ( buffer.charAt(i) == ch ) { |
|
402 return i; |
|
403 } |
|
404 } |
|
405 return -1; |
|
406 } |
|
407 |
|
408 /** |
|
409 * Validates given date/time object accoring to W3C PR Schema |
|
410 * [D.1 ISO 8601 Conventions] |
|
411 * |
|
412 * @param data |
|
413 */ |
|
414 protected void validateDateTime (DateTimeData data) { |
|
415 |
|
416 //REVISIT: should we throw an exception for not valid dates |
|
417 // or reporting an error message should be sufficient? |
|
418 |
|
419 /** |
|
420 * XML Schema 1.1 - RQ-123: Allow year 0000 in date related types. |
|
421 */ |
|
422 if (!Constants.SCHEMA_1_1_SUPPORT && data.year==0 ) { |
|
423 throw new RuntimeException("The year \"0000\" is an illegal year value"); |
|
424 |
|
425 } |
|
426 |
|
427 if ( data.month<1 || data.month>12 ) { |
|
428 throw new RuntimeException("The month must have values 1 to 12"); |
|
429 |
|
430 } |
|
431 |
|
432 //validate days |
|
433 if ( data.day>maxDayInMonthFor(data.year, data.month) || data.day<1 ) { |
|
434 throw new RuntimeException("The day must have values 1 to 31"); |
|
435 } |
|
436 |
|
437 //validate hours |
|
438 if ( data.hour>23 || data.hour<0 ) { |
|
439 if (data.hour == 24 && data.minute == 0 && data.second == 0) { |
|
440 data.hour = 0; |
|
441 if (++data.day > maxDayInMonthFor(data.year, data.month)) { |
|
442 data.day = 1; |
|
443 if (++data.month > 12) { |
|
444 data.month = 1; |
|
445 if (Constants.SCHEMA_1_1_SUPPORT) { |
|
446 ++data.year; |
|
447 } |
|
448 else if (++data.year == 0) { |
|
449 data.year = 1; |
|
450 } |
|
451 } |
|
452 } |
|
453 } |
|
454 else { |
|
455 throw new RuntimeException("Hour must have values 0-23, unless 24:00:00"); |
|
456 } |
|
457 } |
|
458 |
|
459 //validate |
|
460 if ( data.minute>59 || data.minute<0 ) { |
|
461 throw new RuntimeException("Minute must have values 0-59"); |
|
462 } |
|
463 |
|
464 //validate |
|
465 if ( data.second>=60 || data.second<0 ) { |
|
466 throw new RuntimeException("Second must have values 0-59"); |
|
467 |
|
468 } |
|
469 |
|
470 //validate |
|
471 if ( data.timezoneHr>14 || data.timezoneHr<-14 ) { |
|
472 throw new RuntimeException("Time zone should have range -14:00 to +14:00"); |
|
473 } |
|
474 else { |
|
475 if((data.timezoneHr == 14 || data.timezoneHr == -14) && data.timezoneMin != 0) |
|
476 throw new RuntimeException("Time zone should have range -14:00 to +14:00"); |
|
477 else if(data.timezoneMin > 59 || data.timezoneMin < -59) |
|
478 throw new RuntimeException("Minute must have values 0-59"); |
|
479 } |
|
480 |
|
481 } |
|
482 |
|
483 /** |
|
484 * Return index of UTC char: 'Z', '+', '-' |
|
485 * |
|
486 * @param start |
|
487 * @param end |
|
488 * @return index of the UTC character that was found |
|
489 */ |
|
490 protected int findUTCSign (String buffer, int start, int end) { |
|
491 int c; |
|
492 for ( int i=start;i<end;i++ ) { |
|
493 c=buffer.charAt(i); |
|
494 if ( c == 'Z' || c=='+' || c=='-' ) { |
|
495 return i; |
|
496 } |
|
497 |
|
498 } |
|
499 return -1; |
|
500 } |
|
501 |
|
502 /** |
|
503 * Returns <code>true</code> if the character at start is 'Z', '+' or '-'. |
|
504 */ |
519 */ |
505 protected final boolean isNextCharUTCSign(String buffer, int start, int end) { |
520 protected final boolean isNextCharUTCSign(String buffer, int start, int end) { |
506 if (start < end) { |
521 if (start < end) { |
507 char c = buffer.charAt(start); |
522 char c = buffer.charAt(start); |
508 return (c == 'Z' || c == '+' || c == '-'); |
523 return (c == 'Z' || c == '+' || c == '-'); |
509 } |
524 } |
510 return false; |
525 return false; |
511 } |
526 } |
512 |
527 |
513 /** |
528 /** |
514 * Given start and end position, parses string value |
529 * Given start and end position, parses string value |
515 * |
530 * |
516 * @param buffer string to parse |
531 * @param buffer string to parse |
517 * @param start start position |
532 * @param start start position |
518 * @param end end position |
533 * @param end end position |
519 * @return return integer representation of characters |
534 * @return return integer representation of characters |
520 */ |
535 */ |
521 protected int parseInt (String buffer, int start, int end) |
536 protected int parseInt(String buffer, int start, int end) |
522 throws NumberFormatException{ |
537 throws NumberFormatException { |
523 //REVISIT: more testing on this parsing needs to be done. |
538 //REVISIT: more testing on this parsing needs to be done. |
524 int radix=10; |
539 int radix = 10; |
525 int result = 0; |
540 int result = 0; |
526 int digit=0; |
541 int digit = 0; |
527 int limit = -Integer.MAX_VALUE; |
542 int limit = -Integer.MAX_VALUE; |
528 int multmin = limit / radix; |
543 int multmin = limit / radix; |
529 int i = start; |
544 int i = start; |
530 do { |
545 do { |
531 digit = getDigit(buffer.charAt(i)); |
546 digit = getDigit(buffer.charAt(i)); |
532 if ( digit < 0 ) throw new NumberFormatException("'" + buffer + "' has wrong format"); |
547 if (digit < 0) { |
533 if ( result < multmin ) throw new NumberFormatException("'" + buffer + "' has wrong format"); |
548 throw new NumberFormatException("'" + buffer + "' has wrong format"); |
534 result *= radix; |
549 } |
535 if ( result < limit + digit ) throw new NumberFormatException("'" + buffer + "' has wrong format"); |
550 if (result < multmin) { |
536 result -= digit; |
551 throw new NumberFormatException("'" + buffer + "' has wrong format"); |
537 |
552 } |
538 }while ( ++i < end ); |
553 result *= radix; |
539 return -result; |
554 if (result < limit + digit) { |
540 } |
555 throw new NumberFormatException("'" + buffer + "' has wrong format"); |
541 |
556 } |
542 // parse Year differently to support negative value. |
557 result -= digit; |
543 protected int parseIntYear (String buffer, int end){ |
558 |
544 int radix=10; |
559 } while (++i < end); |
545 int result = 0; |
560 return -result; |
546 boolean negative = false; |
561 } |
547 int i=0; |
562 |
548 int limit; |
563 // parse Year differently to support negative value. |
549 int multmin; |
564 protected int parseIntYear(String buffer, int end) { |
550 int digit=0; |
565 int radix = 10; |
551 |
566 int result = 0; |
552 if (buffer.charAt(0) == '-'){ |
567 boolean negative = false; |
553 negative = true; |
568 int i = 0; |
554 limit = Integer.MIN_VALUE; |
569 int limit; |
555 i++; |
570 int multmin; |
556 |
571 int digit = 0; |
557 } |
572 |
558 else{ |
573 if (buffer.charAt(0) == '-') { |
559 limit = -Integer.MAX_VALUE; |
574 negative = true; |
560 } |
575 limit = Integer.MIN_VALUE; |
561 multmin = limit / radix; |
576 i++; |
562 while (i < end) |
577 |
563 { |
578 } else { |
564 digit = getDigit(buffer.charAt(i++)); |
579 limit = -Integer.MAX_VALUE; |
565 if (digit < 0) throw new NumberFormatException("'" + buffer + "' has wrong format"); |
580 } |
566 if (result < multmin) throw new NumberFormatException("'" + buffer + "' has wrong format"); |
581 multmin = limit / radix; |
567 result *= radix; |
582 while (i < end) { |
568 if (result < limit + digit) throw new NumberFormatException("'" + buffer + "' has wrong format"); |
583 digit = getDigit(buffer.charAt(i++)); |
569 result -= digit; |
584 if (digit < 0) { |
570 } |
585 throw new NumberFormatException("'" + buffer + "' has wrong format"); |
571 |
586 } |
572 if (negative) |
587 if (result < multmin) { |
573 { |
588 throw new NumberFormatException("'" + buffer + "' has wrong format"); |
574 if (i > 1) return result; |
589 } |
575 else throw new NumberFormatException("'" + buffer + "' has wrong format"); |
590 result *= radix; |
576 } |
591 if (result < limit + digit) { |
577 return -result; |
592 throw new NumberFormatException("'" + buffer + "' has wrong format"); |
578 |
593 } |
579 } |
594 result -= digit; |
580 |
595 } |
581 /** |
596 |
582 * If timezone present - normalize dateTime [E Adding durations to dateTimes] |
597 if (negative) { |
583 * |
598 if (i > 1) { |
584 * @param date CCYY-MM-DDThh:mm:ss+03 |
599 return result; |
585 */ |
600 } else { |
586 protected void normalize(DateTimeData date) { |
601 throw new NumberFormatException("'" + buffer + "' has wrong format"); |
587 |
602 } |
588 // REVISIT: we have common code in addDuration() for durations |
603 } |
589 // should consider reorganizing it. |
604 return -result; |
590 // |
605 |
591 |
606 } |
592 //add minutes (from time zone) |
607 |
593 int negate = -1; |
608 /** |
594 |
609 * If timezone present - normalize dateTime [E Adding durations to |
595 if ( DEBUG ) { |
610 * dateTimes] |
596 System.out.println("==>date.minute"+date.minute); |
611 * |
597 System.out.println("==>date.timezoneMin" +date.timezoneMin); |
612 * @param date CCYY-MM-DDThh:mm:ss+03 |
598 } |
613 */ |
599 int temp = date.minute + negate * date.timezoneMin; |
614 protected void normalize(DateTimeData date) { |
600 int carry = fQuotient (temp, 60); |
615 |
601 date.minute= mod(temp, 60, carry); |
616 // REVISIT: we have common code in addDuration() for durations |
602 |
617 // should consider reorganizing it. |
603 if ( DEBUG ) { |
618 // |
604 System.out.println("==>carry: " + carry); |
619 |
605 } |
620 //add minutes (from time zone) |
606 //add hours |
621 int negate = -1; |
607 temp = date.hour + negate * date.timezoneHr + carry; |
622 |
608 carry = fQuotient(temp, 24); |
623 if (DEBUG) { |
609 date.hour=mod(temp, 24, carry); |
624 System.out.println("==>date.minute" + date.minute); |
610 if ( DEBUG ) { |
625 System.out.println("==>date.timezoneMin" + date.timezoneMin); |
611 System.out.println("==>date.hour"+date.hour); |
626 } |
612 System.out.println("==>carry: " + carry); |
627 int temp = date.minute + negate * date.timezoneMin; |
613 } |
628 int carry = fQuotient(temp, 60); |
614 |
629 date.minute = mod(temp, 60, carry); |
615 date.day=date.day+carry; |
630 |
616 |
631 if (DEBUG) { |
617 while ( true ) { |
632 System.out.println("==>carry: " + carry); |
618 temp=maxDayInMonthFor(date.year, date.month); |
633 } |
619 if (date.day<1) { |
634 //add hours |
620 date.day = date.day + maxDayInMonthFor(date.year, date.month-1); |
635 temp = date.hour + negate * date.timezoneHr + carry; |
621 carry=-1; |
636 carry = fQuotient(temp, 24); |
622 } |
637 date.hour = mod(temp, 24, carry); |
623 else if ( date.day>temp ) { |
638 if (DEBUG) { |
624 date.day=date.day-temp; |
639 System.out.println("==>date.hour" + date.hour); |
625 carry=1; |
640 System.out.println("==>carry: " + carry); |
626 } |
641 } |
627 else { |
642 |
628 break; |
643 date.day = date.day + carry; |
629 } |
644 |
630 temp=date.month+carry; |
645 while (true) { |
631 date.month=modulo(temp, 1, 13); |
646 temp = maxDayInMonthFor(date.year, date.month); |
632 date.year=date.year+fQuotient(temp, 1, 13); |
647 if (date.day < 1) { |
633 if(date.year == 0 && !Constants.SCHEMA_1_1_SUPPORT) { |
648 date.day = date.day + maxDayInMonthFor(date.year, date.month - 1); |
634 date.year = (date.timezoneHr < 0 || date.timezoneMin < 0)?1:-1; |
649 carry = -1; |
635 } |
650 } else if (date.day > temp) { |
636 } |
651 date.day = date.day - temp; |
637 date.utc='Z'; |
652 carry = 1; |
638 } |
653 } else { |
639 |
654 break; |
640 |
655 } |
641 /** |
656 temp = date.month + carry; |
|
657 date.month = modulo(temp, 1, 13); |
|
658 date.year = date.year + fQuotient(temp, 1, 13); |
|
659 if (date.year == 0 && !Constants.SCHEMA_1_1_SUPPORT) { |
|
660 date.year = (date.timezoneHr < 0 || date.timezoneMin < 0) ? 1 : -1; |
|
661 } |
|
662 } |
|
663 date.utc = 'Z'; |
|
664 } |
|
665 |
|
666 /** |
642 * @param date |
667 * @param date |
643 */ |
668 */ |
644 protected void saveUnnormalized(DateTimeData date) { |
669 protected void saveUnnormalized(DateTimeData date) { |
645 date.unNormYear = date.year; |
670 date.unNormYear = date.year; |
646 date.unNormMonth = date.month; |
671 date.unNormMonth = date.month; |
649 date.unNormMinute = date.minute; |
674 date.unNormMinute = date.minute; |
650 date.unNormSecond = date.second; |
675 date.unNormSecond = date.second; |
651 } |
676 } |
652 |
677 |
653 /** |
678 /** |
654 * Resets object representation of date/time |
679 * Resets object representation of date/time |
655 * |
680 * |
656 * @param data date/time object |
681 * @param data date/time object |
657 */ |
682 */ |
658 protected void resetDateObj(DateTimeData data) { |
683 protected void resetDateObj(DateTimeData data) { |
659 data.year = 0; |
684 data.year = 0; |
660 data.month = 0; |
685 data.month = 0; |
661 data.day = 0; |
686 data.day = 0; |
662 data.hour = 0; |
687 data.hour = 0; |
663 data.minute = 0; |
688 data.minute = 0; |
664 data.second = 0; |
689 data.second = 0; |
665 data.utc = 0; |
690 data.utc = 0; |
666 data.timezoneHr = 0; |
691 data.timezoneHr = 0; |
667 data.timezoneMin = 0; |
692 data.timezoneMin = 0; |
668 } |
693 } |
669 |
694 |
670 /** |
695 /** |
671 * Given {year,month} computes maximum |
696 * Given {year,month} computes maximum number of days for given month |
672 * number of days for given month |
697 * |
673 * |
698 * @param year |
674 * @param year |
699 * @param month |
675 * @param month |
700 * @return integer containg the number of days in a given month |
676 * @return integer containg the number of days in a given month |
701 */ |
677 */ |
702 protected int maxDayInMonthFor(int year, int month) { |
678 protected int maxDayInMonthFor(int year, int month) { |
703 //validate days |
679 //validate days |
704 if (month == 4 || month == 6 || month == 9 || month == 11) { |
680 if ( month==4 || month==6 || month==9 || month==11 ) { |
705 return 30; |
681 return 30; |
706 } else if (month == 2) { |
682 } |
707 if (isLeapYear(year)) { |
683 else if ( month==2 ) { |
708 return 29; |
684 if ( isLeapYear(year) ) { |
709 } else { |
685 return 29; |
710 return 28; |
686 } |
711 } |
687 else { |
712 } else { |
688 return 28; |
713 return 31; |
689 } |
714 } |
690 } |
715 } |
691 else { |
716 |
692 return 31; |
717 private boolean isLeapYear(int year) { |
693 } |
718 |
694 } |
719 //REVISIT: should we take care about Julian calendar? |
695 |
720 return ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0))); |
696 private boolean isLeapYear(int year) { |
721 } |
697 |
722 |
698 //REVISIT: should we take care about Julian calendar? |
723 // |
699 return((year%4 == 0) && ((year%100 != 0) || (year%400 == 0))); |
724 // help function described in W3C PR Schema [E Adding durations to dateTimes] |
700 } |
725 // |
701 |
726 protected int mod(int a, int b, int quotient) { |
702 // |
727 //modulo(a, b) = a - fQuotient(a,b)*b |
703 // help function described in W3C PR Schema [E Adding durations to dateTimes] |
728 return (a - quotient * b); |
704 // |
729 } |
705 protected int mod (int a, int b, int quotient) { |
730 |
706 //modulo(a, b) = a - fQuotient(a,b)*b |
731 // |
707 return (a - quotient*b) ; |
732 // help function described in W3C PR Schema [E Adding durations to dateTimes] |
708 } |
733 // |
709 |
734 protected int fQuotient(int a, int b) { |
710 // |
735 |
711 // help function described in W3C PR Schema [E Adding durations to dateTimes] |
736 //fQuotient(a, b) = the greatest integer less than or equal to a/b |
712 // |
737 return (int) Math.floor((float) a / b); |
713 protected int fQuotient (int a, int b) { |
738 } |
714 |
739 |
715 //fQuotient(a, b) = the greatest integer less than or equal to a/b |
740 // |
716 return (int)Math.floor((float)a/b); |
741 // help function described in W3C PR Schema [E Adding durations to dateTimes] |
717 } |
742 // |
718 |
743 protected int modulo(int temp, int low, int high) { |
719 // |
744 //modulo(a - low, high - low) + low |
720 // help function described in W3C PR Schema [E Adding durations to dateTimes] |
745 int a = temp - low; |
721 // |
746 int b = high - low; |
722 protected int modulo (int temp, int low, int high) { |
747 return (mod(a, b, fQuotient(a, b)) + low); |
723 //modulo(a - low, high - low) + low |
748 } |
724 int a = temp - low; |
749 |
725 int b = high - low; |
750 // |
726 return (mod (a, b, fQuotient(a, b)) + low) ; |
751 // help function described in W3C PR Schema [E Adding durations to dateTimes] |
727 } |
752 // |
728 |
753 protected int fQuotient(int temp, int low, int high) { |
729 // |
754 //fQuotient(a - low, high - low) |
730 // help function described in W3C PR Schema [E Adding durations to dateTimes] |
755 |
731 // |
756 return fQuotient(temp - low, high - low); |
732 protected int fQuotient (int temp, int low, int high) { |
757 } |
733 //fQuotient(a - low, high - low) |
758 |
734 |
759 protected String dateToString(DateTimeData date) { |
735 return fQuotient(temp - low, high - low); |
760 StringBuffer message = new StringBuffer(25); |
736 } |
761 append(message, date.year, 4); |
737 |
762 message.append('-'); |
738 |
763 append(message, date.month, 2); |
739 protected String dateToString(DateTimeData date) { |
764 message.append('-'); |
740 StringBuffer message = new StringBuffer(25); |
765 append(message, date.day, 2); |
741 append(message, date.year, 4); |
766 message.append('T'); |
742 message.append('-'); |
767 append(message, date.hour, 2); |
743 append(message, date.month, 2); |
768 message.append(':'); |
744 message.append('-'); |
769 append(message, date.minute, 2); |
745 append(message, date.day, 2); |
770 message.append(':'); |
746 message.append('T'); |
771 append(message, date.second); |
747 append(message, date.hour, 2); |
772 append(message, (char) date.utc, 0); |
748 message.append(':'); |
773 return message.toString(); |
749 append(message, date.minute, 2); |
774 } |
750 message.append(':'); |
775 |
751 append(message, date.second); |
776 protected final void append(StringBuffer message, int value, int nch) { |
752 append(message, (char)date.utc, 0); |
|
753 return message.toString(); |
|
754 } |
|
755 |
|
756 protected final void append(StringBuffer message, int value, int nch) { |
|
757 if (value == Integer.MIN_VALUE) { |
777 if (value == Integer.MIN_VALUE) { |
758 message.append(value); |
778 message.append(value); |
759 return; |
779 return; |
760 } |
780 } |
761 if (value < 0) { |
781 if (value < 0) { |
762 message.append('-'); |
782 message.append('-'); |
763 value = -value; |
783 value = -value; |
764 } |
784 } |
765 if (nch == 4) { |
785 if (nch == 4) { |
766 if (value < 10) |
786 if (value < 10) { |
767 message.append("000"); |
787 message.append("000"); |
768 else if (value < 100) |
788 } else if (value < 100) { |
769 message.append("00"); |
789 message.append("00"); |
770 else if (value < 1000) |
790 } else if (value < 1000) { |
771 message.append('0'); |
791 message.append('0'); |
772 message.append(value); |
792 } |
773 } |
793 message.append(value); |
774 else if (nch == 2) { |
794 } else if (nch == 2) { |
775 if (value < 10) |
|
776 message.append('0'); |
|
777 message.append(value); |
|
778 } |
|
779 else { |
|
780 if (value != 0) |
|
781 message.append((char)value); |
|
782 } |
|
783 } |
|
784 |
|
785 protected final void append(StringBuffer message, double value) { |
|
786 if (value < 0) { |
|
787 message.append('-'); |
|
788 value = -value; |
|
789 } |
|
790 if (value < 10) { |
795 if (value < 10) { |
791 message.append('0'); |
796 message.append('0'); |
792 } |
797 } |
793 append2(message, value); |
798 message.append(value); |
794 } |
799 } else { |
|
800 if (value != 0) { |
|
801 message.append((char) value); |
|
802 } |
|
803 } |
|
804 } |
|
805 |
|
806 protected final void append(StringBuffer message, double value) { |
|
807 if (value < 0) { |
|
808 message.append('-'); |
|
809 value = -value; |
|
810 } |
|
811 if (value < 10) { |
|
812 message.append('0'); |
|
813 } |
|
814 append2(message, value); |
|
815 } |
795 |
816 |
796 protected final void append2(StringBuffer message, double value) { |
817 protected final void append2(StringBuffer message, double value) { |
797 final int intValue = (int) value; |
818 final int intValue = (int) value; |
798 if (value == intValue) { |
819 if (value == intValue) { |
799 message.append(intValue); |
820 message.append(intValue); |
800 } |
821 } else { |
801 else { |
|
802 append3(message, value); |
822 append3(message, value); |
803 } |
823 } |
804 } |
824 } |
805 |
825 |
806 private void append3(StringBuffer message, double value) { |
826 private void append3(StringBuffer message, double value) { |
871 message.append('0'); |
888 message.append('0'); |
872 } |
889 } |
873 } |
890 } |
874 } |
891 } |
875 |
892 |
876 protected double parseSecond(String buffer, int start, int end) |
893 protected double parseSecond(String buffer, int start, int end) |
877 throws NumberFormatException { |
894 throws NumberFormatException { |
878 int dot = -1; |
895 int dot = -1; |
879 for (int i = start; i < end; i++) { |
896 for (int i = start; i < end; i++) { |
880 char ch = buffer.charAt(i); |
897 char ch = buffer.charAt(i); |
881 if (ch == '.') |
898 if (ch == '.') { |
882 dot = i; |
899 dot = i; |
883 else if (ch > '9' || ch < '0') |
900 } else if (ch > '9' || ch < '0') { |
884 throw new NumberFormatException("'" + buffer + "' has wrong format"); |
901 throw new NumberFormatException("'" + buffer + "' has wrong format"); |
885 } |
902 } |
886 if (dot == -1) { |
903 } |
887 if (start+2 != end) |
904 if (dot == -1) { |
888 throw new NumberFormatException("'" + buffer + "' has wrong format"); |
905 if (start + 2 != end) { |
889 } |
906 throw new NumberFormatException("'" + buffer + "' has wrong format"); |
890 else if (start+2 != dot || dot+1 == end) { |
907 } |
891 throw new NumberFormatException("'" + buffer + "' has wrong format"); |
908 } else if (start + 2 != dot || dot + 1 == end) { |
892 } |
909 throw new NumberFormatException("'" + buffer + "' has wrong format"); |
893 return Double.parseDouble(buffer.substring(start, end)); |
910 } |
894 } |
911 return Double.parseDouble(buffer.substring(start, end)); |
895 |
912 } |
896 // |
913 |
897 //Private help functions |
914 // |
898 // |
915 //Private help functions |
899 |
916 // |
900 private void cloneDate (DateTimeData finalValue, DateTimeData tempDate) { |
917 private void cloneDate(DateTimeData finalValue, DateTimeData tempDate) { |
901 tempDate.year = finalValue.year; |
918 tempDate.year = finalValue.year; |
902 tempDate.month = finalValue.month; |
919 tempDate.month = finalValue.month; |
903 tempDate.day = finalValue.day; |
920 tempDate.day = finalValue.day; |
904 tempDate.hour = finalValue.hour; |
921 tempDate.hour = finalValue.hour; |
905 tempDate.minute = finalValue.minute; |
922 tempDate.minute = finalValue.minute; |
906 tempDate.second = finalValue.second; |
923 tempDate.second = finalValue.second; |
907 tempDate.utc = finalValue.utc; |
924 tempDate.utc = finalValue.utc; |
908 tempDate.timezoneHr = finalValue.timezoneHr; |
925 tempDate.timezoneHr = finalValue.timezoneHr; |
909 tempDate.timezoneMin = finalValue.timezoneMin; |
926 tempDate.timezoneMin = finalValue.timezoneMin; |
910 } |
927 } |
911 |
928 |
912 /** |
929 /** |
913 * Represents date time data |
930 * Represents date time data |
914 */ |
931 */ |
915 static final class DateTimeData implements XSDateTime { |
932 static final class DateTimeData implements XSDateTime { |
916 int year, month, day, hour, minute, utc; |
933 |
917 double second; |
934 int year, month, day, hour, minute, utc; |
918 int timezoneHr, timezoneMin; |
935 double second; |
|
936 int timezoneHr, timezoneMin; |
919 private String originalValue; |
937 private String originalValue; |
920 boolean normalized = true; |
938 boolean normalized = true; |
921 |
|
922 int unNormYear; |
939 int unNormYear; |
923 int unNormMonth; |
940 int unNormMonth; |
924 int unNormDay; |
941 int unNormDay; |
925 int unNormHour; |
942 int unNormHour; |
926 int unNormMinute; |
943 int unNormMinute; |
927 double unNormSecond; |
944 double unNormSecond; |
928 |
945 // used for comparisons - to decide the 'interesting' portions of |
929 // used for comparisons - to decide the 'interesting' portions of |
946 // a date/time based data type. |
930 // a date/time based data type. |
947 int position; |
931 int position; |
948 // a pointer to the type that was used go generate this data |
932 // a pointer to the type that was used go generate this data |
949 // note that this is not the actual simple type, but one of the |
933 // note that this is not the actual simple type, but one of the |
950 // statically created XXXDV objects, so this won't cause any GC problem. |
934 // statically created XXXDV objects, so this won't cause any GC problem. |
951 final AbstractDateTimeDV type; |
935 final AbstractDateTimeDV type; |
952 private volatile String canonical; |
936 private String canonical; |
953 |
937 public DateTimeData(String originalValue, AbstractDateTimeDV type) { |
954 public DateTimeData(String originalValue, AbstractDateTimeDV type) { |
938 this.originalValue = originalValue; |
955 this.originalValue = originalValue; |
939 this.type = type; |
956 this.type = type; |
940 } |
957 } |
941 public DateTimeData(int year, int month, int day, int hour, int minute, |
958 |
942 double second, int utc, String originalValue, boolean normalized, AbstractDateTimeDV type) { |
959 public DateTimeData(int year, int month, int day, int hour, int minute, |
943 this.year = year; |
960 double second, int utc, String originalValue, boolean normalized, AbstractDateTimeDV type) { |
944 this.month = month; |
961 this.year = year; |
945 this.day = day; |
962 this.month = month; |
946 this.hour = hour; |
963 this.day = day; |
947 this.minute = minute; |
964 this.hour = hour; |
948 this.second = second; |
965 this.minute = minute; |
949 this.utc = utc; |
966 this.second = second; |
950 this.type = type; |
967 this.utc = utc; |
|
968 this.type = type; |
951 this.originalValue = originalValue; |
969 this.originalValue = originalValue; |
952 } |
970 } |
953 public boolean equals(Object obj) { |
971 |
954 if (!(obj instanceof DateTimeData)) |
972 @Override |
955 return false; |
973 public boolean equals(Object obj) { |
956 return type.compareDates(this, (DateTimeData)obj, true)==0; |
974 if (!(obj instanceof DateTimeData)) { |
957 } |
975 return false; |
958 public synchronized String toString() { |
976 } |
959 if (canonical == null) { |
977 return type.compareDates(this, (DateTimeData) obj, true) == 0; |
960 canonical = type.dateToString(this); |
978 } |
961 } |
979 |
962 return canonical; |
980 // If two DateTimeData are equals - then they should have the same |
963 } |
981 // hashcode. This means we need to convert the date to UTC before |
964 /* (non-Javadoc) |
982 // we return its hashcode. |
965 * @see org.apache.xerces.xs.datatypes.XSDateTime#getYear() |
983 // The DateTimeData is unfortunately mutable - so we cannot |
966 */ |
984 // cache the result of the conversion... |
967 public int getYears() { |
985 // |
968 if(type instanceof DurationDV) |
986 @Override |
|
987 public int hashCode() { |
|
988 final DateTimeData tempDate = new DateTimeData(null, type); |
|
989 type.cloneDate(this, tempDate); |
|
990 type.normalize(tempDate); |
|
991 return type.dateToString(tempDate).hashCode(); |
|
992 } |
|
993 |
|
994 @Override |
|
995 public String toString() { |
|
996 if (canonical == null) { |
|
997 canonical = type.dateToString(this); |
|
998 } |
|
999 return canonical; |
|
1000 } |
|
1001 /* (non-Javadoc) |
|
1002 * @see org.apache.xerces.xs.datatypes.XSDateTime#getYear() |
|
1003 */ |
|
1004 |
|
1005 @Override |
|
1006 public int getYears() { |
|
1007 if (type instanceof DurationDV) { |
969 return 0; |
1008 return 0; |
970 return normalized?year:unNormYear; |
1009 } |
971 } |
1010 return normalized ? year : unNormYear; |
972 /* (non-Javadoc) |
1011 } |
973 * @see org.apache.xerces.xs.datatypes.XSDateTime#getMonth() |
1012 /* (non-Javadoc) |
974 */ |
1013 * @see org.apache.xerces.xs.datatypes.XSDateTime#getMonth() |
975 public int getMonths() { |
1014 */ |
976 if(type instanceof DurationDV) { |
1015 |
977 return year*12 + month; |
1016 @Override |
978 } |
1017 public int getMonths() { |
979 return normalized?month:unNormMonth; |
1018 if (type instanceof DurationDV) { |
980 } |
1019 return year * 12 + month; |
981 /* (non-Javadoc) |
1020 } |
982 * @see org.apache.xerces.xs.datatypes.XSDateTime#getDay() |
1021 return normalized ? month : unNormMonth; |
983 */ |
1022 } |
984 public int getDays() { |
1023 /* (non-Javadoc) |
985 if(type instanceof DurationDV) |
1024 * @see org.apache.xerces.xs.datatypes.XSDateTime#getDay() |
|
1025 */ |
|
1026 |
|
1027 @Override |
|
1028 public int getDays() { |
|
1029 if (type instanceof DurationDV) { |
986 return 0; |
1030 return 0; |
987 return normalized?day:unNormDay; |
1031 } |
988 } |
1032 return normalized ? day : unNormDay; |
989 /* (non-Javadoc) |
1033 } |
990 * @see org.apache.xerces.xs.datatypes.XSDateTime#getHour() |
1034 /* (non-Javadoc) |
991 */ |
1035 * @see org.apache.xerces.xs.datatypes.XSDateTime#getHour() |
992 public int getHours() { |
1036 */ |
993 if(type instanceof DurationDV) |
1037 |
|
1038 @Override |
|
1039 public int getHours() { |
|
1040 if (type instanceof DurationDV) { |
994 return 0; |
1041 return 0; |
995 return normalized?hour:unNormHour; |
1042 } |
996 } |
1043 return normalized ? hour : unNormHour; |
997 /* (non-Javadoc) |
1044 } |
998 * @see org.apache.xerces.xs.datatypes.XSDateTime#getMinutes() |
1045 /* (non-Javadoc) |
999 */ |
1046 * @see org.apache.xerces.xs.datatypes.XSDateTime#getMinutes() |
1000 public int getMinutes() { |
1047 */ |
1001 if(type instanceof DurationDV) |
1048 |
|
1049 @Override |
|
1050 public int getMinutes() { |
|
1051 if (type instanceof DurationDV) { |
1002 return 0; |
1052 return 0; |
1003 return normalized?minute:unNormMinute; |
1053 } |
1004 } |
1054 return normalized ? minute : unNormMinute; |
1005 /* (non-Javadoc) |
1055 } |
1006 * @see org.apache.xerces.xs.datatypes.XSDateTime#getSeconds() |
1056 /* (non-Javadoc) |
1007 */ |
1057 * @see org.apache.xerces.xs.datatypes.XSDateTime#getSeconds() |
1008 public double getSeconds() { |
1058 */ |
1009 if(type instanceof DurationDV) { |
1059 |
1010 return day*24*60*60 + hour*60*60 + minute*60 + second; |
1060 @Override |
1011 } |
1061 public double getSeconds() { |
1012 return normalized?second:unNormSecond; |
1062 if (type instanceof DurationDV) { |
1013 } |
1063 return day * 24 * 60 * 60 + hour * 60 * 60 + minute * 60 + second; |
1014 /* (non-Javadoc) |
1064 } |
1015 * @see org.apache.xerces.xs.datatypes.XSDateTime#hasTimeZone() |
1065 return normalized ? second : unNormSecond; |
1016 */ |
1066 } |
1017 public boolean hasTimeZone() { |
1067 /* (non-Javadoc) |
1018 return utc != 0; |
1068 * @see org.apache.xerces.xs.datatypes.XSDateTime#hasTimeZone() |
1019 } |
1069 */ |
1020 /* (non-Javadoc) |
1070 |
1021 * @see org.apache.xerces.xs.datatypes.XSDateTime#getTimeZoneHours() |
1071 @Override |
1022 */ |
1072 public boolean hasTimeZone() { |
1023 public int getTimeZoneHours() { |
1073 return utc != 0; |
1024 return timezoneHr; |
1074 } |
1025 } |
1075 /* (non-Javadoc) |
1026 /* (non-Javadoc) |
1076 * @see org.apache.xerces.xs.datatypes.XSDateTime#getTimeZoneHours() |
1027 * @see org.apache.xerces.xs.datatypes.XSDateTime#getTimeZoneMinutes() |
1077 */ |
1028 */ |
1078 |
1029 public int getTimeZoneMinutes() { |
1079 @Override |
1030 return timezoneMin; |
1080 public int getTimeZoneHours() { |
1031 } |
1081 return timezoneHr; |
|
1082 } |
|
1083 /* (non-Javadoc) |
|
1084 * @see org.apache.xerces.xs.datatypes.XSDateTime#getTimeZoneMinutes() |
|
1085 */ |
|
1086 |
|
1087 @Override |
|
1088 public int getTimeZoneMinutes() { |
|
1089 return timezoneMin; |
|
1090 } |
1032 /* (non-Javadoc) |
1091 /* (non-Javadoc) |
1033 * @see org.apache.xerces.xs.datatypes.XSDateTime#getLexicalValue() |
1092 * @see org.apache.xerces.xs.datatypes.XSDateTime#getLexicalValue() |
1034 */ |
1093 */ |
|
1094 |
|
1095 @Override |
1035 public String getLexicalValue() { |
1096 public String getLexicalValue() { |
1036 return originalValue; |
1097 return originalValue; |
1037 } |
1098 } |
1038 /* (non-Javadoc) |
1099 /* (non-Javadoc) |
1039 * @see org.apache.xerces.xs.datatypes.XSDateTime#normalize() |
1100 * @see org.apache.xerces.xs.datatypes.XSDateTime#normalize() |
1040 */ |
1101 */ |
|
1102 |
|
1103 @Override |
1041 public XSDateTime normalize() { |
1104 public XSDateTime normalize() { |
1042 if(!normalized) { |
1105 if (!normalized) { |
1043 DateTimeData dt = (DateTimeData)this.clone(); |
1106 DateTimeData dt = (DateTimeData) this.clone(); |
1044 dt.normalized = true; |
1107 dt.normalized = true; |
1045 return dt; |
1108 return dt; |
1046 } |
1109 } |
1047 return this; |
1110 return this; |
1048 } |
1111 } |
1049 /* (non-Javadoc) |
1112 /* (non-Javadoc) |
1050 * @see org.apache.xerces.xs.datatypes.XSDateTime#isNormalized() |
1113 * @see org.apache.xerces.xs.datatypes.XSDateTime#isNormalized() |
1051 */ |
1114 */ |
|
1115 |
|
1116 @Override |
1052 public boolean isNormalized() { |
1117 public boolean isNormalized() { |
1053 return normalized; |
1118 return normalized; |
1054 } |
1119 } |
1055 |
1120 |
|
1121 @Override |
1056 public Object clone() { |
1122 public Object clone() { |
1057 DateTimeData dt = new DateTimeData(this.year, this.month, this.day, this.hour, |
1123 DateTimeData dt = new DateTimeData(this.year, this.month, this.day, this.hour, |
1058 this.minute, this.second, this.utc, this.originalValue, this.normalized, this.type); |
1124 this.minute, this.second, this.utc, this.originalValue, this.normalized, this.type); |
1059 dt.canonical = this.canonical; |
1125 dt.canonical = this.canonical; |
1060 dt.position = position; |
1126 dt.position = position; |
1061 dt.timezoneHr = this.timezoneHr; |
1127 dt.timezoneHr = this.timezoneHr; |
1062 dt.timezoneMin = this.timezoneMin; |
1128 dt.timezoneMin = this.timezoneMin; |
1063 dt.unNormYear = this.unNormYear; |
1129 dt.unNormYear = this.unNormYear; |