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