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     */
020    package org.apache.directory.shared.ldap.util;
021    
022    
023    import java.text.DecimalFormat;
024    import java.text.NumberFormat;
025    import java.text.ParseException;
026    import java.util.Calendar;
027    import java.util.TimeZone;
028    
029    import org.apache.directory.shared.i18n.I18n;
030    
031    
032    /**
033     * <p>This class represents the generalized time syntax as defined in 
034     * RFC 4517 section 3.3.13.</p>
035     * 
036     * <p>The date, time and time zone information is internally backed
037     * by an {@link java.util.Calendar} object</p>
038     * 
039     * <p>Leap seconds are not supported, as {@link java.util.Calendar}
040     * does not support leap seconds.</p>
041     * 
042     * <pre>
043     * 3.3.13.  Generalized Time
044     *
045     *  A value of the Generalized Time syntax is a character string
046     *  representing a date and time.  The LDAP-specific encoding of a value
047     *  of this syntax is a restriction of the format defined in [ISO8601],
048     *  and is described by the following ABNF:
049     *
050     *     GeneralizedTime = century year month day hour
051     *                          [ minute [ second / leap-second ] ]
052     *                          [ fraction ]
053     *                          g-time-zone
054     *
055     *     century = 2(%x30-39) ; "00" to "99"
056     *     year    = 2(%x30-39) ; "00" to "99"
057     *     month   =   ( %x30 %x31-39 ) ; "01" (January) to "09"
058     *               / ( %x31 %x30-32 ) ; "10" to "12"
059     *     day     =   ( %x30 %x31-39 )    ; "01" to "09"
060     *               / ( %x31-32 %x30-39 ) ; "10" to "29"
061     *               / ( %x33 %x30-31 )    ; "30" to "31"
062     *     hour    = ( %x30-31 %x30-39 ) / ( %x32 %x30-33 ) ; "00" to "23"
063     *     minute  = %x30-35 %x30-39                        ; "00" to "59"
064     *
065     *     second      = ( %x30-35 %x30-39 ) ; "00" to "59"
066     *     leap-second = ( %x36 %x30 )       ; "60"
067     *
068     *     fraction        = ( DOT / COMMA ) 1*(%x30-39)
069     *     g-time-zone     = %x5A  ; "Z"
070     *                       / g-differential
071     *     g-differential  = ( MINUS / PLUS ) hour [ minute ]
072     *     MINUS           = %x2D  ; minus sign ("-")
073     *
074     *  The <DOT>, <COMMA>, and <PLUS> rules are defined in [RFC4512].
075     *
076     *  The above ABNF allows character strings that do not represent valid
077     *  dates (in the Gregorian calendar) and/or valid times (e.g., February
078     *  31, 1994).  Such character strings SHOULD be considered invalid for
079     *  this syntax.
080     *
081     *  The time value represents coordinated universal time (equivalent to
082     *  Greenwich Mean Time) if the "Z" form of <g-time-zone> is used;
083     *  otherwise, the value represents a local time in the time zone
084     *  indicated by <g-differential>.  In the latter case, coordinated
085     *  universal time can be calculated by subtracting the differential from
086     *  the local time.  The "Z" form of <g-time-zone> SHOULD be used in
087     *  preference to <g-differential>.
088     *
089     *  If <minute> is omitted, then <fraction> represents a fraction of an
090     *  hour; otherwise, if <second> and <leap-second> are omitted, then
091     *  <fraction> represents a fraction of a minute; otherwise, <fraction>
092     *  represents a fraction of a second.
093     *
094     *     Examples:
095     *        199412161032Z
096     *        199412160532-0500
097     *
098     *  Both example values represent the same coordinated universal time:
099     *  10:32 AM, December 16, 1994.
100     *
101     *  The LDAP definition for the Generalized Time syntax is:
102     *
103     *     ( 1.3.6.1.4.1.1466.115.121.1.24 DESC 'Generalized Time' )
104     *
105     *  This syntax corresponds to the GeneralizedTime ASN.1 type from
106     *  [ASN.1], with the constraint that local time without a differential
107     *  SHALL NOT be used.
108     *
109     * </pre>
110     */
111    public class GeneralizedTime implements Comparable<GeneralizedTime>
112    {
113    
114        public enum Format
115        {
116            YEAR_MONTH_DAY_HOUR_MIN_SEC, YEAR_MONTH_DAY_HOUR_MIN_SEC_FRACTION,
117    
118            YEAR_MONTH_DAY_HOUR_MIN, YEAR_MONTH_DAY_HOUR_MIN_FRACTION,
119    
120            YEAR_MONTH_DAY_HOUR, YEAR_MONTH_DAY_HOUR_FRACTION, ;
121        }
122    
123        public enum FractionDelimiter
124        {
125            DOT, COMMA
126        }
127    
128        public enum TimeZoneFormat
129        {
130            Z, DIFF_HOUR, DIFF_HOUR_MINUTE;
131        }
132    
133        private static final TimeZone GMT = TimeZone.getTimeZone( "GMT" );
134    
135        /** The user provided value */
136        private String upGeneralizedTime;
137    
138        /** The user provided format */
139        private Format upFormat;
140    
141        /** The user provided time zone format */
142        private TimeZoneFormat upTimeZoneFormat;
143    
144        /** The user provided fraction delimiter */
145        private FractionDelimiter upFractionDelimiter;
146    
147        /** the user provided fraction length */
148        private int upFractionLength;
149    
150        /** The calendar */
151        private Calendar calendar;
152    
153    
154        /**
155         * Creates a new instance of GeneralizedTime, based on the given Calendar object.
156         * Uses <pre>Format.YEAR_MONTH_DAY_HOUR_MIN_SEC</pre> as default format and
157         * <pre>TimeZoneFormat.Z</pre> as default time zone format. 
158         *
159         * @param calendar the calendar containing the date, time and timezone information
160         */
161        public GeneralizedTime( Calendar calendar )
162        {
163            if ( calendar == null )
164            {
165                throw new IllegalArgumentException( I18n.err( I18n.ERR_04358 ) );
166            }
167    
168            this.calendar = calendar;
169            upGeneralizedTime = null;
170            upFormat = Format.YEAR_MONTH_DAY_HOUR_MIN_SEC;
171            upTimeZoneFormat = TimeZoneFormat.Z;
172            upFractionDelimiter = FractionDelimiter.DOT;
173            upFractionLength = 3;
174        }
175    
176    
177        /**
178         * Creates a new instance of GeneralizedTime, based on the
179         * given generalized time string.
180         *
181         * @param generalizedTime the generalized time
182         * 
183         * @throws ParseException if the given generalized time can't be parsed.
184         */
185        public GeneralizedTime( String generalizedTime ) throws ParseException
186        {
187            if ( generalizedTime == null )
188            {
189                throw new ParseException( I18n.err( I18n.ERR_04359 ), 0 );
190            }
191    
192            this.upGeneralizedTime = generalizedTime;
193    
194            calendar = Calendar.getInstance();
195            calendar.setTimeInMillis( 0 );
196            calendar.setLenient( false );
197    
198            parseYear();
199            parseMonth();
200            parseDay();
201            parseHour();
202    
203            if ( upGeneralizedTime.length() < 11 )
204            {
205                throw new ParseException( I18n.err( I18n.ERR_04360 ), 10 );
206            }
207    
208            // pos 10: 
209            // if digit => minute field
210            // if . or , => fraction of hour field
211            // if Z or + or - => timezone field
212            // else error
213            int pos = 10;
214            char c = upGeneralizedTime.charAt( pos );
215            if ( '0' <= c && c <= '9' )
216            {
217                parseMinute();
218    
219                if ( upGeneralizedTime.length() < 13 )
220                {
221                    throw new ParseException( I18n.err( I18n.ERR_04361 ), 12 );
222                }
223    
224                // pos 12: 
225                // if digit => second field
226                // if . or , => fraction of minute field
227                // if Z or + or - => timezone field
228                // else error
229                pos = 12;
230                c = upGeneralizedTime.charAt( pos );
231                if ( '0' <= c && c <= '9' )
232                {
233                    parseSecond();
234    
235                    if ( upGeneralizedTime.length() < 15 )
236                    {
237                        throw new ParseException( I18n.err( I18n.ERR_04362 ), 14 );
238                    }
239    
240                    // pos 14: 
241                    // if . or , => fraction of second field
242                    // if Z or + or - => timezone field
243                    // else error
244                    pos = 14;
245                    c = upGeneralizedTime.charAt( pos );
246                    if ( c == '.' || c == ',' )
247                    {
248                        // read fraction of second
249                        parseFractionOfSecond();
250                        pos += 1 + upFractionLength;
251    
252                        parseTimezone( pos );
253                        upFormat = Format.YEAR_MONTH_DAY_HOUR_MIN_SEC_FRACTION;
254                    }
255                    else if ( c == 'Z' || c == '+' || c == '-' )
256                    {
257                        // read timezone
258                        parseTimezone( pos );
259                        upFormat = Format.YEAR_MONTH_DAY_HOUR_MIN_SEC;
260                    }
261                    else
262                    {
263                        throw new ParseException( I18n.err( I18n.ERR_04363 ), 14 );
264                    }
265                }
266                else if ( c == '.' || c == ',' )
267                {
268                    // read fraction of minute
269                    parseFractionOfMinute();
270                    pos += 1 + upFractionLength;
271    
272                    parseTimezone( pos );
273                    upFormat = Format.YEAR_MONTH_DAY_HOUR_MIN_FRACTION;
274                }
275                else if ( c == 'Z' || c == '+' || c == '-' )
276                {
277                    // read timezone
278                    parseTimezone( pos );
279                    upFormat = Format.YEAR_MONTH_DAY_HOUR_MIN;
280                }
281                else
282                {
283                    throw new ParseException( I18n.err( I18n.ERR_04364 ), 12 );
284                }
285            }
286            else if ( c == '.' || c == ',' )
287            {
288                // read fraction of hour
289                parseFractionOfHour();
290                pos += 1 + upFractionLength;
291    
292                parseTimezone( pos );
293                upFormat = Format.YEAR_MONTH_DAY_HOUR_FRACTION;
294            }
295            else if ( c == 'Z' || c == '+' || c == '-' )
296            {
297                // read timezone
298                parseTimezone( pos );
299                upFormat = Format.YEAR_MONTH_DAY_HOUR;
300            }
301            else
302            {
303                throw new ParseException( I18n.err( I18n.ERR_04365 ), 10 );
304            }
305    
306            // this calculates and verifies the calendar
307            try
308            {
309                calendar.getTimeInMillis();
310            }
311            catch ( IllegalArgumentException iae )
312            {
313                throw new ParseException( I18n.err( I18n.ERR_04366 ), 0 );
314            }
315    
316            calendar.setLenient( true );
317        }
318    
319    
320        private void parseTimezone( int pos ) throws ParseException
321        {
322            if ( upGeneralizedTime.length() < pos + 1 )
323            {
324                throw new ParseException( I18n.err( I18n.ERR_04367 ), pos );
325            }
326    
327            char c = upGeneralizedTime.charAt( pos );
328            if ( c == 'Z' )
329            {
330                calendar.setTimeZone( GMT );
331                upTimeZoneFormat = TimeZoneFormat.Z;
332    
333                if ( upGeneralizedTime.length() > pos + 1 )
334                {
335                    throw new ParseException( I18n.err( I18n.ERR_04368 ), pos + 1 );
336                }
337            }
338            else if ( c == '+' || c == '-' )
339            {
340                StringBuilder sb = new StringBuilder( "GMT" );
341                sb.append( c );
342    
343                String digits = getAllDigits( pos + 1 );
344                sb.append( digits );
345    
346                if ( digits.length() == 2 && digits.matches( "^([01]\\d|2[0-3])$" ) )
347                {
348                    TimeZone timeZone = TimeZone.getTimeZone( sb.toString() );
349                    calendar.setTimeZone( timeZone );
350                    upTimeZoneFormat = TimeZoneFormat.DIFF_HOUR;
351                }
352                else if ( digits.length() == 4 && digits.matches( "^([01]\\d|2[0-3])([0-5]\\d)$" ) )
353                {
354                    TimeZone timeZone = TimeZone.getTimeZone( sb.toString() );
355                    calendar.setTimeZone( timeZone );
356                    upTimeZoneFormat = TimeZoneFormat.DIFF_HOUR_MINUTE;
357                }
358                else
359                {
360                    throw new ParseException( I18n.err( I18n.ERR_04369 ), pos );
361                }
362    
363                if ( upGeneralizedTime.length() > pos + 1 + digits.length() )
364                {
365                    throw new ParseException( I18n.err( I18n.ERR_04370 ), pos + 1 + digits.length() );
366                }
367            }
368        }
369    
370    
371        private void parseFractionOfSecond() throws ParseException
372        {
373            parseFractionDelmiter( 14 );
374            String fraction = getFraction( 14 + 1 );
375            upFractionLength = fraction.length();
376    
377            double fract = Double.parseDouble( "0." + fraction );
378            int millisecond = ( int ) Math.round( fract * 1000 );
379    
380            calendar.set( Calendar.MILLISECOND, millisecond );
381        }
382    
383    
384        private void parseFractionOfMinute() throws ParseException
385        {
386            parseFractionDelmiter( 12 );
387            String fraction = getFraction( 12 + 1 );
388            upFractionLength = fraction.length();
389    
390            double fract = Double.parseDouble( "0." + fraction );
391            int milliseconds = ( int ) Math.round( fract * 1000 * 60 );
392            int second = milliseconds / 1000;
393            int millisecond = milliseconds - ( second * 1000 );
394    
395            calendar.set( Calendar.SECOND, second );
396            calendar.set( Calendar.MILLISECOND, millisecond );
397        }
398    
399    
400        private void parseFractionOfHour() throws ParseException
401        {
402            parseFractionDelmiter( 10 );
403            String fraction = getFraction( 10 + 1 );
404            upFractionLength = fraction.length();
405    
406            double fract = Double.parseDouble( "0." + fraction );
407            int milliseconds = ( int ) Math.round( fract * 1000 * 60 * 60 );
408            int minute = milliseconds / ( 1000 * 60 );
409            int second = ( milliseconds - ( minute * 60 * 1000 ) ) / 1000;
410            int millisecond = milliseconds - ( minute * 60 * 1000 ) - ( second * 1000 );
411    
412            calendar.set( Calendar.MINUTE, minute );
413            calendar.set( Calendar.SECOND, second );
414            calendar.set( Calendar.MILLISECOND, millisecond );
415        }
416    
417    
418        private void parseFractionDelmiter( int fractionDelimiterPos )
419        {
420            char c = upGeneralizedTime.charAt( fractionDelimiterPos );
421            upFractionDelimiter = c == '.' ? FractionDelimiter.DOT : FractionDelimiter.COMMA;
422        }
423    
424    
425        private String getFraction( int startIndex ) throws ParseException
426        {
427            String fraction = getAllDigits( startIndex );
428    
429            // minimum one digit
430            if ( fraction.length() == 0 )
431            {
432                throw new ParseException( I18n.err( I18n.ERR_04371 ), startIndex );
433            }
434    
435            return fraction;
436        }
437    
438    
439        private String getAllDigits( int startIndex )
440        {
441            StringBuilder sb = new StringBuilder();
442            while ( upGeneralizedTime.length() > startIndex )
443            {
444                char c = upGeneralizedTime.charAt( startIndex );
445                if ( '0' <= c && c <= '9' )
446                {
447                    sb.append( c );
448                    startIndex++;
449                }
450                else
451                {
452                    break;
453                }
454            }
455            return sb.toString();
456        }
457    
458    
459        private void parseSecond() throws ParseException
460        {
461            // read minute
462            if ( upGeneralizedTime.length() < 14 )
463            {
464                throw new ParseException( I18n.err( I18n.ERR_04372 ), 12 );
465            }
466            try
467            {
468                int second = Integer.parseInt( upGeneralizedTime.substring( 12, 14 ) );
469                calendar.set( Calendar.SECOND, second );
470            }
471            catch ( NumberFormatException e )
472            {
473                throw new ParseException( I18n.err( I18n.ERR_04373 ), 12 );
474            }
475        }
476    
477    
478        private void parseMinute() throws ParseException
479        {
480            // read minute
481            if ( upGeneralizedTime.length() < 12 )
482            {
483                throw new ParseException( I18n.err( I18n.ERR_04374 ), 10 );
484            }
485            try
486            {
487                int minute = Integer.parseInt( upGeneralizedTime.substring( 10, 12 ) );
488                calendar.set( Calendar.MINUTE, minute );
489            }
490            catch ( NumberFormatException e )
491            {
492                throw new ParseException( I18n.err( I18n.ERR_04375 ), 10 );
493            }
494        }
495    
496    
497        private void parseHour() throws ParseException
498        {
499            if ( upGeneralizedTime.length() < 10 )
500            {
501                throw new ParseException( I18n.err( I18n.ERR_04376 ), 8 );
502            }
503            try
504            {
505                int hour = Integer.parseInt( upGeneralizedTime.substring( 8, 10 ) );
506                calendar.set( Calendar.HOUR_OF_DAY, hour );
507            }
508            catch ( NumberFormatException e )
509            {
510                throw new ParseException( I18n.err( I18n.ERR_04377 ), 8 );
511            }
512        }
513    
514    
515        private void parseDay() throws ParseException
516        {
517            if ( upGeneralizedTime.length() < 8 )
518            {
519                throw new ParseException( I18n.err( I18n.ERR_04378 ), 6 );
520            }
521            try
522            {
523                int day = Integer.parseInt( upGeneralizedTime.substring( 6, 8 ) );
524                calendar.set( Calendar.DAY_OF_MONTH, day );
525            }
526            catch ( NumberFormatException e )
527            {
528                throw new ParseException( I18n.err( I18n.ERR_04379 ), 6 );
529            }
530        }
531    
532    
533        private void parseMonth() throws ParseException
534        {
535            if ( upGeneralizedTime.length() < 6 )
536            {
537                throw new ParseException( I18n.err( I18n.ERR_04380 ), 4 );
538            }
539            try
540            {
541                int month = Integer.parseInt( upGeneralizedTime.substring( 4, 6 ) );
542                calendar.set( Calendar.MONTH, month - 1 );
543            }
544            catch ( NumberFormatException e )
545            {
546                throw new ParseException( I18n.err( I18n.ERR_04381 ), 4 );
547            }
548        }
549    
550    
551        private void parseYear() throws ParseException
552        {
553            if ( upGeneralizedTime.length() < 4 )
554            {
555                throw new ParseException( I18n.err( I18n.ERR_04382 ), 0 );
556            }
557            try
558            {
559                int year = Integer.parseInt( upGeneralizedTime.substring( 0, 4 ) );
560                calendar.set( Calendar.YEAR, year );
561            }
562            catch ( NumberFormatException e )
563            {
564                throw new ParseException( I18n.err( I18n.ERR_04383 ), 0 );
565            }
566        }
567    
568    
569        /**
570         * Returns the string representation of this generalized time. 
571         * This method uses the same format as the user provided format.
572         *
573         * @return the string representation of this generalized time
574         */
575        public String toGeneralizedTime()
576        {
577            return toGeneralizedTime( upFormat, upFractionDelimiter, upFractionLength, upTimeZoneFormat );
578        }
579    
580    
581        /**
582         * Returns the string representation of this generalized time.
583         * 
584         * @param format the target format
585         * @param fractionDelimiter the target fraction delimiter, may be null
586         * @param fractionLenth the fraction length
587         * @param timeZoneFormat the target time zone format
588         * 
589         * @return the string
590         */
591        public String toGeneralizedTime( Format format, FractionDelimiter fractionDelimiter, int fractionLength,
592            TimeZoneFormat timeZoneFormat )
593        {
594            Calendar calendar = ( Calendar ) this.calendar.clone();
595            if ( timeZoneFormat == TimeZoneFormat.Z )
596            {
597                calendar.setTimeZone( GMT );
598            }
599    
600            NumberFormat twoDigits = new DecimalFormat( "00" );
601            NumberFormat fourDigits = new DecimalFormat( "00" );
602            String fractionFormat = "";
603            for ( int i = 0; i < fractionLength && i < 3; i++ )
604            {
605                fractionFormat += "0";
606            }
607    
608            StringBuilder sb = new StringBuilder();
609            sb.append( fourDigits.format( calendar.get( Calendar.YEAR ) ) );
610            sb.append( twoDigits.format( calendar.get( Calendar.MONTH ) + 1 ) );
611            sb.append( twoDigits.format( calendar.get( Calendar.DAY_OF_MONTH ) ) );
612            sb.append( twoDigits.format( calendar.get( Calendar.HOUR_OF_DAY ) ) );
613    
614            switch ( format )
615            {
616                case YEAR_MONTH_DAY_HOUR_MIN_SEC:
617                    sb.append( twoDigits.format( calendar.get( Calendar.MINUTE ) ) );
618                    sb.append( twoDigits.format( calendar.get( Calendar.SECOND ) ) );
619                    break;
620    
621                case YEAR_MONTH_DAY_HOUR_MIN_SEC_FRACTION:
622                    sb.append( twoDigits.format( calendar.get( Calendar.MINUTE ) ) );
623                    sb.append( twoDigits.format( calendar.get( Calendar.SECOND ) ) );
624    
625                    NumberFormat fractionDigits = new DecimalFormat( fractionFormat );
626                    sb.append( fractionDelimiter == FractionDelimiter.COMMA ? ',' : '.' );
627                    sb.append( fractionDigits.format( calendar.get( Calendar.MILLISECOND ) ) );
628                    break;
629    
630                case YEAR_MONTH_DAY_HOUR_MIN:
631                    sb.append( twoDigits.format( calendar.get( Calendar.MINUTE ) ) );
632                    break;
633    
634                case YEAR_MONTH_DAY_HOUR_MIN_FRACTION:
635                    sb.append( twoDigits.format( calendar.get( Calendar.MINUTE ) ) );
636    
637                    // sec + millis => fraction of minute
638                    double millisec = 1000 * calendar.get( Calendar.SECOND ) + calendar.get( Calendar.MILLISECOND );
639                    double fraction = millisec / ( 1000 * 60 );
640                    fractionDigits = new DecimalFormat( "0." + fractionFormat );
641                    sb.append( fractionDelimiter == FractionDelimiter.COMMA ? ',' : '.' );
642                    sb.append( fractionDigits.format( fraction ).substring( 2 ) );
643                    break;
644    
645                case YEAR_MONTH_DAY_HOUR_FRACTION:
646                    // min + sec + millis => fraction of minute
647                    millisec = 1000 * 60 * calendar.get( Calendar.MINUTE ) + 1000 * calendar.get( Calendar.SECOND )
648                        + calendar.get( Calendar.MILLISECOND );
649                    fraction = millisec / ( 1000 * 60 * 60 );
650                    fractionDigits = new DecimalFormat( "0." + fractionFormat );
651                    sb.append( fractionDelimiter == FractionDelimiter.COMMA ? ',' : '.' );
652                    sb.append( fractionDigits.format( fraction ).substring( 2 ) );
653    
654                    break;
655            }
656    
657            if ( timeZoneFormat == TimeZoneFormat.Z && calendar.getTimeZone().hasSameRules( GMT ) )
658            {
659                sb.append( 'Z' );
660            }
661            else
662            {
663                TimeZone timeZone = calendar.getTimeZone();
664                int rawOffset = timeZone.getRawOffset();
665                sb.append( rawOffset < 0 ? '-' : '+' );
666    
667                rawOffset = Math.abs( rawOffset );
668                int hour = rawOffset / ( 60 * 60 * 1000 );
669                int minute = ( rawOffset - ( hour * 60 * 60 * 1000 ) ) / ( 1000 * 60 );
670    
671                if ( hour < 10 )
672                {
673                    sb.append( '0' );
674                }
675                sb.append( hour );
676    
677                if ( timeZoneFormat == TimeZoneFormat.DIFF_HOUR_MINUTE || timeZoneFormat == TimeZoneFormat.Z )
678                {
679                    if ( minute < 10 )
680                    {
681                        sb.append( '0' );
682                    }
683                    sb.append( minute );
684                }
685            }
686    
687            return sb.toString();
688        }
689    
690    
691        /**
692         * Gets the calendar. It could be used to manipulate this 
693         * {@link GeneralizedTime} settings.
694         * 
695         * @return the calendar
696         */
697        public Calendar getCalendar()
698        {
699            return calendar;
700        }
701    
702    
703        @Override
704        public String toString()
705        {
706            return toGeneralizedTime();
707        }
708    
709    
710        @Override
711        public int hashCode()
712        {
713            final int prime = 31;
714            int result = 1;
715            result = prime * result + calendar.hashCode();
716            return result;
717        }
718    
719    
720        @Override
721        public boolean equals( Object obj )
722        {
723            if ( obj instanceof GeneralizedTime )
724            {
725                GeneralizedTime other = ( GeneralizedTime ) obj;
726                return calendar.equals( other.calendar );
727            }
728            else
729            {
730                return false;
731            }
732        }
733    
734    
735        /**
736         * Compares this GeneralizedTime object with the specified GeneralizedTime object.
737         * 
738         * @param other the other GeneralizedTime object
739         * 
740         * @return a negative integer, zero, or a positive integer as this object
741         *      is less than, equal to, or greater than the specified object.
742         * 
743         * @see java.lang.Comparable#compareTo(java.lang.Object)
744         */
745        public int compareTo( GeneralizedTime other )
746        {
747            return calendar.compareTo( other.calendar );
748        }
749    
750    }