Timestamps64.jl

License: MIT Maintenance Stable Documentation Dev Documentation

This package provides an efficient Timestamp64 datetime type with nanosecond precision. It is a wrapper around a single Int64 value (8 bytes) that represents the number of nanoseconds since the UNIX epoch. Benchmarks show that common date/time operations are as fast as Julia's built-in DateTime type, or even significantly faster in some cases (see below).

Timestamp64 can store values ranging from 1970-01-01T00:00:00.000000000 to 2262-04-11 23:47:16.854775807, which should be sufficient for most applications.

This package works with Julia's built-in Dates module, providing methods to convert between Timestamp64 and DateTime, Date, and Time types. Furthermore, the common accessor functions for year, month, day, hour, minute, second, millisecond, microsecond, and nanosecond, among others, are provided.

Every function is unit-tested to ensure correctness, usually against the corresponding Dates function.

Timestamps64 precision vs. Dates.DateTime

The default precision/unit of the difference of two Timestamp64 objects is Nanosecond, while the default precision/unit of the difference of two DateTime objects is Millisecond.

Timestamps64 time rounding vs. Dates.DateTime

Due to a different origin epoch (1970-01-01T00:00:00.000000000 in Timestamp64 vs. 0000-01-01T00:00:00 in DateTime), the rounding of Timestamp64 with time periods smaller than Day(1) is not identical to the rounding of DateTime. This implementation corresponds to C++'s chrono rounding behavior.

Supported platforms

This package is supported on the following platforms (64-bit only):

  • Linux
  • macOS Sierra 10.12 and later (needs clock_gettime support)

Examples of not supported platforms:

  • Any 32-bit system
  • Windows operating systems
  • Older macOS versions before 10.12

Background

The reason this package was created is that the built-in Dates.DateTime type in Julia is not able to represent nanosecond precision. The Dates.DateTime type has millisecond precision, which is insufficient for some specialized applications.

API documentation

using Timestamps64
using Dates

# Create a timestamp
ts = Timestamp64(2021, 12, 31, 23, 58, 59, nanoseconds=123456789)

# Current timestamp in local time zone with nanosecond precision
now(Timestamp64)

# Current timestamp in UTC with nanosecond precision
now(Timestamp64, UTC)

# Today's timestamp in local time zone (at midnight)
today(Timestamp64)

# Today's timestamp in UTC (at midnight)
today(Timestamp64, UTC)

# Convert from various ISO 8601 string formats
Timestamp64("2021-01-01T00:00:01")
Timestamp64("2021-01-01T00:00:01Z")
Timestamp64("2021-01-01T00:00:00.001") # 1 millisecond
Timestamp64("2021-01-01T00:00:00.001Z") # 1 millisecond
Timestamp64("2021-01-01T00:00:00.000001") # 1 microsecond
Timestamp64("2021-01-01T00:00:00.000001Z") # 1 microsecond
Timestamp64("2021-01-01T00:00:00.000000001") # 1 nanosecond
Timestamp64("2021-01-01T00:00:00.000000001Z") # 1 nanosecond

# Base.parse is also supported (only ISO 8601 / RFC 3999 up to nanoseconds formats)
parse(Timestamp64, "2021-01-01T00:00:00.000000001Z", Dates.ISODateTimeFormat)
parse(Timestamp64, "2021-01-01T00:00:00")
parse(Timestamp64, "2021-01-01T00:00:00.001")
parse(Timestamp64, "2021-01-01T00:00:00.000000001Z", ISOTimestamp64Format)


## Dates conversion

# Convert to DateTime, which only has millisecond precision
DateTime(ts)
convert(DateTime, ts)

# Convert to Date
Date(ts)
convert(Date, ts)

# Convert to Time
Time(ts)
convert(Time, ts)

# Create from DateTime (only with millisecond precision)
dt = now()
Timestamp64(dt)

# Create from Date and Time (nanosecond precision)
Timestamp64(Date(2021, 12, 31), Time(23, 58, 59, 123, 456, 789))


## Accessor functions
year(ts)
month(ts)
day(ts)
hour(ts)
minute(ts)
second(ts)
nanosecond(ts)
millisecond(ts)
microsecond(ts)
nanosecond(ts)
yearmonth(ts)
yearmonthday(ts)
monthday(ts)
monthname(ts)
isleapyear(ts)
dayofweek(ts)


## String conversions

# Convert to string (ISO 8601 default)
string(ts)

# Convert to ISO 8601 string explicitly
iso8601(ts)

# Print string (ISO 8601 default)
println(ts)

# Dates.format is also supported
Dates.format(ts, Dates.ISODateTimeFormat)
Dates.format(ts, ISOTimestamp64Format)


## Arithmetics

# Add period
ts2 = ts + Millisecond(100)
ts2 - ts

# Subtract period
ts2 = ts - Microsecond(1)
ts2 - ts

# Difference of two timestamps
ts1 = Timestamp64(2022, 12, 31, 23, 58, 59)
ts2 = Timestamp64(2023, 1, 1, 23, 58, 59)
ts2 - ts1
Day(ts2 - ts1)


## Numeric operations

# Type constants
eps(Timestamp64)
zero(Timestamp64)
iszero(Timestamp64(0))
typemin(Timestamp64)
typemax(Timestamp64)

# Compare timestamps
ts1 < ts2
ts1 > ts2
ts1 == ts2

# Automatic type promotion
Timestamp64(2020, 1, 1) < DateTime(2020, 1, 2)
Timestamp64(2020, 1, 1) < Date(2020, 1, 2)


## Rounding

# Floor to nearest period value
floor(ts, Nanosecond(5))
floor(ts, Microsecond(5))
floor(ts, Millisecond(5))
floor(ts, Second(5))
floor(ts, Minute(5))
floor(ts, Hour(5))
floor(ts, Day(5))
floor(ts, Month(5))
floor(ts, Quarter(5))
floor(ts, Year(5))

# Ceil to nearest period
ceil(ts, Nanosecond(5))
ceil(ts, Microsecond(5))
ceil(ts, Millisecond(5))
ceil(ts, Second(5))
ceil(ts, Minute(5))
ceil(ts, Hour(5))
ceil(ts, Day(5))
ceil(ts, Month(5))
ceil(ts, Quarter(5))
ceil(ts, Year(5))

# Round to nearest period
round(ts, Nanosecond(5))
round(ts, Microsecond(5))
round(ts, Millisecond(5))
round(ts, Second(5))
round(ts, Minute(5))
round(ts, Hour(5))
round(ts, Day(5))
round(ts, Month(5))
round(ts, Quarter(5))
round(ts, Year(5))


## Ranges

# Create a range of timestamps using arbitrary periods
collect(Timestamp64(2020, 1, 1):Day(1):Timestamp64(2020, 1, 10))
collect(Timestamp64(2020, 1, 1):Week(1):Timestamp64(2020, 1, 31))
collect(Timestamp64(2020, 1, 1):Month(1):Timestamp64(2020, 12, 31))
collect(Timestamp64(2020, 1, 1):Quarter(1):Timestamp64(2020, 12, 31))
collect(Timestamp64(2020, 1, 1):Year(1):Timestamp64(2022, 1, 1))


## UNIX timestamp conversions

# create from UNIX timestamp in nanoseconds
Timestamp64(1704412800000000000)

# get UNIX timestamp in nanoseconds
Dates.value(ts)
unix_nanos(ts)
unix(Nanosecond, ts)

# get UNIX timestamp in microseconds
unix_micros(ts)
unix(Microsecond, ts)

# get UNIX timestamp in milliseconds
unix_millis(ts)
unix(Millisecond, ts)

# get UNIX timestamp in seconds
unix_secs(ts)
unix(Second, ts)

Performance

The Timestamp64 implementation is very efficient and has a small memory footprint with only 8 bytes. Common operations such as creating, parsing, converting, and formatting timestamps have been optimized and are as fast as Julia's built-in DateTime implementation, or even faster.

The following benchmark results have been obtained on an Intel(R) Core(TM) i9-12900K CPU on Ubuntu 22.04 using Julia 1.10.4.

Get object with current date/time in UTC

using BenchmarkTools
using Timestamps64
using Dates

@btime now(Timestamp64, UTC);
@btime now(UTC);

Result

julia> @btime now(Timestamp64, UTC);
  11.392 ns (0 allocations: 0 bytes)

julia> @btime now(UTC);
  22.015 ns (0 allocations: 0 bytes)

The Timestamp64 type is almost 2 times faster at getting the current UTC time compared to Julia's built-in DateTime type!

Construction from date parts

using BenchmarkTools
using Timestamps64
using Dates

const year = 2021;

@btime Timestamp64($year, 1, 1, 0, 0, 1, 123*1_000_000); # last parameter in nanoseconds!
@btime DateTime($year, 1, 1, 0, 0, 1, 123);

Result

The Timestamp64 type is 1.7 times faster at constructing a datetime object from its parts compared to Julia's built-in DateTime type!

julia> @btime Timestamp64($year, 1, 1, 0, 0, 1, 123);
  3.364 ns (0 allocations: 0 bytes)

julia> @btime DateTime($year, 1, 1, 0, 0, 1, 123);
  5.644 ns (0 allocations: 0 bytes)

Parsing ISO 8601 strings

using BenchmarkTools
using Timestamps64
using Dates

@btime Timestamp64("2021-01-01T00:00:01");
@btime DateTime("2021-01-01T00:00:01");

@btime Timestamp64("2021-01-01T00:00:01.001");
@btime DateTime("2021-01-01T00:00:01.001");

Result

The Timestamp64 type is almost 1.5 times faster at parsing ISO 8601 strings compared to Julia's built-in DateTime type!

julia> @btime Timestamp64("2021-01-01T00:00:01");
  16.175 ns (0 allocations: 0 bytes)

julia> @btime DateTime("2021-01-01T00:00:01");
  23.312 ns (0 allocations: 0 bytes)

julia> @btime Timestamp64("2021-01-01T00:00:01.001");
  19.271 ns (0 allocations: 0 bytes)

julia> @btime DateTime("2021-01-01T00:00:01.001");
  27.942 ns (0 allocations: 0 bytes)

Format to ISO 8601 strings

using BenchmarkTools
using Timestamps64
using Dates

ts = Timestamp64(2021, 1, 1, 0, 0, 1) + Millisecond(123)
dt = DateTime(2021, 1, 1, 0, 0, 1) + Millisecond(123)

# ISO 8601 string formatting
@btime Dates.format($ts, ISOTimestamp64Format);
@btime Dates.format($dt, Dates.ISODateTimeFormat);

Result

The Timestamp64 type is more than 6 times faster at formatting to a ISO 8601 string compared to Julia's built-in DateTime type, and with fewer allocations!

julia> @btime Dates.format($ts, ISOTimestamp64Format);
  46.626 ns (4 allocations: 200 bytes)

julia> @btime Dates.format($dt, Dates.ISODateTimeFormat);
  286.392 ns (19 allocations: 928 bytes)

Calculate difference

Note that the Timestamp64 type has nanosecond precision, while the DateTime type has millisecond precision, so the return type is correspondingly Nanosecond for Timestamp64 and Millisecond for DateTime.

using BenchmarkTools
using Timestamps64
using Dates

ts1 = Timestamp64(2021, 1, 1, 0, 0, 1);
ts2 = Timestamp64(2021, 1, 1, 0, 0, 2);

dt1 = DateTime(2021, 1, 1, 0, 0, 1);
dt2 = DateTime(2021, 1, 1, 0, 0, 2);

@btime $ts2 - $ts1;
@btime $dt2 - $dt1;

Result

Identical performance for both Timestamp64 and DateTime types.

julia> @btime $ts2 - $ts1;
  1.356 ns (0 allocations: 0 bytes)

julia> @btime $dt2 - $dt1;
  1.358 ns (0 allocations: 0 bytes)

Access UNIX timestamp in seconds

using BenchmarkTools
using Timestamps64
using Dates

ts = Timestamp64(2021, 1, 1, 0, 0, 1) + Millisecond(123);
dt = DateTime(2021, 1, 1, 0, 0, 1) + Millisecond(123);

@btime unix_secs($ts);
@btime trunc(Int64, datetime2unix($dt));

Result

Due to the internal representation of the Timestamp64 value as UNIX timestamp in nanoseconds, the conversion to a UNIX timestamp in seconds is trivial and therefore 2.3 times faster compared to Julia's built-in DateTime type using the datetime2unix function.

julia> @btime unix_secs($ts);
  1.571 ns (0 allocations: 0 bytes)

julia> @btime trunc(Int64, datetime2unix($dt));
  3.654 ns (0 allocations: 0 bytes)

Access UNIX timestamp in milliseconds

using BenchmarkTools
using Timestamps64
using Dates

ts = Timestamp64(2021, 1, 1, 0, 0, 1) + Millisecond(123);
dt = DateTime(2021, 1, 1, 0, 0, 1) + Millisecond(123);

@btime unix_millis($ts);
@btime trunc(Int64, datetime2unix($dt)*1000);

Result

Due to the internal representation of the Timestamp64 value as UNIX timestamp in nanoseconds, the conversion to a UNIX timestamp in seconds is trivial and therefore 2.6 times faster compared to Julia's built-in DateTime type using the datetime2unix function.

julia> @btime unix_millis($ts);
  1.729 ns (0 allocations: 0 bytes)

julia> @btime trunc(Int64, datetime2unix($dt)*1000);
  4.420 ns (0 allocations: 0 bytes)

Create object from UNIX timestamp in milliseconds

using BenchmarkTools
using Timestamps64
using Dates

unix_millis = 1609459201000; # 2021-01-01T00:00:01.000000000

@btime Timestamp64($unix_millis * 1_000_000);
@btime unix2datetime($unix_millis / 1000);

Result

The Timestamp64 type is 3.3 times faster at creating a timestamp from a UNIX timestamp in milliseconds compared to Julia's built-in DateTime type! This is partly due to the additional division operation required for the DateTime type.

julia> @btime Timestamp64($unix_millis * 1_000_000);
  1.378 ns (0 allocations: 0 bytes)

julia> @btime unix2datetime($unix_millis / 1000);
  4.507 ns (0 allocations: 0 bytes)

Create object from UNIX timestamp in seconds

using BenchmarkTools
using Timestamps64
using Dates

unix_secs = 1609459201; # 2021-01-01T00:00:01.000000000

@btime Timestamp64($unix_secs * 1_000_000_000);
@btime unix2datetime($unix_secs);

Result

Almost identical performance for both Timestamp64 and DateTime types.

julia> @btime Timestamp64($unix_secs * 1_000_000_000);
  1.539 ns (0 allocations: 0 bytes)

julia> @btime unix2datetime($unix_secs);
  1.570 ns (0 allocations: 0 bytes)

Bug reports and feature requests

Please report any issues via the GitHub issue tracker.