Skip to content

Commit

Permalink
Support parsing of sub-date formats (year and year-month) and ITU uti…
Browse files Browse the repository at this point in the history
…lity class with static methods
  • Loading branch information
ethlo committed Jan 30, 2018
1 parent f58102f commit 50426ff
Show file tree
Hide file tree
Showing 18 changed files with 238 additions and 141 deletions.
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.ethlo.time</groupId>
<artifactId>itu</artifactId>
<version>1.1-SNAPSHOT</version>
<version>1.2</version>
<name>Internet Time Utility</name>
<description>Extremely fast date/time parser and formatter - RFC 3339 (ISO 8601 profile) and W3C format</description>
<licenses>
Expand All @@ -24,7 +24,7 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,9 @@

import java.time.DateTimeException;

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

private final boolean unknownLocalOffsetConvention;

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

@Override
public boolean allowUnknownLocalOffsetConvention()
{
return unknownLocalOffsetConvention;
}

protected void failUnknownLocalOffsetConvention()
{
throw new DateTimeException("Unknown Local Offset Convention date-times not allowed");
}
public static final int MAX_FRACTION_DIGITS = 9;

protected void assertMaxFractionDigits(int fractionDigits)
{
Expand Down
45 changes: 23 additions & 22 deletions src/main/java/com/ethlo/time/FastInternetDateTimeUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,7 @@
import java.util.Arrays;
import java.util.Date;

/**
* Extreme level of optimization to squeeze every CPU cycle.
*
* @author ethlo - Morten Haraldsen
*/
public class FastInternetDateTimeUtil extends AbstractInternetDateTimeUtil implements W3cDateTimeUtil
public class FastInternetDateTimeUtil extends AbstractRfc3339 implements W3cDateTimeUtil
{
private final StdJdkInternetDateTimeUtil delegate = new StdJdkInternetDateTimeUtil();

Expand All @@ -51,20 +46,29 @@ public class FastInternetDateTimeUtil extends AbstractInternetDateTimeUtil imple
private static final char ZULU_UPPER = 'Z';
private static final char ZULU_LOWER = 'z';
private static final int[] widths = new int[]{100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1};

public FastInternetDateTimeUtil()
{
super(false);
}

@Override
public OffsetDateTime parse(String s)
public OffsetDateTime parseDateTime(String s)
{
return OffsetDateTime.class.cast(doParseLenient(s, OffsetDateTime.class));
final Temporal t = doParseLenient(s, OffsetDateTime.class);
if (t == null)
{
return null;
}
else if (t instanceof OffsetDateTime)
{
return OffsetDateTime.class.cast(t);
}
throw new DateTimeException("Invalid RFC-3339 date-time: " + s);
}

private void assertPositionContains(char[] chars, int offset, char... expected)
{
if (offset >= chars.length)
{
throw new DateTimeException("Abrupt end of input: " + new String(chars));
}

boolean found = false;
for (char e : expected)
{
Expand Down Expand Up @@ -107,12 +111,9 @@ else if (sign != PLUS)
throw new DateTimeException("Invalid character starting at position " + offset + 1);
}

if (! allowUnknownLocalOffsetConvention())
if (sign == MINUS && hours == 0 && minutes == 0)
{
if (sign == MINUS && hours == 0 && minutes == 0)
{
super.failUnknownLocalOffsetConvention();
}
throw new DateTimeException("Unknown 'Local Offset Convention' date-time not allowed");
}

return ZoneOffset.ofHoursMinutes(hours, minutes);
Expand Down Expand Up @@ -216,7 +217,7 @@ public boolean isValid(String dateTime)
{
try
{
parse(dateTime);
parseDateTime(dateTime);
return true;
}
catch (DateTimeException exc)
Expand Down Expand Up @@ -287,17 +288,17 @@ public <T extends Temporal> Temporal doParseLenient(String s, Class<T> type)

// YEAR
final int year = LimitedCharArrayIntegerUtil.parsePositiveInt(chars, 0, 4);
if (maxRequired == Field.YEAR)
if (maxRequired == Field.YEAR || chars.length == 4)
{
return Year.of(year);
}

// MONTH
assertPositionContains(chars, 4, DATE_SEPARATOR);
final int month = LimitedCharArrayIntegerUtil.parsePositiveInt(chars, 5, 7);
if (maxRequired == Field.MONTH)
if (maxRequired == Field.MONTH || chars.length == 7)
{
return type.cast(YearMonth.of(year, month));
return YearMonth.of(year, month);
}

// DAY
Expand Down
133 changes: 133 additions & 0 deletions src/main/java/com/ethlo/time/ITU.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package com.ethlo.time;

/*-
* #%L
* Internet Time Utility
* %%
* Copyright (C) 2017 - 2018 Morten Haraldsen (ethlo)
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/

import java.time.Instant;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.Year;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.temporal.Temporal;
import java.util.Date;

public class ITU
{
private static final FastInternetDateTimeUtil delegate = new FastInternetDateTimeUtil();
private static final ZoneId GMT_ZONE = ZoneId.of("GMT");

private ITU(){}

public static OffsetDateTime parseDateTime(String s)
{
return delegate.parseDateTime(s);
}

public static String formatUtc(OffsetDateTime date, int fractionDigits)
{
return delegate.formatUtc(date, fractionDigits);
}

public static String formatUtc(OffsetDateTime date, Field lastIncluded, int fractionDigits)
{
return delegate.formatUtc(date, lastIncluded, fractionDigits);
}

public static String formatUtc(Date date)
{
return delegate.formatUtc(date);
}

public static String format(Date date, String timezone)
{
return delegate.format(date, timezone);
}

public static boolean isValid(String dateTime)
{
return delegate.isValid(dateTime);
}

public static String formatUtcMilli(OffsetDateTime date)
{
return delegate.formatUtcMilli(date);
}

public static String formatUtcMicro(OffsetDateTime date)
{
return delegate.formatUtcMicro(date);
}

public static String formatUtcNano(OffsetDateTime date)
{
return delegate.formatUtcNano(date);
}

public static String formatUtc(OffsetDateTime date)
{
return delegate.formatUtc(date);
}

public static String formatUtcMilli(Date date)
{
return delegate.formatUtcMilli(date);
}

public static String format(Date date, String timezone, int fractionDigits)
{
return delegate.format(date, timezone, fractionDigits);
}

public static Temporal parseLenient(String s)
{
return delegate.parseLenient(s);
}

public static <T extends Temporal> T parseLenient(String s, Class<T> type)
{
return delegate.parseLenient(s, type);
}

public static long toEpochMillis(Temporal temporal)
{
if (temporal instanceof Instant)
{
return ((Instant)temporal).toEpochMilli();
}
else if (temporal instanceof OffsetDateTime)
{
return toEpochMillis(((OffsetDateTime)temporal).toInstant());
}
else if (temporal instanceof LocalDate)
{
return toEpochMillis(((LocalDate)temporal).atStartOfDay(GMT_ZONE).toInstant());
}
else if (temporal instanceof YearMonth)
{
return toEpochMillis(((YearMonth)temporal).atDay(1));
}
else if (temporal instanceof Year)
{
return toEpochMillis(((Year)temporal).atDay(1));
}
throw new IllegalArgumentException("Unhandled type " + temporal.getClass());
}
}
11 changes: 4 additions & 7 deletions src/main/java/com/ethlo/time/LimitedCharArrayIntegerUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@
import java.time.DateTimeException;
import java.util.Arrays;

/**
*
* @author mha
*
*/
public final class LimitedCharArrayIntegerUtil
{
private static final char ZERO = '0';
Expand All @@ -38,6 +33,8 @@ public final class LimitedCharArrayIntegerUtil
private static final int TABLE_SIZE = (int)Math.pow(RADIX, TABLE_WIDTH);
private static final char[] INT_CONVERSION_CACHE = new char[(TABLE_SIZE * TABLE_WIDTH) + MAX_INT_WIDTH];

private LimitedCharArrayIntegerUtil(){}

static
{
int offset = 0;
Expand All @@ -62,7 +59,7 @@ public static int parsePositiveInt(char[] strNum, int startInclusive, int endExc
{
throw new DateTimeException("Character " + strNum[i] + " is not a digit");
}
int digit = digit(strNum[i], RADIX);
int digit = digit(strNum[i]);
result *= RADIX;
result -= digit;
}
Expand Down Expand Up @@ -152,7 +149,7 @@ public static boolean isDigit(char c)
return (c >= ZERO && c <= '9');
}

protected static int digit(char c, int radix)
protected static int digit(char c)
{
return c - ZERO;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
*
* @author Ethlo, Morten Haraldsen
*/
public interface InternetDateTimeUtil
public interface Rfc3339
{
/**
* Format the {@link Date} as a UTC formatted date-time string
Expand All @@ -39,11 +39,11 @@ public interface InternetDateTimeUtil
String formatUtc(OffsetDateTime date);

/**
* Parse the date-time and return it as a {@link Date} in UTC time-zone.
* Parse the date-time and return it as a {@link OffsetDateTime}.
* @param dateTimeStr The date-time string to parse
* @return The instant defined by the date-time in UTC time-zone
*/
OffsetDateTime parse(String dateTimeStr);
OffsetDateTime parseDateTime(String dateTimeStr);

/**
* See {@link #formatUtc(OffsetDateTime)}
Expand Down Expand Up @@ -111,16 +111,4 @@ public interface InternetDateTimeUtil
* @return the formatted string
*/
String formatUtc(OffsetDateTime date, int fractionDigits);

/**
* RFC 3339 - 4.3. Unknown Local Offset Convention
*
* <p>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.</p>
*
* @return True if allowed, otherwise false
*/
boolean allowUnknownLocalOffsetConvention();
}
11 changes: 5 additions & 6 deletions src/main/java/com/ethlo/time/StdJdkInternetDateTimeUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@
*
* @author ethlo - Morten Haraldsen
*/
public class StdJdkInternetDateTimeUtil extends AbstractInternetDateTimeUtil
public class StdJdkInternetDateTimeUtil extends AbstractRfc3339
{
private SimpleDateFormat[] formats = new SimpleDateFormat[9];
private SimpleDateFormat[] formats = new SimpleDateFormat[MAX_FRACTION_DIGITS];

private DateTimeFormatter rfc3339baseFormatter = new DateTimeFormatterBuilder()
.appendValue(ChronoField.YEAR, 4)
Expand Down Expand Up @@ -112,8 +112,7 @@ private DateTimeFormatter getFormatter(int fractionDigits)

public StdJdkInternetDateTimeUtil()
{
super(false);
for (int i = 1; i < 9; i++)
for (int i = 1; i < MAX_FRACTION_DIGITS; i++)
{
this.formats[i] = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss." + repeat('S', i) + "XXX");
}
Expand All @@ -126,7 +125,7 @@ public String formatUtc(Date date)
}

@Override
public OffsetDateTime parse(final String s)
public OffsetDateTime parseDateTime(final String s)
{
if (s == null || s.isEmpty())
{
Expand Down Expand Up @@ -154,7 +153,7 @@ public boolean isValid(String dateTime)
{
try
{
parse(dateTime);
parseDateTime(dateTime);
return true;
}
catch (DateTimeException exc)
Expand Down
Loading

0 comments on commit 50426ff

Please sign in to comment.