1/*
2 * reserved comment block
3 * DO NOT REMOVE OR ALTER!
4 */
5/*
6 * Licensed to the Apache Software Foundation (ASF) under one or more
7 * contributor license agreements.  See the NOTICE file distributed with
8 * this work for additional information regarding copyright ownership.
9 * The ASF licenses this file to You under the Apache License, Version 2.0
10 * (the "License"); you may not use this file except in compliance with
11 * the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 */
21
22package com.sun.org.apache.xerces.internal.impl.dv.xs;
23
24import java.math.BigDecimal;
25
26import javax.xml.datatype.DatatypeFactory;
27import javax.xml.datatype.Duration;
28import javax.xml.datatype.XMLGregorianCalendar;
29
30import com.sun.org.apache.xerces.internal.impl.Constants;
31import com.sun.org.apache.xerces.internal.jaxp.datatype.DatatypeFactoryImpl;
32import com.sun.org.apache.xerces.internal.xs.datatypes.XSDateTime;
33
34/**
35 * This is the base class of all date/time datatype validators.
36 * It implements common code for parsing, validating and comparing datatypes.
37 * Classes that extend this class, must implement parse() method.
38 *
39 * REVISIT: There are many instance variables, which would cause problems
40 *          when we support grammar caching. A grammar is possibly used by
41 *          two parser instances at the same time, then the same simple type
42 *          decl object can be used to validate two strings at the same time.
43 *          -SG
44 *
45 * @xerces.internal
46 *
47 * @author Elena Litani
48 * @author Len Berman
49 * @author Gopal Sharma, SUN Microsystems Inc.
50 *
51 */
52public abstract class AbstractDateTimeDV extends TypeValidator {
53
54    //debugging
55    private static final boolean DEBUG = false;
56    //define shared variables for date/time
57    //define constants to be used in assigning default values for
58    //all date/time excluding duration
59    protected final static int YEAR = 2000;
60    protected final static int MONTH = 01;
61    protected final static int DAY = 01;
62    protected static final DatatypeFactory datatypeFactory = new DatatypeFactoryImpl();
63
64    @Override
65    public short getAllowedFacets() {
66        return (XSSimpleTypeDecl.FACET_PATTERN | XSSimpleTypeDecl.FACET_WHITESPACE | XSSimpleTypeDecl.FACET_ENUMERATION | XSSimpleTypeDecl.FACET_MAXINCLUSIVE | XSSimpleTypeDecl.FACET_MININCLUSIVE | XSSimpleTypeDecl.FACET_MAXEXCLUSIVE | XSSimpleTypeDecl.FACET_MINEXCLUSIVE);
67    }//getAllowedFacets()
68
69    // distinguishes between identity and equality for date/time values
70    // ie: two values representing the same "moment in time" but with different
71    // remembered timezones are now equal but not identical.
72    @Override
73    public boolean isIdentical(Object value1, Object value2) {
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                    }
467                }
468            } else {
469                throw new RuntimeException("Hour must have values 0-23, unless 24:00:00");
470            }
471        }
472
473        //validate
474        if (data.minute > 59 || data.minute < 0) {
475            throw new RuntimeException("Minute must have values 0-59");
476        }
477
478        //validate
479        if (data.second >= 60 || data.second < 0) {
480            throw new RuntimeException("Second must have values 0-59");
481
482        }
483
484        //validate
485        if (data.timezoneHr > 14 || data.timezoneHr < -14) {
486            throw new RuntimeException("Time zone should have range -14:00 to +14:00");
487        } else {
488            if ((data.timezoneHr == 14 || data.timezoneHr == -14) && data.timezoneMin != 0) {
489                throw new RuntimeException("Time zone should have range -14:00 to +14:00");
490            } else if (data.timezoneMin > 59 || data.timezoneMin < -59) {
491                throw new RuntimeException("Minute must have values 0-59");
492            }
493        }
494
495    }
496
497    /**
498     * Return index of UTC char: 'Z', '+', '-'
499     *
500     * @param start
501     * @param end
502     * @return index of the UTC character that was found
503     */
504    protected int findUTCSign(String buffer, int start, int end) {
505        int c;
506        for (int i = start; i < end; i++) {
507            c = buffer.charAt(i);
508            if (c == 'Z' || c == '+' || c == '-') {
509                return i;
510            }
511
512        }
513        return -1;
514    }
515
516    /**
517     * Returns
518     * <code>true</code> if the character at start is 'Z', '+' or '-'.
519     */
520    protected final boolean isNextCharUTCSign(String buffer, int start, int end) {
521        if (start < end) {
522            char c = buffer.charAt(start);
523            return (c == 'Z' || c == '+' || c == '-');
524        }
525        return false;
526    }
527
528    /**
529     * Given start and end position, parses string value
530     *
531     * @param buffer string to parse
532     * @param start start position
533     * @param end end position
534     * @return return integer representation of characters
535     */
536    protected int parseInt(String buffer, int start, int end)
537            throws NumberFormatException {
538        //REVISIT: more testing on this parsing needs to be done.
539        int radix = 10;
540        int result = 0;
541        int digit = 0;
542        int limit = -Integer.MAX_VALUE;
543        int multmin = limit / radix;
544        int i = start;
545        do {
546            digit = getDigit(buffer.charAt(i));
547            if (digit < 0) {
548                throw new NumberFormatException("'" + buffer + "' has wrong format");
549            }
550            if (result < multmin) {
551                throw new NumberFormatException("'" + buffer + "' has wrong format");
552            }
553            result *= radix;
554            if (result < limit + digit) {
555                throw new NumberFormatException("'" + buffer + "' has wrong format");
556            }
557            result -= digit;
558
559        } while (++i < end);
560        return -result;
561    }
562
563    // parse Year differently to support negative value.
564    protected int parseIntYear(String buffer, int end) {
565        int radix = 10;
566        int result = 0;
567        boolean negative = false;
568        int i = 0;
569        int limit;
570        int multmin;
571        int digit = 0;
572
573        if (buffer.charAt(0) == '-') {
574            negative = true;
575            limit = Integer.MIN_VALUE;
576            i++;
577
578        } else {
579            limit = -Integer.MAX_VALUE;
580        }
581        multmin = limit / radix;
582        while (i < end) {
583            digit = getDigit(buffer.charAt(i++));
584            if (digit < 0) {
585                throw new NumberFormatException("'" + buffer + "' has wrong format");
586            }
587            if (result < multmin) {
588                throw new NumberFormatException("'" + buffer + "' has wrong format");
589            }
590            result *= radix;
591            if (result < limit + digit) {
592                throw new NumberFormatException("'" + buffer + "' has wrong format");
593            }
594            result -= digit;
595        }
596
597        if (negative) {
598            if (i > 1) {
599                return result;
600            } else {
601                throw new NumberFormatException("'" + buffer + "' has wrong format");
602            }
603        }
604        return -result;
605
606    }
607
608    /**
609     * If timezone present - normalize dateTime [E Adding durations to
610     * dateTimes]
611     *
612     * @param date CCYY-MM-DDThh:mm:ss+03
613     */
614    protected void normalize(DateTimeData date) {
615
616        // REVISIT: we have common code in addDuration() for durations
617        //          should consider reorganizing it.
618        //
619
620        //add minutes (from time zone)
621        int negate = -1;
622
623        if (DEBUG) {
624            System.out.println("==>date.minute" + date.minute);
625            System.out.println("==>date.timezoneMin" + date.timezoneMin);
626        }
627        int temp = date.minute + negate * date.timezoneMin;
628        int carry = fQuotient(temp, 60);
629        date.minute = mod(temp, 60, carry);
630
631        if (DEBUG) {
632            System.out.println("==>carry: " + carry);
633        }
634        //add hours
635        temp = date.hour + negate * date.timezoneHr + carry;
636        carry = fQuotient(temp, 24);
637        date.hour = mod(temp, 24, carry);
638        if (DEBUG) {
639            System.out.println("==>date.hour" + date.hour);
640            System.out.println("==>carry: " + carry);
641        }
642
643        date.day = date.day + carry;
644
645        while (true) {
646            temp = maxDayInMonthFor(date.year, date.month);
647            if (date.day < 1) {
648                date.day = date.day + maxDayInMonthFor(date.year, date.month - 1);
649                carry = -1;
650            } else if (date.day > temp) {
651                date.day = date.day - temp;
652                carry = 1;
653            } else {
654                break;
655            }
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    /**
667     * @param date
668     */
669    protected void saveUnnormalized(DateTimeData date) {
670        date.unNormYear = date.year;
671        date.unNormMonth = date.month;
672        date.unNormDay = date.day;
673        date.unNormHour = date.hour;
674        date.unNormMinute = date.minute;
675        date.unNormSecond = date.second;
676    }
677
678    /**
679     * Resets object representation of date/time
680     *
681     * @param data date/time object
682     */
683    protected void resetDateObj(DateTimeData data) {
684        data.year = 0;
685        data.month = 0;
686        data.day = 0;
687        data.hour = 0;
688        data.minute = 0;
689        data.second = 0;
690        data.utc = 0;
691        data.timezoneHr = 0;
692        data.timezoneMin = 0;
693    }
694
695    /**
696     * Given {year,month} computes maximum number of days for given month
697     *
698     * @param year
699     * @param month
700     * @return integer containg the number of days in a given month
701     */
702    protected int maxDayInMonthFor(int year, int month) {
703        //validate days
704        if (month == 4 || month == 6 || month == 9 || month == 11) {
705            return 30;
706        } else if (month == 2) {
707            if (isLeapYear(year)) {
708                return 29;
709            } else {
710                return 28;
711            }
712        } else {
713            return 31;
714        }
715    }
716
717    private boolean isLeapYear(int year) {
718
719        //REVISIT: should we take care about Julian calendar?
720        return ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)));
721    }
722
723    //
724    // help function described in W3C PR Schema [E Adding durations to dateTimes]
725    //
726    protected int mod(int a, int b, int quotient) {
727        //modulo(a, b) = a - fQuotient(a,b)*b
728        return (a - quotient * b);
729    }
730
731    //
732    // help function described in W3C PR Schema [E Adding durations to dateTimes]
733    //
734    protected int fQuotient(int a, int b) {
735
736        //fQuotient(a, b) = the greatest integer less than or equal to a/b
737        return (int) Math.floor((float) a / b);
738    }
739
740    //
741    // help function described in W3C PR Schema [E Adding durations to dateTimes]
742    //
743    protected int modulo(int temp, int low, int high) {
744        //modulo(a - low, high - low) + low
745        int a = temp - low;
746        int b = high - low;
747        return (mod(a, b, fQuotient(a, b)) + low);
748    }
749
750    //
751    // help function described in W3C PR Schema [E Adding durations to dateTimes]
752    //
753    protected int fQuotient(int temp, int low, int high) {
754        //fQuotient(a - low, high - low)
755
756        return fQuotient(temp - low, high - low);
757    }
758
759    protected String dateToString(DateTimeData date) {
760        StringBuffer message = new StringBuffer(25);
761        append(message, date.year, 4);
762        message.append('-');
763        append(message, date.month, 2);
764        message.append('-');
765        append(message, date.day, 2);
766        message.append('T');
767        append(message, date.hour, 2);
768        message.append(':');
769        append(message, date.minute, 2);
770        message.append(':');
771        append(message, date.second);
772        append(message, (char) date.utc, 0);
773        return message.toString();
774    }
775
776    protected final void append(StringBuffer message, int value, int nch) {
777        if (value == Integer.MIN_VALUE) {
778            message.append(value);
779            return;
780        }
781        if (value < 0) {
782            message.append('-');
783            value = -value;
784        }
785        if (nch == 4) {
786            if (value < 10) {
787                message.append("000");
788            } else if (value < 100) {
789                message.append("00");
790            } else if (value < 1000) {
791                message.append('0');
792            }
793            message.append(value);
794        } else if (nch == 2) {
795            if (value < 10) {
796                message.append('0');
797            }
798            message.append(value);
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    }
816
817    protected final void append2(StringBuffer message, double value) {
818        final int intValue = (int) value;
819        if (value == intValue) {
820            message.append(intValue);
821        } else {
822            append3(message, value);
823        }
824    }
825
826    private void append3(StringBuffer message, double value) {
827        String d = String.valueOf(value);
828        int eIndex = d.indexOf('E');
829        if (eIndex == -1) {
830            message.append(d);
831            return;
832        }
833        int exp;
834        if (value < 1) {
835            // Need to convert from scientific notation of the form
836            // n.nnn...E-N (N >= 4) to a normal decimal value.
837            try {
838                exp = parseInt(d, eIndex + 2, d.length());
839            } // This should never happen.
840            // It's only possible if String.valueOf(double) is broken.
841            catch (Exception e) {
842                message.append(d);
843                return;
844            }
845            message.append("0.");
846            for (int i = 1; i < exp; ++i) {
847                message.append('0');
848            }
849            // Remove trailing zeros.
850            int end = eIndex - 1;
851            while (end > 0) {
852                char c = d.charAt(end);
853                if (c != '0') {
854                    break;
855                }
856                --end;
857            }
858            // Now append the digits to the end. Skip over the decimal point.
859            for (int i = 0; i <= end; ++i) {
860                char c = d.charAt(i);
861                if (c != '.') {
862                    message.append(c);
863                }
864            }
865        } else {
866            // Need to convert from scientific notation of the form
867            // n.nnn...EN (N >= 7) to a normal decimal value.
868            try {
869                exp = parseInt(d, eIndex + 1, d.length());
870            } // This should never happen.
871            // It's only possible if String.valueOf(double) is broken.
872            catch (Exception e) {
873                message.append(d);
874                return;
875            }
876            final int integerEnd = exp + 2;
877            for (int i = 0; i < eIndex; ++i) {
878                char c = d.charAt(i);
879                if (c != '.') {
880                    if (i == integerEnd) {
881                        message.append('.');
882                    }
883                    message.append(c);
884                }
885            }
886            // Append trailing zeroes if necessary.
887            for (int i = integerEnd - eIndex; i > 0; --i) {
888                message.append('0');
889            }
890        }
891    }
892
893    protected double parseSecond(String buffer, int start, int end)
894            throws NumberFormatException {
895        int dot = -1;
896        for (int i = start; i < end; i++) {
897            char ch = buffer.charAt(i);
898            if (ch == '.') {
899                dot = i;
900            } else if (ch > '9' || ch < '0') {
901                throw new NumberFormatException("'" + buffer + "' has wrong format");
902            }
903        }
904        if (dot == -1) {
905            if (start + 2 != end) {
906                throw new NumberFormatException("'" + buffer + "' has wrong format");
907            }
908        } else if (start + 2 != dot || dot + 1 == end) {
909            throw new NumberFormatException("'" + buffer + "' has wrong format");
910        }
911        return Double.parseDouble(buffer.substring(start, end));
912    }
913
914    //
915    //Private help functions
916    //
917    private void cloneDate(DateTimeData finalValue, DateTimeData tempDate) {
918        tempDate.year = finalValue.year;
919        tempDate.month = finalValue.month;
920        tempDate.day = finalValue.day;
921        tempDate.hour = finalValue.hour;
922        tempDate.minute = finalValue.minute;
923        tempDate.second = finalValue.second;
924        tempDate.utc = finalValue.utc;
925        tempDate.timezoneHr = finalValue.timezoneHr;
926        tempDate.timezoneMin = finalValue.timezoneMin;
927    }
928
929    /**
930     * Represents date time data
931     */
932    static final class DateTimeData implements XSDateTime {
933
934        int year, month, day, hour, minute, utc;
935        double second;
936        int timezoneHr, timezoneMin;
937        private String originalValue;
938        boolean normalized = true;
939        int unNormYear;
940        int unNormMonth;
941        int unNormDay;
942        int unNormHour;
943        int unNormMinute;
944        double unNormSecond;
945        // used for comparisons - to decide the 'interesting' portions of
946        // a date/time based data type.
947        int position;
948        // 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
950        // statically created XXXDV objects, so this won't cause any GC problem.
951        final AbstractDateTimeDV type;
952        private volatile String canonical;
953
954        public DateTimeData(String originalValue, AbstractDateTimeDV type) {
955            this.originalValue = originalValue;
956            this.type = type;
957        }
958
959        public DateTimeData(int year, int month, int day, int hour, int minute,
960                double second, int utc, String originalValue, boolean normalized, AbstractDateTimeDV type) {
961            this.year = year;
962            this.month = month;
963            this.day = day;
964            this.hour = hour;
965            this.minute = minute;
966            this.second = second;
967            this.utc = utc;
968            this.type = type;
969            this.originalValue = originalValue;
970        }
971
972        @Override
973        public boolean equals(Object obj) {
974            if (!(obj instanceof DateTimeData)) {
975                return false;
976            }
977            return type.compareDates(this, (DateTimeData) obj, true) == 0;
978        }
979
980        // If two DateTimeData are equals - then they should have the same
981        // hashcode. This means we need to convert the date to UTC before
982        // we return its hashcode.
983        // The DateTimeData is unfortunately mutable - so we cannot
984        // cache the result of the conversion...
985        //
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) {
1008                return 0;
1009            }
1010            return normalized ? year : unNormYear;
1011        }
1012        /* (non-Javadoc)
1013         * @see org.apache.xerces.xs.datatypes.XSDateTime#getMonth()
1014         */
1015
1016        @Override
1017        public int getMonths() {
1018            if (type instanceof DurationDV) {
1019                return year * 12 + month;
1020            }
1021            return normalized ? month : unNormMonth;
1022        }
1023        /* (non-Javadoc)
1024         * @see org.apache.xerces.xs.datatypes.XSDateTime#getDay()
1025         */
1026
1027        @Override
1028        public int getDays() {
1029            if (type instanceof DurationDV) {
1030                return 0;
1031            }
1032            return normalized ? day : unNormDay;
1033        }
1034        /* (non-Javadoc)
1035         * @see org.apache.xerces.xs.datatypes.XSDateTime#getHour()
1036         */
1037
1038        @Override
1039        public int getHours() {
1040            if (type instanceof DurationDV) {
1041                return 0;
1042            }
1043            return normalized ? hour : unNormHour;
1044        }
1045        /* (non-Javadoc)
1046         * @see org.apache.xerces.xs.datatypes.XSDateTime#getMinutes()
1047         */
1048
1049        @Override
1050        public int getMinutes() {
1051            if (type instanceof DurationDV) {
1052                return 0;
1053            }
1054            return normalized ? minute : unNormMinute;
1055        }
1056        /* (non-Javadoc)
1057         * @see org.apache.xerces.xs.datatypes.XSDateTime#getSeconds()
1058         */
1059
1060        @Override
1061        public double getSeconds() {
1062            if (type instanceof DurationDV) {
1063                return day * 24 * 60 * 60 + hour * 60 * 60 + minute * 60 + second;
1064            }
1065            return normalized ? second : unNormSecond;
1066        }
1067        /* (non-Javadoc)
1068         * @see org.apache.xerces.xs.datatypes.XSDateTime#hasTimeZone()
1069         */
1070
1071        @Override
1072        public boolean hasTimeZone() {
1073            return utc != 0;
1074        }
1075        /* (non-Javadoc)
1076         * @see org.apache.xerces.xs.datatypes.XSDateTime#getTimeZoneHours()
1077         */
1078
1079        @Override
1080        public int getTimeZoneHours() {
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        }
1091        /* (non-Javadoc)
1092         * @see org.apache.xerces.xs.datatypes.XSDateTime#getLexicalValue()
1093         */
1094
1095        @Override
1096        public String getLexicalValue() {
1097            return originalValue;
1098        }
1099        /* (non-Javadoc)
1100         * @see org.apache.xerces.xs.datatypes.XSDateTime#normalize()
1101         */
1102
1103        @Override
1104        public XSDateTime normalize() {
1105            if (!normalized) {
1106                DateTimeData dt = (DateTimeData) this.clone();
1107                dt.normalized = true;
1108                return dt;
1109            }
1110            return this;
1111        }
1112        /* (non-Javadoc)
1113         * @see org.apache.xerces.xs.datatypes.XSDateTime#isNormalized()
1114         */
1115
1116        @Override
1117        public boolean isNormalized() {
1118            return normalized;
1119        }
1120
1121        @Override
1122        public Object clone() {
1123            DateTimeData dt = new DateTimeData(this.year, this.month, this.day, this.hour,
1124                    this.minute, this.second, this.utc, this.originalValue, this.normalized, this.type);
1125            dt.canonical = this.canonical;
1126            dt.position = position;
1127            dt.timezoneHr = this.timezoneHr;
1128            dt.timezoneMin = this.timezoneMin;
1129            dt.unNormYear = this.unNormYear;
1130            dt.unNormMonth = this.unNormMonth;
1131            dt.unNormDay = this.unNormDay;
1132            dt.unNormHour = this.unNormHour;
1133            dt.unNormMinute = this.unNormMinute;
1134            dt.unNormSecond = this.unNormSecond;
1135            return dt;
1136        }
1137
1138        /* (non-Javadoc)
1139         * @see org.apache.xerces.xs.datatypes.XSDateTime#getXMLGregorianCalendar()
1140         */
1141        @Override
1142        public XMLGregorianCalendar getXMLGregorianCalendar() {
1143            return type.getXMLGregorianCalendar(this);
1144        }
1145        /* (non-Javadoc)
1146         * @see org.apache.xerces.xs.datatypes.XSDateTime#getDuration()
1147         */
1148
1149        @Override
1150        public Duration getDuration() {
1151            return type.getDuration(this);
1152        }
1153    }
1154
1155    protected XMLGregorianCalendar getXMLGregorianCalendar(DateTimeData data) {
1156        return null;
1157    }
1158
1159    protected Duration getDuration(DateTimeData data) {
1160        return null;
1161    }
1162
1163    protected final BigDecimal getFractionalSecondsAsBigDecimal(DateTimeData data) {
1164        final StringBuffer buf = new StringBuffer();
1165        append3(buf, data.unNormSecond);
1166        String value = buf.toString();
1167        final int index = value.indexOf('.');
1168        if (index == -1) {
1169            return null;
1170        }
1171        value = value.substring(index);
1172        final BigDecimal _val = new BigDecimal(value);
1173        if (_val.compareTo(BigDecimal.valueOf(0)) == 0) {
1174            return null;
1175        }
1176        return _val;
1177    }
1178}
1179