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