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.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 * The format of the generalized time. 118 */ 119 public enum Format 120 { 121 /** Time format with minutes and seconds, excluding fraction. */ 122 YEAR_MONTH_DAY_HOUR_MIN_SEC, 123 /** Time format with minutes and seconds, including fraction. */ 124 YEAR_MONTH_DAY_HOUR_MIN_SEC_FRACTION, 125 126 /** Time format with minutes, seconds are omitted, excluding fraction. */ 127 YEAR_MONTH_DAY_HOUR_MIN, 128 /** Time format with minutes seconds are omitted, including fraction. */ 129 YEAR_MONTH_DAY_HOUR_MIN_FRACTION, 130 131 /** Time format, minutes and seconds are omitted, excluding fraction. */ 132 YEAR_MONTH_DAY_HOUR, 133 /** Time format, minutes and seconds are omitted, including fraction. */ 134 YEAR_MONTH_DAY_HOUR_FRACTION 135 } 136 137 /** 138 * The fraction delimiter of the generalized time. 139 */ 140 public enum FractionDelimiter 141 { 142 /** Use a dot as fraction delimiter. */ 143 DOT, 144 /** Use a comma as fraction delimiter. */ 145 COMMA 146 } 147 148 /** 149 * The time zone format of the generalized time. 150 */ 151 public enum TimeZoneFormat 152 { 153 /** g-time-zone (Zulu) format. */ 154 Z, 155 /** g-differential format, using hour only. */ 156 DIFF_HOUR, 157 /** g-differential format, using hour and minute. */ 158 DIFF_HOUR_MINUTE 159 } 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 * 182 * Creates a new instance of GeneralizedTime by setting the date to an instance of Calendar. 183 * @see #GeneralizedTime(Calendar) 184 * 185 * @param date the date 186 */ 187 public GeneralizedTime( Date date ) 188 { 189 calendar = new GregorianCalendar( GMT, Locale.ROOT ); 190 calendar.setTime( date ); 191 setUp( calendar ); 192 } 193 194 195 /** 196 * Creates a new instance of GeneralizedTime, based on the given Calendar object. 197 * Uses <pre>Format.YEAR_MONTH_DAY_HOUR_MIN_SEC</pre> as default format and 198 * <pre>TimeZoneFormat.Z</pre> as default time zone format. 199 * 200 * @param calendar the calendar containing the date, time and timezone information 201 */ 202 public GeneralizedTime( Calendar calendar ) 203 { 204 setUp( calendar ); 205 } 206 207 208 /** 209 * Creates a new instance of GeneralizedTime, based on the 210 * given generalized time string. 211 * 212 * @param generalizedTime the generalized time 213 * 214 * @throws ParseException if the given generalized time can't be parsed. 215 */ 216 public GeneralizedTime( String generalizedTime ) throws ParseException 217 { 218 if ( generalizedTime == null ) 219 { 220 throw new ParseException( I18n.err( I18n.ERR_04359 ), 0 ); 221 } 222 223 this.upGeneralizedTime = generalizedTime; 224 225 calendar = new GregorianCalendar( GMT, Locale.ROOT ); 226 calendar.setTimeInMillis( 0 ); 227 calendar.setLenient( false ); 228 229 parseYear(); 230 parseMonth(); 231 parseDay(); 232 parseHour(); 233 234 if ( upGeneralizedTime.length() < 11 ) 235 { 236 throw new ParseException( I18n.err( I18n.ERR_04360 ), 10 ); 237 } 238 239 // pos 10: 240 // if digit => minute field 241 // if . or , => fraction of hour field 242 // if Z or + or - => timezone field 243 // else error 244 int pos = 10; 245 char c = upGeneralizedTime.charAt( pos ); 246 247 if ( ( '0' <= c ) && ( c <= '9' ) ) 248 { 249 parseMinute(); 250 251 if ( upGeneralizedTime.length() < 13 ) 252 { 253 throw new ParseException( I18n.err( I18n.ERR_04361 ), 12 ); 254 } 255 256 // pos 12: 257 // if digit => second field 258 // if . or , => fraction of minute field 259 // if Z or + or - => timezone field 260 // else error 261 pos = 12; 262 c = upGeneralizedTime.charAt( pos ); 263 264 if ( ( '0' <= c ) && ( c <= '9' ) ) 265 { 266 parseSecond(); 267 268 if ( upGeneralizedTime.length() < 15 ) 269 { 270 throw new ParseException( I18n.err( I18n.ERR_04362 ), 14 ); 271 } 272 273 // pos 14: 274 // if . or , => fraction of second field 275 // if Z or + or - => timezone field 276 // else error 277 pos = 14; 278 c = upGeneralizedTime.charAt( pos ); 279 280 if ( ( c == '.' ) || ( c == ',' ) ) 281 { 282 // read fraction of second 283 parseFractionOfSecond(); 284 pos += 1 + upFractionLength; 285 286 parseTimezone( pos ); 287 upFormat = Format.YEAR_MONTH_DAY_HOUR_MIN_SEC_FRACTION; 288 } 289 else if ( ( c == 'Z' ) || ( c == '+' ) || ( c == '-' ) ) 290 { 291 // read timezone 292 parseTimezone( pos ); 293 upFormat = Format.YEAR_MONTH_DAY_HOUR_MIN_SEC; 294 } 295 else 296 { 297 throw new ParseException( I18n.err( I18n.ERR_04363 ), 14 ); 298 } 299 } 300 else if ( ( c == '.' ) || ( c == ',' ) ) 301 { 302 // read fraction of minute 303 parseFractionOfMinute(); 304 pos += 1 + upFractionLength; 305 306 parseTimezone( pos ); 307 upFormat = Format.YEAR_MONTH_DAY_HOUR_MIN_FRACTION; 308 } 309 else if ( ( c == 'Z' ) || ( c == '+' ) || ( c == '-' ) ) 310 { 311 // read timezone 312 parseTimezone( pos ); 313 upFormat = Format.YEAR_MONTH_DAY_HOUR_MIN; 314 } 315 else 316 { 317 throw new ParseException( I18n.err( I18n.ERR_04364 ), 12 ); 318 } 319 } 320 else if ( ( c == '.' ) || ( c == ',' ) ) 321 { 322 // read fraction of hour 323 parseFractionOfHour(); 324 pos += 1 + upFractionLength; 325 326 parseTimezone( pos ); 327 upFormat = Format.YEAR_MONTH_DAY_HOUR_FRACTION; 328 } 329 else if ( ( c == 'Z' ) || ( c == '+' ) || ( c == '-' ) ) 330 { 331 // read timezone 332 parseTimezone( pos ); 333 upFormat = Format.YEAR_MONTH_DAY_HOUR; 334 } 335 else 336 { 337 throw new ParseException( I18n.err( I18n.ERR_04365 ), 10 ); 338 } 339 340 // this calculates and verifies the calendar 341 /* Not sure we should do that... */ 342 try 343 { 344 calendar.getTimeInMillis(); 345 } 346 catch ( IllegalArgumentException iae ) 347 { 348 throw new ParseException( I18n.err( I18n.ERR_04366 ), 0 ); 349 } 350 351 calendar.setLenient( true ); 352 } 353 354 355 private void setUp( Calendar newCalendar ) 356 { 357 if ( newCalendar == null ) 358 { 359 throw new IllegalArgumentException( I18n.err( I18n.ERR_04358 ) ); 360 } 361 362 this.calendar = newCalendar; 363 upGeneralizedTime = null; 364 upFormat = Format.YEAR_MONTH_DAY_HOUR_MIN_SEC_FRACTION; 365 upTimeZoneFormat = TimeZoneFormat.Z; 366 upFractionDelimiter = FractionDelimiter.DOT; 367 upFractionLength = 3; 368 } 369 370 371 private void parseTimezone( int pos ) throws ParseException 372 { 373 if ( upGeneralizedTime.length() < pos + 1 ) 374 { 375 throw new ParseException( I18n.err( I18n.ERR_04367 ), pos ); 376 } 377 378 char c = upGeneralizedTime.charAt( pos ); 379 380 if ( c == 'Z' ) 381 { 382 calendar.setTimeZone( GMT ); 383 upTimeZoneFormat = TimeZoneFormat.Z; 384 385 if ( upGeneralizedTime.length() > pos + 1 ) 386 { 387 throw new ParseException( I18n.err( I18n.ERR_04368 ), pos + 1 ); 388 } 389 } 390 else if ( ( c == '+' ) || ( c == '-' ) ) 391 { 392 StringBuilder sb = new StringBuilder( "GMT" ); 393 sb.append( c ); 394 395 String digits = getAllDigits( pos + 1 ); 396 sb.append( digits ); 397 398 if ( digits.length() == 2 && digits.matches( "^([01]\\d|2[0-3])$" ) ) 399 { 400 TimeZone timeZone = TimeZone.getTimeZone( sb.toString() ); 401 calendar.setTimeZone( timeZone ); 402 upTimeZoneFormat = TimeZoneFormat.DIFF_HOUR; 403 } 404 else if ( digits.length() == 4 && digits.matches( "^([01]\\d|2[0-3])([0-5]\\d)$" ) ) 405 { 406 TimeZone timeZone = TimeZone.getTimeZone( sb.toString() ); 407 calendar.setTimeZone( timeZone ); 408 upTimeZoneFormat = TimeZoneFormat.DIFF_HOUR_MINUTE; 409 } 410 else 411 { 412 throw new ParseException( I18n.err( I18n.ERR_04369 ), pos ); 413 } 414 415 if ( upGeneralizedTime.length() > pos + 1 + digits.length() ) 416 { 417 throw new ParseException( I18n.err( I18n.ERR_04370 ), pos + 1 + digits.length() ); 418 } 419 } 420 } 421 422 423 private void parseFractionOfSecond() throws ParseException 424 { 425 parseFractionDelmiter( 14 ); 426 String fraction = getFraction( 14 + 1 ); 427 upFractionLength = fraction.length(); 428 429 double fract = Double.parseDouble( "0." + fraction ); 430 int millisecond = ( int ) Math.floor( fract * 1000 ); 431 432 calendar.set( GregorianCalendar.MILLISECOND, millisecond ); 433 } 434 435 436 private void parseFractionOfMinute() throws ParseException 437 { 438 parseFractionDelmiter( 12 ); 439 String fraction = getFraction( 12 + 1 ); 440 upFractionLength = fraction.length(); 441 442 double fract = Double.parseDouble( "0." + fraction ); 443 int milliseconds = ( int ) Math.round( fract * 1000 * 60 ); 444 int second = milliseconds / 1000; 445 int millisecond = milliseconds - ( second * 1000 ); 446 447 calendar.set( Calendar.SECOND, second ); 448 calendar.set( Calendar.MILLISECOND, millisecond ); 449 } 450 451 452 private void parseFractionOfHour() throws ParseException 453 { 454 parseFractionDelmiter( 10 ); 455 String fraction = getFraction( 10 + 1 ); 456 upFractionLength = fraction.length(); 457 458 double fract = Double.parseDouble( "0." + fraction ); 459 int milliseconds = ( int ) Math.round( fract * 1000 * 60 * 60 ); 460 int minute = milliseconds / ( 1000 * 60 ); 461 int second = ( milliseconds - ( minute * 60 * 1000 ) ) / 1000; 462 int millisecond = milliseconds - ( minute * 60 * 1000 ) - ( second * 1000 ); 463 464 calendar.set( Calendar.MINUTE, minute ); 465 calendar.set( Calendar.SECOND, second ); 466 calendar.set( Calendar.MILLISECOND, millisecond ); 467 } 468 469 470 private void parseFractionDelmiter( int fractionDelimiterPos ) 471 { 472 char c = upGeneralizedTime.charAt( fractionDelimiterPos ); 473 upFractionDelimiter = c == '.' ? FractionDelimiter.DOT : FractionDelimiter.COMMA; 474 } 475 476 477 private String getFraction( int startIndex ) throws ParseException 478 { 479 String fraction = getAllDigits( startIndex ); 480 481 // minimum one digit 482 if ( fraction.length() == 0 ) 483 { 484 throw new ParseException( I18n.err( I18n.ERR_04371 ), startIndex ); 485 } 486 487 return fraction; 488 } 489 490 491 private String getAllDigits( int startIndex ) 492 { 493 StringBuilder sb = new StringBuilder(); 494 while ( upGeneralizedTime.length() > startIndex ) 495 { 496 char c = upGeneralizedTime.charAt( startIndex ); 497 if ( '0' <= c && c <= '9' ) 498 { 499 sb.append( c ); 500 startIndex++; 501 } 502 else 503 { 504 break; 505 } 506 } 507 return sb.toString(); 508 } 509 510 511 private void parseSecond() throws ParseException 512 { 513 // read minute 514 if ( upGeneralizedTime.length() < 14 ) 515 { 516 throw new ParseException( I18n.err( I18n.ERR_04372 ), 12 ); 517 } 518 try 519 { 520 int second = Strings.parseInt( upGeneralizedTime.substring( 12, 14 ) ); 521 calendar.set( Calendar.SECOND, second ); 522 } 523 catch ( NumberFormatException e ) 524 { 525 throw new ParseException( I18n.err( I18n.ERR_04373 ), 12 ); 526 } 527 } 528 529 530 private void parseMinute() throws ParseException 531 { 532 // read minute 533 if ( upGeneralizedTime.length() < 12 ) 534 { 535 throw new ParseException( I18n.err( I18n.ERR_04374 ), 10 ); 536 } 537 try 538 { 539 int minute = Strings.parseInt( upGeneralizedTime.substring( 10, 12 ) ); 540 calendar.set( Calendar.MINUTE, minute ); 541 } 542 catch ( NumberFormatException e ) 543 { 544 throw new ParseException( I18n.err( I18n.ERR_04375 ), 10 ); 545 } 546 } 547 548 549 private void parseHour() throws ParseException 550 { 551 if ( upGeneralizedTime.length() < 10 ) 552 { 553 throw new ParseException( I18n.err( I18n.ERR_04376 ), 8 ); 554 } 555 try 556 { 557 int hour = Strings.parseInt( upGeneralizedTime.substring( 8, 10 ) ); 558 calendar.set( Calendar.HOUR_OF_DAY, hour ); 559 } 560 catch ( NumberFormatException e ) 561 { 562 throw new ParseException( I18n.err( I18n.ERR_04377 ), 8 ); 563 } 564 } 565 566 567 private void parseDay() throws ParseException 568 { 569 if ( upGeneralizedTime.length() < 8 ) 570 { 571 throw new ParseException( I18n.err( I18n.ERR_04378 ), 6 ); 572 } 573 try 574 { 575 int day = Strings.parseInt( upGeneralizedTime.substring( 6, 8 ) ); 576 calendar.set( Calendar.DAY_OF_MONTH, day ); 577 } 578 catch ( NumberFormatException e ) 579 { 580 throw new ParseException( I18n.err( I18n.ERR_04379 ), 6 ); 581 } 582 } 583 584 585 private void parseMonth() throws ParseException 586 { 587 if ( upGeneralizedTime.length() < 6 ) 588 { 589 throw new ParseException( I18n.err( I18n.ERR_04380 ), 4 ); 590 } 591 try 592 { 593 int month = Strings.parseInt( upGeneralizedTime.substring( 4, 6 ) ); 594 calendar.set( Calendar.MONTH, month - 1 ); 595 } 596 catch ( NumberFormatException e ) 597 { 598 throw new ParseException( I18n.err( I18n.ERR_04381 ), 4 ); 599 } 600 } 601 602 603 private void parseYear() throws ParseException 604 { 605 if ( upGeneralizedTime.length() < 4 ) 606 { 607 throw new ParseException( I18n.err( I18n.ERR_04382 ), 0 ); 608 } 609 try 610 { 611 int year = Strings.parseInt( upGeneralizedTime.substring( 0, 4 ) ); 612 calendar.set( Calendar.YEAR, year ); 613 } 614 catch ( NumberFormatException e ) 615 { 616 throw new ParseException( I18n.err( I18n.ERR_04383 ), 0 ); 617 } 618 } 619 620 621 /** 622 * Returns the string representation of this generalized time. 623 * This method uses the same format as the user provided format. 624 * 625 * @return the string representation of this generalized time 626 */ 627 public String toGeneralizedTime() 628 { 629 return toGeneralizedTime( upFormat, upFractionDelimiter, upFractionLength, upTimeZoneFormat ); 630 } 631 632 633 /** 634 * Returns the string representation of this generalized time. 635 * This method uses the same format as the user provided format. 636 * 637 * @return the string representation of this generalized time 638 */ 639 public String toGeneralizedTimeWithoutFraction() 640 { 641 return toGeneralizedTime( getFormatWithoutFraction( upFormat ), upFractionDelimiter, upFractionLength, 642 upTimeZoneFormat ); 643 } 644 645 646 /** 647 * Gets the corresponding format with fraction. 648 * 649 * @param f the format 650 * @return the corresponding format without fraction 651 */ 652 private Format getFormatWithoutFraction( Format f ) 653 { 654 switch ( f ) 655 { 656 case YEAR_MONTH_DAY_HOUR_FRACTION: 657 return Format.YEAR_MONTH_DAY_HOUR; 658 case YEAR_MONTH_DAY_HOUR_MIN_FRACTION: 659 return Format.YEAR_MONTH_DAY_HOUR_MIN; 660 case YEAR_MONTH_DAY_HOUR_MIN_SEC_FRACTION: 661 return Format.YEAR_MONTH_DAY_HOUR_MIN_SEC; 662 default: 663 break; 664 } 665 666 return f; 667 } 668 669 670 /** 671 * Returns the string representation of this generalized time. 672 * 673 * @param format the target format 674 * @param fractionDelimiter the target fraction delimiter, may be null 675 * @param fractionLength the fraction length 676 * @param timeZoneFormat the target time zone format 677 * 678 * @return the string 679 */ 680 public String toGeneralizedTime( Format format, FractionDelimiter fractionDelimiter, int fractionLength, 681 TimeZoneFormat timeZoneFormat ) 682 { 683 Calendar clonedCalendar = ( Calendar ) calendar.clone(); 684 685 if ( timeZoneFormat == TimeZoneFormat.Z ) 686 { 687 clonedCalendar.setTimeZone( GMT ); 688 } 689 690 // Create the result. It can contain a maximum of 23 chars 691 byte[] result = new byte[23]; 692 693 // The starting point 694 int pos = 0; 695 696 // Inject the year 697 int year = clonedCalendar.get( Calendar.YEAR ); 698 699 result[pos++] = ( byte ) ( ( year / 1000 ) + '0' ); 700 year %= 1000; 701 702 result[pos++] = ( byte ) ( ( year / 100 ) + '0' ); 703 year %= 100; 704 705 result[pos++] = ( byte ) ( ( year / 10 ) + '0' ); 706 707 result[pos++] = ( byte ) ( ( year % 10 ) + '0' ); 708 709 // Inject the month 710 int month = clonedCalendar.get( Calendar.MONTH ) + 1; 711 712 result[pos++] = ( byte ) ( ( month / 10 ) + '0' ); 713 714 result[pos++] = ( byte ) ( ( month % 10 ) + '0' ); 715 716 // Inject the day 717 int day = clonedCalendar.get( Calendar.DAY_OF_MONTH ); 718 719 result[pos++] = ( byte ) ( ( day / 10 ) + '0' ); 720 721 result[pos++] = ( byte ) ( ( day % 10 ) + '0' ); 722 723 // Inject the hour 724 int hour = clonedCalendar.get( Calendar.HOUR_OF_DAY ); 725 726 result[pos++] = ( byte ) ( ( hour / 10 ) + '0' ); 727 728 result[pos++] = ( byte ) ( ( hour % 10 ) + '0' ); 729 730 switch ( format ) 731 { 732 case YEAR_MONTH_DAY_HOUR_MIN_SEC: 733 // Inject the minutes 734 int minute = clonedCalendar.get( Calendar.MINUTE ); 735 736 result[pos++] = ( byte ) ( ( minute / 10 ) + '0' ); 737 738 result[pos++] = ( byte ) ( ( minute % 10 ) + '0' ); 739 740 // Inject the seconds 741 int second = clonedCalendar.get( Calendar.SECOND ); 742 743 result[pos++] = ( byte ) ( ( second / 10 ) + '0' ); 744 745 result[pos++] = ( byte ) ( ( second % 10 ) + '0' ); 746 747 break; 748 749 case YEAR_MONTH_DAY_HOUR_MIN_SEC_FRACTION: 750 // Inject the minutes 751 minute = clonedCalendar.get( Calendar.MINUTE ); 752 753 result[pos++] = ( byte ) ( ( minute / 10 ) + '0' ); 754 755 result[pos++] = ( byte ) ( ( minute % 10 ) + '0' ); 756 757 // Inject the seconds 758 second = clonedCalendar.get( Calendar.SECOND ); 759 760 result[pos++] = ( byte ) ( ( second / 10 ) + '0' ); 761 762 result[pos++] = ( byte ) ( ( second % 10 ) + '0' ); 763 764 // Inject the fraction 765 if ( fractionDelimiter == FractionDelimiter.COMMA ) 766 { 767 result[pos++] = ','; 768 } 769 else 770 { 771 result[pos++] = '.'; 772 } 773 774 // Inject the fraction 775 int millisecond = clonedCalendar.get( Calendar.MILLISECOND ); 776 777 result[pos++] = ( byte ) ( ( millisecond / 100 ) + '0' ); 778 millisecond %= 100; 779 780 result[pos++] = ( byte ) ( ( millisecond / 10 ) + '0' ); 781 782 //if ( millisecond > 0 ) 783 result[pos++] = ( byte ) ( ( millisecond % 10 ) + '0' ); 784 785 break; 786 787 case YEAR_MONTH_DAY_HOUR_MIN: 788 // Inject the minutes 789 minute = clonedCalendar.get( Calendar.MINUTE ); 790 791 result[pos++] = ( byte ) ( ( minute / 10 ) + '0' ); 792 793 result[pos++] = ( byte ) ( ( minute % 10 ) + '0' ); 794 break; 795 796 case YEAR_MONTH_DAY_HOUR_MIN_FRACTION: 797 // Inject the minutes 798 minute = clonedCalendar.get( Calendar.MINUTE ); 799 800 result[pos++] = ( byte ) ( ( minute / 10 ) + '0' ); 801 802 result[pos++] = ( byte ) ( ( minute % 10 ) + '0' ); 803 804 // sec + millis => fraction of a minute 805 int fraction = 1000 * clonedCalendar.get( Calendar.SECOND ) 806 + clonedCalendar.get( Calendar.MILLISECOND ); 807 fraction /= 60; 808 809 if ( fraction > 0 ) 810 { 811 if ( fractionDelimiter == FractionDelimiter.COMMA ) 812 { 813 result[pos++] = ','; 814 } 815 else 816 { 817 result[pos++] = '.'; 818 } 819 820 // At this point, the fraction should be in [999, 1] 821 result[pos++] = ( byte ) ( ( fraction / 100 ) + '0' ); 822 fraction %= 100; 823 824 if ( fraction > 0 ) 825 { 826 result[pos++] = ( byte ) ( ( fraction / 10 ) + '0' ); 827 828 if ( fraction > 0 ) 829 { 830 result[pos++] = ( byte ) ( ( fraction % 10 ) + '0' ); 831 } 832 } 833 } 834 835 break; 836 837 case YEAR_MONTH_DAY_HOUR: 838 // nothing to add 839 break; 840 841 case YEAR_MONTH_DAY_HOUR_FRACTION: 842 // min + sec + millis => fraction of an hour 843 fraction = 1000 * 60 * clonedCalendar.get( Calendar.MINUTE ) + 1000 844 * clonedCalendar.get( Calendar.SECOND ) 845 + clonedCalendar.get( Calendar.MILLISECOND ); 846 fraction /= 60 * 60; 847 848 // At this point, the fraction should be in [999, 1] 849 if ( fraction > 0 ) 850 { 851 if ( fractionDelimiter == FractionDelimiter.COMMA ) 852 { 853 result[pos++] = ','; 854 } 855 else 856 { 857 result[pos++] = '.'; 858 } 859 860 result[pos++] = ( byte ) ( ( fraction / 100 ) + '0' ); 861 fraction %= 100; 862 863 if ( fraction > 0 ) 864 { 865 result[pos++] = ( byte ) ( ( fraction / 10 ) + '0' ); 866 867 if ( fraction > 0 ) 868 { 869 result[pos++] = ( byte ) ( ( fraction % 10 ) + '0' ); 870 } 871 } 872 } 873 874 break; 875 876 default: 877 throw new IllegalArgumentException( "Unexpected format " + format ); 878 } 879 880 if ( ( timeZoneFormat == TimeZoneFormat.Z ) && clonedCalendar.getTimeZone().hasSameRules( GMT ) ) 881 { 882 result[pos++] = 'Z'; 883 } 884 else 885 { 886 // g-differential 887 TimeZone timeZone = clonedCalendar.getTimeZone(); 888 int rawOffset = timeZone.getRawOffset(); 889 890 if ( rawOffset < 0 ) 891 { 892 result[pos++] = '-'; 893 } 894 else 895 { 896 result[pos++] = '+'; 897 } 898 899 rawOffset = Math.abs( rawOffset ); 900 hour = rawOffset / ( 60 * 60 * 1000 ); 901 int minute = ( rawOffset - ( hour * 60 * 60 * 1000 ) ) / ( 1000 * 60 ); 902 903 // The offset hour 904 result[pos++] = ( byte ) ( ( hour / 10 ) + '0' ); 905 906 result[pos++] = ( byte ) ( ( hour % 10 ) + '0' ); 907 908 if ( ( timeZoneFormat == TimeZoneFormat.DIFF_HOUR_MINUTE ) || ( timeZoneFormat == TimeZoneFormat.Z ) ) 909 { 910 // The offset minute 911 result[pos++] = ( byte ) ( ( minute / 10 ) + '0' ); 912 913 result[pos++] = ( byte ) ( ( minute % 10 ) + '0' ); 914 } 915 } 916 917 return Strings.utf8ToString( result, 0, pos ); 918 } 919 920 921 /** 922 * Gets the calendar. It could be used to manipulate this 923 * {@link GeneralizedTime} settings. 924 * 925 * @return the calendar 926 */ 927 public Calendar getCalendar() 928 { 929 return calendar; 930 } 931 932 933 @Override 934 public String toString() 935 { 936 return toGeneralizedTime(); 937 } 938 939 940 @Override 941 public int hashCode() 942 { 943 final int prime = 31; 944 int result = 1; 945 result = prime * result + calendar.hashCode(); 946 return result; 947 } 948 949 950 @Override 951 public boolean equals( Object obj ) 952 { 953 if ( obj instanceof GeneralizedTime ) 954 { 955 GeneralizedTime other = ( GeneralizedTime ) obj; 956 return calendar.equals( other.calendar ); 957 } 958 else 959 { 960 return false; 961 } 962 } 963 964 965 /** 966 * Compares this GeneralizedTime object with the specified GeneralizedTime object. 967 * 968 * @param other the other GeneralizedTime object 969 * 970 * @return a negative integer, zero, or a positive integer as this object 971 * is less than, equal to, or greater than the specified object. 972 * 973 * @see java.lang.Comparable#compareTo(java.lang.Object) 974 */ 975 @Override 976 public int compareTo( GeneralizedTime other ) 977 { 978 return calendar.compareTo( other.calendar ); 979 } 980 981 982 /** 983 * @return A Date representing the time as milliseconds 984 */ 985 public long getTime() 986 { 987 return calendar.getTimeInMillis(); 988 } 989 990 991 /** 992 * @return A Date representing the time 993 */ 994 public Date getDate() 995 { 996 return calendar.getTime(); 997 } 998 999 1000 /** 1001 * @return The year part of the date 1002 */ 1003 public int getYear() 1004 { 1005 return calendar.get( Calendar.YEAR ); 1006 } 1007 1008 1009 /** 1010 * @return The month part of the date 1011 */ 1012 public int getMonth() 1013 { 1014 return calendar.get( Calendar.MONTH ); 1015 } 1016 1017 1018 /** 1019 * @return The day part of the date 1020 */ 1021 public int getDay() 1022 { 1023 return calendar.get( Calendar.DATE ); 1024 } 1025 1026 1027 /** 1028 * @return The hours part of the date 1029 */ 1030 public int getHour() 1031 { 1032 return calendar.get( Calendar.HOUR_OF_DAY ); 1033 } 1034 1035 1036 /** 1037 * @return The minutes part of the date 1038 */ 1039 public int getMinutes() 1040 { 1041 return calendar.get( Calendar.MINUTE ); 1042 } 1043 1044 1045 /** 1046 * @return The seconds part of the date 1047 */ 1048 public int getSeconds() 1049 { 1050 return calendar.get( Calendar.SECOND ); 1051 } 1052 1053 1054 /** 1055 * @return The fractional (ie, milliseconds) part of the date 1056 */ 1057 public int getFraction() 1058 { 1059 return calendar.get( Calendar.MILLISECOND ); 1060 } 1061 1062 1063 /** 1064 * Get a Date instance from a given String 1065 * 1066 * @param zuluTime The time as a String 1067 * @return A Date instance 1068 * @throws ParseException If the String is not a valid date 1069 */ 1070 public static Date getDate( String zuluTime ) throws ParseException 1071 { 1072 try 1073 { 1074 return new GeneralizedTime( zuluTime ).calendar.getTime(); 1075 } 1076 catch ( ParseException pe ) 1077 { 1078 // Maybe one of the multiple Micro$oft ineptness to cope with Standards ? 1079 if ( "9223372036854775807".equals( zuluTime ) ) 1080 { 1081 // This 0x7FFFFFFFFFFFFFFF, never ending date 1082 return INFINITE; 1083 } 1084 else 1085 { 1086 throw pe; 1087 } 1088 } 1089 } 1090}