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 }