Fix issue 3001: Performance bottleneck in GeneralizedTimeSyntax.format
Thanks to D.J. Hagberg of Millibits Consulting, Inc. for the code contribution.
| | |
| | | * CDDL HEADER END |
| | | * |
| | | * |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | * Copyright 2006-2009 Sun Microsystems, Inc. |
| | | * Portions Copyright 2009 D. J. Hagberg, Millibits Consulting, Inc. |
| | | */ |
| | | package org.opends.server.schema; |
| | | |
| | | |
| | | |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.Calendar; |
| | | import java.util.Date; |
| | | import java.util.GregorianCalendar; |
| | |
| | | */ |
| | | private static final DebugTracer TRACER = getTracer(); |
| | | |
| | | /** |
| | | * The lock that will be used to provide threadsafe access to the date |
| | | * formatter. |
| | | */ |
| | | private static Object dateFormatLock; |
| | | |
| | | |
| | | |
| | | /** |
| | | * The date formatter that will be used to convert dates into generalized time |
| | | * values. Note that all interaction with it must be synchronized. |
| | | */ |
| | | private static SimpleDateFormat dateFormat; |
| | | // UTC TimeZone is assumed to never change over JVM lifetime |
| | | private static final TimeZone TIME_ZONE_UTC_OBJ = |
| | | TimeZone.getTimeZone(TIME_ZONE_UTC); |
| | | |
| | | |
| | | |
| | |
| | | |
| | | |
| | | |
| | | /* |
| | | * Create the date formatter that will be used to construct and parse |
| | | * normalized generalized time values. |
| | | */ |
| | | static |
| | | { |
| | | dateFormat = new SimpleDateFormat(DATE_FORMAT_GENERALIZED_TIME); |
| | | dateFormat.setLenient(false); |
| | | dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); |
| | | |
| | | dateFormatLock = new Object(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Creates a new instance of this syntax. Note that the only thing that |
| | | * should be done here is to invoke the default constructor for the |
| | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public void initializeSyntax(AttributeSyntaxCfg configuration) |
| | | throws ConfigException |
| | | { |
| | |
| | | * |
| | | * @return The common name for this attribute syntax. |
| | | */ |
| | | @Override |
| | | public String getSyntaxName() |
| | | { |
| | | return SYNTAX_GENERALIZED_TIME_NAME; |
| | |
| | | * |
| | | * @return The OID for this attribute syntax. |
| | | */ |
| | | @Override |
| | | public String getOID() |
| | | { |
| | | return SYNTAX_GENERALIZED_TIME_OID; |
| | |
| | | * |
| | | * @return A description for this attribute syntax. |
| | | */ |
| | | @Override |
| | | public String getDescription() |
| | | { |
| | | return SYNTAX_GENERALIZED_TIME_DESCRIPTION; |
| | |
| | | * attributes with this syntax, or <CODE>null</CODE> if equality |
| | | * matches will not be allowed for this type by default. |
| | | */ |
| | | @Override |
| | | public EqualityMatchingRule getEqualityMatchingRule() |
| | | { |
| | | return defaultEqualityMatchingRule; |
| | |
| | | * attributes with this syntax, or <CODE>null</CODE> if ordering |
| | | * matches will not be allowed for this type by default. |
| | | */ |
| | | @Override |
| | | public OrderingMatchingRule getOrderingMatchingRule() |
| | | { |
| | | return defaultOrderingMatchingRule; |
| | |
| | | * attributes with this syntax, or <CODE>null</CODE> if substring |
| | | * matches will not be allowed for this type by default. |
| | | */ |
| | | @Override |
| | | public SubstringMatchingRule getSubstringMatchingRule() |
| | | { |
| | | return defaultSubstringMatchingRule; |
| | |
| | | * attributes with this syntax, or <CODE>null</CODE> if approximate |
| | | * matches will not be allowed for this type by default. |
| | | */ |
| | | @Override |
| | | public ApproximateMatchingRule getApproximateMatchingRule() |
| | | { |
| | | // Approximate matching will not be allowed by default. |
| | |
| | | * @return <CODE>true</CODE> if the provided value is acceptable for use with |
| | | * this syntax, or <CODE>false</CODE> if not. |
| | | */ |
| | | @Override |
| | | public boolean valueIsAcceptable(ByteSequence value, |
| | | MessageBuilder invalidReason) |
| | | { |
| | |
| | | */ |
| | | public static String format(Date d) |
| | | { |
| | | synchronized (dateFormatLock) |
| | | { |
| | | return dateFormat.format(d); |
| | | } |
| | | return d == null ? null : format(d.getTime()); |
| | | } |
| | | |
| | | |
| | |
| | | */ |
| | | public static String format(long t) |
| | | { |
| | | synchronized (dateFormatLock) |
| | | { |
| | | return dateFormat.format(new Date(t)); |
| | | } |
| | | } |
| | | // Generalized time has the format yyyyMMddHHmmss.SSS'Z' |
| | | |
| | | // Do this in a thread-safe non-synchronized fashion. |
| | | // (Simple)DateFormat is neither fast nor thread-safe. |
| | | |
| | | StringBuilder sb = new StringBuilder(19); |
| | | |
| | | GregorianCalendar calendar = new GregorianCalendar(TIME_ZONE_UTC_OBJ); |
| | | calendar.setLenient(false); |
| | | calendar.setTimeInMillis(t); |
| | | |
| | | // Format the year yyyy. |
| | | int n = calendar.get(Calendar.YEAR); |
| | | if (n < 0) |
| | | { |
| | | throw new IllegalArgumentException("Year cannot be < 0:" + n); |
| | | } |
| | | else if (n < 10) |
| | | { |
| | | sb.append("000"); |
| | | } |
| | | else if (n < 100) |
| | | { |
| | | sb.append("00"); |
| | | } |
| | | else if (n < 1000) |
| | | { |
| | | sb.append("0"); |
| | | } |
| | | sb.append(n); |
| | | |
| | | // Format the month MM. |
| | | n = calendar.get(Calendar.MONTH) + 1; |
| | | if (n < 10) |
| | | { |
| | | sb.append("0"); |
| | | } |
| | | sb.append(n); |
| | | |
| | | // Format the day dd. |
| | | n = calendar.get(Calendar.DAY_OF_MONTH); |
| | | if (n < 10) |
| | | { |
| | | sb.append("0"); |
| | | } |
| | | sb.append(n); |
| | | |
| | | // Format the hour HH. |
| | | n = calendar.get(Calendar.HOUR_OF_DAY); |
| | | if (n < 10) |
| | | { |
| | | sb.append("0"); |
| | | } |
| | | sb.append(n); |
| | | |
| | | // Format the minute mm. |
| | | n = calendar.get(Calendar.MINUTE); |
| | | if (n < 10) |
| | | { |
| | | sb.append("0"); |
| | | } |
| | | sb.append(n); |
| | | |
| | | // Format the seconds ss. |
| | | n = calendar.get(Calendar.SECOND); |
| | | if (n < 10) |
| | | { |
| | | sb.append("0"); |
| | | } |
| | | sb.append(n); |
| | | |
| | | // Format the milli-seconds. |
| | | sb.append('.'); |
| | | n = calendar.get(Calendar.MILLISECOND); |
| | | if (n < 10) |
| | | { |
| | | sb.append("00"); |
| | | } |
| | | else if (n < 100) |
| | | { |
| | | sb.append("0"); |
| | | } |
| | | sb.append(n); |
| | | |
| | | // Format the timezone (always Z). |
| | | sb.append('Z'); |
| | | |
| | | return sb.toString(); |
| | | } |
| | | |
| | | |
| | | |
| | |
| | | */ |
| | | public static AttributeValue createGeneralizedTimeValue(long time) |
| | | { |
| | | String valueString; |
| | | |
| | | synchronized (dateFormatLock) |
| | | { |
| | | valueString = dateFormat.format(new Date(time)); |
| | | } |
| | | |
| | | String valueString = format(time); |
| | | return AttributeValues.create(ByteString.valueOf(valueString), |
| | | ByteString.valueOf(valueString)); |
| | | } |
| | |
| | | { |
| | | GregorianCalendar calendar = new GregorianCalendar(); |
| | | calendar.setLenient(false); |
| | | calendar.setTimeZone(TimeZone.getTimeZone(TIME_ZONE_UTC)); |
| | | calendar.setTimeZone(TIME_ZONE_UTC_OBJ); |
| | | calendar.set(year, month, day, hour, minute, second); |
| | | calendar.set(Calendar.MILLISECOND, 0); |
| | | return calendar.getTimeInMillis(); |
| | |
| | | { |
| | | GregorianCalendar calendar = new GregorianCalendar(); |
| | | calendar.setLenient(false); |
| | | calendar.setTimeZone(TimeZone.getTimeZone(TIME_ZONE_UTC)); |
| | | calendar.setTimeZone(TIME_ZONE_UTC_OBJ); |
| | | calendar.set(year, month, day, hour, minute, second); |
| | | calendar.set(Calendar.MILLISECOND, 0); |
| | | return calendar.getTimeInMillis(); |
| | |
| | | { |
| | | GregorianCalendar calendar = new GregorianCalendar(); |
| | | calendar.setLenient(false); |
| | | calendar.setTimeZone(TimeZone.getTimeZone(TIME_ZONE_UTC)); |
| | | calendar.setTimeZone(TIME_ZONE_UTC_OBJ); |
| | | calendar.set(year, month, day, hour, minute, second); |
| | | calendar.set(Calendar.MILLISECOND, 0); |
| | | return calendar.getTimeInMillis(); |
| | |
| | | message); |
| | | } |
| | | |
| | | timeZone = TimeZone.getTimeZone(TIME_ZONE_UTC); |
| | | timeZone = TIME_ZONE_UTC_OBJ; |
| | | break outerLoop; |
| | | |
| | | case '+': |
| | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean isBinary() |
| | | { |
| | | return false; |
| | |
| | | * CDDL HEADER END |
| | | * |
| | | * |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | * Copyright 2006-2009 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.util; |
| | | |
| | |
| | | |
| | | import static org.opends.server.loggers.debug.DebugLogger.*; |
| | | import org.opends.server.loggers.debug.DebugTracer; |
| | | import org.opends.server.schema.GeneralizedTimeSyntax; |
| | | import org.opends.server.types.DebugLogLevel; |
| | | import static org.opends.server.util.ServerConstants.*; |
| | | |
| | |
| | | // The current time in nanoseconds. |
| | | private static volatile long nanoTime; |
| | | |
| | | // The date formatter that will be used to obtain the generalized time. |
| | | private static SimpleDateFormat generalizedTimeFormatter; |
| | | |
| | | // The date formatter that will be used to obtain the local timestamp. |
| | | private static SimpleDateFormat localTimestampFormatter; |
| | | |
| | |
| | | |
| | | TimeZone utcTimeZone = TimeZone.getTimeZone(TIME_ZONE_UTC); |
| | | |
| | | generalizedTimeFormatter = |
| | | new SimpleDateFormat(DATE_FORMAT_GENERALIZED_TIME); |
| | | generalizedTimeFormatter.setTimeZone(utcTimeZone); |
| | | |
| | | gmtTimestampFormatter = new SimpleDateFormat(DATE_FORMAT_GMT_TIME); |
| | | gmtTimestampFormatter.setTimeZone(utcTimeZone); |
| | | |
| | |
| | | date = calendar.getTime(); |
| | | time = date.getTime(); |
| | | nanoTime = System.nanoTime(); |
| | | generalizedTime = generalizedTimeFormatter.format(date); |
| | | generalizedTime = GeneralizedTimeSyntax.format(date); |
| | | localTimestamp = localTimestampFormatter.format(date); |
| | | gmtTimestamp = gmtTimestampFormatter.format(date); |
| | | hourAndMinute = (calendar.get(Calendar.HOUR_OF_DAY) * 100) + |
| | |
| | | * Operates in a loop, getting the current time and then sleeping briefly |
| | | * before checking again. |
| | | */ |
| | | @Override |
| | | public void run() |
| | | { |
| | | while (true) |
| | |
| | | date = calendar.getTime(); |
| | | time = date.getTime(); |
| | | nanoTime = System.nanoTime(); |
| | | generalizedTime = generalizedTimeFormatter.format(date); |
| | | generalizedTime = GeneralizedTimeSyntax.format(date); |
| | | localTimestamp = localTimestampFormatter.format(date); |
| | | gmtTimestamp = gmtTimestampFormatter.format(date); |
| | | hourAndMinute = (calendar.get(Calendar.HOUR_OF_DAY) * 100) + |
| | |
| | | * CDDL HEADER END |
| | | * |
| | | * |
| | | * Copyright 2006-2008 Sun Microsystems, Inc. |
| | | * Copyright 2006-2009 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.schema; |
| | | |
| | | import static org.opends.server.util.ServerConstants.*; |
| | | |
| | | import java.util.Calendar; |
| | | import java.util.Date; |
| | | import java.util.GregorianCalendar; |
| | | import java.util.TimeZone; |
| | | |
| | | import org.opends.server.api.AttributeSyntax; |
| | | import org.testng.Assert; |
| | | import org.testng.annotations.DataProvider; |
| | | import org.testng.annotations.Test; |
| | | |
| | | /** |
| | | * Test the GeneralizedTimeSyntax. |
| | |
| | | }; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Create data for format(...) tests. |
| | | * |
| | | * @return Returns test data. |
| | | */ |
| | | @DataProvider(name="createFormatData") |
| | | public Object[][] createFormatData() |
| | | { |
| | | return new Object [][] { |
| | | // Note that Calendar months run from 0-11, |
| | | // and that there was no such year as year 0 (1 BC -> 1 AD). |
| | | { 1, 0, 1, 0, 0, 0, 0, "00010101000000.000Z"}, |
| | | { 9, 0, 1, 0, 0, 0, 0, "00090101000000.000Z"}, |
| | | { 10, 0, 1, 0, 0, 0, 0, "00100101000000.000Z"}, |
| | | { 99, 0, 1, 0, 0, 0, 0, "00990101000000.000Z"}, |
| | | { 100, 0, 1, 0, 0, 0, 0, "01000101000000.000Z"}, |
| | | { 999, 0, 1, 0, 0, 0, 0, "09990101000000.000Z"}, |
| | | {1000, 0, 1, 0, 0, 0, 0, "10000101000000.000Z"}, |
| | | {2000, 0, 1, 0, 0, 0, 0, "20000101000000.000Z"}, |
| | | {2099, 0, 1, 0, 0, 0, 0, "20990101000000.000Z"}, |
| | | {2000, 8, 1, 0, 0, 0, 0, "20000901000000.000Z"}, |
| | | {2000, 9, 1, 0, 0, 0, 0, "20001001000000.000Z"}, |
| | | {2000, 10, 1, 0, 0, 0, 0, "20001101000000.000Z"}, |
| | | {2000, 11, 1, 0, 0, 0, 0, "20001201000000.000Z"}, |
| | | {2000, 0, 9, 0, 0, 0, 0, "20000109000000.000Z"}, |
| | | {2000, 0, 10, 0, 0, 0, 0, "20000110000000.000Z"}, |
| | | {2000, 0, 19, 0, 0, 0, 0, "20000119000000.000Z"}, |
| | | {2000, 0, 20, 0, 0, 0, 0, "20000120000000.000Z"}, |
| | | {2000, 0, 29, 0, 0, 0, 0, "20000129000000.000Z"}, |
| | | {2000, 0, 30, 0, 0, 0, 0, "20000130000000.000Z"}, |
| | | {2000, 0, 31, 0, 0, 0, 0, "20000131000000.000Z"}, |
| | | {2000, 0, 1, 9, 0, 0, 0, "20000101090000.000Z"}, |
| | | {2000, 0, 1, 10, 0, 0, 0, "20000101100000.000Z"}, |
| | | {2000, 0, 1, 19, 0, 0, 0, "20000101190000.000Z"}, |
| | | {2000, 0, 1, 20, 0, 0, 0, "20000101200000.000Z"}, |
| | | {2000, 0, 1, 23, 0, 0, 0, "20000101230000.000Z"}, |
| | | {2000, 0, 1, 0, 9, 0, 0, "20000101000900.000Z"}, |
| | | {2000, 0, 1, 0, 10, 0, 0, "20000101001000.000Z"}, |
| | | {2000, 0, 1, 0, 59, 0, 0, "20000101005900.000Z"}, |
| | | {2000, 0, 1, 0, 0, 9, 0, "20000101000009.000Z"}, |
| | | {2000, 0, 1, 0, 0, 10, 0, "20000101000010.000Z"}, |
| | | {2000, 0, 1, 0, 0, 59, 0, "20000101000059.000Z"}, |
| | | {2000, 0, 1, 0, 0, 0, 9, "20000101000000.009Z"}, |
| | | {2000, 0, 1, 0, 0, 0, 10, "20000101000000.010Z"}, |
| | | {2000, 0, 1, 0, 0, 0, 99, "20000101000000.099Z"}, |
| | | {2000, 0, 1, 0, 0, 0, 100, "20000101000000.100Z"}, |
| | | {2000, 0, 1, 0, 0, 0, 999, "20000101000000.999Z"}, |
| | | }; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Tests {@link GeneralizedTimeSyntax#format(long)}. |
| | | * |
| | | * @param yyyy |
| | | * The year. |
| | | * @param MM |
| | | * The month. |
| | | * @param dd |
| | | * The day. |
| | | * @param HH |
| | | * The hour. |
| | | * @param mm |
| | | * The minute. |
| | | * @param ss |
| | | * The second. |
| | | * @param SSS |
| | | * The milli-seconds. |
| | | * @param expected |
| | | * The expected generalized time formatted string. |
| | | * @throws Exception |
| | | * If an unexpected error occurred. |
| | | */ |
| | | @Test(dataProvider="createFormatData") |
| | | public void testFormatLong(int yyyy, int MM, int dd, int HH, int mm, |
| | | int ss, int SSS, String expected) throws Exception |
| | | { |
| | | Calendar calendar = |
| | | new GregorianCalendar(TimeZone.getTimeZone(TIME_ZONE_UTC)); |
| | | calendar.set(yyyy, MM, dd, HH, mm, ss); |
| | | calendar.set(Calendar.MILLISECOND, SSS); |
| | | long time = calendar.getTimeInMillis(); |
| | | String actual = GeneralizedTimeSyntax.format(time); |
| | | Assert.assertEquals(actual, expected); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Tests {@link GeneralizedTimeSyntax#format(Date)}. |
| | | * |
| | | * @param yyyy |
| | | * The year. |
| | | * @param MM |
| | | * The month. |
| | | * @param dd |
| | | * The day. |
| | | * @param HH |
| | | * The hour. |
| | | * @param mm |
| | | * The minute. |
| | | * @param ss |
| | | * The second. |
| | | * @param SSS |
| | | * The milli-seconds. |
| | | * @param expected |
| | | * The expected generalized time formatted string. |
| | | * @throws Exception |
| | | * If an unexpected error occurred. |
| | | */ |
| | | @Test(dataProvider="createFormatData") |
| | | public void testFormatDate(int yyyy, int MM, int dd, int HH, int mm, |
| | | int ss, int SSS, String expected) throws Exception |
| | | { |
| | | Calendar calendar = |
| | | new GregorianCalendar(TimeZone.getTimeZone(TIME_ZONE_UTC)); |
| | | calendar.set(yyyy, MM, dd, HH, mm, ss); |
| | | calendar.set(Calendar.MILLISECOND, SSS); |
| | | Date time = new Date(calendar.getTimeInMillis()); |
| | | String actual = GeneralizedTimeSyntax.format(time); |
| | | Assert.assertEquals(actual, expected); |
| | | } |
| | | } |