View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.api.util;
21  
22  import static org.apache.directory.api.util.TimeZones.GMT;
23  
24  import java.text.ParseException;
25  import java.util.Calendar;
26  import java.util.Date;
27  import java.util.GregorianCalendar;
28  import java.util.Locale;
29  import java.util.TimeZone;
30  
31  import org.apache.directory.api.i18n.I18n;
32  
33  
34  /**
35   * <p>This class represents the generalized time syntax as defined in 
36   * RFC 4517 section 3.3.13.</p>
37   * 
38   * <p>The date, time and time zone information is internally backed
39   * by an {@link java.util.Calendar} object</p>
40   * 
41   * <p>Leap seconds are not supported, as {@link java.util.Calendar}
42   * does not support leap seconds.</p>
43   * 
44   * <pre>
45   * 3.3.13.  Generalized Time
46   *
47   *  A value of the Generalized Time syntax is a character string
48   *  representing a date and time.  The LDAP-specific encoding of a value
49   *  of this syntax is a restriction of the format defined in [ISO8601],
50   *  and is described by the following ABNF:
51   *
52   *     GeneralizedTime = century year month day hour
53   *                          [ minute [ second / leap-second ] ]
54   *                          [ fraction ]
55   *                          g-time-zone
56   *
57   *     century = 2(%x30-39) ; "00" to "99"
58   *     year    = 2(%x30-39) ; "00" to "99"
59   *     month   =   ( %x30 %x31-39 ) ; "01" (January) to "09"
60   *               / ( %x31 %x30-32 ) ; "10" to "12"
61   *     day     =   ( %x30 %x31-39 )    ; "01" to "09"
62   *               / ( %x31-32 %x30-39 ) ; "10" to "29"
63   *               / ( %x33 %x30-31 )    ; "30" to "31"
64   *     hour    = ( %x30-31 %x30-39 ) / ( %x32 %x30-33 ) ; "00" to "23"
65   *     minute  = %x30-35 %x30-39                        ; "00" to "59"
66   *
67   *     second      = ( %x30-35 %x30-39 ) ; "00" to "59"
68   *     leap-second = ( %x36 %x30 )       ; "60"
69   *
70   *     fraction        = ( DOT / COMMA ) 1*(%x30-39)
71   *     g-time-zone     = %x5A  ; "Z"
72   *                       / g-differential
73   *     g-differential  = ( MINUS / PLUS ) hour [ minute ]
74   *     MINUS           = %x2D  ; minus sign ("-")
75   *
76   *  The &lt;DOT&gt;, &lt;COMMA&gt;, and &lt;PLUS&gt; rules are defined in [RFC4512].
77   *
78   *  The above ABNF allows character strings that do not represent valid
79   *  dates (in the Gregorian calendar) and/or valid times (e.g., February
80   *  31, 1994).  Such character strings SHOULD be considered invalid for
81   *  this syntax.
82   * <br>
83   *  The time value represents coordinated universal time (equivalent to
84   *  Greenwich Mean Time) if the "Z" form of &lt;g-time-zone&gt; is used;
85   *  otherwise, the value represents a local time in the time zone
86   *  indicated by &lt;g-differential&gt;.  In the latter case, coordinated
87   *  universal time can be calculated by subtracting the differential from
88   *  the local time.  The "Z" form of &lt;g-time-zone&gt; SHOULD be used in
89   *  preference to &lt;g-differential&gt;.
90   *  <br>
91   *  If &lt;minute&gt; is omitted, then &lt;fraction&gt; represents a fraction of an
92   *  hour; otherwise, if &lt;second&gt; and &lt;leap-second&gt; are omitted, then
93   *  &lt;fraction&gt; represents a fraction of a minute; otherwise, &lt;fraction&gt;
94   *  represents a fraction of a second.
95   *
96   *     Examples:
97   *        199412161032Z
98   *        199412160532-0500
99   *  
100  *  Both example values represent the same coordinated universal time:
101  *  10:32 AM, December 16, 1994.
102  *  <br>
103  *  The LDAP definition for the Generalized Time syntax is:
104  *  
105  *     ( 1.3.6.1.4.1.1466.115.121.1.24 DESC 'Generalized Time' )
106  *  
107  *  This syntax corresponds to the GeneralizedTime ASN.1 type from
108  *  [ASN.1], with the constraint that local time without a differential
109  *  SHALL NOT be used.
110  * </pre>
111  */
112 public class GeneralizedTime implements Comparable<GeneralizedTime>
113 {
114     /** A Date far in the future, when Micro$oft would have vanished for a long time... */
115     private static final Date INFINITE = new Date( 0x7FFFFFFFFFFFFFFFL );
116     /**
117      * The format of the generalized time.
118      */
119     public enum Format
120     {
121         /** Time format with minutes and seconds, excluding fraction. */
122         YEAR_MONTH_DAY_HOUR_MIN_SEC,
123         /** Time format with minutes and seconds, including fraction. */
124         YEAR_MONTH_DAY_HOUR_MIN_SEC_FRACTION,
125 
126         /** Time format with minutes, seconds are omitted, excluding fraction. */
127         YEAR_MONTH_DAY_HOUR_MIN,
128         /** Time format with minutes seconds are omitted, including fraction. */
129         YEAR_MONTH_DAY_HOUR_MIN_FRACTION,
130 
131         /** Time format, minutes and seconds are omitted, excluding fraction. */
132         YEAR_MONTH_DAY_HOUR,
133         /** Time format, minutes and seconds are omitted, including fraction. */
134         YEAR_MONTH_DAY_HOUR_FRACTION
135     }
136 
137     /**
138      * The fraction delimiter of the generalized time.
139      */
140     public enum FractionDelimiter
141     {
142         /** Use a dot as fraction delimiter. */
143         DOT,
144         /** Use a comma as fraction delimiter. */
145         COMMA
146     }
147 
148     /**
149      * The time zone format of the generalized time.
150      */
151     public enum TimeZoneFormat
152     {
153         /** g-time-zone (Zulu) format. */
154         Z,
155         /** g-differential format, using hour only. */
156         DIFF_HOUR,
157         /** g-differential format, using hour and minute. */
158         DIFF_HOUR_MINUTE
159     }
160 
161     /** The user provided value */
162     private String upGeneralizedTime;
163 
164     /** The user provided format */
165     private Format upFormat;
166 
167     /** The user provided time zone format */
168     private TimeZoneFormat upTimeZoneFormat;
169 
170     /** The user provided fraction delimiter */
171     private FractionDelimiter upFractionDelimiter;
172 
173     /** the user provided fraction length */
174     private int upFractionLength;
175 
176     /** The calendar */
177     private Calendar calendar;
178 
179 
180     /**
181      * 
182      * Creates a new instance of GeneralizedTime by setting the date to an instance of Calendar.
183      * @see #GeneralizedTime(Calendar)
184      * 
185      * @param date the date
186      */
187     public GeneralizedTime( Date date )
188     {
189         calendar = new GregorianCalendar( GMT, Locale.ROOT );
190         calendar.setTime( date );
191         setUp( calendar );
192     }
193 
194 
195     /**
196      * Creates a new instance of GeneralizedTime, based on the given Calendar object.
197      * Uses <pre>Format.YEAR_MONTH_DAY_HOUR_MIN_SEC</pre> as default format and
198      * <pre>TimeZoneFormat.Z</pre> as default time zone format. 
199      *
200      * @param calendar the calendar containing the date, time and timezone information
201      */
202     public GeneralizedTime( Calendar calendar )
203     {
204         setUp( calendar );
205     }
206 
207 
208     /**
209      * Creates a new instance of GeneralizedTime, based on the
210      * given generalized time string.
211      *
212      * @param generalizedTime the generalized time
213      * 
214      * @throws ParseException if the given generalized time can't be parsed.
215      */
216     public GeneralizedTime( String generalizedTime ) throws ParseException
217     {
218         if ( generalizedTime == null )
219         {
220             throw new ParseException( I18n.err( I18n.ERR_04359 ), 0 );
221         }
222 
223         this.upGeneralizedTime = generalizedTime;
224 
225         calendar = new GregorianCalendar( GMT, Locale.ROOT );
226         calendar.setTimeInMillis( 0 );
227         calendar.setLenient( false );
228 
229         parseYear();
230         parseMonth();
231         parseDay();
232         parseHour();
233 
234         if ( upGeneralizedTime.length() < 11 )
235         {
236             throw new ParseException( I18n.err( I18n.ERR_04360 ), 10 );
237         }
238 
239         // pos 10: 
240         // if digit => minute field
241         // if . or , => fraction of hour field
242         // if Z or + or - => timezone field
243         // else error
244         int pos = 10;
245         char c = upGeneralizedTime.charAt( pos );
246         
247         if ( ( '0' <= c ) && ( c <= '9' ) )
248         {
249             parseMinute();
250 
251             if ( upGeneralizedTime.length() < 13 )
252             {
253                 throw new ParseException( I18n.err( I18n.ERR_04361 ), 12 );
254             }
255 
256             // pos 12: 
257             // if digit => second field
258             // if . or , => fraction of minute field
259             // if Z or + or - => timezone field
260             // else error
261             pos = 12;
262             c = upGeneralizedTime.charAt( pos );
263             
264             if ( ( '0' <= c ) && ( c <= '9' ) )
265             {
266                 parseSecond();
267 
268                 if ( upGeneralizedTime.length() < 15 )
269                 {
270                     throw new ParseException( I18n.err( I18n.ERR_04362 ), 14 );
271                 }
272 
273                 // pos 14: 
274                 // if . or , => fraction of second field
275                 // if Z or + or - => timezone field
276                 // else error
277                 pos = 14;
278                 c = upGeneralizedTime.charAt( pos );
279                 
280                 if ( ( c == '.' ) || ( c == ',' ) )
281                 {
282                     // read fraction of second
283                     parseFractionOfSecond();
284                     pos += 1 + upFractionLength;
285 
286                     parseTimezone( pos );
287                     upFormat = Format.YEAR_MONTH_DAY_HOUR_MIN_SEC_FRACTION;
288                 }
289                 else if ( ( c == 'Z' ) || ( c == '+' ) || ( c == '-' ) )
290                 {
291                     // read timezone
292                     parseTimezone( pos );
293                     upFormat = Format.YEAR_MONTH_DAY_HOUR_MIN_SEC;
294                 }
295                 else
296                 {
297                     throw new ParseException( I18n.err( I18n.ERR_04363 ), 14 );
298                 }
299             }
300             else if ( ( c == '.' ) || ( c == ',' ) )
301             {
302                 // read fraction of minute
303                 parseFractionOfMinute();
304                 pos += 1 + upFractionLength;
305 
306                 parseTimezone( pos );
307                 upFormat = Format.YEAR_MONTH_DAY_HOUR_MIN_FRACTION;
308             }
309             else if ( ( c == 'Z' ) || ( c == '+' ) || ( c == '-' ) )
310             {
311                 // read timezone
312                 parseTimezone( pos );
313                 upFormat = Format.YEAR_MONTH_DAY_HOUR_MIN;
314             }
315             else
316             {
317                 throw new ParseException( I18n.err( I18n.ERR_04364 ), 12 );
318             }
319         }
320         else if ( ( c == '.' ) || ( c == ',' ) )
321         {
322             // read fraction of hour
323             parseFractionOfHour();
324             pos += 1 + upFractionLength;
325 
326             parseTimezone( pos );
327             upFormat = Format.YEAR_MONTH_DAY_HOUR_FRACTION;
328         }
329         else if ( ( c == 'Z' ) || ( c == '+' ) || ( c == '-' ) )
330         {
331             // read timezone
332             parseTimezone( pos );
333             upFormat = Format.YEAR_MONTH_DAY_HOUR;
334         }
335         else
336         {
337             throw new ParseException( I18n.err( I18n.ERR_04365 ), 10 );
338         }
339 
340         // this calculates and verifies the calendar
341         /* Not sure we should do that... */
342         try
343         {
344             calendar.getTimeInMillis();
345         }
346         catch ( IllegalArgumentException iae )
347         {
348             throw new ParseException( I18n.err( I18n.ERR_04366 ), 0 );
349         }
350 
351         calendar.setLenient( true );
352     }
353 
354 
355     private void setUp( Calendar newCalendar )
356     {
357         if ( newCalendar == null )
358         {
359             throw new IllegalArgumentException( I18n.err( I18n.ERR_04358 ) );
360         }
361 
362         this.calendar = newCalendar;
363         upGeneralizedTime = null;
364         upFormat = Format.YEAR_MONTH_DAY_HOUR_MIN_SEC_FRACTION;
365         upTimeZoneFormat = TimeZoneFormat.Z;
366         upFractionDelimiter = FractionDelimiter.DOT;
367         upFractionLength = 3;
368     }
369 
370 
371     private void parseTimezone( int pos ) throws ParseException
372     {
373         if ( upGeneralizedTime.length() < pos + 1 )
374         {
375             throw new ParseException( I18n.err( I18n.ERR_04367 ), pos );
376         }
377 
378         char c = upGeneralizedTime.charAt( pos );
379         
380         if ( c == 'Z' )
381         {
382             calendar.setTimeZone( GMT );
383             upTimeZoneFormat = TimeZoneFormat.Z;
384 
385             if ( upGeneralizedTime.length() > pos + 1 )
386             {
387                 throw new ParseException( I18n.err( I18n.ERR_04368 ), pos + 1 );
388             }
389         }
390         else if ( ( c == '+' ) || ( c == '-' ) )
391         {
392             StringBuilder sb = new StringBuilder( "GMT" );
393             sb.append( c );
394 
395             String digits = getAllDigits( pos + 1 );
396             sb.append( digits );
397 
398             if ( digits.length() == 2 && digits.matches( "^([01]\\d|2[0-3])$" ) )
399             {
400                 TimeZone timeZone = TimeZone.getTimeZone( sb.toString() );
401                 calendar.setTimeZone( timeZone );
402                 upTimeZoneFormat = TimeZoneFormat.DIFF_HOUR;
403             }
404             else if ( digits.length() == 4 && digits.matches( "^([01]\\d|2[0-3])([0-5]\\d)$" ) )
405             {
406                 TimeZone timeZone = TimeZone.getTimeZone( sb.toString() );
407                 calendar.setTimeZone( timeZone );
408                 upTimeZoneFormat = TimeZoneFormat.DIFF_HOUR_MINUTE;
409             }
410             else
411             {
412                 throw new ParseException( I18n.err( I18n.ERR_04369 ), pos );
413             }
414 
415             if ( upGeneralizedTime.length() > pos + 1 + digits.length() )
416             {
417                 throw new ParseException( I18n.err( I18n.ERR_04370 ), pos + 1 + digits.length() );
418             }
419         }
420     }
421 
422 
423     private void parseFractionOfSecond() throws ParseException
424     {
425         parseFractionDelmiter( 14 );
426         String fraction = getFraction( 14 + 1 );
427         upFractionLength = fraction.length();
428 
429         double fract = Double.parseDouble( "0." + fraction );
430         int millisecond = ( int ) Math.floor( fract * 1000 );
431 
432         calendar.set( GregorianCalendar.MILLISECOND, millisecond );
433     }
434 
435 
436     private void parseFractionOfMinute() throws ParseException
437     {
438         parseFractionDelmiter( 12 );
439         String fraction = getFraction( 12 + 1 );
440         upFractionLength = fraction.length();
441 
442         double fract = Double.parseDouble( "0." + fraction );
443         int milliseconds = ( int ) Math.round( fract * 1000 * 60 );
444         int second = milliseconds / 1000;
445         int millisecond = milliseconds - ( second * 1000 );
446 
447         calendar.set( Calendar.SECOND, second );
448         calendar.set( Calendar.MILLISECOND, millisecond );
449     }
450 
451 
452     private void parseFractionOfHour() throws ParseException
453     {
454         parseFractionDelmiter( 10 );
455         String fraction = getFraction( 10 + 1 );
456         upFractionLength = fraction.length();
457 
458         double fract = Double.parseDouble( "0." + fraction );
459         int milliseconds = ( int ) Math.round( fract * 1000 * 60 * 60 );
460         int minute = milliseconds / ( 1000 * 60 );
461         int second = ( milliseconds - ( minute * 60 * 1000 ) ) / 1000;
462         int millisecond = milliseconds - ( minute * 60 * 1000 ) - ( second * 1000 );
463 
464         calendar.set( Calendar.MINUTE, minute );
465         calendar.set( Calendar.SECOND, second );
466         calendar.set( Calendar.MILLISECOND, millisecond );
467     }
468 
469 
470     private void parseFractionDelmiter( int fractionDelimiterPos )
471     {
472         char c = upGeneralizedTime.charAt( fractionDelimiterPos );
473         upFractionDelimiter = c == '.' ? FractionDelimiter.DOT : FractionDelimiter.COMMA;
474     }
475 
476 
477     private String getFraction( int startIndex ) throws ParseException
478     {
479         String fraction = getAllDigits( startIndex );
480 
481         // minimum one digit
482         if ( fraction.length() == 0 )
483         {
484             throw new ParseException( I18n.err( I18n.ERR_04371 ), startIndex );
485         }
486 
487         return fraction;
488     }
489 
490 
491     private String getAllDigits( int startIndex )
492     {
493         StringBuilder sb = new StringBuilder();
494         while ( upGeneralizedTime.length() > startIndex )
495         {
496             char c = upGeneralizedTime.charAt( startIndex );
497             if ( '0' <= c && c <= '9' )
498             {
499                 sb.append( c );
500                 startIndex++;
501             }
502             else
503             {
504                 break;
505             }
506         }
507         return sb.toString();
508     }
509 
510 
511     private void parseSecond() throws ParseException
512     {
513         // read minute
514         if ( upGeneralizedTime.length() < 14 )
515         {
516             throw new ParseException( I18n.err( I18n.ERR_04372 ), 12 );
517         }
518         try
519         {
520             int second = Strings.parseInt( upGeneralizedTime.substring( 12, 14 ) );
521             calendar.set( Calendar.SECOND, second );
522         }
523         catch ( NumberFormatException e )
524         {
525             throw new ParseException( I18n.err( I18n.ERR_04373 ), 12 );
526         }
527     }
528 
529 
530     private void parseMinute() throws ParseException
531     {
532         // read minute
533         if ( upGeneralizedTime.length() < 12 )
534         {
535             throw new ParseException( I18n.err( I18n.ERR_04374 ), 10 );
536         }
537         try
538         {
539             int minute = Strings.parseInt( upGeneralizedTime.substring( 10, 12 ) );
540             calendar.set( Calendar.MINUTE, minute );
541         }
542         catch ( NumberFormatException e )
543         {
544             throw new ParseException( I18n.err( I18n.ERR_04375 ), 10 );
545         }
546     }
547 
548 
549     private void parseHour() throws ParseException
550     {
551         if ( upGeneralizedTime.length() < 10 )
552         {
553             throw new ParseException( I18n.err( I18n.ERR_04376 ), 8 );
554         }
555         try
556         {
557             int hour = Strings.parseInt( upGeneralizedTime.substring( 8, 10 ) );
558             calendar.set( Calendar.HOUR_OF_DAY, hour );
559         }
560         catch ( NumberFormatException e )
561         {
562             throw new ParseException( I18n.err( I18n.ERR_04377 ), 8 );
563         }
564     }
565 
566 
567     private void parseDay() throws ParseException
568     {
569         if ( upGeneralizedTime.length() < 8 )
570         {
571             throw new ParseException( I18n.err( I18n.ERR_04378 ), 6 );
572         }
573         try
574         {
575             int day = Strings.parseInt( upGeneralizedTime.substring( 6, 8 ) );
576             calendar.set( Calendar.DAY_OF_MONTH, day );
577         }
578         catch ( NumberFormatException e )
579         {
580             throw new ParseException( I18n.err( I18n.ERR_04379 ), 6 );
581         }
582     }
583 
584 
585     private void parseMonth() throws ParseException
586     {
587         if ( upGeneralizedTime.length() < 6 )
588         {
589             throw new ParseException( I18n.err( I18n.ERR_04380 ), 4 );
590         }
591         try
592         {
593             int month = Strings.parseInt( upGeneralizedTime.substring( 4, 6 ) );
594             calendar.set( Calendar.MONTH, month - 1 );
595         }
596         catch ( NumberFormatException e )
597         {
598             throw new ParseException( I18n.err( I18n.ERR_04381 ), 4 );
599         }
600     }
601 
602 
603     private void parseYear() throws ParseException
604     {
605         if ( upGeneralizedTime.length() < 4 )
606         {
607             throw new ParseException( I18n.err( I18n.ERR_04382 ), 0 );
608         }
609         try
610         {
611             int year = Strings.parseInt( upGeneralizedTime.substring( 0, 4 ) );
612             calendar.set( Calendar.YEAR, year );
613         }
614         catch ( NumberFormatException e )
615         {
616             throw new ParseException( I18n.err( I18n.ERR_04383 ), 0 );
617         }
618     }
619 
620 
621     /**
622      * Returns the string representation of this generalized time. 
623      * This method uses the same format as the user provided format.
624      *
625      * @return the string representation of this generalized time
626      */
627     public String toGeneralizedTime()
628     {
629         return toGeneralizedTime( upFormat, upFractionDelimiter, upFractionLength, upTimeZoneFormat );
630     }
631 
632 
633     /**
634      * Returns the string representation of this generalized time. 
635      * This method uses the same format as the user provided format.
636      *
637      * @return the string representation of this generalized time
638      */
639     public String toGeneralizedTimeWithoutFraction()
640     {
641         return toGeneralizedTime( getFormatWithoutFraction( upFormat ), upFractionDelimiter, upFractionLength,
642             upTimeZoneFormat );
643     }
644 
645 
646     /**
647      * Gets the corresponding format with fraction.
648      *
649      * @param f the format
650      * @return the corresponding format without fraction
651      */
652     private Format getFormatWithoutFraction( Format f )
653     {
654         switch ( f )
655         {
656             case YEAR_MONTH_DAY_HOUR_FRACTION:
657                 return Format.YEAR_MONTH_DAY_HOUR;
658             case YEAR_MONTH_DAY_HOUR_MIN_FRACTION:
659                 return Format.YEAR_MONTH_DAY_HOUR_MIN;
660             case YEAR_MONTH_DAY_HOUR_MIN_SEC_FRACTION:
661                 return Format.YEAR_MONTH_DAY_HOUR_MIN_SEC;
662             default:
663                 break;
664         }
665 
666         return f;
667     }
668 
669 
670     /**
671      * Returns the string representation of this generalized time.
672      * 
673      * @param format the target format
674      * @param fractionDelimiter the target fraction delimiter, may be null
675      * @param fractionLength the fraction length
676      * @param timeZoneFormat the target time zone format
677      * 
678      * @return the string
679      */
680     public String toGeneralizedTime( Format format, FractionDelimiter fractionDelimiter, int fractionLength,
681         TimeZoneFormat timeZoneFormat )
682     {
683         Calendar clonedCalendar = ( Calendar ) calendar.clone();
684 
685         if ( timeZoneFormat == TimeZoneFormat.Z )
686         {
687             clonedCalendar.setTimeZone( GMT );
688         }
689 
690         // Create the result. It can contain a maximum of 23 chars
691         byte[] result = new byte[23];
692 
693         // The starting point
694         int pos = 0;
695 
696         // Inject the year
697         int year = clonedCalendar.get( Calendar.YEAR );
698 
699         result[pos++] = ( byte ) ( ( year / 1000 ) + '0' );
700         year %= 1000;
701 
702         result[pos++] = ( byte ) ( ( year / 100 ) + '0' );
703         year %= 100;
704 
705         result[pos++] = ( byte ) ( ( year / 10 ) + '0' );
706 
707         result[pos++] = ( byte ) ( ( year % 10 ) + '0' );
708 
709         // Inject the month
710         int month = clonedCalendar.get( Calendar.MONTH ) + 1;
711 
712         result[pos++] = ( byte ) ( ( month / 10 ) + '0' );
713 
714         result[pos++] = ( byte ) ( ( month % 10 ) + '0' );
715 
716         // Inject the day
717         int day = clonedCalendar.get( Calendar.DAY_OF_MONTH );
718 
719         result[pos++] = ( byte ) ( ( day / 10 ) + '0' );
720 
721         result[pos++] = ( byte ) ( ( day % 10 ) + '0' );
722 
723         // Inject the hour
724         int hour = clonedCalendar.get( Calendar.HOUR_OF_DAY );
725 
726         result[pos++] = ( byte ) ( ( hour / 10 ) + '0' );
727 
728         result[pos++] = ( byte ) ( ( hour % 10 ) + '0' );
729 
730         switch ( format )
731         {
732             case YEAR_MONTH_DAY_HOUR_MIN_SEC:
733                 // Inject the minutes
734                 int minute = clonedCalendar.get( Calendar.MINUTE );
735 
736                 result[pos++] = ( byte ) ( ( minute / 10 ) + '0' );
737 
738                 result[pos++] = ( byte ) ( ( minute % 10 ) + '0' );
739 
740                 // Inject the seconds
741                 int second = clonedCalendar.get( Calendar.SECOND );
742 
743                 result[pos++] = ( byte ) ( ( second / 10 ) + '0' );
744 
745                 result[pos++] = ( byte ) ( ( second % 10 ) + '0' );
746 
747                 break;
748 
749             case YEAR_MONTH_DAY_HOUR_MIN_SEC_FRACTION:
750                 // Inject the minutes
751                 minute = clonedCalendar.get( Calendar.MINUTE );
752 
753                 result[pos++] = ( byte ) ( ( minute / 10 ) + '0' );
754 
755                 result[pos++] = ( byte ) ( ( minute % 10 ) + '0' );
756 
757                 // Inject the seconds
758                 second = clonedCalendar.get( Calendar.SECOND );
759 
760                 result[pos++] = ( byte ) ( ( second / 10 ) + '0' );
761 
762                 result[pos++] = ( byte ) ( ( second % 10 ) + '0' );
763 
764                 // Inject the fraction
765                 if ( fractionDelimiter == FractionDelimiter.COMMA )
766                 {
767                     result[pos++] = ',';
768                 }
769                 else
770                 {
771                     result[pos++] = '.';
772                 }
773 
774                 // Inject the fraction
775                 int millisecond = clonedCalendar.get( Calendar.MILLISECOND );
776 
777                 result[pos++] = ( byte ) ( ( millisecond / 100 ) + '0' );
778                 millisecond %= 100;
779 
780                 result[pos++] = ( byte ) ( ( millisecond / 10 ) + '0' );
781 
782                 //if ( millisecond > 0 )
783                 result[pos++] = ( byte ) ( ( millisecond % 10 ) + '0' );
784 
785                 break;
786 
787             case YEAR_MONTH_DAY_HOUR_MIN:
788                 // Inject the minutes
789                 minute = clonedCalendar.get( Calendar.MINUTE );
790 
791                 result[pos++] = ( byte ) ( ( minute / 10 ) + '0' );
792 
793                 result[pos++] = ( byte ) ( ( minute % 10 ) + '0' );
794                 break;
795 
796             case YEAR_MONTH_DAY_HOUR_MIN_FRACTION:
797                 // Inject the minutes
798                 minute = clonedCalendar.get( Calendar.MINUTE );
799 
800                 result[pos++] = ( byte ) ( ( minute / 10 ) + '0' );
801 
802                 result[pos++] = ( byte ) ( ( minute % 10 ) + '0' );
803 
804                 // sec + millis => fraction of a minute
805                 int fraction = 1000 * clonedCalendar.get( Calendar.SECOND )
806                     + clonedCalendar.get( Calendar.MILLISECOND );
807                 fraction /= 60;
808 
809                 if ( fraction > 0 )
810                 {
811                     if ( fractionDelimiter == FractionDelimiter.COMMA )
812                     {
813                         result[pos++] = ',';
814                     }
815                     else
816                     {
817                         result[pos++] = '.';
818                     }
819 
820                     // At this point, the fraction should be in [999, 1]
821                     result[pos++] = ( byte ) ( ( fraction / 100 ) + '0' );
822                     fraction %= 100;
823 
824                     if ( fraction > 0 )
825                     {
826                         result[pos++] = ( byte ) ( ( fraction / 10 ) + '0' );
827 
828                         if ( fraction > 0 )
829                         {
830                             result[pos++] = ( byte ) ( ( fraction % 10 ) + '0' );
831                         }
832                     }
833                 }
834 
835                 break;
836 
837             case YEAR_MONTH_DAY_HOUR:
838                 // nothing to add
839                 break;
840 
841             case YEAR_MONTH_DAY_HOUR_FRACTION:
842                 // min + sec + millis => fraction of an hour
843                 fraction = 1000 * 60 * clonedCalendar.get( Calendar.MINUTE ) + 1000
844                     * clonedCalendar.get( Calendar.SECOND )
845                     + clonedCalendar.get( Calendar.MILLISECOND );
846                 fraction /= 60 * 60;
847 
848                 // At this point, the fraction should be in [999, 1]
849                 if ( fraction > 0 )
850                 {
851                     if ( fractionDelimiter == FractionDelimiter.COMMA )
852                     {
853                         result[pos++] = ',';
854                     }
855                     else
856                     {
857                         result[pos++] = '.';
858                     }
859 
860                     result[pos++] = ( byte ) ( ( fraction / 100 ) + '0' );
861                     fraction %= 100;
862 
863                     if ( fraction > 0 )
864                     {
865                         result[pos++] = ( byte ) ( ( fraction / 10 ) + '0' );
866 
867                         if ( fraction > 0 )
868                         {
869                             result[pos++] = ( byte ) ( ( fraction % 10 ) + '0' );
870                         }
871                     }
872                 }
873 
874                 break;
875 
876             default:
877                 throw new IllegalArgumentException( "Unexpected format " + format );
878         }
879 
880         if ( ( timeZoneFormat == TimeZoneFormat.Z ) && clonedCalendar.getTimeZone().hasSameRules( GMT ) )
881         {
882             result[pos++] = 'Z';
883         }
884         else
885         {
886             // g-differential
887             TimeZone timeZone = clonedCalendar.getTimeZone();
888             int rawOffset = timeZone.getRawOffset();
889 
890             if ( rawOffset < 0 )
891             {
892                 result[pos++] = '-';
893             }
894             else
895             {
896                 result[pos++] = '+';
897             }
898 
899             rawOffset = Math.abs( rawOffset );
900             hour = rawOffset / ( 60 * 60 * 1000 );
901             int minute = ( rawOffset - ( hour * 60 * 60 * 1000 ) ) / ( 1000 * 60 );
902 
903             // The offset hour
904             result[pos++] = ( byte ) ( ( hour / 10 ) + '0' );
905 
906             result[pos++] = ( byte ) ( ( hour % 10 ) + '0' );
907 
908             if ( ( timeZoneFormat == TimeZoneFormat.DIFF_HOUR_MINUTE ) || ( timeZoneFormat == TimeZoneFormat.Z ) )
909             {
910                 // The offset minute
911                 result[pos++] = ( byte ) ( ( minute / 10 ) + '0' );
912 
913                 result[pos++] = ( byte ) ( ( minute % 10 ) + '0' );
914             }
915         }
916 
917         return Strings.utf8ToString( result, 0, pos );
918     }
919 
920 
921     /**
922      * Gets the calendar. It could be used to manipulate this 
923      * {@link GeneralizedTime} settings.
924      * 
925      * @return the calendar
926      */
927     public Calendar getCalendar()
928     {
929         return calendar;
930     }
931 
932 
933     @Override
934     public String toString()
935     {
936         return toGeneralizedTime();
937     }
938 
939 
940     @Override
941     public int hashCode()
942     {
943         final int prime = 31;
944         int result = 1;
945         result = prime * result + calendar.hashCode();
946         return result;
947     }
948 
949 
950     @Override
951     public boolean equals( Object obj )
952     {
953         if ( obj instanceof GeneralizedTime )
954         {
955             GeneralizedTime other = ( GeneralizedTime ) obj;
956             return calendar.equals( other.calendar );
957         }
958         else
959         {
960             return false;
961         }
962     }
963 
964 
965     /**
966      * Compares this GeneralizedTime object with the specified GeneralizedTime object.
967      * 
968      * @param other the other GeneralizedTime object
969      * 
970      * @return a negative integer, zero, or a positive integer as this object
971      *      is less than, equal to, or greater than the specified object.
972      * 
973      * @see java.lang.Comparable#compareTo(java.lang.Object)
974      */
975     @Override
976     public int compareTo( GeneralizedTime other )
977     {
978         return calendar.compareTo( other.calendar );
979     }
980 
981 
982     /**
983      * @return A Date representing the time as milliseconds
984      */
985     public long getTime()
986     {
987         return calendar.getTimeInMillis();
988     }
989 
990 
991     /**
992      * @return A Date representing the time
993      */
994     public Date getDate()
995     {
996         return calendar.getTime();
997     }
998 
999 
1000     /**
1001      * @return The year part of the date
1002      */
1003     public int getYear()
1004     {
1005         return calendar.get( Calendar.YEAR );
1006     }
1007 
1008 
1009     /**
1010      * @return The month part of the date
1011      */
1012     public int getMonth()
1013     {
1014         return calendar.get( Calendar.MONTH );
1015     }
1016 
1017 
1018     /**
1019      * @return The day part of the date
1020      */
1021     public int getDay()
1022     {
1023         return calendar.get( Calendar.DATE );
1024     }
1025 
1026 
1027     /**
1028      * @return The hours part of the date
1029      */
1030     public int getHour()
1031     {
1032         return calendar.get( Calendar.HOUR_OF_DAY );
1033     }
1034 
1035 
1036     /**
1037      * @return The minutes part of the date
1038      */
1039     public int getMinutes()
1040     {
1041         return calendar.get( Calendar.MINUTE );
1042     }
1043 
1044 
1045     /**
1046      * @return The seconds part of the date
1047      */
1048     public int getSeconds()
1049     {
1050         return calendar.get( Calendar.SECOND );
1051     }
1052 
1053 
1054     /**
1055      * @return The fractional (ie, milliseconds) part of the date
1056      */
1057     public int getFraction()
1058     {
1059         return calendar.get( Calendar.MILLISECOND );
1060     }
1061 
1062 
1063     /**
1064      * Get a Date instance from a given String
1065      *
1066      * @param zuluTime The time as a String
1067      * @return A Date instance
1068      * @throws ParseException If the String is not a valid date
1069      */
1070     public static Date getDate( String zuluTime ) throws ParseException
1071     {
1072         try 
1073         {
1074             return new GeneralizedTime( zuluTime ).calendar.getTime();
1075         }
1076         catch ( ParseException pe )
1077         {
1078             // Maybe one of the multiple Micro$oft ineptness to cope with Standards ?
1079             if ( "9223372036854775807".equals( zuluTime ) )
1080             {
1081                 // This 0x7FFFFFFFFFFFFFFF, never ending date
1082                 return INFINITE;
1083             }
1084             else
1085             {
1086                 throw pe;
1087             }
1088         }
1089     }
1090 }