OPENDJ-2397: fix sort order of generalized time values older than 1970
Force normalized ms time values to always be non-negative for valid
generalized time values. Beforehand any time values older than the epoch
(1970) would have a negative time in ms breaking the natural byte sort
order since the twos complement encoding of a negative number sorts
after the encoding of a positive number.
| | |
| | | * CDDL HEADER END |
| | | * |
| | | * |
| | | * Copyright 2012-2013 ForgeRock AS. |
| | | * Copyright 2012-2015 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.ldap; |
| | | |
| | |
| | | /** UTC TimeZone is assumed to never change over JVM lifetime. */ |
| | | private static final TimeZone TIME_ZONE_UTC_OBJ = TimeZone.getTimeZone("UTC"); |
| | | |
| | | /** The smallest time representable using the generalized time syntax. */ |
| | | public static final GeneralizedTime MIN_GENERALIZED_TIME = valueOf("00010101000000Z"); |
| | | |
| | | /** The smallest time in milli-seconds representable using the generalized time syntax. */ |
| | | public static final long MIN_GENERALIZED_TIME_MS = MIN_GENERALIZED_TIME.getTimeInMillis(); |
| | | |
| | | /** |
| | | * Returns a generalized time whose value is the current time, using the |
| | | * default time zone and locale. |
| | |
| | | */ |
| | | public static GeneralizedTime valueOf(final Calendar calendar) { |
| | | Reject.ifNull(calendar); |
| | | return new GeneralizedTime((Calendar) calendar.clone(), null, -1L, null); |
| | | return new GeneralizedTime((Calendar) calendar.clone(), null, Long.MIN_VALUE, null); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | public static GeneralizedTime valueOf(final Date date) { |
| | | Reject.ifNull(date); |
| | | return new GeneralizedTime(null, (Date) date.clone(), -1L, null); |
| | | return new GeneralizedTime(null, (Date) date.clone(), Long.MIN_VALUE, null); |
| | | } |
| | | |
| | | /** |
| | |
| | | * since the epoch. |
| | | */ |
| | | public static GeneralizedTime valueOf(final long timeMS) { |
| | | Reject.ifFalse(timeMS >= 0, "timeMS must be >= 0"); |
| | | Reject.ifTrue(timeMS < MIN_GENERALIZED_TIME_MS, "timeMS is too old to represent as a generalized time"); |
| | | return new GeneralizedTime(null, null, timeMS, null); |
| | | } |
| | | |
| | |
| | | calendar.setTimeZone(tz); |
| | | calendar.set(year, month, day, hour, minute, second); |
| | | calendar.set(Calendar.MILLISECOND, 0); |
| | | return new GeneralizedTime(calendar, null, -1L, value); |
| | | return new GeneralizedTime(calendar, null, Long.MIN_VALUE, value); |
| | | } catch (final Exception e) { |
| | | // This should only happen if the provided date wasn't legal |
| | | // (e.g., September 31). |
| | |
| | | calendar.setTimeZone(timeZone); |
| | | calendar.set(year, month, day, hour, minute, second); |
| | | calendar.set(Calendar.MILLISECOND, additionalMilliseconds); |
| | | return new GeneralizedTime(calendar, null, -1L, value); |
| | | return new GeneralizedTime(calendar, null, Long.MIN_VALUE, value); |
| | | } catch (final Exception e) { |
| | | // This should only happen if the provided date wasn't legal |
| | | // (e.g., September 31). |
| | |
| | | */ |
| | | public long getTimeInMillis() { |
| | | long tmpTimeMS = timeMS; |
| | | if (tmpTimeMS == -1) { |
| | | if (tmpTimeMS == Long.MIN_VALUE) { |
| | | if (date != null) { |
| | | tmpTimeMS = date.getTime(); |
| | | } else { |
| | |
| | | super(EMR_GENERALIZED_TIME_NAME); |
| | | } |
| | | |
| | | public ByteString normalizeAttributeValue(final Schema schema, final ByteSequence value) |
| | | throws DecodeException { |
| | | public ByteString normalizeAttributeValue(final Schema schema, final ByteSequence value) throws DecodeException { |
| | | return normalizeAttributeValue(value); |
| | | } |
| | | |
| | | static ByteString normalizeAttributeValue(final ByteSequence value) throws DecodeException { |
| | | try { |
| | | return ByteString.valueOfLong(GeneralizedTime.valueOf(value.toString()).getTimeInMillis()); |
| | | final GeneralizedTime time = GeneralizedTime.valueOf(value.toString()); |
| | | return createNormalizedAttributeValue(time.getTimeInMillis()); |
| | | } catch (LocalizedIllegalArgumentException e) { |
| | | throw DecodeException.error(e.getMessageObject()); |
| | | } |
| | | } |
| | | |
| | | static ByteString createNormalizedAttributeValue(final long timeInMillis) { |
| | | /* Dates older than 1970 will be negative and will sort after dates more recent than 1970 due to twos |
| | | * complement encoding. Therefore mangle the time in order to ensure that it is positive for all valid values |
| | | * of a generalized time. |
| | | */ |
| | | return ByteString.valueOfLong(timeInMillis - GeneralizedTime.MIN_GENERALIZED_TIME_MS); |
| | | } |
| | | } |
| | |
| | | |
| | | import static org.forgerock.opendj.ldap.schema.SchemaConstants.*; |
| | | |
| | | import org.forgerock.i18n.LocalizedIllegalArgumentException; |
| | | import org.forgerock.opendj.ldap.ByteSequence; |
| | | import org.forgerock.opendj.ldap.ByteString; |
| | | import org.forgerock.opendj.ldap.DecodeException; |
| | | import org.forgerock.opendj.ldap.GeneralizedTime; |
| | | |
| | | /** |
| | | * This class defines the generalizedTimeOrderingMatch matching rule defined in |
| | |
| | | super(EMR_GENERALIZED_TIME_NAME); |
| | | } |
| | | |
| | | public ByteString normalizeAttributeValue(final Schema schema, final ByteSequence value) |
| | | throws DecodeException { |
| | | try { |
| | | return ByteString.valueOfLong(GeneralizedTime.valueOf(value.toString()).getTimeInMillis()); |
| | | } catch (LocalizedIllegalArgumentException e) { |
| | | throw DecodeException.error(e.getMessageObject()); |
| | | } |
| | | public ByteString normalizeAttributeValue(final Schema schema, final ByteSequence value) throws DecodeException { |
| | | return GeneralizedTimeEqualityMatchingRuleImpl.normalizeAttributeValue(value); |
| | | } |
| | | } |
| | |
| | | import java.util.List; |
| | | import java.util.TimeZone; |
| | | |
| | | import org.forgerock.i18n.LocalizedIllegalArgumentException; |
| | | import org.forgerock.opendj.ldap.Assertion; |
| | | import org.forgerock.opendj.ldap.ByteSequence; |
| | | import org.forgerock.opendj.ldap.ByteSequenceReader; |
| | |
| | | import static com.forgerock.opendj.ldap.CoreMessages.*; |
| | | import static com.forgerock.opendj.util.StaticUtils.*; |
| | | import static org.forgerock.opendj.ldap.DecodeException.*; |
| | | import static org.forgerock.opendj.ldap.schema.GeneralizedTimeEqualityMatchingRuleImpl.createNormalizedAttributeValue; |
| | | import static org.forgerock.opendj.ldap.schema.SchemaConstants.*; |
| | | |
| | | /** Implementations of time-based matching rules. */ |
| | |
| | | |
| | | @Override |
| | | public final ByteString normalizeAttributeValue(Schema schema, ByteSequence value) throws DecodeException { |
| | | try { |
| | | return ByteString.valueOfLong(GeneralizedTime.valueOf(value.toString()).getTimeInMillis()); |
| | | } catch (final LocalizedIllegalArgumentException e) { |
| | | throw error(e.getMessageObject()); |
| | | } |
| | | return GeneralizedTimeEqualityMatchingRuleImpl.normalizeAttributeValue(value); |
| | | } |
| | | |
| | | /** Utility method to convert the provided integer and the provided byte representing a digit to an integer. */ |
| | |
| | | |
| | | long delta = (second + minute * 60 + hour * 3600 + day * 24 * 3600 + week * 7 * 24 * 3600) * 1000; |
| | | long now = timeService.now(); |
| | | return ByteString.valueOfLong(signed ? now - delta : now + delta); |
| | | return createNormalizedAttributeValue(signed ? now - delta : now + delta); |
| | | } |
| | | |
| | | } |
| | |
| | | // Build the information from the attribute value. |
| | | GregorianCalendar cal = new GregorianCalendar(TIME_ZONE_UTC); |
| | | cal.setLenient(false); |
| | | cal.setTimeInMillis(((ByteString) attributeValue).toLong()); |
| | | cal.setTimeInMillis(attributeValue.toByteString().toLong() + GeneralizedTime.MIN_GENERALIZED_TIME_MS); |
| | | int second = cal.get(Calendar.SECOND); |
| | | int minute = cal.get(Calendar.MINUTE); |
| | | int hour = cal.get(Calendar.HOUR_OF_DAY); |
| | |
| | | * |
| | | * |
| | | * Copyright 2009 Sun Microsystems, Inc. |
| | | * Portions Copyright 2015 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.ldap.schema; |
| | | |
| | |
| | | {"20060912180129.000Z", "20060912180130.001Z", -1}, |
| | | {"20060912180129.1Z", "20060912180130.2Z", -1}, |
| | | {"20060912180129.11Z", "20060912180130.12Z", -1}, |
| | | // OPENDJ-2397 - dates before 1970 have negative ms. |
| | | {"19000101010203Z", "20000101010203Z", -1}, |
| | | {"20000101010203Z", "19000101010203Z", 1}, |
| | | }; |
| | | } |
| | | |
| | |
| | | { nowGT, "+1h", ConditionResult.FALSE }, |
| | | { nowGT, "+1m", ConditionResult.FALSE }, |
| | | { nowGT, "+1w", ConditionResult.FALSE }, |
| | | // OPENDJ-2397 - dates before 1970 have negative ms. |
| | | {"19000101010203Z", "1d", ConditionResult.FALSE}, |
| | | }; |
| | | } |
| | | |
| | |
| | | { nowGT, "-2w", ConditionResult.FALSE }, |
| | | { nowGT, "-10m", ConditionResult.FALSE }, |
| | | { nowGT, "-1s", ConditionResult.FALSE }, |
| | | // OPENDJ-2397 - dates before 1970 have negative ms. |
| | | {"19000101010203Z", "1d", ConditionResult.TRUE}, |
| | | }; |
| | | } |
| | | |