Skip to content

Commit

Permalink
Add more tests. New method to format dates with chosen number of digits.
Browse files Browse the repository at this point in the history
  • Loading branch information
ethlo committed Feb 27, 2017
1 parent a24f4dc commit 0242866
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 32 deletions.
43 changes: 43 additions & 0 deletions src/main/java/com/ethlo/time/AbstractInternetDateTimeUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.ethlo.time;

import java.time.DateTimeException;

public abstract class AbstractInternetDateTimeUtil implements InternetDateTimeUtil
{
private final static int MAX_FRACTION_DIGITS = 9;

private final boolean unknownLocalOffsetConvention;

public AbstractInternetDateTimeUtil(boolean unknownLocalOffsetConvention)
{
this.unknownLocalOffsetConvention = unknownLocalOffsetConvention;
}

/**
* RFC 3339 - 4.3. Unknown Local Offset Convention
*
* If the time in UTC is known, but the offset to local time is unknown,
* this can be represented with an offset of "-00:00". This differs
* semantically from an offset of "Z" or "+00:00", which imply that UTC
* is the preferred reference point for the specified time.
*
* @return True if allowed, otherwise false
*/
public boolean allowUnknownLocalOffsetConvention()
{
return unknownLocalOffsetConvention;
}

protected void failUnknownLocalOffsetConvention()
{
throw new DateTimeException("Unknown Local Offset Convention date-times not allowed. See #allowUnknownLocalOffsetConvention()");
}

protected void assertMaxFractionDigits(int fractionDigits)
{
if (fractionDigits > MAX_FRACTION_DIGITS )
{
throw new DateTimeException("Maximum support number of fraction dicits in second is " + MAX_FRACTION_DIGITS + ", got " + fractionDigits);
}
}
}
61 changes: 47 additions & 14 deletions src/main/java/com/ethlo/time/FastInternetDateTimeUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,18 @@
import com.ethlo.util.CharArrayIntegerUtil;
import com.ethlo.util.CharArrayUtil;

public class FastInternetDateTimeUtil implements InternetDateTimeUtil
/**
* Extreme level of optimization to squeeze every CPU cycle.
*
* @author ethlo - Morten Haraldsen
*/
public class FastInternetDateTimeUtil extends AbstractInternetDateTimeUtil
{
public FastInternetDateTimeUtil()
{
super(false);
}

private final StdJdkInternetDateTimeUtil delegate = new StdJdkInternetDateTimeUtil();

private static final char dateSep = '-';
Expand Down Expand Up @@ -95,7 +105,7 @@ else if (chars[19] == '+' || chars[19] == '-')
}
else
{
throw new UnsupportedOperationException();
throw new DateTimeException("Unexpected character at offset 19:" + chars[19]);
}

return OffsetDateTime.of(year, month, day, hour, minute, second, fractions, offset);
Expand Down Expand Up @@ -123,7 +133,27 @@ private ZoneOffset parseTz(char[] chars, int offset)
throw new DateTimeException("Invalid offset: " + new String(chars, offset, left));
}

return ZoneOffset.of(new String(chars, offset, left));
final char sign = chars[offset];
int hours = CharArrayIntegerUtil.parsePositiveInt(chars, 10, offset + 1, offset + 3);
int minutes = CharArrayIntegerUtil.parsePositiveInt(chars, 10, offset + 4, offset + 4 + 2);
if (sign == '-')
{
hours = -hours;
}
else if (sign != '+')
{
throw new DateTimeException("Invalid character starting at position " + offset);
}

if (! allowUnknownLocalOffsetConvention())
{
if (sign == '-' && hours == 0 && minutes == 0)
{
super.failUnknownLocalOffsetConvention();
}
}

return ZoneOffset.ofHoursMinutes(hours, minutes);
}

private void assertNoMoreChars(char[] chars, int lastUsed)
Expand All @@ -137,6 +167,7 @@ private void assertNoMoreChars(char[] chars, int lastUsed)
@Override
public String formatUtc(OffsetDateTime date, int fractionDigits)
{
assertMaxFractionDigits(fractionDigits);
final LocalDateTime utc = LocalDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC);

final char[] buf = new char[64];
Expand Down Expand Up @@ -175,16 +206,6 @@ public String formatUtc(OffsetDateTime date, int fractionDigits)

private void addFractions(char[] buf, int fractionDigits, int nano)
{
if (fractionDigits < 0)
{
fractionDigits = 0;
}

if (fractionDigits > 9)
{
fractionDigits = 9;
}

final double d = widths[fractionDigits - 1];
CharArrayIntegerUtil.toString((int)(nano / d), buf, 20, fractionDigits);
}
Expand All @@ -198,7 +219,7 @@ public String format(OffsetDateTime date, String timezone)
@Override
public String formatUtc(Date date)
{
return formatUtc(OffsetDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC));
return formatUtc(OffsetDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC), 3);
}

@Override
Expand Down Expand Up @@ -244,4 +265,16 @@ public String formatUtc(OffsetDateTime date)
{
return formatUtc(date, 0);
}

@Override
public String formatUtcMilli(Date date)
{
return formatUtcMilli(OffsetDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC));
}

@Override
public String format(Date date, String timezone, int fractionDigits)
{
return delegate.format(date, timezone, fractionDigits);
}
}
38 changes: 38 additions & 0 deletions src/main/java/com/ethlo/time/InternetDateTimeUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ public interface InternetDateTimeUtil
* @return The formatted string
*/
String formatUtc(Date date);

/**
* See {@link #formatUtcMilli(OffsetDateTime)}
* @param date The date to format
* @return The formatted string
*/
String formatUtcMilli(Date date);

/**
* See {@link #format(OffsetDateTime, String)}
Expand All @@ -48,18 +55,49 @@ public interface InternetDateTimeUtil
*/
String format(Date date, String timezone);

/**
* Format the date as a date-time String with specified resolution and time-zone offset, for example 1999-12-31T16:48:36[.123456789]-05:00
* @param date The date to format
* @param timezone The time-zone
* @param fractionDigits The number of fraction digits
* @return the formatted string
*/
String format(Date date, String timezone, int fractionDigits);

/**
* Check whether the string is a valid date-time according to RFC-3339
* @param dateTime
* @return True if valid, false otherwise
*/
boolean isValid(String dateTime);

/**
* Format the date as a date-time String with millisecond resolution, for example 1999-12-31T16:48:36.123Z
* @param date The date to format
* @return the formatted string
*/
String formatUtcMilli(OffsetDateTime date);

/**
* Format the date as a date-time String with microsecond resolution, aka 1999-12-31T16:48:36.123456Z
* @param date The date to format
* @return the formatted string
*/
String formatUtcMicro(OffsetDateTime date);

/**
* Format the date as a date-time String with nanosecond resolution, aka 1999-12-31T16:48:36.123456789Z
* @param date The date to format
* @return the formatted string
*/
String formatUtcNano(OffsetDateTime date);

/**
* Format the date as a date-time String with specified resolution, aka 1999-12-31T16:48:36[.123456789]Z
* @param date The date to format
* @return the formatted string
*/
String formatUtc(OffsetDateTime date, int fractionDigits);

boolean allowUnknownLocalOffsetConvention();
}
47 changes: 34 additions & 13 deletions src/main/java/com/ethlo/time/StdJdkInternetDateTimeUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@
import java.util.TimeZone;

/**
* The recommendation for date-time exchange in modern APIs is to use RFC-3339, available at https://tools.ietf.org/html/rfc3339
* This class supports both validation, parsing and formatting of such date-times.
* Java 8 JDK classes. The safe and normally "efficient enough" choice.
*
* @author Ethlo, Morten Haraldsen
* @author ethlo - Morten Haraldsen
*/
public class StdJdkInternetDateTimeUtil implements InternetDateTimeUtil
public class StdJdkInternetDateTimeUtil extends AbstractInternetDateTimeUtil
{
private SimpleDateFormat format;
private SimpleDateFormat[] formats = new SimpleDateFormat[9];

private DateTimeFormatter rfc3339baseFormatter = new DateTimeFormatterBuilder()
.appendValue(ChronoField.YEAR, 4)
Expand Down Expand Up @@ -78,18 +77,19 @@ private DateTimeFormatter getFormatter(int fractionDigits)
.optionalStart()
.appendOffset("+HH:MM", "Z")
.optionalEnd()
.optionalStart()
.appendOffset("+HH:MM", "z")
.optionalEnd()

.toFormatter();

public StdJdkInternetDateTimeUtil()
{
this.format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss." + repeat('S', 3) + "XX");
}

@Override
public String format(Date date, String timezone)
{
format.setTimeZone(TimeZone.getTimeZone(timezone));
return format.format(date);
super(false);
for (int i = 1; i < 9; i++)
{
this.formats[i] = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss." + repeat('S', i) + "XXX");
}
}

@Override
Expand Down Expand Up @@ -163,6 +163,27 @@ public String formatUtcNano(OffsetDateTime date)
@Override
public String formatUtc(OffsetDateTime date, int fractionDigits)
{
assertMaxFractionDigits(fractionDigits);
return getFormatter(fractionDigits).format(date);
}

@Override
public String formatUtcMilli(Date date)
{
return formatUtcMilli(OffsetDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC));
}

@Override
public String format(Date date, String timezone)
{
return format(date, timezone, 3);
}

@Override
public String format(Date date, String timezone, int fractionDigits)
{
final SimpleDateFormat formatter = (SimpleDateFormat)formats[fractionDigits].clone();
formatter.setTimeZone(TimeZone.getTimeZone(timezone));
return formatter.format(date);
}
}
53 changes: 50 additions & 3 deletions src/test/java/com/ethlo/time/CorrectnessTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public abstract class CorrectnessTest extends AbstractTest<InternetDateTimeUtil>
"2017-02-21T15:27:39.123456+00:00", "2017-02-21T15:27:39.123456789+00:00",
"2017-02-21T15:27:39.1+00:00", "2017-02-21T15:27:39.12+00:00",
"2017-02-21T15:27:39.123+00:00", "2017-02-21T15:27:39.1234+00:00",
"2017-02-21T15:27:39.112345+00:00", "2017-02-21T15:27:39.123456+00:00",
"2017-02-21T15:27:39.12345+00:00", "2017-02-21T15:27:39.123456+00:00",
"2017-02-21T15:27:39.1234567+00:00", "2017-02-21T15:27:39.12345678+00:00"
};

Expand Down Expand Up @@ -62,6 +62,20 @@ public void testFormat4()
assertThat(instance.format(date, "EST")).isEqualTo("2017-02-21T10:00:00.123-05:00");
}

@Test(expected=DateTimeException.class)
public void testParseMoreThanNanoResolutionFails()
{
instance.parse("2017-02-21T15:00:00.1234567891Z");
}

@Test(expected=DateTimeException.class)
public void testFormatMoreThanNanoResolutionFails()
{
final OffsetDateTime d = instance.parse("2017-02-21T15:00:00.123456789Z");
final int fractionDigits = 10;
instance.formatUtc(d, fractionDigits);
}

@Test
public void testFormatUtc()
{
Expand All @@ -80,6 +94,14 @@ public void testFormatUtcMilli()
assertThat(instance.formatUtcMilli(date)).isEqualTo("2017-02-21T15:00:00.123Z");
}

@Test
public void testFormatUtcMilliWithDate()
{
final String s = "2017-02-21T15:00:00.123456789Z";
final OffsetDateTime date = instance.parse(s);
assertThat(instance.formatUtcMilli(new Date(date.toInstant().toEpochMilli()))).isEqualTo("2017-02-21T15:00:00.123Z");
}

@Test
public void testFormatUtcMicro()
{
Expand Down Expand Up @@ -166,12 +188,37 @@ public void testMilitaryOffset()
final String s = "2017-02-21T15:27:39+0000";
assertThat(instance.isValid(s)).isFalse();
}

@Test(expected=DateTimeException.class)
public void testParseUnknownLocalOffsetConvention()
{
final String s = "2017-02-21T15:27:39-00:00";
instance.parse(s);
}

@Test
public void testParseLowercaseZ()
{
final String s = "2017-02-21T15:27:39.000z";
instance.parse(s);
}

@Test
public void testFormatWithNamedTimeZoneDate()
{
final String s = "2017-02-21T15:27:39.321+00:00";
final OffsetDateTime d = instance.parse(s);
final String formatted = instance.format(new Date(d.toInstant().toEpochMilli()), "EST");
assertThat(formatted).isEqualTo("2017-02-21T10:27:39.321-05:00");
}

@Test
public void testFormatWithNamedTimeZone()
{
// TODO: Add assertions
instance.format(new Date(), "EST");
final String s = "2017-02-21T15:27:39.321+00:00";
final OffsetDateTime d = instance.parse(s);
final String formatted = instance.format(new Date(d.toInstant().toEpochMilli()), "EST", 3);
assertThat(formatted).isEqualTo("2017-02-21T10:27:39.321-05:00");
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/com/ethlo/time/StdJdkBenchmarkTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
public class StdJdkBenchmarkTest extends BenchmarkTest
{
@Override
protected StdJdkInternetDateTimeUtil getInstance()
protected InternetDateTimeUtil getInstance()
{
return new StdJdkInternetDateTimeUtil();
}
Expand Down
Loading

0 comments on commit 0242866

Please sign in to comment.