From 03818b13c1f667d1c092c04004c248993dcdca9c Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Wed, 18 Apr 2007 22:54:37 +0000
Subject: [PATCH] Re-implement the way that the server handles the generalized time syntax to make it more standards compliant and fix problems reported with its behavior.

---
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/GeneralizedTimeEqualityMatchingRuleTest.java |   33 
 opendj-sdk/opends/src/server/org/opends/server/schema/GeneralizedTimeOrderingMatchingRule.java                             |  707 -----------
 opendj-sdk/opends/src/server/org/opends/server/schema/GeneralizedTimeSyntax.java                                           | 1956 +++++++++++++++++++++------------
 opendj-sdk/opends/src/server/org/opends/server/messages/SchemaMessages.java                                                |   58 +
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/GeneralizedTimeOrderingMatchingRuleTest.java |    8 
 opendj-sdk/opends/src/server/org/opends/server/schema/GeneralizedTimeEqualityMatchingRule.java                             |  667 -----------
 6 files changed, 1,373 insertions(+), 2,056 deletions(-)

diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/SchemaMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/SchemaMessages.java
index 308a94b..942ee92 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/SchemaMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/SchemaMessages.java
@@ -3074,6 +3074,48 @@
 
 
   /**
+   * The message ID for the message that will be used if a generalized time
+   * value contains an illegal character in the fraction component.  This takes
+   * two arguments, which are the value string and the illegal character.
+   */
+  public static final int
+       MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_FRACTION_CHAR =
+            CATEGORY_MASK_SCHEMA | SEVERITY_MASK_SEVERE_WARNING | 275;
+
+
+
+  /**
+   * The message ID for the message that will be used if a generalized time
+   * value contains an empty fractional component.  This takes a single
+   * argument, which is the value string.
+   */
+  public static final int MSGID_ATTR_SYNTAX_GENERALIZED_TIME_EMPTY_FRACTION =
+       CATEGORY_MASK_SCHEMA | SEVERITY_MASK_SEVERE_WARNING | 276;
+
+
+
+  /**
+   * The message ID for the message that will be used if a generalized time
+   * value does not contain any time zone information.  This takes a single
+   * argument, which is the value string.
+   */
+  public static final int MSGID_ATTR_SYNTAX_GENERALIZED_TIME_NO_TIME_ZONE_INFO =
+       CATEGORY_MASK_SCHEMA | SEVERITY_MASK_SEVERE_WARNING | 277;
+
+
+
+  /**
+   * The message ID for the message that will be used if a generalized time
+   * value represents an invalid date or time (e.g., September 31).  This takes
+   * two arguments, which are the value string and a string representation of
+   * the exception that was caught.
+   */
+  public static final int MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME =
+       CATEGORY_MASK_SCHEMA | SEVERITY_MASK_SEVERE_WARNING | 278;
+
+
+
+  /**
    * Associates a set of generic messages with the message IDs defined in this
    * class.
    */
@@ -3181,6 +3223,22 @@
     registerMessage(MSGID_ATTR_SYNTAX_GENERALIZED_TIME_NORMALIZE_FAILURE,
                     "An unexpected error occurred while trying to normalize " +
                     "value %s as a generalized time value:  %s.");
+    registerMessage(MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_FRACTION_CHAR,
+                    "The provided value %s is not a valid generalized time " +
+                    "value because it contains illegal character %s in the " +
+                    "fraction component.");
+    registerMessage(MSGID_ATTR_SYNTAX_GENERALIZED_TIME_EMPTY_FRACTION,
+                    "The provided value %s is not a valid generalized time " +
+                    "value because it does not contain at least one digit " +
+                    "after the period to use as the fractional component.");
+    registerMessage(MSGID_ATTR_SYNTAX_GENERALIZED_TIME_NO_TIME_ZONE_INFO,
+                    "The provided value %s is not a valid generalized time " +
+                    "value because it does not end with 'Z' or a time zone " +
+                    "offset.");
+    registerMessage(MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME,
+                    "The provided value %s is not a valid generalized time " +
+                    "value because it represents an invalid time (e.g., a " +
+                    "date that does not exist):  %s.");
 
 
     registerMessage(MSGID_ATTR_SYNTAX_DN_INVALID,
diff --git a/opendj-sdk/opends/src/server/org/opends/server/schema/GeneralizedTimeEqualityMatchingRule.java b/opendj-sdk/opends/src/server/org/opends/server/schema/GeneralizedTimeEqualityMatchingRule.java
index 6fbb6f8..be01196 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/schema/GeneralizedTimeEqualityMatchingRule.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/schema/GeneralizedTimeEqualityMatchingRule.java
@@ -29,9 +29,6 @@
 
 
 import java.text.SimpleDateFormat;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
 import java.util.TimeZone;
 import java.util.concurrent.locks.ReentrantLock;
 
@@ -40,13 +37,11 @@
 import org.opends.server.config.ConfigException;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.protocols.asn1.ASN1OctetString;
-import org.opends.server.types.AcceptRejectWarn;
 import org.opends.server.types.ByteString;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.ErrorLogCategory;
 import org.opends.server.types.ErrorLogSeverity;
 import org.opends.server.types.InitializationException;
-import org.opends.server.types.ResultCode;
 
 import static org.opends.server.loggers.debug.DebugLogger.debugCaught;
 import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
@@ -67,9 +62,6 @@
 public class GeneralizedTimeEqualityMatchingRule
        extends EqualityMatchingRule
 {
-
-
-
   /**
    * The lock that will be used to provide threadsafe access to the date
    * formatter.
@@ -209,651 +201,30 @@
   public ByteString normalizeValue(ByteString value)
          throws DirectoryException
   {
-    String valueString = value.stringValue().toUpperCase();
-    int length = valueString.length();
-
-
-    //Make sure that it has at least eleven characters and parse the first ten
-    // as the year, month, day, and hour.
-    if (length < 11)
-    {
-      int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_TOO_SHORT;
-      String message = getMessage(msgID, valueString);
-
-      switch (DirectoryServer.getSyntaxEnforcementPolicy())
-      {
-        case REJECT:
-          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                       message, msgID);
-        case WARN:
-          logError(ErrorLogCategory.SCHEMA, ErrorLogSeverity.SEVERE_WARNING,
-                   message, msgID);
-          return new ASN1OctetString(valueString);
-        default:
-          return new ASN1OctetString(valueString);
-      }
-    }
-
-
-    // The year, month, day, and hour must always be specified.
-    int year;
-    int month;
-    int day;
-    int hour;
     try
     {
-      year  = Integer.parseInt(valueString.substring(0, 4));
-      month = Integer.parseInt(valueString.substring(4, 6));
-      day   = Integer.parseInt(valueString.substring(6, 8));
-      hour  = Integer.parseInt(valueString.substring(8, 10));
+      long timestamp = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(value);
+      return new ASN1OctetString(GeneralizedTimeSyntax.format(timestamp));
     }
-    catch (Exception e)
+    catch (DirectoryException de)
     {
       if (debugEnabled())
       {
-        debugCaught(DebugLogLevel.ERROR, e);
+        debugCaught(DebugLogLevel.ERROR, de);
       }
 
-      int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_CANNOT_PARSE;
-      String message = getMessage(msgID, valueString,
-                                  String.valueOf(e));
-
       switch (DirectoryServer.getSyntaxEnforcementPolicy())
       {
         case REJECT:
-          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                       message, msgID);
+          throw de;
+
         case WARN:
           logError(ErrorLogCategory.SCHEMA, ErrorLogSeverity.SEVERE_WARNING,
-                   message, msgID);
-          return new ASN1OctetString(valueString);
+                   de.getErrorMessage(), de.getErrorMessageID());
+          return new ASN1OctetString(value.value());
+
         default:
-          return new ASN1OctetString(valueString);
-      }
-    }
-
-
-    // The minute may come next, but if not then it should indicate that we've
-    // hit the end of the value.
-    int  minute;
-    if (isDigit(valueString.charAt(10)))
-    {
-      try
-      {
-        minute = Integer.parseInt(valueString.substring(10, 12));
-      }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          debugCaught(DebugLogLevel.ERROR, e);
-        }
-
-        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_CANNOT_PARSE;
-        String message = getMessage(msgID, valueString,
-                                    String.valueOf(e));
-
-        switch (DirectoryServer.getSyntaxEnforcementPolicy())
-        {
-          case REJECT:
-            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                         message, msgID);
-          case WARN:
-            logError(ErrorLogCategory.SCHEMA, ErrorLogSeverity.SEVERE_WARNING,
-                     message, msgID);
-            return new ASN1OctetString(valueString);
-          default:
-            return new ASN1OctetString(valueString);
-        }
-      }
-    }
-    else
-    {
-      return processValueEnd(valueString, 10, year, month, day, hour, 0, 0, 0);
-    }
-
-
-    // The second should come next, but if not then it should indicate that
-    // we've hit the end of the value.
-    int second;
-    if (length < 13)
-    {
-      // Technically, this is invalid.  If we're enforcing strict syntax
-      // adherence, then throw an exception.  Otherwise, just assume that it's
-      // a time with a second of zero and parse it in the local time zone.
-      if (DirectoryServer.getSyntaxEnforcementPolicy() ==
-          AcceptRejectWarn.REJECT)
-      {
-        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_TOO_SHORT;
-        String message = getMessage(msgID, valueString);
-        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                     message, msgID);
-      }
-      else
-      {
-        GregorianCalendar calendar =
-             new GregorianCalendar(year, (month-1), day, hour, minute, 0);
-        calendar.setTimeZone(utcTimeZone);
-
-        dateFormatLock.lock();
-
-        try
-        {
-          return new ASN1OctetString(dateFormat.format(calendar.getTime()));
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_NORMALIZE_FAILURE;
-          String message = getMessage(msgID, valueString,
-                                      stackTraceToSingleLineString(e));
-
-          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                       message, msgID, e);
-        }
-        finally
-        {
-          dateFormatLock.unlock();
-        }
-      }
-    }
-    else
-    {
-      if (isDigit(valueString.charAt(12)))
-      {
-        try
-        {
-          second = Integer.parseInt(valueString.substring(12, 14));
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_CANNOT_PARSE;
-          String message = getMessage(msgID, valueString,
-                                      String.valueOf(e));
-
-          switch (DirectoryServer.getSyntaxEnforcementPolicy())
-          {
-            case REJECT:
-              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                           message, msgID);
-            case WARN:
-              logError(ErrorLogCategory.SCHEMA, ErrorLogSeverity.SEVERE_WARNING,
-                       message, msgID);
-              return new ASN1OctetString(valueString);
-            default:
-              return new ASN1OctetString(valueString);
-          }
-        }
-      }
-      else
-      {
-        return processValueEnd(valueString, 12, year, month, day, hour, minute,
-                               0, 0);
-      }
-    }
-
-
-    // If the next character is a period, then it will start the sub-second
-    // portion of the value.  Otherwise, it should indicate that we've hit the
-    // end of the value.
-    if (length < 15)
-    {
-      // Technically, this is invalid.  If we're enforcing strict syntax
-      // adherence, then throw an exception.  Otherwise, just assume that it's
-      // a time with a second of zero and parse it in the local time zone.
-      if (DirectoryServer.getSyntaxEnforcementPolicy() ==
-          AcceptRejectWarn.REJECT)
-      {
-        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_TOO_SHORT;
-        String message = getMessage(msgID, valueString);
-        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                     message, msgID);
-      }
-      else
-      {
-        GregorianCalendar calendar =
-             new GregorianCalendar(year, (month-1), day, hour, minute, second);
-        calendar.setTimeZone(utcTimeZone);
-
-        dateFormatLock.lock();
-
-        try
-        {
-          return new ASN1OctetString(dateFormat.format(calendar.getTime()));
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_NORMALIZE_FAILURE;
-          String message = getMessage(msgID, valueString,
-                                      stackTraceToSingleLineString(e));
-
-          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                       message, msgID, e);
-        }
-        finally
-        {
-          dateFormatLock.unlock();
-        }
-      }
-    }
-    else
-    {
-      if (valueString.charAt(14) == '.')
-      {
-        // There should be some number of digits following the decimal point to
-        // indicate the sub-second value.  We'll read all of them now, but may
-        // throw some away later.
-        char c;
-        int pos = 15;
-        StringBuilder buffer = new StringBuilder(3);
-        for ( ; pos < length; pos++)
-        {
-          if (isDigit(c = valueString.charAt(pos)))
-          {
-            buffer.append(c);
-          }
-          else
-          {
-            break;
-          }
-        }
-
-        int millisecond;
-        switch (buffer.length())
-        {
-          case 0:
-            millisecond = 0;
-            break;
-          case 1:
-            millisecond = (100 * Integer.parseInt(buffer.toString()));
-            break;
-          case 2:
-            millisecond = (10 * Integer.parseInt(buffer.toString()));
-            break;
-          case 3:
-            millisecond = Integer.parseInt(buffer.toString());
-            break;
-          default:
-            // We only want three digits for the millisecond, but if the fourth
-            // digit is greater than or equal to five, then we may need to round
-            // up.
-            millisecond = Integer.parseInt(buffer.toString().substring(0, 3));
-            switch (buffer.charAt(3))
-            {
-              case '5':
-              case '6':
-              case '7':
-              case '8':
-              case '9':
-                millisecond++;
-                break;
-            }
-            break;
-        }
-
-        return processValueEnd(valueString, pos, year, month, day, hour, minute,
-                               second, millisecond);
-      }
-      else
-      {
-        return processValueEnd(valueString, 14, year, month, day, hour, minute,
-                               second, 0);
-      }
-    }
-  }
-
-
-
-  /**
-   * Processes the specified portion of the value as the end of the generalized
-   * time specification.  If the character at the specified location is a 'Z',
-   * then it will be assumed that the value is already in UTC.  If it is a '+'
-   * or '-', then it will be assumed that the remainder is an offset from UTC.
-   * Otherwise, it will be an error.
-   *
-   * @param  valueString  The value being parsed as a generalized time string.
-   * @param  endPos       The position at which the end of the value begins.
-   * @param  year         The year parsed from the value.
-   * @param  month        The month parsed from the value.
-   * @param  day          The day parsed from the value.
-   * @param  hour         The hour parsed from the value.
-   * @param  minute       The minute parsed from the value.
-   * @param  second       The second parsed from the value.
-   * @param  millisecond  The millisecond parsed from the value.
-   *
-   * @return  The normalized representation of the generalized time parsed from
-   *          the value.
-   *
-   * @throws  DirectoryException  If a problem occurs while attempting to decode
-   *                              the end of the generalized time value.
-   */
-  private ByteString processValueEnd(String valueString, int endPos, int year,
-                                     int month, int day, int hour, int minute,
-                                     int second, int millisecond)
-          throws DirectoryException
-  {
-    // First, check to see if we are at the end of the string.  If so, then
-    // that could either result in an exception or assuming that we should just
-    // use the local time zone.
-    int length = valueString.length();
-    if (endPos >= length)
-    {
-      if (DirectoryServer.getSyntaxEnforcementPolicy() ==
-          AcceptRejectWarn.REJECT)
-      {
-        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_TOO_SHORT;
-        String message = getMessage(msgID, valueString);
-        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                     message, msgID);
-      }
-      else
-      {
-        GregorianCalendar calendar =
-             new GregorianCalendar(year, (month-1), day, hour, minute, second);
-        calendar.setTimeZone(utcTimeZone);
-        calendar.set(Calendar.MILLISECOND, millisecond);
-
-        dateFormatLock.lock();
-
-        try
-        {
-          return new ASN1OctetString(dateFormat.format(calendar.getTime()));
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_NORMALIZE_FAILURE;
-          String message = getMessage(msgID, valueString,
-                                      stackTraceToSingleLineString(e));
-
-          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                       message, msgID, e);
-        }
-        finally
-        {
-          dateFormatLock.unlock();
-        }
-      }
-    }
-
-
-    // See what the character is at the specified position.  If it is a 'Z',
-    // then make sure it's the end of the value and treat it as a UTC date.
-    char c = valueString.charAt(endPos);
-    if (c == 'Z')
-    {
-      if (endPos == (length-1))
-      {
-        GregorianCalendar calendar =
-             new GregorianCalendar(year, (month-1), day, hour, minute, second);
-        calendar.setTimeZone(utcTimeZone);
-        calendar.set(Calendar.MILLISECOND, millisecond);
-
-        dateFormatLock.lock();
-
-        try
-        {
-          return new ASN1OctetString(dateFormat.format(calendar.getTime()));
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_NORMALIZE_FAILURE;
-          String message = getMessage(msgID, valueString,
-                                      stackTraceToSingleLineString(e));
-
-          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                       message, msgID, e);
-        }
-        finally
-        {
-          dateFormatLock.unlock();
-        }
-      }
-      else
-      {
-        // This is weird because the Z wasn't the last character.  If we should
-        // enforce strict syntax checking, then throw an exception.  Otherwise,
-        // return what we've got so far.
-        if (DirectoryServer.getSyntaxEnforcementPolicy() ==
-            AcceptRejectWarn.REJECT)
-        {
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
-          String message = getMessage(msgID, valueString, 'Z', endPos);
-          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                       message, msgID);
-        }
-        else
-        {
-          GregorianCalendar calendar =
-               new GregorianCalendar(TimeZone.getTimeZone("UTC"));
-          calendar.setTimeZone(utcTimeZone);
-          calendar.set(year, (month-1), day, hour, minute, second);
-          calendar.set(Calendar.MILLISECOND, millisecond);
-
-          dateFormatLock.lock();
-
-          try
-          {
-            return new ASN1OctetString(dateFormat.format(calendar.getTime()));
-          }
-          catch (Exception e)
-          {
-            if (debugEnabled())
-            {
-              debugCaught(DebugLogLevel.ERROR, e);
-            }
-
-            int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_NORMALIZE_FAILURE;
-            String message = getMessage(msgID, valueString,
-                                        stackTraceToSingleLineString(e));
-
-            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                         message, msgID, e);
-          }
-          finally
-          {
-            dateFormatLock.unlock();
-          }
-        }
-      }
-    }
-
-
-    // If the character is a plus or minus, then take the next two or four
-    // digits and use them as a time zone offset.
-    else if ((c == '-') || (c == '+'))
-    {
-      int offset;
-      int charsRemaining = length - endPos - 1;
-      if (charsRemaining == 2)
-      {
-        // The offset specifies the number of hours off GMT.
-        try
-        {
-          offset = Integer.parseInt(valueString.substring(endPos+1)) * 3600000;
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET;
-          String message = getMessage(msgID, valueString,
-                                      valueString.substring(endPos));
-
-          if (DirectoryServer.getSyntaxEnforcementPolicy() ==
-              AcceptRejectWarn.REJECT)
-          {
-            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                         message, msgID);
-          }
-          else
-          {
-            logError(ErrorLogCategory.SCHEMA, ErrorLogSeverity.MILD_ERROR,
-                     message, msgID);
-            offset = 0;
-          }
-        }
-      }
-      else if (charsRemaining == 4)
-      {
-        // The offset specifies the number of hours and minutes off GMT.
-        try
-        {
-          String hourStr = valueString.substring(endPos+1, endPos+3);
-          String minStr  = valueString.substring(endPos+3, endPos+5);
-          offset = (Integer.parseInt(hourStr) * 3600000) +
-                   (Integer.parseInt(minStr) * 60000);
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET;
-          String message = getMessage(msgID, valueString,
-                                      valueString.substring(endPos));
-
-          if (DirectoryServer.getSyntaxEnforcementPolicy() ==
-              AcceptRejectWarn.REJECT)
-          {
-            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                         message, msgID);
-          }
-          else
-          {
-            logError(ErrorLogCategory.SCHEMA, ErrorLogSeverity.MILD_ERROR,
-                     message, msgID);
-            offset = 0;
-          }
-        }
-      }
-      else
-      {
-        // It is an invalid offset, so either throw an exception or assume the
-        // local time zone.
-        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET;
-        String message = getMessage(msgID, valueString,
-                                    valueString.substring(endPos));
-
-        if (DirectoryServer.getSyntaxEnforcementPolicy() ==
-            AcceptRejectWarn.REJECT)
-        {
-          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                       message, msgID);
-        }
-        else
-        {
-          logError(ErrorLogCategory.SCHEMA, ErrorLogSeverity.MILD_ERROR,
-                   message, msgID);
-          offset = TimeZone.getDefault().getRawOffset();
-        }
-      }
-
-      GregorianCalendar calendar = new GregorianCalendar(year, (month-1), day,
-                                                         hour, minute, second);
-      calendar.setTimeZone(utcTimeZone);
-      calendar.set(Calendar.MILLISECOND, millisecond);
-      calendar.set(Calendar.ZONE_OFFSET, offset);
-
-      dateFormatLock.lock();
-
-      try
-      {
-        return new ASN1OctetString(dateFormat.format(calendar.getTime()));
-      }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          debugCaught(DebugLogLevel.ERROR, e);
-        }
-
-        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_NORMALIZE_FAILURE;
-        String message = getMessage(msgID, valueString,
-                                    stackTraceToSingleLineString(e));
-
-        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                     message, msgID, e);
-      }
-      finally
-      {
-        dateFormatLock.unlock();
-      }
-    }
-
-
-    // If we've gotten here, then there was an illegal character at the end of
-    // the value.  Either throw an exception or assume the default time zone.
-    int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
-    String message = getMessage(msgID, valueString, c, endPos);
-
-    if (DirectoryServer.getSyntaxEnforcementPolicy() ==
-        AcceptRejectWarn.REJECT)
-    {
-      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                   message, msgID);
-    }
-    else
-    {
-      logError(ErrorLogCategory.SCHEMA, ErrorLogSeverity.MILD_ERROR, message,
-               msgID);
-
-      GregorianCalendar calendar = new GregorianCalendar(year, (month-1), day,
-                                                         hour, minute, second);
-      calendar.setTimeZone(utcTimeZone);
-      calendar.set(Calendar.MILLISECOND, millisecond);
-
-      dateFormatLock.lock();
-
-      try
-      {
-        return new ASN1OctetString(dateFormat.format(calendar.getTime()));
-      }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          debugCaught(DebugLogLevel.ERROR, e);
-        }
-
-        msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_NORMALIZE_FAILURE;
-        message = getMessage(msgID, valueString,
-                             stackTraceToSingleLineString(e));
-
-        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                     message, msgID, e);
-      }
-      finally
-      {
-        dateFormatLock.unlock();
+          return new ASN1OctetString(value.value());
       }
     }
   }
@@ -872,9 +243,21 @@
    */
   public boolean areEqual(ByteString value1, ByteString value2)
   {
-    // Since the values are already normalized, we just need to compare the
-    // associated byte arrays.
-    return Arrays.equals(value1.value(), value2.value());
+    try
+    {
+      long time1 = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(value1);
+      long time2 = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(value2);
+      return (time1 == time2);
+    }
+    catch (DirectoryException de)
+    {
+      if (debugEnabled())
+      {
+        debugCaught(DebugLogLevel.ERROR, de);
+      }
+
+      return false;
+    }
   }
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/schema/GeneralizedTimeOrderingMatchingRule.java b/opendj-sdk/opends/src/server/org/opends/server/schema/GeneralizedTimeOrderingMatchingRule.java
index 7b8148f..e99d558 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/schema/GeneralizedTimeOrderingMatchingRule.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/schema/GeneralizedTimeOrderingMatchingRule.java
@@ -29,8 +29,6 @@
 
 
 import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
 import java.util.TimeZone;
 import java.util.concurrent.locks.ReentrantLock;
 
@@ -39,13 +37,11 @@
 import org.opends.server.config.ConfigException;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.protocols.asn1.ASN1OctetString;
-import org.opends.server.types.AcceptRejectWarn;
 import org.opends.server.types.ByteString;
 import org.opends.server.types.DirectoryException;
 import org.opends.server.types.ErrorLogCategory;
 import org.opends.server.types.ErrorLogSeverity;
 import org.opends.server.types.InitializationException;
-import org.opends.server.types.ResultCode;
 
 import static org.opends.server.loggers.debug.DebugLogger.debugCaught;
 import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
@@ -66,9 +62,6 @@
 public class GeneralizedTimeOrderingMatchingRule
        extends OrderingMatchingRule
 {
-
-
-
   /**
    * The serial version identifier required to satisfy the compiler because this
    * class implements the <CODE>java.io.Serializable</CODE> interface.  This
@@ -218,651 +211,30 @@
   public ByteString normalizeValue(ByteString value)
          throws DirectoryException
   {
-    String valueString = value.stringValue().toUpperCase();
-    int length = valueString.length();
-
-
-    //Make sure that it has at least eleven characters and parse the first ten
-    // as the year, month, day, and hour.
-    if (length < 11)
-    {
-      int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_TOO_SHORT;
-      String message = getMessage(msgID, valueString);
-
-      switch (DirectoryServer.getSyntaxEnforcementPolicy())
-      {
-        case REJECT:
-          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                       message, msgID);
-        case WARN:
-          logError(ErrorLogCategory.SCHEMA, ErrorLogSeverity.SEVERE_WARNING,
-                   message, msgID);
-          return new ASN1OctetString(valueString);
-        default:
-          return new ASN1OctetString(valueString);
-      }
-    }
-
-
-    // The year, month, day, and hour must always be specified.
-    int year;
-    int month;
-    int day;
-    int hour;
     try
     {
-      year  = Integer.parseInt(valueString.substring(0, 4));
-      month = Integer.parseInt(valueString.substring(4, 6));
-      day   = Integer.parseInt(valueString.substring(6, 8));
-      hour  = Integer.parseInt(valueString.substring(8, 10));
+      long timestamp = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(value);
+      return new ASN1OctetString(GeneralizedTimeSyntax.format(timestamp));
     }
-    catch (Exception e)
+    catch (DirectoryException de)
     {
       if (debugEnabled())
       {
-        debugCaught(DebugLogLevel.ERROR, e);
+        debugCaught(DebugLogLevel.ERROR, de);
       }
 
-      int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_CANNOT_PARSE;
-      String message = getMessage(msgID, valueString,
-                                  String.valueOf(e));
-
       switch (DirectoryServer.getSyntaxEnforcementPolicy())
       {
         case REJECT:
-          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                       message, msgID);
+          throw de;
+
         case WARN:
           logError(ErrorLogCategory.SCHEMA, ErrorLogSeverity.SEVERE_WARNING,
-                   message, msgID);
-          return new ASN1OctetString(valueString);
+                   de.getErrorMessage(), de.getErrorMessageID());
+          return new ASN1OctetString(value.value());
+
         default:
-          return new ASN1OctetString(valueString);
-      }
-    }
-
-
-    // The minute may come next, but if not then it should indicate that we've
-    // hit the end of the value.
-    int  minute;
-    if (isDigit(valueString.charAt(10)))
-    {
-      try
-      {
-        minute = Integer.parseInt(valueString.substring(10, 12));
-      }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          debugCaught(DebugLogLevel.ERROR, e);
-        }
-
-        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_CANNOT_PARSE;
-        String message = getMessage(msgID, valueString,
-                                    String.valueOf(e));
-
-        switch (DirectoryServer.getSyntaxEnforcementPolicy())
-        {
-          case REJECT:
-            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                         message, msgID);
-          case WARN:
-            logError(ErrorLogCategory.SCHEMA, ErrorLogSeverity.SEVERE_WARNING,
-                     message, msgID);
-            return new ASN1OctetString(valueString);
-          default:
-            return new ASN1OctetString(valueString);
-        }
-      }
-    }
-    else
-    {
-      return processValueEnd(valueString, 10, year, month, day, hour, 0, 0, 0);
-    }
-
-
-    // The second should come next, but if not then it should indicate that
-    // we've hit the end of the value.
-    int second;
-    if (length < 13)
-    {
-      // Technically, this is invalid.  If we're enforcing strict syntax
-      // adherence, then throw an exception.  Otherwise, just assume that it's
-      // a time with a second of zero and parse it in the local time zone.
-      if (DirectoryServer.getSyntaxEnforcementPolicy() ==
-          AcceptRejectWarn.REJECT)
-      {
-        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_TOO_SHORT;
-        String message = getMessage(msgID, valueString);
-        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                     message, msgID);
-      }
-      else
-      {
-        GregorianCalendar calendar =
-             new GregorianCalendar(year, (month-1), day, hour, minute, 0);
-        calendar.setTimeZone(utcTimeZone);
-
-        dateFormatLock.lock();
-
-        try
-        {
-          return new ASN1OctetString(dateFormat.format(calendar.getTime()));
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_NORMALIZE_FAILURE;
-          String message = getMessage(msgID, valueString,
-                                      stackTraceToSingleLineString(e));
-
-          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                       message, msgID, e);
-        }
-        finally
-        {
-          dateFormatLock.unlock();
-        }
-      }
-    }
-    else
-    {
-      if (isDigit(valueString.charAt(12)))
-      {
-        try
-        {
-          second = Integer.parseInt(valueString.substring(12, 14));
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_CANNOT_PARSE;
-          String message = getMessage(msgID, valueString,
-                                      String.valueOf(e));
-
-          if (DirectoryServer.getSyntaxEnforcementPolicy() ==
-              AcceptRejectWarn.REJECT)
-          {
-            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                         message, msgID, e);
-          }
-          else
-          {
-            logError(ErrorLogCategory.SCHEMA, ErrorLogSeverity.MILD_ERROR,
-                     message, msgID);
-            return new ASN1OctetString(valueString);
-          }
-        }
-      }
-      else
-      {
-        return processValueEnd(valueString, 12, year, month, day, hour, minute,
-                               0, 0);
-      }
-    }
-
-
-    // If the next character is a period, then it will start the sub-second
-    // portion of the value.  Otherwise, it should indicate that we've hit the
-    // end of the value.
-    if (length < 15)
-    {
-      // Technically, this is invalid.  If we're enforcing strict syntax
-      // adherence, then throw an exception.  Otherwise, just assume that it's
-      // a time with a second of zero and parse it in the local time zone.
-      if (DirectoryServer.getSyntaxEnforcementPolicy() ==
-          AcceptRejectWarn.REJECT)
-      {
-        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_TOO_SHORT;
-        String message = getMessage(msgID, valueString);
-        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                     message, msgID);
-      }
-      else
-      {
-        GregorianCalendar calendar =
-             new GregorianCalendar(year, (month-1), day, hour, minute, second);
-        calendar.setTimeZone(utcTimeZone);
-
-        dateFormatLock.lock();
-
-        try
-        {
-          return new ASN1OctetString(dateFormat.format(calendar.getTime()));
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_NORMALIZE_FAILURE;
-          String message = getMessage(msgID, valueString,
-                                      stackTraceToSingleLineString(e));
-
-          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                       message, msgID, e);
-        }
-        finally
-        {
-          dateFormatLock.unlock();
-        }
-      }
-    }
-    else
-    {
-      if (valueString.charAt(14) == '.')
-      {
-        // There should be some number of digits following the decimal point to
-        // indicate the sub-second value.  We'll read all of them now, but may
-        // throw some away later.
-        char c;
-        int pos = 15;
-        StringBuilder buffer = new StringBuilder(3);
-        for ( ; pos < length; pos++)
-        {
-          if (isDigit(c = valueString.charAt(pos)))
-          {
-            buffer.append(c);
-          }
-          else
-          {
-            break;
-          }
-        }
-
-        int millisecond;
-        switch (buffer.length())
-        {
-          case 0:
-            millisecond = 0;
-            break;
-          case 1:
-            millisecond = (100 * Integer.parseInt(buffer.toString()));
-            break;
-          case 2:
-            millisecond = (10 * Integer.parseInt(buffer.toString()));
-            break;
-          case 3:
-            millisecond = Integer.parseInt(buffer.toString());
-            break;
-          default:
-            // We only want three digits for the millisecond, but if the fourth
-            // digit is greater than or equal to five, then we may need to round
-            // up.
-            millisecond = Integer.parseInt(buffer.toString().substring(0, 3));
-            switch (buffer.charAt(3))
-            {
-              case 5:
-              case 6:
-              case 7:
-              case 8:
-              case 9:
-                millisecond++;
-                break;
-            }
-            break;
-        }
-
-        return processValueEnd(valueString, pos, year, month, day, hour, minute,
-                               second, millisecond);
-      }
-      else
-      {
-        return processValueEnd(valueString, 14, year, month, day, hour, minute,
-                               second, 0);
-      }
-    }
-  }
-
-
-
-  /**
-   * Processes the specified portion of the value as the end of the generalized
-   * time specification.  If the character at the specified location is a 'Z',
-   * then it will be assumed that the value is already in UTC.  If it is a '+'
-   * or '-', then it will be assumed that the remainder is an offset from UTC.
-   * Otherwise, it will be an error.
-   *
-   * @param  valueString  The value being parsed as a generalized time string.
-   * @param  endPos       The position at which the end of the value begins.
-   * @param  year         The year parsed from the value.
-   * @param  month        The month parsed from the value.
-   * @param  day          The day parsed from the value.
-   * @param  hour         The hour parsed from the value.
-   * @param  minute       The minute parsed from the value.
-   * @param  second       The second parsed from the value.
-   * @param  millisecond  The millisecond parsed from the value.
-   *
-   * @return  The normalized representation of the generalized time parsed from
-   *          the value.
-   *
-   * @throws  DirectoryException  If a problem occurs while attempting to decode
-   *                              the end of the generalized time value.
-   */
-  private ByteString processValueEnd(String valueString, int endPos, int year,
-                                     int month, int day, int hour, int minute,
-                                     int second, int millisecond)
-          throws DirectoryException
-  {
-    // First, check to see if we are at the end of the string.  If so, then
-    // that could either result in an exception or assuming that we should just
-    // use the local time zone.
-    int length = valueString.length();
-    if (endPos >= length)
-    {
-      if (DirectoryServer.getSyntaxEnforcementPolicy() ==
-          AcceptRejectWarn.REJECT)
-      {
-        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_TOO_SHORT;
-        String message = getMessage(msgID, valueString);
-        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                     message, msgID);
-      }
-      else
-      {
-        GregorianCalendar calendar =
-             new GregorianCalendar(year, (month-1), day, hour, minute, second);
-        calendar.setTimeZone(utcTimeZone);
-        calendar.set(Calendar.MILLISECOND, millisecond);
-
-        dateFormatLock.lock();
-
-        try
-        {
-          return new ASN1OctetString(dateFormat.format(calendar.getTime()));
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_NORMALIZE_FAILURE;
-          String message = getMessage(msgID, valueString,
-                                      stackTraceToSingleLineString(e));
-
-          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                       message, msgID, e);
-        }
-        finally
-        {
-          dateFormatLock.unlock();
-        }
-      }
-    }
-
-
-    // See what the character is at the specified position.  If it is a 'Z',
-    // then make sure it's the end of the value and treat it as a UTC date.
-    char c = valueString.charAt(endPos);
-    if (c == 'Z')
-    {
-      if (endPos == (length-1))
-      {
-        GregorianCalendar calendar =
-             new GregorianCalendar(year, (month-1), day, hour, minute, second);
-        calendar.setTimeZone(utcTimeZone);
-        calendar.set(Calendar.MILLISECOND, millisecond);
-
-        dateFormatLock.lock();
-
-        try
-        {
-          return new ASN1OctetString(dateFormat.format(calendar.getTime()));
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_NORMALIZE_FAILURE;
-          String message = getMessage(msgID, valueString,
-                                      stackTraceToSingleLineString(e));
-
-          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                       message, msgID, e);
-        }
-        finally
-        {
-          dateFormatLock.unlock();
-        }
-      }
-      else
-      {
-        // This is weird because the Z wasn't the last character.  If we should
-        // enforce strict syntax checking, then throw an exception.  Otherwise,
-        // return what we've got so far.
-        if (DirectoryServer.getSyntaxEnforcementPolicy() ==
-            AcceptRejectWarn.REJECT)
-        {
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
-          String message = getMessage(msgID, valueString, 'Z', endPos);
-          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                       message, msgID);
-        }
-        else
-        {
-          GregorianCalendar calendar =
-               new GregorianCalendar(TimeZone.getTimeZone("UTC"));
-          calendar.setTimeZone(utcTimeZone);
-          calendar.set(year, (month-1), day, hour, minute, second);
-          calendar.set(Calendar.MILLISECOND, millisecond);
-
-          dateFormatLock.lock();
-
-          try
-          {
-            return new ASN1OctetString(dateFormat.format(calendar.getTime()));
-          }
-          catch (Exception e)
-          {
-            if (debugEnabled())
-            {
-              debugCaught(DebugLogLevel.ERROR, e);
-            }
-
-            int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_NORMALIZE_FAILURE;
-            String message = getMessage(msgID, valueString,
-                                        stackTraceToSingleLineString(e));
-
-            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                         message, msgID, e);
-          }
-          finally
-          {
-            dateFormatLock.unlock();
-          }
-        }
-      }
-    }
-
-
-    // If the character is a plus or minus, then take the next two or four
-    // digits and use them as a time zone offset.
-    else if ((c == '-') || (c == '+'))
-    {
-      int offset;
-      int charsRemaining = length - endPos - 1;
-      if (charsRemaining == 2)
-      {
-        // The offset specifies the number of hours off GMT.
-        try
-        {
-          offset = Integer.parseInt(valueString.substring(endPos+1)) * 3600000;
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET;
-          String message = getMessage(msgID, valueString,
-                                      valueString.substring(endPos));
-
-          if (DirectoryServer.getSyntaxEnforcementPolicy() ==
-              AcceptRejectWarn.REJECT)
-          {
-            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                         message, msgID);
-          }
-          else
-          {
-            logError(ErrorLogCategory.SCHEMA, ErrorLogSeverity.MILD_ERROR,
-                     message, msgID);
-            offset = 0;
-          }
-        }
-      }
-      else if (charsRemaining == 4)
-      {
-        // The offset specifies the number of hours and minutes off GMT.
-        try
-        {
-          String hourStr = valueString.substring(endPos+1, endPos+3);
-          String minStr  = valueString.substring(endPos+3, endPos+5);
-          offset = (Integer.parseInt(hourStr) * 3600000) +
-                   (Integer.parseInt(minStr) * 1000);
-        }
-        catch (Exception e)
-        {
-          if (debugEnabled())
-          {
-            debugCaught(DebugLogLevel.ERROR, e);
-          }
-
-          int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET;
-          String message = getMessage(msgID, valueString,
-                                      valueString.substring(endPos));
-
-          if (DirectoryServer.getSyntaxEnforcementPolicy() ==
-              AcceptRejectWarn.REJECT)
-          {
-            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                         message, msgID);
-          }
-          else
-          {
-            logError(ErrorLogCategory.SCHEMA, ErrorLogSeverity.MILD_ERROR,
-                     message, msgID);
-            offset = 0;
-          }
-        }
-      }
-      else
-      {
-        // It is an invalid offset, so either throw an exception or assume the
-        // local time zone.
-        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET;
-        String message = getMessage(msgID, valueString,
-                                    valueString.substring(endPos));
-
-        if (DirectoryServer.getSyntaxEnforcementPolicy() ==
-            AcceptRejectWarn.REJECT)
-        {
-          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                       message, msgID);
-        }
-        else
-        {
-          logError(ErrorLogCategory.SCHEMA, ErrorLogSeverity.MILD_ERROR,
-                   message, msgID);
-          offset = TimeZone.getDefault().getRawOffset();
-        }
-      }
-
-      GregorianCalendar calendar = new GregorianCalendar(year, (month-1), day,
-                                                         hour, minute, second);
-      calendar.setTimeZone(utcTimeZone);
-      calendar.set(Calendar.MILLISECOND, millisecond);
-      calendar.set(Calendar.ZONE_OFFSET, offset);
-
-      dateFormatLock.lock();
-
-      try
-      {
-        return new ASN1OctetString(dateFormat.format(calendar.getTime()));
-      }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          debugCaught(DebugLogLevel.ERROR, e);
-        }
-
-        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_NORMALIZE_FAILURE;
-        String message = getMessage(msgID, valueString,
-                                    stackTraceToSingleLineString(e));
-
-        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                     message, msgID, e);
-      }
-      finally
-      {
-        dateFormatLock.unlock();
-      }
-    }
-
-
-    // If we've gotten here, then there was an illegal character at the end of
-    // the value.  Either throw an exception or assume the default time zone.
-    int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
-    String message = getMessage(msgID, valueString, c, endPos);
-
-    if (DirectoryServer.getSyntaxEnforcementPolicy() ==
-        AcceptRejectWarn.REJECT)
-    {
-      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                   message, msgID);
-    }
-    else
-    {
-      logError(ErrorLogCategory.SCHEMA, ErrorLogSeverity.MILD_ERROR, message,
-               msgID);
-
-      GregorianCalendar calendar = new GregorianCalendar(year, (month-1), day,
-                                                         hour, minute, second);
-      calendar.setTimeZone(utcTimeZone);
-      calendar.set(Calendar.MILLISECOND, millisecond);
-
-      dateFormatLock.lock();
-
-      try
-      {
-        return new ASN1OctetString(dateFormat.format(calendar.getTime()));
-      }
-      catch (Exception e)
-      {
-        if (debugEnabled())
-        {
-          debugCaught(DebugLogLevel.ERROR, e);
-        }
-
-        msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_NORMALIZE_FAILURE;
-        message = getMessage(msgID, valueString,
-                                    stackTraceToSingleLineString(e));
-
-        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                     message, msgID, e);
-      }
-      finally
-      {
-        dateFormatLock.unlock();
+          return new ASN1OctetString(value.value());
       }
     }
   }
@@ -884,7 +256,33 @@
    */
   public int compareValues(ByteString value1, ByteString value2)
   {
-    return compare(value1.value(), value2.value());
+    try
+    {
+      long time1 = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(value1);
+      long time2 = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(value2);
+
+      if (time1 == time2)
+      {
+        return 0;
+      }
+      else if (time1 > time2)
+      {
+        return 1;
+      }
+      else
+      {
+        return -1;
+      }
+    }
+    catch (DirectoryException de)
+    {
+      if (debugEnabled())
+      {
+        debugCaught(DebugLogLevel.ERROR, de);
+      }
+
+      return 0;
+    }
   }
 
 
@@ -904,36 +302,7 @@
    */
   public int compare(byte[] b1, byte[] b2)
   {
-    int minLength = Math.min(b1.length, b2.length);
-
-    for (int i=0; i < minLength; i++)
-    {
-      if (b1[i] == b2[i])
-      {
-        continue;
-      }
-      else if (b1[i] < b2[i])
-      {
-        return -1;
-      }
-      else if (b1[i] > b2[i])
-      {
-        return 1;
-      }
-    }
-
-    if (b1.length == b2.length)
-    {
-      return 0;
-    }
-    else if (b1.length < b2.length)
-    {
-      return -1;
-    }
-    else
-    {
-      return 1;
-    }
+    return compareValues(new ASN1OctetString(b1), new ASN1OctetString(b2));
   }
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/schema/GeneralizedTimeSyntax.java b/opendj-sdk/opends/src/server/org/opends/server/schema/GeneralizedTimeSyntax.java
index f507779..cc204be 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/schema/GeneralizedTimeSyntax.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/schema/GeneralizedTimeSyntax.java
@@ -29,7 +29,9 @@
 
 
 import java.text.SimpleDateFormat;
+import java.util.Calendar;
 import java.util.Date;
+import java.util.GregorianCalendar;
 import java.util.TimeZone;
 import java.util.concurrent.locks.ReentrantLock;
 
@@ -70,9 +72,6 @@
 public class GeneralizedTimeSyntax
        extends AttributeSyntax
 {
-
-
-
   /**
    * The lock that will be used to provide threadsafe access to the date
    * formatter.
@@ -284,714 +283,16 @@
   public boolean valueIsAcceptable(ByteString value,
                                    StringBuilder invalidReason)
   {
-    // Get the value as a string and verify that it is at least long enough for
-    // "YYYYMMDDhhZ", which is the shortest allowed value.
-    String valueString = value.stringValue().toUpperCase();
-    int    length      = valueString.length();
-    if (length < 11)
+    try
     {
-      int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_TOO_SHORT;
-      String message = getMessage(msgID, valueString);
-      invalidReason.append(message);
+      decodeGeneralizedTimeValue(value);
+      return true;
+    }
+    catch (DirectoryException de)
+    {
+      invalidReason.append(de.getErrorMessage());
       return false;
     }
-
-
-    // The first four characters are the century and year, and they must be
-    // numeric digits between 0 and 9.
-    for (int i=0; i < 4; i++)
-    {
-      switch (valueString.charAt(i))
-      {
-        case '0':
-        case '1':
-        case '2':
-        case '3':
-        case '4':
-        case '5':
-        case '6':
-        case '7':
-        case '8':
-        case '9':
-          // These are all fine.
-          break;
-        default:
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_YEAR;
-          String message = getMessage(msgID, valueString,
-                                      valueString.charAt(i));
-          invalidReason.append(message);
-          return false;
-      }
-    }
-
-
-    // The next two characters are the month, and they must form the string
-    // representation of an integer between 01 and 12.
-    char m1 = valueString.charAt(4);
-    char m2 = valueString.charAt(5);
-    switch (m1)
-    {
-      case '0':
-        // m2 must be a digit between 1 and 9.
-        switch (m2)
-        {
-          case '1':
-          case '2':
-          case '3':
-          case '4':
-          case '5':
-          case '6':
-          case '7':
-          case '8':
-          case '9':
-            // These are all fine.
-            break;
-          default:
-            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MONTH;
-            String message = getMessage(msgID, valueString,
-                                        valueString.substring(4, 6));
-            invalidReason.append(message);
-            return false;
-        }
-        break;
-      case '1':
-        // m2 must be a digit between 0 and 2.
-        switch (m2)
-        {
-          case '0':
-          case '1':
-          case '2':
-            // These are all fine.
-            break;
-          default:
-            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MONTH;
-            String message = getMessage(msgID, valueString,
-                                        valueString.substring(4, 6));
-            invalidReason.append(message);
-            return false;
-        }
-        break;
-      default:
-        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MONTH;
-        String message = getMessage(msgID, valueString,
-                                    valueString.substring(4, 6));
-        invalidReason.append(message);
-        return false;
-    }
-
-
-    // The next two characters should be the day of the month, and they must
-    // form the string representation of an integer between 01 and 31.
-    // This doesn't do any validation against the year or month, so it will
-    // allow dates like April 31, or February 29 in a non-leap year, but we'll
-    // let those slide.
-    char d1 = valueString.charAt(6);
-    char d2 = valueString.charAt(7);
-    switch (d1)
-    {
-      case '0':
-        // d2 must be a digit between 1 and 9.
-        switch (d2)
-        {
-          case '1':
-          case '2':
-          case '3':
-          case '4':
-          case '5':
-          case '6':
-          case '7':
-          case '8':
-          case '9':
-            // These are all fine.
-            break;
-          default:
-            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY;
-            String message = getMessage(msgID, valueString,
-                                        valueString.substring(6, 8));
-            invalidReason.append(message);
-            return false;
-        }
-        break;
-      case '1':
-        // Treated the same as '2'.
-      case '2':
-        // d2 must be a digit between 0 and 9.
-        switch (d2)
-        {
-          case '0':
-          case '1':
-          case '2':
-          case '3':
-          case '4':
-          case '5':
-          case '6':
-          case '7':
-          case '8':
-          case '9':
-            // These are all fine.
-            break;
-          default:
-            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY;
-            String message = getMessage(msgID, valueString,
-                                        valueString.substring(6, 8));
-            invalidReason.append(message);
-            return false;
-        }
-        break;
-      case '3':
-        // d2 must be either 0 or 1.
-        switch (d2)
-        {
-          case '0':
-          case '1':
-            // These are all fine.
-            break;
-          default:
-            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY;
-            String message = getMessage(msgID, valueString,
-                                        valueString.substring(6, 8));
-            invalidReason.append(message);
-            return false;
-        }
-        break;
-      default:
-        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY;
-        String message = getMessage(msgID, valueString,
-                                    valueString.substring(6, 8));
-        invalidReason.append(message);
-        return false;
-    }
-
-
-    // The next two characters must be the hour, and they must form the string
-    // representation of an integer between 00 and 23.
-    char h1 = valueString.charAt(8);
-    char h2 = valueString.charAt(9);
-    switch (h1)
-    {
-      case '0':
-        // This is treated the same as '1'.
-      case '1':
-        switch (h2)
-        {
-          case '0':
-          case '1':
-          case '2':
-          case '3':
-          case '4':
-          case '5':
-          case '6':
-          case '7':
-          case '8':
-          case '9':
-            // These are all fine.
-            break;
-          default:
-            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR;
-            String message = getMessage(msgID, valueString,
-                                        valueString.substring(8, 10));
-            invalidReason.append(message);
-            return false;
-        }
-        break;
-      case '2':
-        // This must be a digit between 0 and 3.
-        switch (h2)
-        {
-          case '0':
-          case '1':
-          case '2':
-          case '3':
-            // These are all fine.
-            break;
-          default:
-            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR;
-            String message = getMessage(msgID, valueString,
-                                        valueString.substring(8, 10));
-            invalidReason.append(message);
-            return false;
-        }
-        break;
-      default:
-        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR;
-        String message = getMessage(msgID, valueString,
-                                    valueString.substring(8, 10));
-        invalidReason.append(message);
-        return false;
-    }
-
-
-    // Next, there should be either two digits comprising an integer between 00
-    // and 59 (for the minute), a letter 'Z' (for the UTC specifier), or a plus
-    // or minus sign followed by two or four digits (for the UTC offset).
-    m1 = valueString.charAt(10);
-    switch (m1)
-    {
-      case '0':
-      case '1':
-      case '2':
-      case '3':
-      case '4':
-      case '5':
-        // There must be at least two more characters, and the next one must
-        // be a digit between 0 and 9.
-        if (length < 13)
-        {
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
-          String message = getMessage(msgID, valueString, m1, 10);
-          invalidReason.append(message);
-          return false;
-        }
-
-        switch (valueString.charAt(11))
-        {
-          case '0':
-          case '1':
-          case '2':
-          case '3':
-          case '4':
-          case '5':
-          case '6':
-          case '7':
-          case '8':
-          case '9':
-            // These are all fine.
-            break;
-          default:
-            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MINUTE;
-            String message = getMessage(msgID, valueString,
-                                        valueString.substring(10, 12));
-            invalidReason.append(message);
-            return false;
-        }
-
-        break;
-      case 'Z':
-        // This is fine only if we are at the end of the value.
-        if (length == 11)
-        {
-          return true;
-        }
-        else
-        {
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
-          String message = getMessage(msgID, valueString, m1, 10);
-          invalidReason.append(message);
-          return false;
-        }
-
-      case '+':
-      case '-':
-        // These are fine only if there are exactly two or four more digits that
-        // specify a valid offset.
-        if ((length == 13) || (length == 15))
-        {
-          return hasValidOffset(valueString, 11, invalidReason);
-        }
-        else
-        {
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
-          String message = getMessage(msgID, valueString, m1, 10);
-          invalidReason.append(message);
-          return false;
-        }
-
-      default:
-        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
-        String message = getMessage(msgID, valueString, m1, 10);
-        invalidReason.append(message);
-        return false;
-    }
-
-
-    // Next, there should be either two digits comprising an integer between 00
-    // and 60 (for the second, including a possible leap second), a letter 'Z'
-    // (for the UTC specifier), or a plus or minus sign followed by two or four
-    // digits (for the UTC offset).
-    char s1 = valueString.charAt(12);
-    switch (s1)
-    {
-      case '0':
-      case '1':
-      case '2':
-      case '3':
-      case '4':
-      case '5':
-        // There must be at least two more characters, and the next one must
-        // be a digit between 0 and 9.
-        if (length < 15)
-        {
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
-          String message = getMessage(msgID, valueString, s1, 12);
-          invalidReason.append(message);
-          return false;
-        }
-
-        switch (valueString.charAt(13))
-        {
-          case '0':
-          case '1':
-          case '2':
-          case '3':
-          case '4':
-          case '5':
-          case '6':
-          case '7':
-          case '8':
-          case '9':
-            // These are all fine.
-            break;
-          default:
-            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_SECOND;
-            String message = getMessage(msgID, valueString,
-                                        valueString.substring(12, 14));
-            invalidReason.append(message);
-            return false;
-        }
-
-        break;
-      case '6':
-        // There must be at least two more characters and the next one must be
-        // a 0.
-        if (length < 15)
-        {
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
-          String message = getMessage(msgID, valueString, s1, 12);
-          invalidReason.append(message);
-          return false;
-        }
-
-        if (valueString.charAt(13) != '0')
-        {
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_SECOND;
-          String message = getMessage(msgID, valueString,
-                                      valueString.substring(12, 14));
-          invalidReason.append(message);
-          return false;
-        }
-
-        break;
-      case 'Z':
-        // This is fine only if we are at the end of the value.
-        if (length == 13)
-        {
-          return true;
-        }
-        else
-        {
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
-          String message = getMessage(msgID, valueString, s1, 12);
-          invalidReason.append(message);
-          return false;
-        }
-
-      case '+':
-      case '-':
-        // These are fine only if there are exactly two or four more digits that
-        // specify a valid offset.
-        if ((length == 15) || (length == 17))
-        {
-          return hasValidOffset(valueString, 13, invalidReason);
-        }
-        else
-        {
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
-          String message = getMessage(msgID, valueString, s1, 12);
-          invalidReason.append(message);
-          return false;
-        }
-
-      default:
-        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
-        String message = getMessage(msgID, valueString, s1, 12);
-        invalidReason.append(message);
-        return false;
-    }
-
-
-    // Next, there should be either a period or comma followed by between one
-    // and three digits (to specify the sub-second), a letter 'Z' (for the UTC
-    // specifier), or a plus or minus sign followed by two our four digits (for
-    // the UTC offset).
-    switch (valueString.charAt(14))
-    {
-      case '.':
-      case ',':
-        // There will be a sub-second portion.  Walk through the rest of the
-        // value until we find a Z, +, or -.
-        boolean endFound = false;
-        int pos = 15;
-        while (pos < length)
-        {
-
-          switch (valueString.charAt(pos))
-          {
-            case '0':
-            case '1':
-            case '2':
-            case '3':
-            case '4':
-            case '5':
-            case '6':
-            case '7':
-            case '8':
-            case '9':
-              // These are fine as long as we don't have more than three.
-              if (pos > 17)
-              {
-                int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_LONG_SUBSECOND;
-                String message = getMessage(msgID, value);
-                invalidReason.append(message);
-                return false;
-              }
-
-              break;
-            case 'Z':
-              // This must be the end of the string and there must have been
-              // at least one sub-second digit.
-              if (pos == 15)
-              {
-                int msgID =
-                     MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_SUBSECOND;
-                String message = getMessage(msgID, value);
-                invalidReason.append(message);
-                return false;
-              }
-
-              if (pos != (length-1))
-              {
-                int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
-                String message = getMessage(msgID, valueString,
-                                            valueString.charAt(pos), pos);
-                invalidReason.append(message);
-                return false;
-              }
-
-              return true;
-
-            case '+':
-            case '-':
-              // There must have been at least one sub-second digit, and there
-              // must be either two or four digits left.
-              if (pos == 15)
-              {
-                int msgID =
-                     MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_SUBSECOND;
-                String message = getMessage(msgID, value);
-                invalidReason.append(message);
-                return false;
-              }
-              else if ((length != 17) && (length != 19))
-              {
-                int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
-                String message = getMessage(msgID, valueString,
-                                            valueString.charAt(pos), pos);
-                invalidReason.append(message);
-                return false;
-              }
-              else
-              {
-                return hasValidOffset(valueString, pos+1, invalidReason);
-              }
-
-            default:
-              int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
-              String message = getMessage(msgID, valueString,
-                                          valueString.charAt(pos), pos);
-              invalidReason.append(message);
-              return false;
-          }
-
-          pos++;
-        }
-
-
-        // There must be at least two more characters and the first must be a
-        // digit.
-        if (length < 16)
-        {
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
-          String message = getMessage(msgID, valueString, s1, 12);
-          invalidReason.append(message);
-          return false;
-        }
-
-        break;
-
-      case 'Z':
-        // This is fine only if we are at the end of the value.
-        if (length == 15)
-        {
-          return true;
-        }
-        else
-        {
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
-          String message = getMessage(msgID, valueString,
-                                      valueString.charAt(14), 14);
-          invalidReason.append(message);
-          return false;
-        }
-
-      case '+':
-      case '-':
-        // These are fine only if there are exactly two or four more digits that
-        // specify a valid offset.
-        if ((length == 17) || (length == 19))
-        {
-          return hasValidOffset(valueString, 15, invalidReason);
-        }
-        else
-        {
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
-          String message = getMessage(msgID, valueString,
-                                      valueString.charAt(14), 14);
-          invalidReason.append(message);
-          return false;
-        }
-
-      default:
-        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
-        String message = getMessage(msgID, valueString, valueString.charAt(14),
-                                    14);
-        invalidReason.append(message);
-        return false;
-    }
-
-    return true;
-  }
-
-
-
-  /**
-   * Indicates whether the provided string contains a valid set of two or four
-   * UTC offset digits.  The provided string must have either two or four
-   * characters from the provided start position to the end of the value.
-   *
-   * @param  value          The whole value, including the offset.
-   * @param  startPos       The position of the first character that is
-   *                        contained in the offset.
-   * @param  invalidReason  The buffer to which the invalid reason may be
-   *                        appended if the string does not contain a valid set
-   *                        of UTC offset digits.
-   *
-   * @return  <CODE>true</CODE> if the provided offset string is valid, or
-   *          <CODE>false</CODE> if it is not.
-   */
-  private boolean hasValidOffset(String value, int startPos,
-                                 StringBuilder invalidReason)
-  {
-    int offsetLength = value.length() - startPos;
-    if (offsetLength < 2)
-    {
-      int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_TOO_SHORT;
-      String message = getMessage(msgID, value);
-      invalidReason.append(message);
-      return false;
-    }
-
-    // The first two characters must be an integer between 00 and 23.
-    switch (value.charAt(startPos))
-    {
-      case '0':
-      case '1':
-        switch (value.charAt(startPos+1))
-        {
-          case '0':
-          case '1':
-          case '2':
-          case '3':
-          case '4':
-          case '5':
-          case '6':
-          case '7':
-          case '8':
-          case '9':
-            // These are all fine.
-            break;
-          default:
-            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET;
-            String message = getMessage(msgID, value,
-                                        value.substring(startPos,
-                                                        startPos+offsetLength));
-            invalidReason.append(message);
-            return false;
-        }
-        break;
-      case '2':
-        switch (value.charAt(startPos+1))
-        {
-          case '0':
-          case '1':
-          case '2':
-          case '3':
-            // These are all fine.
-            break;
-          default:
-            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET;
-            String message = getMessage(msgID, value,
-                                        value.substring(startPos,
-                                                        startPos+offsetLength));
-            invalidReason.append(message);
-            return false;
-        }
-        break;
-      default:
-        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET;
-        String message = getMessage(msgID, value,
-                                    value.substring(startPos,
-                                                    startPos+offsetLength));
-        invalidReason.append(message);
-        return false;
-    }
-
-
-    // If there are two more characters, then they must be an integer between
-    // 00 and 59.
-    if (offsetLength == 4)
-    {
-      switch (value.charAt(startPos+2))
-      {
-        case '0':
-        case '1':
-        case '2':
-        case '3':
-        case '4':
-        case '5':
-          switch (value.charAt(startPos+3))
-          {
-            case '0':
-            case '1':
-            case '2':
-            case '3':
-            case '4':
-            case '5':
-            case '6':
-            case '7':
-            case '8':
-            case '9':
-              // These are all fine.
-              break;
-            default:
-              int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET;
-              String message =
-                   getMessage(msgID, value,value.substring(startPos,
-                                                      startPos+offsetLength));
-              invalidReason.append(message);
-              return false;
-          }
-          break;
-        default:
-          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET;
-          String message = getMessage(msgID, value,
-                                      value.substring(startPos,
-                                                      startPos+offsetLength));
-          invalidReason.append(message);
-          return false;
-      }
-    }
-
-    return true;
   }
 
 
@@ -1086,35 +387,1082 @@
    * Decodes the provided normalized value as a generalized time value and
    * retrieves a timestamp containing its representation.
    *
-   * @param  normalizedValue  The normalized generalized time value to decode to
-   *                          a Java <CODE>Date</CODE>.
+   * @param  value  The normalized value to decode using the generalized time
+   *                syntax.
    *
    * @return  The timestamp created from the provided generalized time value.
    *
    * @throws  DirectoryException  If the provided value cannot be parsed as a
    *                              valid generalized time string.
    */
-  public static long decodeGeneralizedTimeValue(ByteString normalizedValue)
+  public static long decodeGeneralizedTimeValue(ByteString value)
          throws DirectoryException
   {
-    String valueString = normalizedValue.stringValue();
+    int year        = 0;
+    int month       = 0;
+    int day         = 0;
+    int hour        = 0;
+    int minute      = 0;
+    int second      = 0;
+
+
+    // Get the value as a string and verify that it is at least long enough for
+    // "YYYYMMDDhhZ", which is the shortest allowed value.
+    String valueString = value.stringValue().toUpperCase();
+    int    length      = valueString.length();
+    if (length < 11)
+    {
+      int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_TOO_SHORT;
+      String message = getMessage(msgID, valueString);
+      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
+                                   msgID);
+    }
+
+
+    // The first four characters are the century and year, and they must be
+    // numeric digits between 0 and 9.
+    for (int i=0; i < 4; i++)
+    {
+      switch (valueString.charAt(i))
+      {
+        case '0':
+          year = (year * 10);
+          break;
+
+        case '1':
+          year = (year * 10) + 1;
+          break;
+
+        case '2':
+          year = (year * 10) + 2;
+          break;
+
+        case '3':
+          year = (year * 10) + 3;
+          break;
+
+        case '4':
+          year = (year * 10) + 4;
+          break;
+
+        case '5':
+          year = (year * 10) + 5;
+          break;
+
+        case '6':
+          year = (year * 10) + 6;
+          break;
+
+        case '7':
+          year = (year * 10) + 7;
+          break;
+
+        case '8':
+          year = (year * 10) + 8;
+          break;
+
+        case '9':
+          year = (year * 10) + 9;
+          break;
+
+        default:
+          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_YEAR;
+          String message = getMessage(msgID, valueString,
+                                      valueString.charAt(i));
+          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                       message, msgID);
+      }
+    }
+
+
+    // The next two characters are the month, and they must form the string
+    // representation of an integer between 01 and 12.
+    char m1 = valueString.charAt(4);
+    char m2 = valueString.charAt(5);
+    switch (m1)
+    {
+      case '0':
+        // m2 must be a digit between 1 and 9.
+        switch (m2)
+        {
+          case '1':
+            month = Calendar.JANUARY;
+            break;
+
+          case '2':
+            month = Calendar.FEBRUARY;
+            break;
+
+          case '3':
+            month = Calendar.MARCH;
+            break;
+
+          case '4':
+            month = Calendar.APRIL;
+            break;
+
+          case '5':
+            month = Calendar.MAY;
+            break;
+
+          case '6':
+            month = Calendar.JUNE;
+            break;
+
+          case '7':
+            month = Calendar.JULY;
+            break;
+
+          case '8':
+            month = Calendar.AUGUST;
+            break;
+
+          case '9':
+            month = Calendar.SEPTEMBER;
+            break;
+
+          default:
+            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MONTH;
+            String message = getMessage(msgID, valueString,
+                                        valueString.substring(4, 6));
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                         message, msgID);
+        }
+        break;
+      case '1':
+        // m2 must be a digit between 0 and 2.
+        switch (m2)
+        {
+          case '0':
+            month = Calendar.OCTOBER;
+            break;
+
+          case '1':
+            month = Calendar.NOVEMBER;
+            break;
+
+          case '2':
+            month = Calendar.DECEMBER;
+            break;
+
+          default:
+            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MONTH;
+            String message = getMessage(msgID, valueString,
+                                        valueString.substring(4, 6));
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                         message, msgID);
+        }
+        break;
+      default:
+        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MONTH;
+        String message = getMessage(msgID, valueString,
+                                    valueString.substring(4, 6));
+        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                     message, msgID);
+    }
+
+
+    // The next two characters should be the day of the month, and they must
+    // form the string representation of an integer between 01 and 31.
+    // This doesn't do any validation against the year or month, so it will
+    // allow dates like April 31, or February 29 in a non-leap year, but we'll
+    // let those slide.
+    char d1 = valueString.charAt(6);
+    char d2 = valueString.charAt(7);
+    switch (d1)
+    {
+      case '0':
+        // d2 must be a digit between 1 and 9.
+        switch (d2)
+        {
+          case '1':
+            day = 1;
+            break;
+
+          case '2':
+            day = 2;
+            break;
+
+          case '3':
+            day = 3;
+            break;
+
+          case '4':
+            day = 4;
+            break;
+
+          case '5':
+            day = 5;
+            break;
+
+          case '6':
+            day = 6;
+            break;
+
+          case '7':
+            day = 7;
+            break;
+
+          case '8':
+            day = 8;
+            break;
+
+          case '9':
+            day = 9;
+            break;
+
+          default:
+            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY;
+            String message = getMessage(msgID, valueString,
+                                        valueString.substring(6, 8));
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                         message, msgID);
+        }
+        break;
+
+      case '1':
+        // d2 must be a digit between 0 and 9.
+        switch (d2)
+        {
+          case '0':
+            day = 10;
+            break;
+
+          case '1':
+            day = 11;
+            break;
+
+          case '2':
+            day = 12;
+            break;
+
+          case '3':
+            day = 13;
+            break;
+
+          case '4':
+            day = 14;
+            break;
+
+          case '5':
+            day = 15;
+            break;
+
+          case '6':
+            day = 16;
+            break;
+
+          case '7':
+            day = 17;
+            break;
+
+          case '8':
+            day = 18;
+            break;
+
+          case '9':
+            day = 19;
+            break;
+
+          default:
+            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY;
+            String message = getMessage(msgID, valueString,
+                                        valueString.substring(6, 8));
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                         message, msgID);
+        }
+        break;
+
+      case '2':
+        // d2 must be a digit between 0 and 9.
+        switch (d2)
+        {
+          case '0':
+            day = 20;
+            break;
+
+          case '1':
+            day = 21;
+            break;
+
+          case '2':
+            day = 22;
+            break;
+
+          case '3':
+            day = 23;
+            break;
+
+          case '4':
+            day = 24;
+            break;
+
+          case '5':
+            day = 25;
+            break;
+
+          case '6':
+            day = 26;
+            break;
+
+          case '7':
+            day = 27;
+            break;
+
+          case '8':
+            day = 28;
+            break;
+
+          case '9':
+            day = 29;
+            break;
+
+          default:
+            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY;
+            String message = getMessage(msgID, valueString,
+                                        valueString.substring(6, 8));
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                         message, msgID);
+        }
+        break;
+
+      case '3':
+        // d2 must be either 0 or 1.
+        switch (d2)
+        {
+          case '0':
+            day = 30;
+            break;
+
+          case '1':
+            day = 31;
+            break;
+
+          default:
+            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY;
+            String message = getMessage(msgID, valueString,
+                                        valueString.substring(6, 8));
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                         message, msgID);
+        }
+        break;
+
+      default:
+        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY;
+        String message = getMessage(msgID, valueString,
+                                    valueString.substring(6, 8));
+        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                     message, msgID);
+    }
+
+
+    // The next two characters must be the hour, and they must form the string
+    // representation of an integer between 00 and 23.
+    char h1 = valueString.charAt(8);
+    char h2 = valueString.charAt(9);
+    switch (h1)
+    {
+      case '0':
+        switch (h2)
+        {
+          case '0':
+            hour = 0;
+            break;
+
+          case '1':
+            hour = 1;
+            break;
+
+          case '2':
+            hour = 2;
+            break;
+
+          case '3':
+            hour = 3;
+            break;
+
+          case '4':
+            hour = 4;
+            break;
+
+          case '5':
+            hour = 5;
+            break;
+
+          case '6':
+            hour = 6;
+            break;
+
+          case '7':
+            hour = 7;
+            break;
+
+          case '8':
+            hour = 8;
+            break;
+
+          case '9':
+            hour = 9;
+            break;
+
+          default:
+            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR;
+            String message = getMessage(msgID, valueString,
+                                        valueString.substring(8, 10));
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                         message, msgID);
+        }
+        break;
+
+      case '1':
+        switch (h2)
+        {
+          case '0':
+            hour = 10;
+            break;
+
+          case '1':
+            hour = 11;
+            break;
+
+          case '2':
+            hour = 12;
+            break;
+
+          case '3':
+            hour = 13;
+            break;
+
+          case '4':
+            hour = 14;
+            break;
+
+          case '5':
+            hour = 15;
+            break;
+
+          case '6':
+            hour = 16;
+            break;
+
+          case '7':
+            hour = 17;
+            break;
+
+          case '8':
+            hour = 18;
+            break;
+
+          case '9':
+            hour = 19;
+            break;
+
+          default:
+            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR;
+            String message = getMessage(msgID, valueString,
+                                        valueString.substring(8, 10));
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                         message, msgID);
+        }
+        break;
+
+      case '2':
+        switch (h2)
+        {
+          case '0':
+            hour = 20;
+            break;
+
+          case '1':
+            hour = 21;
+            break;
+
+          case '2':
+            hour = 22;
+            break;
+
+          case '3':
+            hour = 23;
+            break;
+
+          default:
+            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR;
+            String message = getMessage(msgID, valueString,
+                                        valueString.substring(8, 10));
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                         message, msgID);
+        }
+        break;
+
+      default:
+        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR;
+        String message = getMessage(msgID, valueString,
+                                    valueString.substring(8, 10));
+        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                     message, msgID);
+    }
+
+
+    // Next, there should be either two digits comprising an integer between 00
+    // and 59 (for the minute), a letter 'Z' (for the UTC specifier), a plus
+    // or minus sign followed by two or four digits (for the UTC offset), or a
+    // period or comma representing the fraction.
+    m1 = valueString.charAt(10);
+    switch (m1)
+    {
+      case '0':
+      case '1':
+      case '2':
+      case '3':
+      case '4':
+      case '5':
+        // There must be at least two more characters, and the next one must
+        // be a digit between 0 and 9.
+        if (length < 13)
+        {
+          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
+          String message = getMessage(msgID, valueString, m1, 10);
+          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                       message, msgID);
+        }
+
+
+        minute = 10 * (m1 - '0');
+
+        switch (valueString.charAt(11))
+        {
+          case '0':
+            break;
+
+          case '1':
+            minute += 1;
+            break;
+
+          case '2':
+            minute += 2;
+            break;
+
+          case '3':
+            minute += 3;
+            break;
+
+          case '4':
+            minute += 4;
+            break;
+
+          case '5':
+            minute += 5;
+            break;
+
+          case '6':
+            minute += 6;
+            break;
+
+          case '7':
+            minute += 7;
+            break;
+
+          case '8':
+            minute += 8;
+            break;
+
+          case '9':
+            minute += 9;
+            break;
+
+          default:
+            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MINUTE;
+            String message = getMessage(msgID, valueString,
+                                        valueString.substring(10, 12));
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                         message, msgID);
+        }
+
+        break;
+
+      case 'Z':
+        // This is fine only if we are at the end of the value.
+        if (length == 11)
+        {
+          try
+          {
+            GregorianCalendar calendar = new GregorianCalendar();
+            calendar.setLenient(false);
+            calendar.setTimeZone(TimeZone.getTimeZone(TIME_ZONE_UTC));
+            calendar.set(year, month, day, hour, minute, second);
+            calendar.set(Calendar.MILLISECOND, 0);
+            return calendar.getTimeInMillis();
+          }
+          catch (Exception e)
+          {
+            if (debugEnabled())
+            {
+              debugCaught(DebugLogLevel.ERROR, e);
+            }
+
+            // This should only happen if the provided date wasn't legal
+            // (e.g., September 31).
+            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME;
+            String message = getMessage(msgID, valueString, String.valueOf(e));
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                         message, msgID, e);
+          }
+        }
+        else
+        {
+          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
+          String message = getMessage(msgID, valueString, m1, 10);
+          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                       message, msgID);
+        }
+
+      case '+':
+      case '-':
+        // These are fine only if there are exactly two or four more digits that
+        // specify a valid offset.
+        if ((length == 13) || (length == 15))
+        {
+          try
+          {
+            GregorianCalendar calendar = new GregorianCalendar();
+            calendar.setLenient(false);
+            calendar.setTimeZone(getTimeZoneForOffset(valueString, 10));
+            calendar.set(year, month, day, hour, minute, second);
+            calendar.set(Calendar.MILLISECOND, 0);
+            return calendar.getTimeInMillis();
+          }
+          catch (Exception e)
+          {
+            if (debugEnabled())
+            {
+              debugCaught(DebugLogLevel.ERROR, e);
+            }
+
+            // This should only happen if the provided date wasn't legal
+            // (e.g., September 31).
+            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME;
+            String message = getMessage(msgID, valueString, String.valueOf(e));
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                         message, msgID, e);
+          }
+        }
+        else
+        {
+          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
+          String message = getMessage(msgID, valueString, m1, 10);
+          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                       message, msgID);
+        }
+
+      case '.':
+      case ',':
+        return finishDecodingFraction(valueString, 11, year, month, day, hour,
+                                      minute, second, 3600000);
+
+      default:
+        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
+        String message = getMessage(msgID, valueString, m1, 10);
+        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                     message, msgID);
+    }
+
+
+    // Next, there should be either two digits comprising an integer between 00
+    // and 60 (for the second, including a possible leap second), a letter 'Z'
+    // (for the UTC specifier), a plus or minus sign followed by two or four
+    // digits (for the UTC offset), or a period or comma to start the fraction.
+    char s1 = valueString.charAt(12);
+    switch (s1)
+    {
+      case '0':
+      case '1':
+      case '2':
+      case '3':
+      case '4':
+      case '5':
+        // There must be at least two more characters, and the next one must
+        // be a digit between 0 and 9.
+        if (length < 15)
+        {
+          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
+          String message = getMessage(msgID, valueString, s1, 12);
+          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                       message, msgID);
+        }
+
+
+        second = 10 * (s1 - '0');
+
+        switch (valueString.charAt(13))
+        {
+          case '0':
+            break;
+
+          case '1':
+            second += 1;
+            break;
+
+          case '2':
+            second += 2;
+            break;
+
+          case '3':
+            second += 3;
+            break;
+
+          case '4':
+            second += 4;
+            break;
+
+          case '5':
+            second += 5;
+            break;
+
+          case '6':
+            second += 6;
+            break;
+
+          case '7':
+            second += 7;
+            break;
+
+          case '8':
+            second += 8;
+            break;
+
+          case '9':
+            second += 9;
+            break;
+
+          default:
+            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MINUTE;
+            String message = getMessage(msgID, valueString,
+                                        valueString.substring(12, 14));
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                         message, msgID);
+        }
+
+        break;
+
+      case '6':
+        // There must be at least two more characters and the next one must be
+        // a 0.
+        if (length < 15)
+        {
+          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
+          String message = getMessage(msgID, valueString, s1, 12);
+          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                       message, msgID);
+        }
+
+        if (valueString.charAt(13) != '0')
+        {
+          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_SECOND;
+          String message = getMessage(msgID, valueString,
+                                      valueString.substring(12, 14));
+          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                       message, msgID);
+        }
+
+        second = 60;
+        break;
+
+      case 'Z':
+        // This is fine only if we are at the end of the value.
+        if (length == 13)
+        {
+          try
+          {
+            GregorianCalendar calendar = new GregorianCalendar();
+            calendar.setLenient(false);
+            calendar.setTimeZone(TimeZone.getTimeZone(TIME_ZONE_UTC));
+            calendar.set(year, month, day, hour, minute, second);
+            calendar.set(Calendar.MILLISECOND, 0);
+            return calendar.getTimeInMillis();
+          }
+          catch (Exception e)
+          {
+            if (debugEnabled())
+            {
+              debugCaught(DebugLogLevel.ERROR, e);
+            }
+
+            // This should only happen if the provided date wasn't legal
+            // (e.g., September 31).
+            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME;
+            String message = getMessage(msgID, valueString, String.valueOf(e));
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                         message, msgID, e);
+          }
+        }
+        else
+        {
+          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
+          String message = getMessage(msgID, valueString, s1, 12);
+          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                       message, msgID);
+        }
+
+      case '+':
+      case '-':
+        // These are fine only if there are exactly two or four more digits that
+        // specify a valid offset.
+        if ((length == 15) || (length == 17))
+        {
+          try
+          {
+            GregorianCalendar calendar = new GregorianCalendar();
+            calendar.setLenient(false);
+            calendar.setTimeZone(getTimeZoneForOffset(valueString, 12));
+            calendar.set(year, month, day, hour, minute, second);
+            calendar.set(Calendar.MILLISECOND, 0);
+            return calendar.getTimeInMillis();
+          }
+          catch (Exception e)
+          {
+            if (debugEnabled())
+            {
+              debugCaught(DebugLogLevel.ERROR, e);
+            }
+
+            // This should only happen if the provided date wasn't legal
+            // (e.g., September 31).
+            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME;
+            String message = getMessage(msgID, valueString, String.valueOf(e));
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                         message, msgID, e);
+          }
+        }
+        else
+        {
+          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
+          String message = getMessage(msgID, valueString, s1, 12);
+          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                       message, msgID);
+        }
+
+      case '.':
+      case ',':
+        return finishDecodingFraction(valueString, 13, year, month, day, hour,
+                                      minute, second, 60000);
+
+      default:
+        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
+        String message = getMessage(msgID, valueString, s1, 12);
+        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                     message, msgID);
+    }
+
+
+    // Next, there should be either a period or comma followed by between one
+    // and three digits (to specify the sub-second), a letter 'Z' (for the UTC
+    // specifier), or a plus or minus sign followed by two our four digits (for
+    // the UTC offset).
+    switch (valueString.charAt(14))
+    {
+      case '.':
+      case ',':
+        return finishDecodingFraction(valueString, 15, year, month, day, hour,
+                                      minute, second, 1000);
+
+      case 'Z':
+        // This is fine only if we are at the end of the value.
+        if (length == 15)
+        {
+          try
+          {
+            GregorianCalendar calendar = new GregorianCalendar();
+            calendar.setLenient(false);
+            calendar.setTimeZone(TimeZone.getTimeZone(TIME_ZONE_UTC));
+            calendar.set(year, month, day, hour, minute, second);
+            calendar.set(Calendar.MILLISECOND, 0);
+            return calendar.getTimeInMillis();
+          }
+          catch (Exception e)
+          {
+            if (debugEnabled())
+            {
+              debugCaught(DebugLogLevel.ERROR, e);
+            }
+
+            // This should only happen if the provided date wasn't legal
+            // (e.g., September 31).
+            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME;
+            String message = getMessage(msgID, valueString, String.valueOf(e));
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                         message, msgID, e);
+          }
+        }
+        else
+        {
+          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
+          String message = getMessage(msgID, valueString,
+                                      valueString.charAt(14), 14);
+          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                       message, msgID);
+        }
+
+      case '+':
+      case '-':
+        // These are fine only if there are exactly two or four more digits that
+        // specify a valid offset.
+        if ((length == 17) || (length == 19))
+        {
+          try
+          {
+            GregorianCalendar calendar = new GregorianCalendar();
+            calendar.setLenient(false);
+            calendar.setTimeZone(getTimeZoneForOffset(valueString, 14));
+            calendar.set(year, month, day, hour, minute, second);
+            calendar.set(Calendar.MILLISECOND, 0);
+            return calendar.getTimeInMillis();
+          }
+          catch (Exception e)
+          {
+            if (debugEnabled())
+            {
+              debugCaught(DebugLogLevel.ERROR, e);
+            }
+
+            // This should only happen if the provided date wasn't legal
+            // (e.g., September 31).
+            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME;
+            String message = getMessage(msgID, valueString, String.valueOf(e));
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                         message, msgID, e);
+          }
+        }
+        else
+        {
+          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
+          String message = getMessage(msgID, valueString,
+                                      valueString.charAt(14), 14);
+          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                       message, msgID);
+        }
+
+      default:
+        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
+        String message = getMessage(msgID, valueString, valueString.charAt(14),
+                                    14);
+        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                     message, msgID);
+    }
+  }
+
+
+
+  /**
+   * Completes decoding the generalized time value containing a fractional
+   * component.  It will also decode the trailing 'Z' or offset.
+   *
+   * @param  value       The whole value, including the fractional component and
+   *                     time zone information.
+   * @param  startPos    The position of the first character after the period
+   *                     in the value string.
+   * @param  year        The year decoded from the provided value.
+   * @param  month       The month decoded from the provided value.
+   * @param  day         The day decoded from the provided value.
+   * @param  hour        The hour decoded from the provided value.
+   * @param  minute      The minute decoded from the provided value.
+   * @param  second      The second decoded from the provided value.
+   * @param  multiplier  The multiplier value that should be used to scale the
+   *                     fraction appropriately.  If it's a fraction of an hour,
+   *                     then it should be 3600000 (60*60*1000).  If it's a
+   *                     fraction of a minute, then it should be 60000.  If it's
+   *                     a fraction of a second, then it should be 1000.
+   *
+   * @return  The timestamp created from the provided generalized time value
+   *          including the fractional element.
+   *
+   * @throws  DirectoryException  If the provided value cannot be parsed as a
+   *                              valid generalized time string.
+   */
+  private static long finishDecodingFraction(String value, int startPos,
+                                             int year, int month, int day,
+                                             int hour, int minute, int second,
+                                             int multiplier)
+          throws DirectoryException
+  {
+    int length = value.length();
+    StringBuilder fractionBuffer = new StringBuilder(2 + length - startPos);
+    fractionBuffer.append("0.");
+
+    TimeZone timeZone = null;
+
+outerLoop:
+    for (int i=startPos; i < length; i++)
+    {
+      char c = value.charAt(i);
+      switch (c)
+      {
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+          fractionBuffer.append(c);
+          break;
+
+        case 'Z':
+          // This is only acceptable if we're at the end of the value.
+          if (i != (value.length() - 1))
+          {
+            int msgID =
+                 MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_FRACTION_CHAR;
+            String message = getMessage(msgID, value, String.valueOf(c));
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                         message, msgID);
+          }
+
+          timeZone = TimeZone.getTimeZone(TIME_ZONE_UTC);
+          break outerLoop;
+
+        case '+':
+        case '-':
+          timeZone = getTimeZoneForOffset(value, i);
+          break outerLoop;
+
+        default:
+          int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_FRACTION_CHAR;
+          String message = getMessage(msgID, value, String.valueOf(c));
+          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                       message, msgID);
+      }
+    }
+
+    if (fractionBuffer.length() == 2)
+    {
+      int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_EMPTY_FRACTION;
+      String message = getMessage(msgID, value);
+      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
+                                   msgID);
+    }
+
+    if (timeZone == null)
+    {
+      int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_NO_TIME_ZONE_INFO;
+      String message = getMessage(msgID, value);
+      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
+                                   msgID);
+    }
+
+    Double fractionValue = Double.parseDouble(fractionBuffer.toString());
+    long additionalMilliseconds = Math.round(fractionValue * multiplier);
+
     try
     {
-      dateFormatLock.lock();
-
-      try
-      {
-        return dateFormat.parse(valueString).getTime();
-      }
-      catch (Exception e)
-      {
-        // We'll let this one be handled by the outer try/catch block.
-        throw e;
-      }
-      finally
-      {
-        dateFormatLock.unlock();
-      }
+      GregorianCalendar calendar = new GregorianCalendar();
+      calendar.setLenient(false);
+      calendar.setTimeZone(timeZone);
+      calendar.set(year, month, day, hour, minute, second);
+      calendar.set(Calendar.MILLISECOND, 0);
+      return calendar.getTimeInMillis() + additionalMilliseconds;
     }
     catch (Exception e)
     {
@@ -1123,12 +1471,160 @@
         debugCaught(DebugLogLevel.ERROR, e);
       }
 
-      int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_CANNOT_PARSE;
-      String message = getMessage(msgID, valueString, String.valueOf(e));
-
+      // This should only happen if the provided date wasn't legal
+      // (e.g., September 31).
+      int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME;
+      String message = getMessage(msgID, value, String.valueOf(e));
       throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
                                    message, msgID, e);
     }
   }
+
+
+
+  /**
+   * Decodes a time zone offset from the provided value.
+   *
+   * @param  value          The whole value, including the offset.
+   * @param  startPos       The position of the first character that is
+   *                        contained in the offset.  This should be the
+   *                        position of the plus or minus character.
+   *
+   * @return  The {@code TimeZone} object representing the decoded time zone.
+   *
+   * @throws  DirectoryException  If the provided value does not contain a valid
+   *                              offset.
+   */
+  private static TimeZone getTimeZoneForOffset(String value, int startPos)
+          throws DirectoryException
+  {
+    String offSetStr = value.substring(startPos);
+    if ((offSetStr.length() != 3) && (offSetStr.length() != 5))
+    {
+      int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET;
+      String message = getMessage(msgID, value, offSetStr);
+      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
+                                   msgID);
+    }
+
+
+    // The first character must be either a plus or minus.
+    switch (offSetStr.charAt(0))
+    {
+      case '+':
+      case '-':
+        // These are OK.
+        break;
+
+      default:
+        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET;
+        String message = getMessage(msgID, value, offSetStr);
+        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                     message, msgID);
+    }
+
+
+    // The first two characters must be an integer between 00 and 23.
+    switch (offSetStr.charAt(1))
+    {
+      case '0':
+      case '1':
+        switch (offSetStr.charAt(2))
+        {
+          case '0':
+          case '1':
+          case '2':
+          case '3':
+          case '4':
+          case '5':
+          case '6':
+          case '7':
+          case '8':
+          case '9':
+            // These are all fine.
+            break;
+
+          default:
+            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET;
+            String message = getMessage(msgID, value, offSetStr);
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                         message, msgID);
+        }
+        break;
+
+      case '2':
+        switch (offSetStr.charAt(2))
+        {
+          case '0':
+          case '1':
+          case '2':
+          case '3':
+            // These are all fine.
+            break;
+
+          default:
+            int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET;
+            String message = getMessage(msgID, value, offSetStr);
+            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                         message, msgID);
+        }
+        break;
+
+      default:
+        int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET;
+        String message = getMessage(msgID, value, offSetStr);
+        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                     message, msgID);
+    }
+
+
+    // If there are two more characters, then they must be an integer between
+    // 00 and 59.
+    if (offSetStr.length() == 5)
+    {
+      switch (offSetStr.charAt(3))
+      {
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+          switch (offSetStr.charAt(4))
+          {
+            case '0':
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+              // These are all fine.
+              break;
+
+            default:
+              int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET;
+              String message = getMessage(msgID, value, offSetStr);
+              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                           message, msgID);
+          }
+          break;
+
+        default:
+          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET;
+          String message = getMessage(msgID, value, offSetStr);
+          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                       message, msgID);
+      }
+    }
+
+
+    // If we've gotten here, then it looks like a valid offset.  We can create a
+    // time zone by using "GMT" followed by the offset.
+    return TimeZone.getTimeZone("GMT" + offSetStr);
+  }
 }
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/GeneralizedTimeEqualityMatchingRuleTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/GeneralizedTimeEqualityMatchingRuleTest.java
index 201f56f..7cd4e24 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/GeneralizedTimeEqualityMatchingRuleTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/GeneralizedTimeEqualityMatchingRuleTest.java
@@ -52,6 +52,15 @@
         {"2006090613mmZ"},
         {"20060906135030.011"},
         {"20060906135030Zx"},
+        {"20060906135030.Z"},
+        {"20060906135030.aZ"},
+        {"20060906135030"},
+        {"20060906135030.123"},
+        {"20060906135030-2500"},
+        {"20060906135030-2070"},
+        {"20060931135030Z"},
+        {"20060229135030Z"},
+        {"20060230135030Z"},
     };
   }
 
@@ -63,16 +72,20 @@
   public Object[][] createEqualityMatchingRuleTest()
   {
     return new Object [][] {
-        {"2006090613Z",          "20060906130000.000Z", true},
-        {"200609061350Z",        "20060906135000.000Z", true},
-        {"200609061351Z",        "20060906135000.000Z", false},
-        {"20060906135030Z",      "20060906135030.000Z", true},
-        {"20060906135030.3Z",    "20060906135030.300Z", true},
-        {"20060906135030.30Z",   "20060906135030.300Z", true},
-        {"20060906135030.Z",     "20060906135030.000Z", true},
-        {"20060906135030.0118Z", "20060906135030.012Z", true},
-        {"20060906135030+01",    "20060906125030.000Z", true},
-        {"20060906135030+0101",  "20060906124930.000Z", true},
+        {"2006090613Z",             "20060906130000.000Z", true},
+        {"200609061350Z",           "20060906135000.000Z", true},
+        {"200609061351Z",           "20060906135000.000Z", false},
+        {"20060906135030Z",         "20060906135030.000Z", true},
+        {"20060906135030.3Z",       "20060906135030.300Z", true},
+        {"20060906135030.30Z",      "20060906135030.300Z", true},
+        {"20060906135030Z",         "20060906135030.000Z", true},
+        {"20060906135030.0Z",       "20060906135030.000Z", true},
+        {"20060906135030.0118Z",    "20060906135030.012Z", true},
+        {"20060906135030+01",       "20060906125030.000Z", true},
+        {"20060906135030+0101",     "20060906124930.000Z", true},
+        {"20070417055812.318-0500", "20070417105812.318Z", true},
+        {"2007041705.5Z",           "20070417053000.000Z", true},
+        {"200704170558.5Z",         "20070417055830.000Z", true},
     };
   }
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/GeneralizedTimeOrderingMatchingRuleTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/GeneralizedTimeOrderingMatchingRuleTest.java
index e99d7e4..b66674f 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/GeneralizedTimeOrderingMatchingRuleTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/GeneralizedTimeOrderingMatchingRuleTest.java
@@ -56,11 +56,9 @@
         {"20060912180129.1hZ"},
         {"20060906135030+aa01"},
         {"2006"},
-        /* disabled because these tests are failing
-         * see issue 675
-         * {"20060906135030+3359"},
-         * {"20060906135030+2389"},
-         * {"20060906135030+2361"},*/
+        {"20060906135030+3359"},
+        {"20060906135030+2389"},
+        {"20060906135030+2361"},
         {"20060906135030+"},
         {"20060906135030+0"},
         {"20060906135030+010"},

--
Gitblit v1.10.0