blob: e94a8e5702d3098df2b3d04069fbcd76378da170 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.directory.api.util;
import static org.apache.directory.api.util.TimeZones.GMT;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import org.apache.directory.api.i18n.I18n;
/**
* <p>This class represents the generalized time syntax as defined in
* RFC 4517 section 3.3.13.</p>
*
* <p>The date, time and time zone information is internally backed
* by an {@link java.util.Calendar} object</p>
*
* <p>Leap seconds are not supported, as {@link java.util.Calendar}
* does not support leap seconds.</p>
*
* <pre>
* 3.3.13. Generalized Time
*
* A value of the Generalized Time syntax is a character string
* representing a date and time. The LDAP-specific encoding of a value
* of this syntax is a restriction of the format defined in [ISO8601],
* and is described by the following ABNF:
*
* GeneralizedTime = century year month day hour
* [ minute [ second / leap-second ] ]
* [ fraction ]
* g-time-zone
*
* century = 2(%x30-39) ; "00" to "99"
* year = 2(%x30-39) ; "00" to "99"
* month = ( %x30 %x31-39 ) ; "01" (January) to "09"
* / ( %x31 %x30-32 ) ; "10" to "12"
* day = ( %x30 %x31-39 ) ; "01" to "09"
* / ( %x31-32 %x30-39 ) ; "10" to "29"
* / ( %x33 %x30-31 ) ; "30" to "31"
* hour = ( %x30-31 %x30-39 ) / ( %x32 %x30-33 ) ; "00" to "23"
* minute = %x30-35 %x30-39 ; "00" to "59"
*
* second = ( %x30-35 %x30-39 ) ; "00" to "59"
* leap-second = ( %x36 %x30 ) ; "60"
*
* fraction = ( DOT / COMMA ) 1*(%x30-39)
* g-time-zone = %x5A ; "Z"
* / g-differential
* g-differential = ( MINUS / PLUS ) hour [ minute ]
* MINUS = %x2D ; minus sign ("-")
*
* The &lt;DOT&gt;, &lt;COMMA&gt;, and &lt;PLUS&gt; rules are defined in [RFC4512].
*
* The above ABNF allows character strings that do not represent valid
* dates (in the Gregorian calendar) and/or valid times (e.g., February
* 31, 1994). Such character strings SHOULD be considered invalid for
* this syntax.
* <br>
* The time value represents coordinated universal time (equivalent to
* Greenwich Mean Time) if the "Z" form of &lt;g-time-zone&gt; is used;
* otherwise, the value represents a local time in the time zone
* indicated by &lt;g-differential&gt;. In the latter case, coordinated
* universal time can be calculated by subtracting the differential from
* the local time. The "Z" form of &lt;g-time-zone&gt; SHOULD be used in
* preference to &lt;g-differential&gt;.
* <br>
* If &lt;minute&gt; is omitted, then &lt;fraction&gt; represents a fraction of an
* hour; otherwise, if &lt;second&gt; and &lt;leap-second&gt; are omitted, then
* &lt;fraction&gt; represents a fraction of a minute; otherwise, &lt;fraction&gt;
* represents a fraction of a second.
*
* Examples:
* 199412161032Z
* 199412160532-0500
*
* Both example values represent the same coordinated universal time:
* 10:32 AM, December 16, 1994.
* <br>
* The LDAP definition for the Generalized Time syntax is:
*
* ( 1.3.6.1.4.1.1466.115.121.1.24 DESC 'Generalized Time' )
*
* This syntax corresponds to the GeneralizedTime ASN.1 type from
* [ASN.1], with the constraint that local time without a differential
* SHALL NOT be used.
* </pre>
*/
public class GeneralizedTime implements Comparable<GeneralizedTime>
{
/** A Date far in the future, when Micro$oft would have vanished for a long time... */
private static final Date INFINITE = new Date( 0x7FFFFFFFFFFFFFFFL );
/**
* The format of the generalized time.
*/
public enum Format
{
/** Time format with minutes and seconds, excluding fraction. */
YEAR_MONTH_DAY_HOUR_MIN_SEC,
/** Time format with minutes and seconds, including fraction. */
YEAR_MONTH_DAY_HOUR_MIN_SEC_FRACTION,
/** Time format with minutes, seconds are omitted, excluding fraction. */
YEAR_MONTH_DAY_HOUR_MIN,
/** Time format with minutes seconds are omitted, including fraction of a minute. */
YEAR_MONTH_DAY_HOUR_MIN_FRACTION,
/** Time format, minutes and seconds are omitted, excluding fraction. */
YEAR_MONTH_DAY_HOUR,
/** Time format, minutes and seconds are omitted, including fraction of an hour. */
YEAR_MONTH_DAY_HOUR_FRACTION;
}
/**
* The fraction delimiter of the generalized time.
*/
public enum FractionDelimiter
{
/** Use a dot as fraction delimiter. */
DOT,
/** Use a comma as fraction delimiter. */
COMMA
}
/**
* The time zone format of the generalized time.
*/
public enum TimeZoneFormat
{
/** g-time-zone (Zulu) format. */
Z,
/** g-differential format, using hour only. */
DIFF_HOUR,
/** g-differential format, using hour and minute. */
DIFF_HOUR_MINUTE
}
/** The user provided value */
private String upGeneralizedTime;
/** The user provided format */
private Format upFormat;
/** The user provided time zone format */
private TimeZoneFormat upTimeZoneFormat;
/** The user provided fraction delimiter */
private FractionDelimiter upFractionDelimiter;
/** the user provided fraction length */
private int upFractionLength;
/** The calendar */
private Calendar calendar;
/**
*
* Creates a new instance of GeneralizedTime by setting the date to an instance of Calendar.
* @see #GeneralizedTime(Calendar)
*
* @param date the date
*/
public GeneralizedTime( Date date )
{
calendar = new GregorianCalendar( GMT, Locale.ROOT );
calendar.setTime( date );
setUp( calendar );
}
/**
* Creates a new instance of GeneralizedTime, based on the given Calendar object.
* Uses <pre>Format.YEAR_MONTH_DAY_HOUR_MIN_SEC</pre> as default format and
* <pre>TimeZoneFormat.Z</pre> as default time zone format.
*
* @param calendar the calendar containing the date, time and timezone information
*/
public GeneralizedTime( Calendar calendar )
{
setUp( calendar );
}
/**
* Creates a new instance of GeneralizedTime by setting the time to an instance of Calendar.
* @see #GeneralizedTime(Calendar)
*
* @param timeInMillis the time in milliseconds
*/
public GeneralizedTime( long timeInMillis )
{
Locale locale = Locale.getDefault();
TimeZone timeZone = TimeZone.getTimeZone( "GMT" );
calendar = Calendar.getInstance( timeZone, locale );
calendar.setTimeInMillis( timeInMillis );
setUp( calendar );
}
/**
* Creates a new instance of GeneralizedTime, based on the
* given generalized time string.
*
* @param generalizedTime the generalized time
*
* @throws ParseException if the given generalized time can't be parsed.
*/
public GeneralizedTime( String generalizedTime ) throws ParseException
{
if ( generalizedTime == null )
{
throw new ParseException( I18n.err( I18n.ERR_17043_GENERALIZED_TIME_NULL ), 0 );
}
this.upGeneralizedTime = generalizedTime;
calendar = new GregorianCalendar( GMT, Locale.ROOT );
calendar.setTimeInMillis( 0 );
calendar.setLenient( false );
parseYear();
parseMonth();
parseDay();
parseHour();
if ( upGeneralizedTime.length() < 11 )
{
throw new ParseException( I18n.err( I18n.ERR_17044_BAD_GENERALIZED_TIME ), 10 );
}
// pos 10:
// if digit => minute field
// if . or , => fraction of hour field
// if Z or + or - => timezone field
// else error
int pos = 10;
char c = upGeneralizedTime.charAt( pos );
if ( ( '0' <= c ) && ( c <= '9' ) )
{
parseMinute();
if ( upGeneralizedTime.length() < 13 )
{
throw new ParseException( I18n.err( I18n.ERR_17045_BAD_GENERALIZED_TIME ), 12 );
}
// pos 12:
// if digit => second field
// if . or , => fraction of minute field
// if Z or + or - => timezone field
// else error
pos = 12;
c = upGeneralizedTime.charAt( pos );
if ( ( '0' <= c ) && ( c <= '9' ) )
{
parseSecond();
if ( upGeneralizedTime.length() < 15 )
{
throw new ParseException( I18n.err( I18n.ERR_17046_BAD_GENERALIZED_TIME ), 14 );
}
// pos 14:
// if . or , => fraction of second field
// if Z or + or - => timezone field
// else error
pos = 14;
c = upGeneralizedTime.charAt( pos );
if ( ( c == '.' ) || ( c == ',' ) )
{
// read fraction of second
parseFractionOfSecond();
pos += 1 + upFractionLength;
parseTimezone( pos );
upFormat = Format.YEAR_MONTH_DAY_HOUR_MIN_SEC_FRACTION;
}
else if ( ( c == 'Z' ) || ( c == '+' ) || ( c == '-' ) )
{
// read timezone
parseTimezone( pos );
upFormat = Format.YEAR_MONTH_DAY_HOUR_MIN_SEC;
}
else
{
throw new ParseException( I18n.err( I18n.ERR_17047_TIME_TOO_SHORT ), 14 );
}
}
else if ( ( c == '.' ) || ( c == ',' ) )
{
// read fraction of minute
parseFractionOfMinute();
pos += 1 + upFractionLength;
parseTimezone( pos );
upFormat = Format.YEAR_MONTH_DAY_HOUR_MIN_FRACTION;
}
else if ( ( c == 'Z' ) || ( c == '+' ) || ( c == '-' ) )
{
// read timezone
parseTimezone( pos );
upFormat = Format.YEAR_MONTH_DAY_HOUR_MIN;
}
else
{
throw new ParseException( I18n.err( I18n.ERR_17048_TIME_TOO_SHORT ), 12 );
}
}
else if ( ( c == '.' ) || ( c == ',' ) )
{
// read fraction of hour
parseFractionOfHour();
pos += 1 + upFractionLength;
parseTimezone( pos );
upFormat = Format.YEAR_MONTH_DAY_HOUR_FRACTION;
}
else if ( ( c == 'Z' ) || ( c == '+' ) || ( c == '-' ) )
{
// read timezone
parseTimezone( pos );
upFormat = Format.YEAR_MONTH_DAY_HOUR;
}
else
{
throw new ParseException( I18n.err( I18n.ERR_17049_INVALID_GENERALIZED_TIME ), 10 );
}
// this calculates and verifies the calendar
/* Not sure we should do that... */
try
{
calendar.getTimeInMillis();
}
catch ( IllegalArgumentException iae )
{
throw new ParseException( I18n.err( I18n.ERR_17050_INVALID_DATE_TIME ), 0 );
}
calendar.setLenient( true );
}
private void setUp( Calendar newCalendar )
{
if ( newCalendar == null )
{
throw new IllegalArgumentException( I18n.err( I18n.ERR_17051_CALENDAR_NULL ) );
}
this.calendar = newCalendar;
upGeneralizedTime = null;
upFormat = Format.YEAR_MONTH_DAY_HOUR_MIN_SEC_FRACTION;
upTimeZoneFormat = TimeZoneFormat.Z;
upFractionDelimiter = FractionDelimiter.DOT;
upFractionLength = 3;
}
private void parseTimezone( int pos ) throws ParseException
{
if ( upGeneralizedTime.length() < pos + 1 )
{
throw new ParseException( I18n.err( I18n.ERR_17052_TIME_TOO_SHOR_NO_TZ ), pos );
}
char c = upGeneralizedTime.charAt( pos );
if ( c == 'Z' )
{
calendar.setTimeZone( GMT );
upTimeZoneFormat = TimeZoneFormat.Z;
if ( upGeneralizedTime.length() > pos + 1 )
{
throw new ParseException( I18n.err( I18n.ERR_17053_MISSING_TZ ), pos + 1 );
}
}
else if ( ( c == '+' ) || ( c == '-' ) )
{
StringBuilder sb = new StringBuilder( "GMT" );
sb.append( c );
String digits = getAllDigits( pos + 1 );
sb.append( digits );
if ( digits.length() == 2 && digits.matches( "^([01]\\d|2[0-3])$" ) )
{
TimeZone timeZone = TimeZone.getTimeZone( sb.toString() );
calendar.setTimeZone( timeZone );
upTimeZoneFormat = TimeZoneFormat.DIFF_HOUR;
}
else if ( digits.length() == 4 && digits.matches( "^([01]\\d|2[0-3])([0-5]\\d)$" ) )
{
TimeZone timeZone = TimeZone.getTimeZone( sb.toString() );
calendar.setTimeZone( timeZone );
upTimeZoneFormat = TimeZoneFormat.DIFF_HOUR_MINUTE;
}
else
{
throw new ParseException( I18n.err( I18n.ERR_17054_TZ_MUST_BE_2_OR_4_DIGITS ), pos );
}
if ( upGeneralizedTime.length() > pos + 1 + digits.length() )
{
throw new ParseException( I18n.err( I18n.ERR_17053_MISSING_TZ ), pos + 1 + digits.length() );
}
}
}
private void parseFractionOfSecond() throws ParseException
{
parseFractionDelmiter( 14 );
String fraction = getFraction( 14 + 1 );
upFractionLength = fraction.length();
double fract = Double.parseDouble( "0." + fraction );
int millisecond = ( int ) Math.floor( fract * 1000 );
calendar.set( GregorianCalendar.MILLISECOND, millisecond );
}
private void parseFractionOfMinute() throws ParseException
{
parseFractionDelmiter( 12 );
String fraction = getFraction( 12 + 1 );
upFractionLength = fraction.length();
double fract = Double.parseDouble( "0." + fraction );
int milliseconds = ( int ) Math.round( fract * 1000 * 60 );
int second = milliseconds / 1000;
int millisecond = milliseconds - ( second * 1000 );
calendar.set( Calendar.SECOND, second );
calendar.set( Calendar.MILLISECOND, millisecond );
}
private void parseFractionOfHour() throws ParseException
{
parseFractionDelmiter( 10 );
String fraction = getFraction( 10 + 1 );
upFractionLength = fraction.length();
double fract = Double.parseDouble( "0." + fraction );
int milliseconds = ( int ) Math.round( fract * 1000 * 60 * 60 );
int minute = milliseconds / ( 1000 * 60 );
int second = ( milliseconds - ( minute * 60 * 1000 ) ) / 1000;
int millisecond = milliseconds - ( minute * 60 * 1000 ) - ( second * 1000 );
calendar.set( Calendar.MINUTE, minute );
calendar.set( Calendar.SECOND, second );
calendar.set( Calendar.MILLISECOND, millisecond );
}
private void parseFractionDelmiter( int fractionDelimiterPos )
{
char c = upGeneralizedTime.charAt( fractionDelimiterPos );
upFractionDelimiter = c == '.' ? FractionDelimiter.DOT : FractionDelimiter.COMMA;
}
private String getFraction( int startIndex ) throws ParseException
{
String fraction = getAllDigits( startIndex );
// minimum one digit
if ( fraction.length() == 0 )
{
throw new ParseException( I18n.err( I18n.ERR_17055_MISSING_FRACTION ), startIndex );
}
return fraction;
}
private String getAllDigits( int startIndex )
{
StringBuilder sb = new StringBuilder();
while ( upGeneralizedTime.length() > startIndex )
{
char c = upGeneralizedTime.charAt( startIndex );
if ( '0' <= c && c <= '9' )
{
sb.append( c );
startIndex++;
}
else
{
break;
}
}
return sb.toString();
}
private void parseSecond() throws ParseException
{
// read minute
if ( upGeneralizedTime.length() < 14 )
{
throw new ParseException( I18n.err( I18n.ERR_17056_TIME_TOO_SHORT_NO_SECOND ), 12 );
}
try
{
int second = Strings.parseInt( upGeneralizedTime.substring( 12, 14 ) );
calendar.set( Calendar.SECOND, second );
}
catch ( NumberFormatException e )
{
throw new ParseException( I18n.err( I18n.ERR_17057_SECOND_NOT_NUM ), 12 );
}
}
private void parseMinute() throws ParseException
{
// read minute
if ( upGeneralizedTime.length() < 12 )
{
throw new ParseException( I18n.err( I18n.ERR_17058_MISSING_MINUTE ), 10 );
}
try
{
int minute = Strings.parseInt( upGeneralizedTime.substring( 10, 12 ) );
calendar.set( Calendar.MINUTE, minute );
}
catch ( NumberFormatException e )
{
throw new ParseException( I18n.err( I18n.ERR_17059_MIN_NOT_NUM ), 10 );
}
}
private void parseHour() throws ParseException
{
if ( upGeneralizedTime.length() < 10 )
{
throw new ParseException( I18n.err( I18n.ERR_17060_TIME_TO_SHORT_MISSING_HOUR ), 8 );
}
try
{
int hour = Strings.parseInt( upGeneralizedTime.substring( 8, 10 ) );
calendar.set( Calendar.HOUR_OF_DAY, hour );
}
catch ( NumberFormatException e )
{
throw new ParseException( I18n.err( I18n.ERR_17061_HOUR_NOT_NUM ), 8 );
}
}
private void parseDay() throws ParseException
{
if ( upGeneralizedTime.length() < 8 )
{
throw new ParseException( I18n.err( I18n.ERR_17062_TIME_TO_SHORT_MISSING_DAY ), 6 );
}
try
{
int day = Strings.parseInt( upGeneralizedTime.substring( 6, 8 ) );
calendar.set( Calendar.DAY_OF_MONTH, day );
}
catch ( NumberFormatException e )
{
throw new ParseException( I18n.err( I18n.ERR_17063_DAY_NOT_NUM ), 6 );
}
}
private void parseMonth() throws ParseException
{
if ( upGeneralizedTime.length() < 6 )
{
throw new ParseException( I18n.err( I18n.ERR_17064_TIME_TO_SHORT_MISSING_MONTH ), 4 );
}
try
{
int month = Strings.parseInt( upGeneralizedTime.substring( 4, 6 ) );
calendar.set( Calendar.MONTH, month - 1 );
}
catch ( NumberFormatException e )
{
throw new ParseException( I18n.err( I18n.ERR_17065_MONTH_NOT_NUM ), 4 );
}
}
private void parseYear() throws ParseException
{
if ( upGeneralizedTime.length() < 4 )
{
throw new ParseException( I18n.err( I18n.ERR_17066_TIME_TO_SHORT_MISSING_YEAR ), 0 );
}
try
{
int year = Strings.parseInt( upGeneralizedTime.substring( 0, 4 ) );
calendar.set( Calendar.YEAR, year );
}
catch ( NumberFormatException e )
{
throw new ParseException( I18n.err( I18n.ERR_17067_YEAR_NOT_NUM ), 0 );
}
}
/**
* Returns the string representation of this generalized time.
* This method uses the same format as the user provided format.
*
* @return the string representation of this generalized time
*/
public String toGeneralizedTime()
{
return toGeneralizedTime( upFormat, upFractionDelimiter, upFractionLength, upTimeZoneFormat );
}
/**
* Returns the string representation of this generalized time.
* This method uses the same format as the user provided format.
*
* @return the string representation of this generalized time
*/
public String toGeneralizedTimeWithoutFraction()
{
return toGeneralizedTime( getFormatWithoutFraction( upFormat ), upFractionDelimiter, upFractionLength,
upTimeZoneFormat );
}
/**
* Gets the corresponding format with fraction.
*
* @param f the format
* @return the corresponding format without fraction
*/
private Format getFormatWithoutFraction( Format f )
{
switch ( f )
{
case YEAR_MONTH_DAY_HOUR_FRACTION:
return Format.YEAR_MONTH_DAY_HOUR;
case YEAR_MONTH_DAY_HOUR_MIN_FRACTION:
return Format.YEAR_MONTH_DAY_HOUR_MIN;
case YEAR_MONTH_DAY_HOUR_MIN_SEC_FRACTION:
return Format.YEAR_MONTH_DAY_HOUR_MIN_SEC;
default:
break;
}
return f;
}
/**
* Returns the string representation of this generalized time.
*
* @param format the target format
* @param fractionDelimiter the target fraction delimiter, may be null
* @param fractionLength the fraction length
* @param timeZoneFormat the target time zone format
*
* @return the string
*/
public String toGeneralizedTime( Format format, FractionDelimiter fractionDelimiter, int fractionLength,
TimeZoneFormat timeZoneFormat )
{
Calendar clonedCalendar = ( Calendar ) calendar.clone();
if ( timeZoneFormat == TimeZoneFormat.Z )
{
clonedCalendar.setTimeZone( GMT );
}
// Create the result. It can contain a maximum of 23 chars
byte[] result = new byte[23];
// The starting point
int pos = 0;
// Inject the year
int year = clonedCalendar.get( Calendar.YEAR );
result[pos++] = ( byte ) ( ( year / 1000 ) + '0' );
year %= 1000;
result[pos++] = ( byte ) ( ( year / 100 ) + '0' );
year %= 100;
result[pos++] = ( byte ) ( ( year / 10 ) + '0' );
result[pos++] = ( byte ) ( ( year % 10 ) + '0' );
// Inject the month
int month = clonedCalendar.get( Calendar.MONTH ) + 1;
result[pos++] = ( byte ) ( ( month / 10 ) + '0' );
result[pos++] = ( byte ) ( ( month % 10 ) + '0' );
// Inject the day
int day = clonedCalendar.get( Calendar.DAY_OF_MONTH );
result[pos++] = ( byte ) ( ( day / 10 ) + '0' );
result[pos++] = ( byte ) ( ( day % 10 ) + '0' );
// Inject the hour
int hour = clonedCalendar.get( Calendar.HOUR_OF_DAY );
result[pos++] = ( byte ) ( ( hour / 10 ) + '0' );
result[pos++] = ( byte ) ( ( hour % 10 ) + '0' );
switch ( format )
{
case YEAR_MONTH_DAY_HOUR_MIN_SEC:
// Inject the minutes
int minute = clonedCalendar.get( Calendar.MINUTE );
result[pos++] = ( byte ) ( ( minute / 10 ) + '0' );
result[pos++] = ( byte ) ( ( minute % 10 ) + '0' );
// Inject the seconds
int second = clonedCalendar.get( Calendar.SECOND );
result[pos++] = ( byte ) ( ( second / 10 ) + '0' );
result[pos++] = ( byte ) ( ( second % 10 ) + '0' );
break;
case YEAR_MONTH_DAY_HOUR_MIN_SEC_FRACTION:
// Inject the minutes
minute = clonedCalendar.get( Calendar.MINUTE );
result[pos++] = ( byte ) ( ( minute / 10 ) + '0' );
result[pos++] = ( byte ) ( ( minute % 10 ) + '0' );
// Inject the seconds
second = clonedCalendar.get( Calendar.SECOND );
result[pos++] = ( byte ) ( ( second / 10 ) + '0' );
result[pos++] = ( byte ) ( ( second % 10 ) + '0' );
// Inject the fraction
if ( fractionDelimiter == FractionDelimiter.COMMA )
{
result[pos++] = ',';
}
else
{
result[pos++] = '.';
}
// Inject the fraction
int millisecond = clonedCalendar.get( Calendar.MILLISECOND );
result[pos++] = ( byte ) ( ( millisecond / 100 ) + '0' );
millisecond %= 100;
result[pos++] = ( byte ) ( ( millisecond / 10 ) + '0' );
//if ( millisecond > 0 )
result[pos++] = ( byte ) ( ( millisecond % 10 ) + '0' );
break;
case YEAR_MONTH_DAY_HOUR_MIN:
// Inject the minutes
minute = clonedCalendar.get( Calendar.MINUTE );
result[pos++] = ( byte ) ( ( minute / 10 ) + '0' );
result[pos++] = ( byte ) ( ( minute % 10 ) + '0' );
break;
case YEAR_MONTH_DAY_HOUR_MIN_FRACTION:
// Inject the minutes
minute = clonedCalendar.get( Calendar.MINUTE );
result[pos++] = ( byte ) ( ( minute / 10 ) + '0' );
result[pos++] = ( byte ) ( ( minute % 10 ) + '0' );
// sec + millis => fraction of a minute
int fraction = 1000 * clonedCalendar.get( Calendar.SECOND )
+ clonedCalendar.get( Calendar.MILLISECOND );
fraction /= 60;
if ( fraction > 0 )
{
if ( fractionDelimiter == FractionDelimiter.COMMA )
{
result[pos++] = ',';
}
else
{
result[pos++] = '.';
}
// At this point, the fraction should be in [999, 1]
result[pos++] = ( byte ) ( ( fraction / 100 ) + '0' );
fraction %= 100;
if ( fraction > 0 )
{
result[pos++] = ( byte ) ( ( fraction / 10 ) + '0' );
if ( fraction > 0 )
{
result[pos++] = ( byte ) ( ( fraction % 10 ) + '0' );
}
}
}
break;
case YEAR_MONTH_DAY_HOUR:
// nothing to add
break;
case YEAR_MONTH_DAY_HOUR_FRACTION:
// min + sec + millis => fraction of an hour
fraction = 1000 * 60 * clonedCalendar.get( Calendar.MINUTE ) + 1000
* clonedCalendar.get( Calendar.SECOND )
+ clonedCalendar.get( Calendar.MILLISECOND );
fraction /= 60 * 60;
// At this point, the fraction should be in [999, 1]
if ( fraction > 0 )
{
if ( fractionDelimiter == FractionDelimiter.COMMA )
{
result[pos++] = ',';
}
else
{
result[pos++] = '.';
}
result[pos++] = ( byte ) ( ( fraction / 100 ) + '0' );
fraction %= 100;
if ( fraction > 0 )
{
result[pos++] = ( byte ) ( ( fraction / 10 ) + '0' );
if ( fraction > 0 )
{
result[pos++] = ( byte ) ( ( fraction % 10 ) + '0' );
}
}
}
break;
default:
throw new IllegalArgumentException( I18n.err( I18n.ERR_17069_UNEXPECTED_FORMAT, format ) );
}
if ( ( timeZoneFormat == TimeZoneFormat.Z ) && clonedCalendar.getTimeZone().hasSameRules( GMT ) )
{
result[pos++] = 'Z';
}
else
{
// g-differential
int offset = clonedCalendar.get( Calendar.ZONE_OFFSET ) + clonedCalendar.get( Calendar.DST_OFFSET );
if ( offset < 0 )
{
result[pos++] = '-';
}
else
{
result[pos++] = '+';
}
offset = Math.abs( offset );
hour = offset / ( 60 * 60 * 1000 );
int minute = ( offset - ( hour * 60 * 60 * 1000 ) ) / ( 1000 * 60 );
// The offset hour
result[pos++] = ( byte ) ( ( hour / 10 ) + '0' );
result[pos++] = ( byte ) ( ( hour % 10 ) + '0' );
if ( ( timeZoneFormat == TimeZoneFormat.DIFF_HOUR_MINUTE ) || ( timeZoneFormat == TimeZoneFormat.Z ) )
{
// The offset minute
result[pos++] = ( byte ) ( ( minute / 10 ) + '0' );
result[pos++] = ( byte ) ( ( minute % 10 ) + '0' );
}
}
return Strings.utf8ToString( result, 0, pos );
}
/**
* Gets the calendar. It could be used to manipulate this
* {@link GeneralizedTime} settings.
*
* @return the calendar
*/
public Calendar getCalendar()
{
return calendar;
}
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
return toGeneralizedTime();
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + calendar.hashCode();
return result;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals( Object obj )
{
if ( obj instanceof GeneralizedTime )
{
GeneralizedTime other = ( GeneralizedTime ) obj;
return calendar.equals( other.calendar );
}
else
{
return false;
}
}
/**
* Compares this GeneralizedTime object with the specified GeneralizedTime object.
*
* @param other the other GeneralizedTime object
*
* @return a negative integer, zero, or a positive integer as this object
* is less than, equal to, or greater than the specified object.
*
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo( GeneralizedTime other )
{
return calendar.compareTo( other.calendar );
}
/**
* @return A Date representing the time as milliseconds
*/
public long getTime()
{
return calendar.getTimeInMillis();
}
/**
* @return A Date representing the time
*/
public Date getDate()
{
return calendar.getTime();
}
/**
* @return The year part of the date
*/
public int getYear()
{
return calendar.get( Calendar.YEAR );
}
/**
* @return The month part of the date
*/
public int getMonth()
{
return calendar.get( Calendar.MONTH );
}
/**
* @return The day part of the date
*/
public int getDay()
{
return calendar.get( Calendar.DATE );
}
/**
* @return The hours part of the date
*/
public int getHour()
{
return calendar.get( Calendar.HOUR_OF_DAY );
}
/**
* @return The minutes part of the date
*/
public int getMinutes()
{
return calendar.get( Calendar.MINUTE );
}
/**
* @return The seconds part of the date
*/
public int getSeconds()
{
return calendar.get( Calendar.SECOND );
}
/**
* @return The fractional (ie, milliseconds) part of the date
*/
public int getFraction()
{
return calendar.get( Calendar.MILLISECOND );
}
/**
* Get a Dat einstance from a given String
*
* @param zuluTime The time as a String
* @return A Date instance
* @throws ParseException If the String is not a valid date
*/
public static Date getDate( String zuluTime ) throws ParseException
{
try
{
return new GeneralizedTime( zuluTime ).calendar.getTime();
}
catch ( ParseException pe )
{
// Maybe one of the multiple Micro$oft ineptness to cope with Standards ?
if ( "9223372036854775807".equals( zuluTime ) )
{
// This 0x7FFFFFFFFFFFFFFF, never ending date
return INFINITE;
}
else
{
throw pe;
}
}
}
}