An open API service indexing awesome lists of open source software.

https://github.com/ethlo/itu

An extremely fast parser and formatter of standardized date and date-times supporting RFC-3339 (ISO-8601 profile) and more.
https://github.com/ethlo/itu

date dateparser datetime formatter iso-8601 java library no-dependencies optimized parser performance rfc3339 time

Last synced: 22 days ago
JSON representation

An extremely fast parser and formatter of standardized date and date-times supporting RFC-3339 (ISO-8601 profile) and more.

Awesome Lists containing this project

README

        

# Internet Time Utility

[![Maven Central](https://img.shields.io/maven-central/v/com.ethlo.time/itu.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.ethlo.time%22%20a%3A%22itu%22)
[![javadoc](https://javadoc.io/badge2/com.ethlo.time/itu/javadoc.svg)](https://javadoc.io/doc/com.ethlo.time/itu/latest/com/ethlo/time/ITU.html)
[![Hex.pm](https://img.shields.io/hexpm/l/plug.svg)](../../LICENSE)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/598913bc1fe9405c82be73d9a4f105c8)](https://app.codacy.com/gh/ethlo/itu/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
[![codecov](https://codecov.io/gh/ethlo/itu/graph/badge.svg?token=V3H15LKC5V)](https://codecov.io/gh/ethlo/itu)

An extremely fast parser and formatter of ISO-8601 date-times. Handle
[RFC-3339 Timestamps](https://www.ietf.org/rfc/rfc3339.txt) and W3C [Date and Time Formats](https://www.w3.org/TR/NOTE-datetime) with ease!
Now also supports a subset of duration strings!

## Features

Low ceremony, high productivity with a very easy to use API.
* [Well-documented](https://javadoc.io/doc/com.ethlo.time/itu/latest/com/ethlo/time/ITU.html).
* Aim for 100% specification compliance.
* Handling leap-seconds.
* Zero dependencies.
* Java 8 compatible.
* Apache 2 licensed.

## Performance

Typically, **10x to 30x faster** than parsing and formatting with Java JDK classes.

The details and tests are available in a separate repository, [date-time-wars](https://github.com/ethlo/date-time-wars).

## Usage

Add dependency

```xml

com.ethlo.time
itu
1.14.0

```

Below you find some samples of usage of this library. Please check out the [javadoc](https://javadoc.io/doc/com.ethlo.time/itu/latest/com/ethlo/time/ITU.html) for more details.

## Parsing

This is a collection of usage examples for parsing.

#### parseRfc3339
[source »](src/test/java/samples/parsing/ITUParserSamples.java#L60C5-L69C6)

The simplest and fastest way to parse an RFC-3339 timestamp by far!
```java
final String text = "2012-12-27T19:07:22.123456789-03:00";
final OffsetDateTime dateTime = ITU.parseDateTime(text);
assertThat(dateTime.toString()).isEqualTo(text);
```

#### parseLenient
[source »](src/test/java/samples/parsing/ITUParserSamples.java#L71C5-L83C6)

Parses a date-time with flexible granularity. Works for anything from a year to a timestamp with nanoseconds, with or without timezone offset.
```java
final String text = "2012-12-27T19:07:23.123";
final DateTime dateTime = ITU.parseLenient(text);
final String formatted = dateTime.toString();
assertThat(formatted).isEqualTo(text);
```

#### parseLenientWithCustomSeparators
[source »](src/test/java/samples/parsing/ITUParserSamples.java#L85C5-L97C6)

In case you encounter the need for a somewhat different time-separator or fraction separator
you can use the `ParseConfig` to set up you preferred delimiters.
```java
final ParseConfig config = ParseConfig.DEFAULT
.withDateTimeSeparators('T', '|')
.withFractionSeparators('.', ',');
final DateTime result = ITU.parseLenient("1999-11-22|11:22:17,191", config);
assertThat(result.toString()).isEqualTo("1999-11-22T11:22:17.191");
```

#### parsePosition
[source »](src/test/java/samples/parsing/ITUParserSamples.java#L99C5-L109C6)

This allows you to track where to start reading. Note that the check for trailing junk is disabled when using `ParsePosition`.
```java
final ParsePosition pos = new ParsePosition(10);
final OffsetDateTime result = ITU.parseDateTime("some-data,1999-11-22T11:22:19+05:30,some-other-data", pos);
assertThat(result.toString()).isEqualTo("1999-11-22T11:22:19+05:30");
assertThat(pos.getIndex()).isEqualTo(35);
```

#### explicitGranularity
[source »](src/test/java/samples/parsing/ITUParserSamples.java#L111C5-L134C6)

This is useful if you need to handle different granularity with different logic or interpolation.
```java
final TemporalHandler handler = new TemporalHandler()
{
@Override
public OffsetDateTime handle(final LocalDate localDate)
{
return localDate.atTime(OffsetTime.of(LocalTime.of(0, 0), ZoneOffset.UTC));
}

@Override
public OffsetDateTime handle(final OffsetDateTime offsetDateTime)
{
return offsetDateTime;
}
};
final OffsetDateTime result = ITU.parse("2017-12-06", handler);
assertThat(result.toString()).isEqualTo("2017-12-06T00:00Z");
```

#### lenientTimestamp
[source »](src/test/java/samples/parsing/ITUParserSamples.java#L136C5-L146C6)

In some real world scenarios, it is useful to parse a best-effort timestamp. To ease usage, we can easily convert a raw `DateTime` instance into `Instant`.

Note the limitations and the assumption of UTC time-zone, as mentioned in the javadoc.
```java
final Instant instant = ITU.parseLenient("2017-12-06").toInstant();
assertThat(instant.toString()).isEqualTo("2017-12-06T00:00:00Z");
```

#### parseCustomFormat
[source »](src/test/java/samples/parsing/ITUParserSamples.java#L148C5-L170C6)

In case the format is not supported directly, you can build your own parser.
```java
final DateTimeParser parser = DateTimeParsers.of(
digits(DAY, 2),
separators('-'),
digits(MONTH, 2),
separators('-'),
digits(YEAR, 4),
separators(' '),
digits(HOUR, 2),
digits(MINUTE, 2),
digits(SECOND, 2),
separators(','),
fractions()
);
final String text = "31-12-2000 235937,123456";
final DateTime result = parser.parse(text);
assertThat(result.toString()).isEqualTo("2000-12-31T23:59:37.123456");
```

#### parseUsingInterfaceRfc33939
[source »](src/test/java/samples/parsing/ITUParserSamples.java#L172C5-L182C6)

`DateTimerParser` interface for RFC-3339.
```java
final DateTimeParser parser = DateTimeParsers.rfc3339();
final String text = "2000-12-31 23:59:37.123456";
final DateTime result = parser.parse(text);
assertThat(result.toString()).isEqualTo("2000-12-31T23:59:37.123456");
```

#### parseUsingInterfaceLocalTime
[source »](src/test/java/samples/parsing/ITUParserSamples.java#L184C5-L194C6)

`DateTimerParser` interface for local time.
```java
final DateTimeParser parser = DateTimeParsers.localTime();
final String text = "23:59:37.123456";
final LocalTime result = parser.parse(text).toLocalTime();
assertThat(result.toString()).isEqualTo(text);
```

#### parseUsingInterfaceLocalDate
[source »](src/test/java/samples/parsing/ITUParserSamples.java#L196C5-L206C6)

`DateTimerParser` interface for local date.
```java
final DateTimeParser parser = DateTimeParsers.localDate();
final String text = "2013-12-24";
final LocalDate result = parser.parse(text).toLocalDate();
assertThat(result.toString()).isEqualTo(text);
```

## Formatting

This is a collection of usage examples for formatting.

#### formatRfc3339WithUTC
[source »](src/test/java/samples/formatting/ITUFormattingSamples.java#L43C5-L54C6)

The simplest and fastest way to format an RFC-3339 timestamp by far!
```java
final OffsetDateTime input = OffsetDateTime.of(2012, 12, 27, 19, 7, 22, 123456789, ZoneOffset.ofHoursMinutes(-3, 0));
assertThat(ITU.formatUtcNano(input)).isEqualTo("2012-12-27T22:07:22.123456789Z");
assertThat(ITU.formatUtcMicro(input)).isEqualTo("2012-12-27T22:07:22.123456Z");
assertThat(ITU.formatUtcMilli(input)).isEqualTo("2012-12-27T22:07:22.123Z");
assertThat(ITU.formatUtc(input)).isEqualTo("2012-12-27T22:07:22Z");
```

#### formatWithDateTime
[source »](src/test/java/samples/formatting/ITUFormattingSamples.java#L56C5-L65C6)

Format with `DateTime`.
```java
final DateTime input = DateTime.of(2020, 11, 27, 12, 39, 19, null);
assertThat(input.toString(Field.MINUTE)).isEqualTo("2020-11-27T12:39");
assertThat(input.toString(Field.SECOND)).isEqualTo("2020-11-27T12:39:19");
```

## Leap-second handling

#### parseLeapSecond
[source »](src/test/java/samples/leapsecond/ITULeapSecondSamples.java#L40C5-L57C6)

Parse a valid leap-second (i.e. it is on a date that would allow for it, and it is also in the list of known actual leap-seconds).
```java
try
{
ITU.parseDateTime("1990-12-31T15:59:60-08:00");
}
catch (LeapSecondException exc)
{
// The following helper methods are available let you decide how to progress
assertThat(exc.getSecondsInMinute()).isEqualTo(60);
assertThat(exc.getNearestDateTime()).isEqualTo(OffsetDateTime.of(1990, 12, 31, 16, 0, 0, 0, ZoneOffset.ofHours(-8)));
assertThat(exc.isVerifiedValidLeapYearMonth()).isTrue();
}
```

## Duration Parser

Parses a duration string, a strict subset of ISO 8601 durations.

### Supported Units
This method supports time-based durations with the following units:

- **Weeks** (`W`)
- **Days** (`D`)
- **Hours** (`H`)
- **Minutes** (`M`)
- **Seconds** (`S`), including fractional seconds up to nanosecond precision

#### Not Allowed Units
The following units are **explicitly not allowed** to avoid ambiguity:

- **Years** (`Y`)
- **Months** (`M` in the date section)

### Negative Durations
Negative durations are supported and must be prefixed with `-P`, as specified in ISO 8601.
The parsed duration will be represented using:

- A **`long`** for total seconds
- An **`int`** for nanosecond precision

The nanosecond component is always positive, with the sign absorbed by the seconds field,
following Java and ISO 8601 conventions.

### Examples

#### Valid Input
- `P2DT3H4M5.678901234S` → 2 days, 3 hours, 4 minutes, 5.678901234 seconds
- `PT5M30S` → 5 minutes, 30 seconds
- `-PT2.5S` → Negative 2.5 seconds
- `-P1D` → Negative 1 day

#### Invalid Input
- `P1Y2M3DT4H` → Contains `Y` and `M`
- `PT` → Missing time values after `T`
- `P-1D` → Incorrect negative placement

#### simple
[source »](src/test/java/samples/durationparsing/DurationParsingSamples.java#L32C5-L37C6)

```java
final Duration duration = ITU.parseDuration("P4W");
assertThat(duration.getSeconds()).isEqualTo(2_419_200L);
```

#### fullNotNormalizedToNormalized
[source »](src/test/java/samples/durationparsing/DurationParsingSamples.java#L39C5-L44C6)

```java
final Duration duration = ITU.parseDuration("P4W10DT28H122M1.123456S");
assertThat(duration.normalized()).isEqualTo("P5W4DT6H2M1.123456S");
```

## Q & A

### Why this little project?

There are an endless amount of APIs with non-standard date/time exchange, and the goal of this project is to make it a
breeze to do the right thing!

### Why the performance focus?

Some projects use epoch time-stamps for date-time exchange, and from a performance perspective this *may* make sense
in some cases. With this project one can do-the-right-thing and maintain performance in date-time handling.

Importantly, this project is _not_ a premature optimization. In real-life scenarios there are examples of date-time parsing hindering optimal performance. The samples include data ingestion into
databases and search engines, to importing/exporting data on less powerful devices, like cheaper Android devices.

### What is wrong with epoch timestamps?

* It is not human-readable, so debugging and direct manipulation is harder
* Limited resolution and/or time-range available
* Unclear resolution and/or time-range

### What is RFC-3339?

[RFC-3339](https://www.ietf.org/rfc/rfc3339.txt) is a subset/profile defined by [W3C](https://www.w3.org/) of the
formats defined in [ISO-8601](http://www.iso.org/iso/home/standards/iso8601.htm), to simplify date and time exhange in
modern Internet protocols.

Typical formats include:

* `2017-12-27T23:45:32Z` - No fractional seconds, UTC/Zulu time
* `2017-12-27T23:45:32.999Z` - Millisecond fractions, UTC/Zulu time
* `2017-12-27T23:45:32.999999Z` - Microsecond fractions, UTC/Zulu time
* `2017-12-27T23:45:32.999999999Z` - Nanosecond fractions, UTC/Zulu time
* `2017-12-27T18:45:32-05:00` - No fractional seconds, EST time
* `2017-12-27T18:45:32.999-05:00` - Millisecond fractions, EST time
* `2017-12-27T18:45:32.999999-05:00` - Microsecond fractions, EST time
* `2017-12-27T18:45:32.999999999-05:00` - Nanosecond fractions, EST time

### What is W3C - Date and Time Formats

[Date and Time Formats](https://www.w3.org/TR/NOTE-datetime) is a _note_, meaning it is not endorsed, but it still
serves as a sane subset of ISO-8601, just like RFC-3339.

Typical formats include:

* `2017-12-27T23:45Z` - Minute resolution, UTC/Zulu time
* `2017-12-27` - Date only, no timezone (like someone's birthday)
* `2017-12` - Year and month only. Like an expiry date.

## Limitations

### Local offset

For the sake of avoiding data integrity issues, this library will not allow offset of `-00:00`. Such offset is described
in RFC3339 section 4.3., named "Unknown Local Offset Convention". Such offset is explicitly prohibited in ISO-8601 as
well.

> 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.

### Leap second parsing

Since Java's `java.time` classes do not support storing leap seconds, ITU will throw a `LeapSecondException` if one is
encountered to signal that this is a leap second. The exception can then be queried for the second-value. Storing such
values is not possible in a `java.time.OffsetDateTime`, the `60` is therefore abandoned and the date-time will use `59`
instead of `60`.