CFTime.jl

In many Earth science disciplines and beyond, expressing a time instance and a duration is essential. The CF conventions provide a rich and flexible framework for handling time, equally applicable to observations and model data.

CFTime.jl implements the time structures standardized by the CF conventions, namely:

  • Julian calendar (DateTimeJulian). A year is a leap year if it is divisible by 4. For example, 1900 and 2000 are both leap years in the Julian calendar.
  • Proleptic Gregorian calendar (DateTimeProlepticGregorian). A year is a leap year if it is divisible by 4 but not 100 or if it is divisible by 400. For example, 1900 is not a leap year in the proleptic Gregorian calendar but 2000 is.
  • The default mixed Gregorian/Julian calendar (DateTimeStandard). This calendar uses the Julian calendar for time instances before 15th October 1582 and Gregorian calendar afterwards.
  • A calendar without leap years (DateTimeNoLeap). All years are 365 days long.
  • A calendar with only leap years (DateTimeAllLeap). All years are 366 days long.
  • A calendar with every year being 360 days long (divided into 30-day months) (DateTime360Day).

The first three calendars (with different rules for leap years) can be used to express the time instances of observations or model. The remaining three calendars correspond to idealised model configurations where the duration of a year (revolution of the Earth around the Sun) is assumed to be exactly 365, 366 or 360 days.

While almost all datasets used in Earth Science use dates after the year 1582, some datasets or software systems use a time origin before this date, which makes it necessary to handle the transition from Julian to Gregorian calendar. Additionally, some dataset use microseconds and nanoseconds as time resolution, whereas Julia's Dates.DateTime has milliseconds as time resolution.

Note that time zones and leap seconds are currently not supported by CFTime.jl.

Installation

Inside the Julia shell, you can download and install the package by issuing:

using Pkg
Pkg.add("CFTime")

CFTime is a pure Julia package and currently depends only on the modules Dates and Printf, which are part of Julia’s standard library.

Latest development version

If you want to try the latest development version, you can do this with the following commands:

using Pkg
Pkg.add(PackageSpec(url="https://github.com/JuliaGeo/CFTime.jl", rev="master"))

Types

DateTimeStandard, DateTimeProlepticGregorian, DateTimeJulian, DateTimeNoLeap, DateTimeAllLeap and DateTime360Day are the core types that define time instances according to the corresponding calendars. The main focus of this package is to instantiate and to manipulate these types.

CFTime.DateTimeStandardType
DateTimeStandard([Ti::DataType], y, [m, d, h, mi, s, ms, µs, ns...],
                 origin = (1900, 1, 1),
                 units = ...) -> DateTimeStandard

Construct a DateTimeStandard type by year (y), month (m, default 1), day (d, default 1), hour (h, default 0), minute (mi, default 0), second (s, default 0), millisecond (ms, default 0), microsecond (µs, default 0), nanosecond (ns, default 0), .... Currently attosecond is the smallest supported time unit.

All arguments must be convertible to Int64. DateTimeStandard is a subtype of AbstractCFDateTime.

The date is stored a duration since the time origin (epoch) expressed as milliseconds but smaller time units will be used if necessary. For example if the user provides 8 integers, they will be interpreted as year, month, day, hour, minute, second, millisecond and microsecond. The internal time units will be microsecond in this case.

Valid values for units are :day, :hour, :second, ..., :attosecond as symbols or value types of symbols (e.g. Val(:day)).

origin is a tuple of integers representing the year, month and day as well as smaller time divisions if necessary.

The duration is stored as a number of type Ti (Int64 per default). Any integer types (such as Int32, Int128 or BigInt) or floating-point types can be used. Using an integer to encode a time instance is recommended for most applications, as it makes reasoning about the time resolution easier.

Example:

using CFTime

# 31st December 2000, 00:00:00 using the CF calendaro "standard"
# The internal time unit is millisecond.
dt1 = DateTimeStandard(2000,12,31)

# 31st December 2000, 00:00:00 and 1 nanosecond.
# The internal time unit is nanosecond.
dt2 = DateTimeStandard(2000,12,31, 0,0,0, 0,0,1)

dt2 - dt1
# output: 1 nanosecond

# 31st December 2000, 00:00:00
# The internal time unit is microsecond. Internally the duration is stored
# as an Int128.
dt3 = DateTimeStandard(Int128, 2000,12,31, units = :microsecond)

dt1 == dt3
# output: true

The netCDF CF calendars are defined in the CF Standard. This type implements the calendar defined as "standard".

source
DateTimeStandard(dt::AbstractString, format::AbstractString;
                 locale="english") -> DateTimeStandard

Construct a DateTimeStandard by parsing the dt date time string following the pattern given in the format string. In Julia, the only currently defined locale is English, therefore locale must be "english" or omitted.

See Dates.DateFormat for more information about the date time and format string.

Example:

using CFTime

# 31st December 2000
dt1 = DateTimeStandard("2000-December-31","yyyy-U-dd")
Note

This function is experimental and might be removed in the future. It relies on some internal function of Dates for parsing the format.

source
CFTime.DateTimeJulianType
DateTimeJulian([Ti::DataType], y, [m, d, h, mi, s, ms, µs, ns...],
               origin = (1900, 1, 1),
               units = ...) -> DateTimeJulian

Construct a DateTimeJulian type by year (y), month (m, default 1), day (d, default 1), hour (h, default 0), minute (mi, default 0), second (s, default 0), millisecond (ms, default 0), microsecond (µs, default 0), nanosecond (ns, default 0), .... Currently attosecond is the smallest supported time unit.

All arguments must be convertible to Int64. DateTimeJulian is a subtype of AbstractCFDateTime.

The date is stored a duration since the time origin (epoch) expressed as milliseconds but smaller time units will be used if necessary. For example if the user provides 8 integers, they will be interpreted as year, month, day, hour, minute, second, millisecond and microsecond. The internal time units will be microsecond in this case.

Valid values for units are :day, :hour, :second, ..., :attosecond as symbols or value types of symbols (e.g. Val(:day)).

origin is a tuple of integers representing the year, month and day as well as smaller time divisions if necessary.

The duration is stored as a number of type Ti (Int64 per default). Any integer types (such as Int32, Int128 or BigInt) or floating-point types can be used. Using an integer to encode a time instance is recommended for most applications, as it makes reasoning about the time resolution easier.

Example:

using CFTime

# 31st December 2000, 00:00:00 using the CF calendaro "julian"
# The internal time unit is millisecond.
dt1 = DateTimeJulian(2000,12,31)

# 31st December 2000, 00:00:00 and 1 nanosecond.
# The internal time unit is nanosecond.
dt2 = DateTimeJulian(2000,12,31, 0,0,0, 0,0,1)

dt2 - dt1
# output: 1 nanosecond

# 31st December 2000, 00:00:00
# The internal time unit is microsecond. Internally the duration is stored
# as an Int128.
dt3 = DateTimeJulian(Int128, 2000,12,31, units = :microsecond)

dt1 == dt3
# output: true

The netCDF CF calendars are defined in the CF Standard. This type implements the calendar defined as "julian".

source
DateTimeJulian(dt::AbstractString, format::AbstractString;
               locale="english") -> DateTimeJulian

Construct a DateTimeJulian by parsing the dt date time string following the pattern given in the format string. In Julia, the only currently defined locale is English, therefore locale must be "english" or omitted.

See Dates.DateFormat for more information about the date time and format string.

Example:

using CFTime

# 31st December 2000
dt1 = DateTimeStandard("2000-December-31","yyyy-U-dd")
Note

This function is experimental and might be removed in the future. It relies on some internal function of Dates for parsing the format.

source
CFTime.DateTimeProlepticGregorianType
DateTimeProlepticGregorian([Ti::DataType], y, [m, d, h, mi, s, ms, µs, ns...],
                           origin = (1900, 1, 1),
                           units = ...) -> DateTimeProlepticGregorian

Construct a DateTimeProlepticGregorian type by year (y), month (m, default 1), day (d, default 1), hour (h, default 0), minute (mi, default 0), second (s, default 0), millisecond (ms, default 0), microsecond (µs, default 0), nanosecond (ns, default 0), .... Currently attosecond is the smallest supported time unit.

All arguments must be convertible to Int64. DateTimeProlepticGregorian is a subtype of AbstractCFDateTime.

The date is stored a duration since the time origin (epoch) expressed as milliseconds but smaller time units will be used if necessary. For example if the user provides 8 integers, they will be interpreted as year, month, day, hour, minute, second, millisecond and microsecond. The internal time units will be microsecond in this case.

Valid values for units are :day, :hour, :second, ..., :attosecond as symbols or value types of symbols (e.g. Val(:day)).

origin is a tuple of integers representing the year, month and day as well as smaller time divisions if necessary.

The duration is stored as a number of type Ti (Int64 per default). Any integer types (such as Int32, Int128 or BigInt) or floating-point types can be used. Using an integer to encode a time instance is recommended for most applications, as it makes reasoning about the time resolution easier.

Example:

using CFTime

# 31st December 2000, 00:00:00 using the CF calendaro "prolepticgregorian"
# The internal time unit is millisecond.
dt1 = DateTimeProlepticGregorian(2000,12,31)

# 31st December 2000, 00:00:00 and 1 nanosecond.
# The internal time unit is nanosecond.
dt2 = DateTimeProlepticGregorian(2000,12,31, 0,0,0, 0,0,1)

dt2 - dt1
# output: 1 nanosecond

# 31st December 2000, 00:00:00
# The internal time unit is microsecond. Internally the duration is stored
# as an Int128.
dt3 = DateTimeProlepticGregorian(Int128, 2000,12,31, units = :microsecond)

dt1 == dt3
# output: true

The netCDF CF calendars are defined in the CF Standard. This type implements the calendar defined as "prolepticgregorian".

source
DateTimeProlepticGregorian(dt::AbstractString, format::AbstractString;
                           locale="english") -> DateTimeProlepticGregorian

Construct a DateTimeProlepticGregorian by parsing the dt date time string following the pattern given in the format string. In Julia, the only currently defined locale is English, therefore locale must be "english" or omitted.

See Dates.DateFormat for more information about the date time and format string.

Example:

using CFTime

# 31st December 2000
dt1 = DateTimeStandard("2000-December-31","yyyy-U-dd")
Note

This function is experimental and might be removed in the future. It relies on some internal function of Dates for parsing the format.

source
CFTime.DateTimeAllLeapType
DateTimeAllLeap([Ti::DataType], y, [m, d, h, mi, s, ms, µs, ns...],
                origin = (1900, 1, 1),
                units = ...) -> DateTimeAllLeap

Construct a DateTimeAllLeap type by year (y), month (m, default 1), day (d, default 1), hour (h, default 0), minute (mi, default 0), second (s, default 0), millisecond (ms, default 0), microsecond (µs, default 0), nanosecond (ns, default 0), .... Currently attosecond is the smallest supported time unit.

All arguments must be convertible to Int64. DateTimeAllLeap is a subtype of AbstractCFDateTime.

The date is stored a duration since the time origin (epoch) expressed as milliseconds but smaller time units will be used if necessary. For example if the user provides 8 integers, they will be interpreted as year, month, day, hour, minute, second, millisecond and microsecond. The internal time units will be microsecond in this case.

Valid values for units are :day, :hour, :second, ..., :attosecond as symbols or value types of symbols (e.g. Val(:day)).

origin is a tuple of integers representing the year, month and day as well as smaller time divisions if necessary.

The duration is stored as a number of type Ti (Int64 per default). Any integer types (such as Int32, Int128 or BigInt) or floating-point types can be used. Using an integer to encode a time instance is recommended for most applications, as it makes reasoning about the time resolution easier.

Example:

using CFTime

# 31st December 2000, 00:00:00 using the CF calendaro "allleap"
# The internal time unit is millisecond.
dt1 = DateTimeAllLeap(2000,12,31)

# 31st December 2000, 00:00:00 and 1 nanosecond.
# The internal time unit is nanosecond.
dt2 = DateTimeAllLeap(2000,12,31, 0,0,0, 0,0,1)

dt2 - dt1
# output: 1 nanosecond

# 31st December 2000, 00:00:00
# The internal time unit is microsecond. Internally the duration is stored
# as an Int128.
dt3 = DateTimeAllLeap(Int128, 2000,12,31, units = :microsecond)

dt1 == dt3
# output: true

The netCDF CF calendars are defined in the CF Standard. This type implements the calendar defined as "allleap".

source
DateTimeAllLeap(dt::AbstractString, format::AbstractString;
                locale="english") -> DateTimeAllLeap

Construct a DateTimeAllLeap by parsing the dt date time string following the pattern given in the format string. In Julia, the only currently defined locale is English, therefore locale must be "english" or omitted.

See Dates.DateFormat for more information about the date time and format string.

Example:

using CFTime

# 31st December 2000
dt1 = DateTimeStandard("2000-December-31","yyyy-U-dd")
Note

This function is experimental and might be removed in the future. It relies on some internal function of Dates for parsing the format.

source
CFTime.DateTimeNoLeapType
DateTimeNoLeap([Ti::DataType], y, [m, d, h, mi, s, ms, µs, ns...],
               origin = (1900, 1, 1),
               units = ...) -> DateTimeNoLeap

Construct a DateTimeNoLeap type by year (y), month (m, default 1), day (d, default 1), hour (h, default 0), minute (mi, default 0), second (s, default 0), millisecond (ms, default 0), microsecond (µs, default 0), nanosecond (ns, default 0), .... Currently attosecond is the smallest supported time unit.

All arguments must be convertible to Int64. DateTimeNoLeap is a subtype of AbstractCFDateTime.

The date is stored a duration since the time origin (epoch) expressed as milliseconds but smaller time units will be used if necessary. For example if the user provides 8 integers, they will be interpreted as year, month, day, hour, minute, second, millisecond and microsecond. The internal time units will be microsecond in this case.

Valid values for units are :day, :hour, :second, ..., :attosecond as symbols or value types of symbols (e.g. Val(:day)).

origin is a tuple of integers representing the year, month and day as well as smaller time divisions if necessary.

The duration is stored as a number of type Ti (Int64 per default). Any integer types (such as Int32, Int128 or BigInt) or floating-point types can be used. Using an integer to encode a time instance is recommended for most applications, as it makes reasoning about the time resolution easier.

Example:

using CFTime

# 31st December 2000, 00:00:00 using the CF calendaro "noleap"
# The internal time unit is millisecond.
dt1 = DateTimeNoLeap(2000,12,31)

# 31st December 2000, 00:00:00 and 1 nanosecond.
# The internal time unit is nanosecond.
dt2 = DateTimeNoLeap(2000,12,31, 0,0,0, 0,0,1)

dt2 - dt1
# output: 1 nanosecond

# 31st December 2000, 00:00:00
# The internal time unit is microsecond. Internally the duration is stored
# as an Int128.
dt3 = DateTimeNoLeap(Int128, 2000,12,31, units = :microsecond)

dt1 == dt3
# output: true

The netCDF CF calendars are defined in the CF Standard. This type implements the calendar defined as "noleap".

source
DateTimeNoLeap(dt::AbstractString, format::AbstractString;
               locale="english") -> DateTimeNoLeap

Construct a DateTimeNoLeap by parsing the dt date time string following the pattern given in the format string. In Julia, the only currently defined locale is English, therefore locale must be "english" or omitted.

See Dates.DateFormat for more information about the date time and format string.

Example:

using CFTime

# 31st December 2000
dt1 = DateTimeStandard("2000-December-31","yyyy-U-dd")
Note

This function is experimental and might be removed in the future. It relies on some internal function of Dates for parsing the format.

source
CFTime.DateTime360DayType
DateTime360Day([Ti::DataType], y, [m, d, h, mi, s, ms, µs, ns...],
               origin = (1900, 1, 1),
               units = ...) -> DateTime360Day

Construct a DateTime360Day type by year (y), month (m, default 1), day (d, default 1), hour (h, default 0), minute (mi, default 0), second (s, default 0), millisecond (ms, default 0), microsecond (µs, default 0), nanosecond (ns, default 0), .... Currently attosecond is the smallest supported time unit.

All arguments must be convertible to Int64. DateTime360Day is a subtype of AbstractCFDateTime.

The date is stored a duration since the time origin (epoch) expressed as milliseconds but smaller time units will be used if necessary. For example if the user provides 8 integers, they will be interpreted as year, month, day, hour, minute, second, millisecond and microsecond. The internal time units will be microsecond in this case.

Valid values for units are :day, :hour, :second, ..., :attosecond as symbols or value types of symbols (e.g. Val(:day)).

origin is a tuple of integers representing the year, month and day as well as smaller time divisions if necessary.

The duration is stored as a number of type Ti (Int64 per default). Any integer types (such as Int32, Int128 or BigInt) or floating-point types can be used. Using an integer to encode a time instance is recommended for most applications, as it makes reasoning about the time resolution easier.

Example:

using CFTime

# 31st December 2000, 00:00:00 using the CF calendaro "360day"
# The internal time unit is millisecond.
dt1 = DateTime360Day(2000,12,31)

# 31st December 2000, 00:00:00 and 1 nanosecond.
# The internal time unit is nanosecond.
dt2 = DateTime360Day(2000,12,31, 0,0,0, 0,0,1)

dt2 - dt1
# output: 1 nanosecond

# 31st December 2000, 00:00:00
# The internal time unit is microsecond. Internally the duration is stored
# as an Int128.
dt3 = DateTime360Day(Int128, 2000,12,31, units = :microsecond)

dt1 == dt3
# output: true

The netCDF CF calendars are defined in the CF Standard. This type implements the calendar defined as "360day".

source
DateTime360Day(dt::AbstractString, format::AbstractString;
               locale="english") -> DateTime360Day

Construct a DateTime360Day by parsing the dt date time string following the pattern given in the format string. In Julia, the only currently defined locale is English, therefore locale must be "english" or omitted.

See Dates.DateFormat for more information about the date time and format string.

Example:

using CFTime

# 31st December 2000
dt1 = DateTimeStandard("2000-December-31","yyyy-U-dd")
Note

This function is experimental and might be removed in the future. It relies on some internal function of Dates for parsing the format.

source
CFTime.AbstractCFDateTimeType
AbstractCFDateTime{T,Torigintuple}

Supertype for all DateTime structures following the CF conventions where the time instance is represented as a duration of type T since a time origin specified by the value type of the tuple Torigintuple.

The tuple is composed of the integers representing the year, month and day as well as smaller time divisions if necessary.

The type parameter T and Torigintuple are considered as internal API. Only reducing the number of type parameters of AbstractCFDateTime will be considered as a breaking change.

source

Time encoding and decoding

CFTime.timedecodeFunction
dt = timedecode(data,units,calendar = "standard"; prefer_datetime = true)

Decode the time information in data as given by the units units according to the specified calendar.

units has the format "DURATION_UNIT since TIME_ORIGIN" where DURATION_UNIT can be the year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, picosecond, femtosecond or attosecond (in singular or plural). The string TIME_ORIGIN is a time instance written as year-month-day hour:minute:second.subseconds... using 24-hour clock system where subseconds is the decimal fraction of the second. day and hour can also be separated by the character T following ISO 8601.

Valid values for calendar are "standard", "gregorian", "proleptic_gregorian", "julian", "noleap", "365_day", "all_leap", "366_day" and "360_day".

If prefer_datetime is true (default), dates are converted to the DateTime type (for the calendars "standard", "gregorian", "proleptic_gregorian" and "julian") unless the time unit is expressed in microseconds or smaller. Such conversion is not possible for the other calendars.

CalendarType (prefer_datetime=true)Type (prefer_datetime=false)
standard, gregorianDateTimeDateTimeStandard
proleptic_gregorianDateTimeDateTimeProlepticGregorian
julianDateTimeDateTimeJulian
noleap, 365_dayDateTimeNoLeapDateTimeNoLeap
all_leap, 366_dayDateTimeAllLeapDateTimeAllLeap
360_dayDateTime360DayDateTime360Day

Example:

using CFTime, Dates
# standard calendar
dt = CFTime.timedecode([0,1,2,3],"days since 2000-01-01 00:00:00")
# 4-element Vector{Dates.DateTime}:
#  2000-01-01T00:00:00
#  2000-01-02T00:00:00
#  2000-01-03T00:00:00
#  2000-01-04T00:00:00

dt = CFTime.timedecode([0,1,2,3],"days since 2000-01-01 00:00:00","360_day")
# 4-element Vector{DateTime360Day{CFTime.Period{Int64, Val{86400}(), Val{0}()}, Val{(2000, 1, 1)}()}}:
#  2000-01-01T00:00:00
#  2000-01-02T00:00:00
#  2000-01-03T00:00:00
#  2000-01-04T00:00:00
source
CFTime.timeencodeFunction
data = timeencode(dt,units,calendar = "standard")

Convert a vector or array of DateTime (or DateTimeStandard, DateTimeProlepticGregorian, DateTimeJulian, DateTimeNoLeap, DateTimeAllLeap, DateTime360Day) according to the specified units (e.g. "days since 2000-01-01 00:00:00") using the calendar calendar.

units has the format "DURATION_UNIT since TIME_ORIGIN" where DURATION_UNIT can be the year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, picosecond, femtosecond or attosecond (in singular or plural). The string TIME_ORIGIN is a time instance written as year-month-day hour:minute:second.subseconds... using 24-hour clock system where subseconds is the decimal fraction of the second. day and hour can also be separated by the character T following ISO 8601.

Valid values for calendar are: "standard", "gregorian", "proleptic_gregorian", "julian", "noleap", "365_day", "all_leap", "366_day", "360_day".

Example:

using CFTime
dt = [DateTimeStandard(2000,1,1),DateTimeStandard(2000,1,2),DateTimeStandard(2000,1,3)]
CFTime.timeencode(dt,"days since 2000-01-01 00:00:00")
# output: [0., 1., 2.]
source

Accessor Functions

Dates.yearMethod
Dates.year(dt::AbstractCFDateTime) -> Int64

Extract the year part of an AbstractCFDateTime as an Int64.

source
Dates.monthMethod
Dates.month(dt::AbstractCFDateTime) -> Int64

Extract the month part of an AbstractCFDateTime as an Int64.

source
Dates.dayMethod
Dates.day(dt::AbstractCFDateTime) -> Int64

Extract the day part of an AbstractCFDateTime as an Int64.

source
Dates.hourMethod
Dates.hour(dt::AbstractCFDateTime) -> Int64

Extract the hour part of an AbstractCFDateTime as an Int64.

source
Dates.minuteMethod
Dates.minute(dt::AbstractCFDateTime) -> Int64

Extract the minute part of an AbstractCFDateTime as an Int64.

source
Dates.secondMethod
Dates.second(dt::AbstractCFDateTime) -> Int64

Extract the second part of an AbstractCFDateTime as an Int64.

source
Dates.millisecondMethod
Dates.millisecond(dt::AbstractCFDateTime) -> Int64

Extract the millisecond part of an AbstractCFDateTime as an Int64.

source
Dates.microsecondMethod
Dates.microsecond(dt::AbstractCFDateTime) -> Int64

Extract the microsecond part of an AbstractCFDateTime as an Int64.

source
Dates.nanosecondMethod
Dates.nanosecond(dt::AbstractCFDateTime) -> Int64

Extract the nanosecond part of an AbstractCFDateTime as an Int64.

source
CFTime.picosecondMethod
CFTime.picosecond(dt::AbstractCFDateTime) -> Int64

Extract the picosecond part of an AbstractCFDateTime as an Int64.

source
CFTime.femtosecondMethod
CFTime.femtosecond(dt::AbstractCFDateTime) -> Int64

Extract the femtosecond part of an AbstractCFDateTime as an Int64.

source
CFTime.attosecondMethod
CFTime.attosecond(dt::AbstractCFDateTime) -> Int64

Extract the attosecond part of an AbstractCFDateTime as an Int64.

source

Query Functions

Dates.daysinmonthFunction
monthlength = daysinmonth(::Type{DT},y,m)

Returns the number of days in a month for the year y and the month m according to the calendar given by the type DT (any subtype of AbstractCFDateTime).

Example

julia> daysinmonth(DateTimeAllLeap,2001,2)
29
source
monthlength = daysinmonth(t)

Returns the number of days in a month containing the date t

Example

julia> daysinmonth(DateTimeAllLeap(2001,2,1))
29
source
Dates.daysinyearFunction
yearlength = daysinyear(::Type{DT},y)

Returns the number of days in a year for the year y according to the calendar given by the type DT (any subtype of AbstractCFDateTime).

Example

julia> daysinyear(DateTimeAllLeap,2001,2)
366
source
yearlength = daysinyear(t)

Returns the number of days in a year containing the date t

Example

julia> daysinyear(DateTimeAllLeap(2001,2,1))
366
source
Dates.yearmonthdayFunction
yearmonthday(dt::AbstractCFDateTime)

Simultaneously return the year, month and day parts of dt.

source
Dates.yearmonthFunction
yearmonth(dt::AbstractCFDateTime)

Simultaneously return the year and month parts of dt.

source
Dates.monthdayFunction
monthday(dt::AbstractCFDateTime)

Simultaneously return the month and day parts of dt.

source
Dates.firstdayofyearFunction
firstdayofyear(dt::AbstractCFDateTime)

Return the first day of the year including the date dt

source
Dates.dayofyearFunction
dayofyear(dt::AbstractCFDateTime)

Return the day of the year for dt with January 1st being day 1.

source

Conversion Functions

The flexibility of CFTime's datetime (related to the time origin, time resolution and type of the time counter) comes with some cost. When merging data from different sources, the resulting merged time vector may not have a concrete type, as there is no implicit conversion to a common time origin or internal unit, unlike Julia's DateTime. In some cases, the user might decide to explicitly convert all times to a common time origin and internal unit for optimal performance.

The convert function can be used to convert dates between the different calendars:

using CFTime, Dates
convert(DateTime,DateTimeJulian(2024,4,4))
# output
2024-04-17T00:00:00
convert(DateTimeJulian,DateTime(2024,4,17))
# output
DateTimeJulian(2024-04-04T00:00:00)
Base.convertMethod
dt2 = convert(::Type{T}, dt)

Convert a DateTime dt of type DateTimeStandard, DateTimeProlepticGregorian, DateTimeJulian or DateTime into the type T which can also be either DateTimeStandard, DateTimeProlepticGregorian, DateTimeJulian or DateTime.

Conversion is done such that duration (difference of DateTime types) are preserved. For dates on and after 1582-10-15, the year, month and days are the same for the types DateTimeStandard, DateTimeProlepticGregorian and DateTime.

For dates before 1582-10-15, the year, month and days are the same for the types DateTimeStandard and DateTimeJulian.

source
Base.reinterpretFunction
dt2 = reinterpret(::Type{T}, dt)

Convert a variable dt of type DateTime, DateTimeStandard, DateTimeJulian, DateTimeProlepticGregorian, DateTimeAllLeap, DateTimeNoLeap or DateTime360Day into the date time type T using the same values for year, month, day, minute, second, ... attosecond. The conversion might fail if a particular date does not exist in the target calendar.

For example, the difference of the 1 January 2000 in the Julian and and the 1 January 2000 in the standard calendar is 13 days:

using CFTime, Dates
dt = DateTimeJulian(2000,1,1)
Dates.Day(dt - reinterpret(DateTimeStandard,dt))
# 13 days
source

Arithmetic

Adding and subtracting time periods is supported:

using CFTime, Dates
DateTimeStandard(1582,10,4) + Dates.Day(1)
# output
DateTimeStandard(1582-10-15T00:00:00)

1582-10-15 is the adoption of the Gregorian Calendar.

Comparison operator can be used to check if a date is before or after another date.

DateTimeStandard(2000,01,01) < DateTimeStandard(2000,01,02)
# output
true

Ranges

Time ranges can be constructed using a start date, end date and a time increment:

using CFTime, Dates
range = DateTimeStandard(2000,1,1):Dates.Day(1):DateTimeStandard(2000,12,31)
length(range)
# output
366
step(range)
# output
1 day

Note that there is no default increment for range.

Rounding

Julia's DateTime records the time relative to a time origin (January 1st, 1 BC or 0000-01-01 in ISO_8601) with a millisecond accuracy. Converting CFTime date time structures to Julia's DateTime (using convert(DateTime,dt)) can trigger an inexact exception if the conversion cannot be done without loss of precision. One can use the round function in order to round to the nearest time represenatable by DateTime:

using CFTime: DateTimeStandard
using Dates: DateTime
dt = DateTimeStandard(24*60*60*1000*1000 + 123,"microsecond since 2000-01-01")
round(DateTime,dt)
# output
2000-01-02T00:00:00

The functions floor and ceil are also supported. They can be used to effectively reduce the time resolution, for example:

using CFTime: DateTimeStandard
using Dates

dt = DateTimeStandard(24*60*60,"second since 2000-01-01")

floor(dt+Second(9),Second(10)) == dt
# output
true
ceil(dt+Second(9),Second(10)) == dt + Second(10)
# output
true
round(dt+Second(9),Second(10)) == dt + Second(10)
# output
true

Type-stable constructors

To create a type-stable date time structure, use the DateTimeStandard (and similar) either with the default units and time origin, a constant unit/origin or a value type of the unit and origin. For example:

using CFTime: DateTimeStandard

function foo(year,month,day,hour,minute,second)
   DateTimeStandard(year,month,day,hour,minute,second;
                    units=:second, origin=(1970,1,1))
end

# Type-stable thanks to constant propagation
@code_warntype foo(2000,1,1,0,0,0)


function foo2(year,month,day,hour,minute,second,units,origin)
   DateTimeStandard(year,month,day,hour,minute,second; units, origin)
end

# This not type-stable as the type depends on the value of units and origin
units = :second
origin = (1970,1,1)
@code_warntype foo2(2000,1,1,0,0,0,units,origin)


# But this is again type-stable
units = Val(:second)
origin = Val((1970,1,1))
@code_warntype foo2(2000,1,1,0,0,0,units,origin)

Limitations and caveats

In Julia, integer overflows can occur in various operations such as:

using Dates
typemax(Int64)+1
# -9223372036854775808

DateTime(2000,1,1) + Dates.Day(typemax(Int64))
# 1999-12-31T00:00:00

DateTime(typemax(Int64),1,1)
# -0002-12-31T13:26:24

None of these calls issue a warning or an error in Julia (due to the performance impact affecting the vast majority of cases). The same approach is taken in CFTime: the user should be aware than under some (exeptional) circumstances, the default Int64 can overflow. But in addition to Julia's Dates, CFTime gives us user the option to use different storage types like Int128 or BigInt:

using CFTime
DateTimeStandard(Int128,typemax(Int64),1,1)
# DateTimeStandard(9223372036854775807-01-01T00:00:00)

In this example, the year 9223372036854775807 (about nine quintillion years) is about 60 millions time longer than the age of the universe. It dependens on the users' application whether such long time frames are needed.

The default Int64 time counter can also overflow when using a very small time resolution are used:

using CFTime
DateTimeStandard(2000,1,1, units=:attosecond)
# which is the same as
DateTimeStandard(Int64,2000,1,1, units=:attosecond,origin=(1900,1,1))
# -> overflow

An Int64 time counter does not allow to represent the number of attoseconds between the 1st January of the years 2000 and 1900. In fact, only 18 s around the time origin can be represent with a 64-bit integer and attoseconds as time unit.

This table sumarizes the maximal time span for Int64 around the time origin (1900-01-01 00:00:00, per default)

unittime span
day±2.525e+16 years
hour±1.052e+15 years
minute±1.754e+13 years
second±2.923e+11 years
millisecond±2.923e+08 years
microsecond±2.923e+05 years
nanosecond±292.3 years
picosecond±106.8 days
femtosecond±2.562 hours
attosecond±9.223 seconds

An Int128 is sufficiently large for the case above:

DateTimeStandard(Int128,2000,1,1, units=:attosecond,origin=(1900,1,1))
# DateTimeStandard(2000-01-01T00:00:00)

Operations can typically be 2 to 4 times slower when using an Int128 rather than an Int64.

It is important to note that when small durations are added to a time instances, then internal type promotion will also change the unit of the time instance (which is milliseconds since 1900-01-01):

dt = DateTimeStandard(2000,1,1) # default unit is :millisecond
dt2 = dt + Dates.Nanosecond(1) # now the unit is :nanosecond -> but no overflow
# DateTimeStandard(2000-01-01T00:00:00.000000001)

dt = DateTimeStandard(2160,1,1) # default unit is :millisecond
dt2 = dt + Dates.Nanosecond(1) # now the unit is :nanosecond -> overflow
# DateTimeStandard(1575-06-03T00:25:26.290448385)

Whether an overflow occurs or not cannot be statically inferred and a warning at runtime would impact performance. To avoid overflows, one should use Int128 or BigInt in these cases.

Note that on a 32-bit system, the default internal time counter is still Int64 as in Julia's Dates module.

Internal API

For CFTime 0.1.4 and before all date-times are encoded using internally milliseconds since a fixed time origin and stored as an Int64 similar to julia's Dates.DateTime. However, this approach does not allow to encode time with a sub-millisecond precision allowed by the CF convention and supported by e.g. numpy. While numpy allows attosecond precision, it can only encode a time span of ±9.2 around the date 00:00:00 UTC on 1 January 1970. In CFTime the time origin and the number containing the duration and the time precision are now encoded as two additional type parameters.

When wrapping a CFTime date-time type, it is recommended for performance reasons to make the containing structure also parametric, for example

struct MyStuct{T1,T2}
  dt::DateTimeStandard{T1,T2}
end

Future version of CFTime might add other type parameters. Internally, T1 corresponds to a CFTime.Period{T,Tfactor,Texponent} structure wrapping a number type T representing the duration expressed in seconds as:

duration * factor * 10^exponent

where Tfactor and Texponent are value types of factor and exponent respectively.

For example, duration 3600000 milliseconds is represented as duration = 3600000, Tfactor = Val(1), Texponent = Val(-3), as

3600000 milliseconds = 3600000 * 1 * 10⁻³ seconds

or the duration 1 hours is duration = 1, Tfactor = Val(3600) and Texponent = Val(0) since:

1 hour = 1 * 3600 * 10⁰ seconds

There is no normalization of the time duration per default as it could lead to under-/overflow.

The type parameter T2 of DateTimeStandard encodes the time origin as a tuple of integers starting with the year (year,month,day,hour,minute,seconds,...attoseconds). Only the year, month and day need to be specified; all other default to zero. For example T2 would be Val((1970,1,1)) if the time origin is the 1st January 1970.

By using value types as type parametes, the time origin, time resolution... are known to the compiler. For example, computing the difference between between two date time expressed in as the same time origin and units as a single substraction:

using BenchmarkTools
using Dates
using CFTime: DateTimeStandard

dt0 = DateTimeStandard(1,"days since 2000-01-01")
dt1 = DateTimeStandard(1000,"days since 2000-01-01")

difference_datetime(dt0,dt1) = Dates.Millisecond(dt1 - dt0).value
@btime difference_datetime($dt0,$dt1)

# output (minimum of 5 @btime trails)
# 1.689 ns (0 allocations: 0 bytes)

v0 = 1
v1 = 1000

difference_numbers(v0,v1) = (v1-v0)*(86_400_000)
@btime difference_numbers($v0,$v1)

# output (minimum of 5 @btime trails)
# 1.683 ns (0 allocations: 0 bytes)

The information in this section and any other information marked as internal or experimental is not part of the public API and not covered by the semantic versioning. Future version of CFTime might add or changing the meaning of type parameters as patch-level changes. However, removing a type parameter would be considered as a breaking change.