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