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