mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

neil_a_wilson
19.54.2007 c5ff2497147025d89987b2554267c9ee6b449467
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.

OpenDS Issue Numbers: 675, 1520, 1521, 1522
6 files modified
3425 ■■■■■ changed files
opends/src/server/org/opends/server/messages/SchemaMessages.java 58 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/schema/GeneralizedTimeEqualityMatchingRule.java 679 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/schema/GeneralizedTimeOrderingMatchingRule.java 719 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/schema/GeneralizedTimeSyntax.java 1946 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/schema/GeneralizedTimeEqualityMatchingRuleTest.java 15 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/schema/GeneralizedTimeOrderingMatchingRuleTest.java 8 ●●●●● patch | view | raw | blame | history
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,
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)
    try
    {
      int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_TOO_SHORT;
      String message = getMessage(msgID, valueString);
      long timestamp = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(value);
      return new ASN1OctetString(GeneralizedTimeSyntax.format(timestamp));
    }
    catch (DirectoryException de)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, de);
      }
      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 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));
    }
    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);
      }
    }
    // 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;
    }
  }
}
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)
    try
    {
      int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_TOO_SHORT;
      String message = getMessage(msgID, valueString);
      long timestamp = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(value);
      return new ASN1OctetString(GeneralizedTimeSyntax.format(timestamp));
    }
    catch (DirectoryException de)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, de);
      }
      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 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));
    }
    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);
      }
    }
    // 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));
  }
}
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);
      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)
        {
      decodeGeneralizedTimeValue(value);
          return true;
        }
        else
    catch (DirectoryException de)
        {
          int    msgID   = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR;
          String message = getMessage(msgID, valueString, m1, 10);
          invalidReason.append(message);
      invalidReason.append(de.getErrorMessage());
          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,611 @@
   * 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();
    try
    {
      dateFormatLock.lock();
    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
      {
        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.getTimeZone(TIME_ZONE_UTC));
            calendar.set(year, month, day, hour, minute, second);
            calendar.set(Calendar.MILLISECOND, 0);
            return calendar.getTimeInMillis();
    }
    catch (Exception e)
    {
@@ -1123,12 +1000,631 @@
        debugCaught(DebugLogLevel.ERROR, e);
      }
      int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_CANNOT_PARSE;
            // 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
    {
      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)
    {
      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, 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);
  }
}
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"},
    };
  }
@@ -69,10 +78,14 @@
        {"20060906135030Z",      "20060906135030.000Z", true},
        {"20060906135030.3Z",    "20060906135030.300Z", true},
        {"20060906135030.30Z",   "20060906135030.300Z", true},
        {"20060906135030.Z",     "20060906135030.000Z", 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},
    };
  }
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"},