You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by se...@apache.org on 2008/08/18 13:26:52 UTC

svn commit: r686726 - in /directory/shared/branches/bigbang/ldap/src: main/java/org/apache/directory/shared/ldap/util/GeneralizedTime.java test/java/org/apache/directory/shared/ldap/util/GeneralizedTimeTest.java

Author: seelmann
Date: Mon Aug 18 04:26:51 2008
New Revision: 686726

URL: http://svn.apache.org/viewvc?rev=686726&view=rev
Log:
Fix for DIRSHARED-19: Added GeneralizedTime class plus tests

Added:
    directory/shared/branches/bigbang/ldap/src/main/java/org/apache/directory/shared/ldap/util/GeneralizedTime.java
    directory/shared/branches/bigbang/ldap/src/test/java/org/apache/directory/shared/ldap/util/GeneralizedTimeTest.java

Added: directory/shared/branches/bigbang/ldap/src/main/java/org/apache/directory/shared/ldap/util/GeneralizedTime.java
URL: http://svn.apache.org/viewvc/directory/shared/branches/bigbang/ldap/src/main/java/org/apache/directory/shared/ldap/util/GeneralizedTime.java?rev=686726&view=auto
==============================================================================
--- directory/shared/branches/bigbang/ldap/src/main/java/org/apache/directory/shared/ldap/util/GeneralizedTime.java (added)
+++ directory/shared/branches/bigbang/ldap/src/main/java/org/apache/directory/shared/ldap/util/GeneralizedTime.java Mon Aug 18 04:26:51 2008
@@ -0,0 +1,749 @@
+/*
+ *  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
+ *  
+ *    http://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.shared.ldap.util;
+
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.TimeZone;
+
+
+/**
+ * <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 <DOT>, <COMMA>, and <PLUS> 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.
+ *
+ *  The time value represents coordinated universal time (equivalent to
+ *  Greenwich Mean Time) if the "Z" form of <g-time-zone> is used;
+ *  otherwise, the value represents a local time in the time zone
+ *  indicated by <g-differential>.  In the latter case, coordinated
+ *  universal time can be calculated by subtracting the differential from
+ *  the local time.  The "Z" form of <g-time-zone> SHOULD be used in
+ *  preference to <g-differential>.
+ *
+ *  If <minute> is omitted, then <fraction> represents a fraction of an
+ *  hour; otherwise, if <second> and <leap-second> are omitted, then
+ *  <fraction> represents a fraction of a minute; otherwise, <fraction>
+ *  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.
+ *
+ *  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>
+{
+
+    public enum Format
+    {
+        YEAR_MONTH_DAY_HOUR_MIN_SEC, YEAR_MONTH_DAY_HOUR_MIN_SEC_FRACTION,
+
+        YEAR_MONTH_DAY_HOUR_MIN, YEAR_MONTH_DAY_HOUR_MIN_FRACTION,
+
+        YEAR_MONTH_DAY_HOUR, YEAR_MONTH_DAY_HOUR_FRACTION, ;
+    }
+
+    public enum FractionDelimiter
+    {
+        DOT, COMMA
+    }
+
+    public enum TimeZoneFormat
+    {
+        Z, DIFF_HOUR, DIFF_HOUR_MINUTE;
+    }
+
+    private static final TimeZone GMT = TimeZone.getTimeZone( "GMT" );
+
+    /** 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, 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 )
+    {
+        if ( calendar == null )
+        {
+            throw new IllegalArgumentException( "Calendar must not be null." );
+        }
+
+        this.calendar = calendar;
+        upGeneralizedTime = null;
+        upFormat = Format.YEAR_MONTH_DAY_HOUR_MIN_SEC;
+        upTimeZoneFormat = TimeZoneFormat.Z;
+        upFractionDelimiter = FractionDelimiter.DOT;
+        upFractionLength = 3;
+    }
+
+
+    /**
+     * 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( "generalizedTime is null", 0 );
+        }
+
+        this.upGeneralizedTime = generalizedTime;
+
+        calendar = Calendar.getInstance();
+        calendar.setTimeInMillis( 0 );
+        calendar.setLenient( false );
+
+        parseYear();
+        parseMonth();
+        parseDay();
+        parseHour();
+
+        if ( upGeneralizedTime.length() < 11 )
+        {
+            throw new ParseException(
+                "Generalized Time too short, doesn't contain field 'minute' or 'fraction of hour' or 'timezone'.", 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(
+                    "Generalized Time too short, doesn't contain field 'second' or 'fraction of minute' or 'timezone'.",
+                    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(
+                        "Generalized Time too short, doesn't contain field 'fraction of second' or 'timezone'.", 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(
+                        "Invalid Time too short, expected field 'fraction of second' or 'timezone'.", 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(
+                    "Invalid Time too short, expected field 'second' or 'fraction of minute' or 'timezone'.", 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(
+                "Invalid Generalized Time, expected field 'minute' or 'fraction of hour' or 'timezone'.", 10 );
+        }
+
+        // this calculates and verifies the calendar
+        try
+        {
+            calendar.getTimeInMillis();
+        }
+        catch ( IllegalArgumentException iae )
+        {
+            throw new ParseException( "Invalid date/time values.", 0 );
+        }
+    }
+
+
+    private void parseTimezone( int pos ) throws ParseException
+    {
+        if ( upGeneralizedTime.length() < pos + 1 )
+        {
+            throw new ParseException( "Generalized Time too short, doesn't contain field 'timezone'.", pos );
+        }
+
+        char c = upGeneralizedTime.charAt( pos );
+        if ( c == 'Z' )
+        {
+            calendar.setTimeZone( GMT );
+            upTimeZoneFormat = TimeZoneFormat.Z;
+
+            if ( upGeneralizedTime.length() > pos + 1 )
+            {
+                throw new ParseException( "Invalid Generalized Time, expected 'timezone' as the last field.", 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(
+                    "Invalid Generalized Time, expected field 'timezone' must contain 2 or 4 digits.", pos );
+            }
+
+            if ( upGeneralizedTime.length() > pos + 1 + digits.length() )
+            {
+                throw new ParseException( "Invalid Generalized Time, expected 'timezone' as the last field.", 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.round( fract * 1000 );
+
+        calendar.set( Calendar.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( "Generalized Time too short, doesn't contain number for '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( "Generalized Time too short, doesn't contain field 'second'.", 12 );
+        }
+        try
+        {
+            int second = Integer.parseInt( upGeneralizedTime.substring( 12, 14 ) );
+            calendar.set( Calendar.SECOND, second );
+        }
+        catch ( NumberFormatException e )
+        {
+            throw new ParseException( "Invalid Generalized Time, field 'second' is not numeric.", 12 );
+        }
+    }
+
+
+    private void parseMinute() throws ParseException
+    {
+        // read minute
+        if ( upGeneralizedTime.length() < 12 )
+        {
+            throw new ParseException( "Generalized Time too short, doesn't contain field 'minute'.", 10 );
+        }
+        try
+        {
+            int minute = Integer.parseInt( upGeneralizedTime.substring( 10, 12 ) );
+            calendar.set( Calendar.MINUTE, minute );
+        }
+        catch ( NumberFormatException e )
+        {
+            throw new ParseException( "Invalid Generalized Time, field 'minute' is not numeric.", 10 );
+        }
+    }
+
+
+    private void parseHour() throws ParseException
+    {
+        if ( upGeneralizedTime.length() < 10 )
+        {
+            throw new ParseException( "Generalized Time too short, doesn't contain field 'hour'.", 8 );
+        }
+        try
+        {
+            int hour = Integer.parseInt( upGeneralizedTime.substring( 8, 10 ) );
+            calendar.set( Calendar.HOUR_OF_DAY, hour );
+        }
+        catch ( NumberFormatException e )
+        {
+            throw new ParseException( "Invalid Generalized Time, field 'hour' is not numeric.", 8 );
+        }
+    }
+
+
+    private void parseDay() throws ParseException
+    {
+        if ( upGeneralizedTime.length() < 8 )
+        {
+            throw new ParseException( "Generalized Time too short, doesn't contain field 'day'.", 6 );
+        }
+        try
+        {
+            int day = Integer.parseInt( upGeneralizedTime.substring( 6, 8 ) );
+            calendar.set( Calendar.DAY_OF_MONTH, day );
+        }
+        catch ( NumberFormatException e )
+        {
+            throw new ParseException( "Invalid Generalized Time, field 'day' is not numeric.", 6 );
+        }
+    }
+
+
+    private void parseMonth() throws ParseException
+    {
+        if ( upGeneralizedTime.length() < 6 )
+        {
+            throw new ParseException( "Generalized Time too short, doesn't contain field 'month'.", 4 );
+        }
+        try
+        {
+            int month = Integer.parseInt( upGeneralizedTime.substring( 4, 6 ) );
+            calendar.set( Calendar.MONTH, month - 1 );
+        }
+        catch ( NumberFormatException e )
+        {
+            throw new ParseException( "Invalid Generalized Time, field 'month' is not numeric.", 4 );
+        }
+    }
+
+
+    private void parseYear() throws ParseException
+    {
+        if ( upGeneralizedTime.length() < 4 )
+        {
+            throw new ParseException( "Generalized Time too short, doesn't contain field 'century/year'.", 0 );
+        }
+        try
+        {
+            int year = Integer.parseInt( upGeneralizedTime.substring( 0, 4 ) );
+            calendar.set( Calendar.YEAR, year );
+        }
+        catch ( NumberFormatException e )
+        {
+            throw new ParseException( "Invalid Generalized Time, field 'century/year' is not numeric.", 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.
+     * 
+     * @param format the target format
+     * @param fractionDelimiter the target fraction delimiter, may be null
+     * @param fractionLenth the fraction length
+     * @param timeZoneFormat the target time zone format
+     * 
+     * @return the string
+     */
+    public String toGeneralizedTime( Format format, FractionDelimiter fractionDelimiter, int fractionLength,
+        TimeZoneFormat timeZoneFormat )
+    {
+        NumberFormat twoDigits = new DecimalFormat( "00" );
+        NumberFormat fourDigits = new DecimalFormat( "00" );
+        String fractionFormat = "";
+        for ( int i = 0; i < fractionLength && i < 3; i++ )
+        {
+            fractionFormat += "0";
+        }
+
+        StringBuilder sb = new StringBuilder();
+        sb.append( fourDigits.format( calendar.get( Calendar.YEAR ) ) );
+        sb.append( twoDigits.format( calendar.get( Calendar.MONTH ) + 1 ) );
+        sb.append( twoDigits.format( calendar.get( Calendar.DAY_OF_MONTH ) ) );
+        sb.append( twoDigits.format( calendar.get( Calendar.HOUR_OF_DAY ) ) );
+
+        switch ( format )
+        {
+            case YEAR_MONTH_DAY_HOUR_MIN_SEC:
+                sb.append( twoDigits.format( calendar.get( Calendar.MINUTE ) ) );
+                sb.append( twoDigits.format( calendar.get( Calendar.SECOND ) ) );
+                break;
+
+            case YEAR_MONTH_DAY_HOUR_MIN_SEC_FRACTION:
+                sb.append( twoDigits.format( calendar.get( Calendar.MINUTE ) ) );
+                sb.append( twoDigits.format( calendar.get( Calendar.SECOND ) ) );
+
+                NumberFormat fractionDigits = new DecimalFormat( fractionFormat );
+                sb.append( fractionDelimiter == FractionDelimiter.COMMA ? ',' : '.' );
+                sb.append( fractionDigits.format( calendar.get( Calendar.MILLISECOND ) ) );
+                break;
+
+            case YEAR_MONTH_DAY_HOUR_MIN:
+                sb.append( twoDigits.format( calendar.get( Calendar.MINUTE ) ) );
+                break;
+
+            case YEAR_MONTH_DAY_HOUR_MIN_FRACTION:
+                sb.append( twoDigits.format( calendar.get( Calendar.MINUTE ) ) );
+
+                // sec + millis => fraction of minute
+                double millisec = 1000 * calendar.get( Calendar.SECOND ) + calendar.get( Calendar.MILLISECOND );
+                double fraction = millisec / ( 1000 * 60 );
+                fractionDigits = new DecimalFormat( "0." + fractionFormat );
+                sb.append( fractionDelimiter == FractionDelimiter.COMMA ? ',' : '.' );
+                sb.append( fractionDigits.format( fraction ).substring( 2 ) );
+                break;
+
+            case YEAR_MONTH_DAY_HOUR_FRACTION:
+                // min + sec + millis => fraction of minute
+                millisec = 1000 * 60 * calendar.get( Calendar.MINUTE ) + 1000 * calendar.get( Calendar.SECOND )
+                    + calendar.get( Calendar.MILLISECOND );
+                fraction = millisec / ( 1000 * 60 * 60 );
+                fractionDigits = new DecimalFormat( "0." + fractionFormat );
+                sb.append( fractionDelimiter == FractionDelimiter.COMMA ? ',' : '.' );
+                sb.append( fractionDigits.format( fraction ).substring( 2 ) );
+
+                break;
+        }
+
+        if ( timeZoneFormat == TimeZoneFormat.Z && calendar.getTimeZone().hasSameRules( GMT ) )
+        {
+            sb.append( 'Z' );
+        }
+        else
+        {
+            TimeZone timeZone = calendar.getTimeZone();
+            int rawOffset = timeZone.getRawOffset();
+            sb.append( rawOffset < 0 ? '-' : '+' );
+
+            rawOffset = Math.abs( rawOffset );
+            int hour = rawOffset / ( 60 * 60 * 1000 );
+            int minute = ( rawOffset - ( hour * 60 * 60 * 1000 ) ) / ( 1000 * 60 );
+
+            if ( hour < 10 )
+            {
+                sb.append( '0' );
+            }
+            sb.append( hour );
+
+            if ( timeZoneFormat == TimeZoneFormat.DIFF_HOUR_MINUTE || timeZoneFormat == TimeZoneFormat.Z )
+            {
+                if ( hour < 10 )
+                {
+                    sb.append( '0' );
+                }
+                sb.append( minute );
+            }
+        }
+
+        return sb.toString();
+    }
+
+
+    /**
+     * Gets the calendar. It could be used to manipulate this 
+     * {@link GeneralizedTime} settings.
+     * 
+     * @return the calendar
+     */
+    public Calendar getCalendar()
+    {
+        return calendar;
+    }
+
+
+    @Override
+    public String toString()
+    {
+        return toGeneralizedTime();
+    }
+
+
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + calendar.hashCode();
+        return result;
+    }
+
+
+    @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)
+     */
+    public int compareTo( GeneralizedTime other )
+    {
+        return calendar.compareTo( other.calendar );
+    }
+
+}

Added: directory/shared/branches/bigbang/ldap/src/test/java/org/apache/directory/shared/ldap/util/GeneralizedTimeTest.java
URL: http://svn.apache.org/viewvc/directory/shared/branches/bigbang/ldap/src/test/java/org/apache/directory/shared/ldap/util/GeneralizedTimeTest.java?rev=686726&view=auto
==============================================================================
--- directory/shared/branches/bigbang/ldap/src/test/java/org/apache/directory/shared/ldap/util/GeneralizedTimeTest.java (added)
+++ directory/shared/branches/bigbang/ldap/src/test/java/org/apache/directory/shared/ldap/util/GeneralizedTimeTest.java Mon Aug 18 04:26:51 2008
@@ -0,0 +1,1043 @@
+/*
+ *  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
+ *  
+ *    http://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.shared.ldap.util;
+
+
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.TimeZone;
+
+import junit.framework.TestCase;
+
+import org.apache.directory.shared.ldap.schema.syntax.GeneralizedTimeSyntaxCheckerTest;
+import org.apache.directory.shared.ldap.util.GeneralizedTime.Format;
+import org.apache.directory.shared.ldap.util.GeneralizedTime.TimeZoneFormat;
+
+
+/**
+ * Tests the DateUtils class methods.
+ * 
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev: 542077 $
+ */
+public class GeneralizedTimeTest extends TestCase
+{
+
+    // Test all valid variants:
+    // Time: min + sec / min + no sec / no min + no sec 
+    // Fraction: no fraction, dot, comma
+    // Timezone: Z / +HH / +HHmm / -HH / -HHmm
+
+    /**
+     * Tests yyyyMMddHHmmssZ.
+     */
+    public void testYearMonthDayHourMinSecZulu() throws ParseException
+    {
+        String gt = "20080102121314Z";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHHmmss+04.
+     */
+    public void testYearMonthDayHourMinSecPlusHour() throws ParseException
+    {
+        String gt = "20080102121314+04";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHHmmss-1030.
+     */
+    public void testYearMonthDayHourMinSecMinusHourMin() throws ParseException
+    {
+        String gt = "20080102121314-1030";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHHmmss.SSSZ.
+     */
+    public void testYearMonthDayHourMinSecDotFractionZulu() throws ParseException
+    {
+        String gt = "20080102121314.987Z";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHHmmss.SSS+0100.
+     */
+    public void testYearMonthDayHourMinSecDotFractionPlusHour() throws ParseException
+    {
+        String gt = "20080102121314.987+0100";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHHmmss.SSS-1030.
+     */
+    public void testYearMonthDayHourMinSecDotFractionMinusHourMin() throws ParseException
+    {
+        String gt = "20080102121314.987-1030";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHHmmss,SSSZ.
+     */
+    public void testYearMonthDayHourMinSecCommaFractionZulu() throws ParseException
+    {
+        String gt = "20080102121314,987Z";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHHmmss,SSS+0100.
+     */
+    public void testYearMonthDayHourMinSecCommaFractionPlusHour() throws ParseException
+    {
+        String gt = "20080102121314,987+0100";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHHmmss,SSS-1030.
+     */
+    public void testYearMonthDayHourMinSecCommaFractionMinusHourMin() throws ParseException
+    {
+        String gt = "20080102121314,987-1030";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHHmmZ.
+     */
+    public void testYearMonthDayHourMinZulu() throws ParseException
+    {
+        String gt = "200801021213Z";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHHmm+HH.
+     */
+    public void testYearMonthDayHourMinPlusHour() throws ParseException
+    {
+        String gt = "200801021213+04";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHHmm-HHmm.
+     */
+    public void testYearMonthDayHourMinMinusHourMin() throws ParseException
+    {
+        String gt = "200801021213-1030";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHHmm.SSSZ.
+     */
+    public void testYearMonthDayHourMinDotFractionZulu() throws ParseException
+    {
+        String gt = "200801021213.987Z";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHHmm.SSS+0100.
+     */
+    public void testYearMonthDayHourMinDotFractionPlusHour() throws ParseException
+    {
+        String gt = "200801021213.987+0100";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHHmm.SSS-1030.
+     */
+    public void testYearMonthDayHourMinDotFractionMinusHourMin() throws ParseException
+    {
+        String gt = "200801021213.987-1030";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHHmm,SSSZ.
+     */
+    public void testYearMonthDayHourMinCommaFractionZulu() throws ParseException
+    {
+        String gt = "200801021213,987Z";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHHmm,SSS+0100.
+     */
+    public void testYearMonthDayHourMinCommaFractionPlusHour() throws ParseException
+    {
+        String gt = "200801021213,987+0100";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHHmm,SSS-1030.
+     */
+    public void testYearMonthDayHourMinCommaFractionMinusHourMin() throws ParseException
+    {
+        String gt = "200801021213,987-1030";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHHZ.
+     */
+    public void testYearMonthDayHourZulu() throws ParseException
+    {
+        String gt = "2008010212Z";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHH+HH.
+     */
+    public void testYearMonthDayHourPlusHour() throws ParseException
+    {
+        String gt = "2008010212+04";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHH-HHmm.
+     */
+    public void testYearMonthDayHourMinusHourMin() throws ParseException
+    {
+        String gt = "2008010212-1030";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHH.SSSZ.
+     */
+    public void testYearMonthDayHourDotFractionZulu() throws ParseException
+    {
+        String gt = "200801021213.987Z";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHH.SSS+0100.
+     */
+    public void testYearMonthDayHourDotFractionPlusHour() throws ParseException
+    {
+        String gt = "2008010212.987+0100";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHH.SSS-1030.
+     */
+    public void testYearMonthDayHourDotFractionMinusHourMin() throws ParseException
+    {
+        String gt = "2008010212.987-1030";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHH,SSSZ.
+     */
+    public void testYearMonthDayHourCommaFractionZulu() throws ParseException
+    {
+        String gt = "2008010212,987Z";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHH,SSS+0100.
+     */
+    public void testYearMonthDayHourCommaFractionPlusHour() throws ParseException
+    {
+        String gt = "2008010212,987+0100";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests yyyyMMddHH,SSS-1030.
+     */
+    public void testYearMonthDayHourCommaFractionMinusHourMin() throws ParseException
+    {
+        String gt = "2008010212,987-1030";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests fraction of a second.
+     */
+    public void testFractionOfSecond() throws ParseException
+    {
+        String gt = "20080102121314,987Z";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+        assertEquals( 987, generalizedTime.getCalendar().get( Calendar.MILLISECOND ) );
+    }
+
+
+    /**
+     * Tests fraction of a minute.
+     */
+    public void testFractionOfMinute1() throws ParseException
+    {
+        String gt = "200801021213,5Z";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+        assertEquals( 30, generalizedTime.getCalendar().get( Calendar.SECOND ) );
+        assertEquals( 0, generalizedTime.getCalendar().get( Calendar.MILLISECOND ) );
+    }
+
+
+    /**
+     * Tests fraction of a minute.
+     */
+    public void testFractionOfMinute2() throws ParseException
+    {
+        String gt = "200801021213,125Z";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+        assertEquals( 7, generalizedTime.getCalendar().get( Calendar.SECOND ) );
+        assertEquals( 500, generalizedTime.getCalendar().get( Calendar.MILLISECOND ) );
+    }
+
+
+    /**
+     * Tests fraction of an hour.
+     */
+    public void testFractionOfHour1() throws ParseException
+    {
+        String gt = "2008010212,5Z";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+        assertEquals( 30, generalizedTime.getCalendar().get( Calendar.MINUTE ) );
+        assertEquals( 0, generalizedTime.getCalendar().get( Calendar.SECOND ) );
+        assertEquals( 0, generalizedTime.getCalendar().get( Calendar.MILLISECOND ) );
+    }
+
+
+    /**
+     * Tests fraction of an hour.
+     */
+    public void testFractionOfHour2() throws ParseException
+    {
+        String gt = "2008010212,125Z";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+        assertEquals( 7, generalizedTime.getCalendar().get( Calendar.MINUTE ) );
+        assertEquals( 30, generalizedTime.getCalendar().get( Calendar.SECOND ) );
+        assertEquals( 0, generalizedTime.getCalendar().get( Calendar.MILLISECOND ) );
+    }
+
+
+    /**
+     * Test formatting
+     */
+    public void testFormatting() throws ParseException
+    {
+        String gt = "20080102121314Z";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+
+        result = generalizedTime.toGeneralizedTime( Format.YEAR_MONTH_DAY_HOUR_MIN, null, 0, TimeZoneFormat.Z );
+        assertEquals( "200801021213Z", result );
+
+        result = generalizedTime.toGeneralizedTime( Format.YEAR_MONTH_DAY_HOUR, null, 0, TimeZoneFormat.Z );
+        assertEquals( "2008010212Z", result );
+
+        result = generalizedTime.toGeneralizedTime( Format.YEAR_MONTH_DAY_HOUR_MIN, null, 0,
+            TimeZoneFormat.DIFF_HOUR_MINUTE );
+        assertEquals( "200801021213+0000", result );
+
+        result = generalizedTime.toGeneralizedTime( Format.YEAR_MONTH_DAY_HOUR, null, 0,
+            TimeZoneFormat.DIFF_HOUR_MINUTE );
+        assertEquals( "2008010212+0000", result );
+    }
+
+
+    /**
+     * Testcases from {@link GeneralizedTimeSyntaxCheckerTest#testCorrectCase()}.
+     */
+    public void testGeneralizedTimeSyntaxCheckerTestCorrectCase() throws ParseException
+    {
+        new GeneralizedTime( "20061205184527Z" );
+        new GeneralizedTime( "20061205184527+0500" );
+        new GeneralizedTime( "20061205184527-1234" );
+        new GeneralizedTime( "20061205184527.123Z" );
+        new GeneralizedTime( "20061205184527,123+0100" );
+        new GeneralizedTime( "2006120519Z" );
+    }
+
+
+    /**
+     * Testcases from {@link GeneralizedTimeSyntaxCheckerTest#testErrorCase()}.
+     */
+    public void testGeneralizedTimeSyntaxCheckerTestErrorCase()
+    {
+        try
+        {
+            new GeneralizedTime( "20060005184527Z" );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+        try
+        {
+            new GeneralizedTime( "20061305184527Z" );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+        try
+        {
+            new GeneralizedTime( "20062205184527Z" );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+        try
+        {
+            new GeneralizedTime( "20061200184527Z" );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+        try
+        {
+            new GeneralizedTime( "20061235184527Z" );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+        try
+        {
+            new GeneralizedTime( "20061205604527Z" );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+        try
+        {
+            new GeneralizedTime( "20061205186027Z" );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+        try
+        {
+            new GeneralizedTime( "20061205184561Z" );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+        try
+        {
+            new GeneralizedTime( "20061205184527Z+" );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+        try
+        {
+            new GeneralizedTime( "20061205184527+2400" );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+        try
+        {
+            new GeneralizedTime( "20061205184527+9900" );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+        try
+        {
+            new GeneralizedTime( "20061205184527+1260" );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+        try
+        {
+            new GeneralizedTime( "20061205184527+1299" );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+    }
+
+
+    /**
+     * Tests leap second.
+     * The GeneralizedTime class does not support leap seconds!
+     */
+    public void testLeapSecond() throws ParseException
+    {
+        String gt = "20051231235960Z";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+    }
+
+
+    /**
+     * Tests Feb 29 in a leap year.
+     */
+    public void testFebruary29inLeapYear() throws ParseException
+    {
+        String gt = "20080229000000Z";
+        GeneralizedTime generalizedTime = new GeneralizedTime( gt );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( gt, result );
+    }
+
+
+    /**
+     * Tests Feb 29 in a non-leap year.
+     */
+    public void testFebruary29inNonLeapYear() throws ParseException
+    {
+        String gt = "20070229000000Z";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+    }
+
+
+    /**
+     * Tests null.
+     */
+    public void testNull() throws ParseException
+    {
+        try
+        {
+            String gt = null;
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        try
+        {
+            Calendar calendar = null;
+            new GeneralizedTime( calendar );
+            fail( "Expected IllegalArgumentException" );
+        }
+        catch ( IllegalArgumentException iae )
+        {
+            // expected
+        }
+
+    }
+
+
+    /**
+     * Tests empty string.
+     */
+    public void testEmpty() throws ParseException
+    {
+        String gt = "";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+    }
+
+
+    /**
+     * Tests invalid cases.
+     */
+    public void testInvalid() throws ParseException
+    {
+        // too short year
+        String gt = "200";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // non-digits in year
+        gt = "2XX8";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // too short month
+        gt = "20081";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // non-digits in month
+        gt = "20081X";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // too short day
+        gt = "2008122";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // non-digits in day
+        gt = "2008122X";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // too short hour
+        gt = "200812211";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // non-digits in hour
+        gt = "20081221X1";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // too short minute
+        gt = "20081221121";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // non-digits in minute
+        gt = "20081221121X";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // too short second
+        gt = "2008122112131";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // non-digits in minute
+        gt = "2008122112131X";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // missing time zone
+        gt = "2008010212";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // missing time zone
+        gt = "200801021213";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // missing time zone
+        gt = "20080102121314";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // no digit
+        gt = "2008010212X";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // no digit
+        gt = "200801021213X";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // no digit
+        gt = "20080102121314X";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // missing time zone
+        gt = "20080102121314,1";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // time zone is not last char
+        gt = "20080102121314ZX";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // time zone is not last char
+        gt = "20080102121314+0430X";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+
+        // no fraction digit
+        gt = "20080102121314,Z";
+        try
+        {
+            new GeneralizedTime( gt );
+            fail( "Expected ParseException" );
+        }
+        catch ( ParseException pe )
+        {
+            // expected
+        }
+    }
+
+
+    /**
+     * Tests constructor with calendar object.
+     */
+    public void testCalendar() throws ParseException
+    {
+        Calendar calendar = Calendar.getInstance();
+        calendar.set( Calendar.YEAR, 2008 );
+        calendar.set( Calendar.MONTH, 0 );
+        calendar.set( Calendar.DAY_OF_MONTH, 2 );
+        calendar.set( Calendar.HOUR_OF_DAY, 12 );
+        calendar.set( Calendar.MINUTE, 13 );
+        calendar.set( Calendar.SECOND, 14 );
+        calendar.setTimeZone( TimeZone.getTimeZone( "GMT" ) );
+
+        GeneralizedTime generalizedTime = new GeneralizedTime( calendar );
+        String result = generalizedTime.toGeneralizedTime();
+        assertEquals( "20080102121314Z", result );
+
+    }
+
+
+    /**
+     * Tests the compareTo() method.
+     */
+    public void testCompareTo() throws ParseException
+    {
+        String gt1 = "20080102121313,999Z";
+        GeneralizedTime generalizedTime1 = new GeneralizedTime( gt1 );
+
+        String gt2 = "20080102121314Z";
+        GeneralizedTime generalizedTime2 = new GeneralizedTime( gt2 );
+
+        String gt3 = "20080102121314,001Z";
+        GeneralizedTime generalizedTime3 = new GeneralizedTime( gt3 );
+
+        assertTrue( generalizedTime1.compareTo( generalizedTime2 ) < 0 );
+        assertTrue( generalizedTime1.compareTo( generalizedTime3 ) < 0 );
+        assertTrue( generalizedTime2.compareTo( generalizedTime3 ) < 0 );
+
+        assertTrue( generalizedTime2.compareTo( generalizedTime1 ) > 0 );
+        assertTrue( generalizedTime3.compareTo( generalizedTime1 ) > 0 );
+        assertTrue( generalizedTime3.compareTo( generalizedTime2 ) > 0 );
+
+        assertTrue( generalizedTime1.compareTo( generalizedTime1 ) == 0 );
+        assertTrue( generalizedTime2.compareTo( generalizedTime2 ) == 0 );
+        assertTrue( generalizedTime3.compareTo( generalizedTime3 ) == 0 );
+    }
+
+
+    /**
+     * Tests the equals() method.
+     */
+    public void testEquals() throws ParseException
+    {
+        String gt1 = "20080102121314Z";
+        GeneralizedTime generalizedTime1 = new GeneralizedTime( gt1 );
+
+        String gt2 = "20080102121314Z";
+        GeneralizedTime generalizedTime2 = new GeneralizedTime( gt2 );
+
+        String gt3 = "20080102121314,001Z";
+        GeneralizedTime generalizedTime3 = new GeneralizedTime( gt3 );
+
+        assertTrue( generalizedTime1.equals( generalizedTime2 ) );
+        assertFalse( generalizedTime1.equals( generalizedTime3 ) );
+        assertFalse( generalizedTime1.equals( null ) );
+    }
+}