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.DateTimeStandard
— TypeDateTimeStandard([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".
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")
CFTime.DateTimeJulian
— TypeDateTimeJulian([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".
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")
CFTime.DateTimeProlepticGregorian
— TypeDateTimeProlepticGregorian([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".
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")
CFTime.DateTimeAllLeap
— TypeDateTimeAllLeap([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".
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")
CFTime.DateTimeNoLeap
— TypeDateTimeNoLeap([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".
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")
CFTime.DateTime360Day
— TypeDateTime360Day([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".
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")
CFTime.AbstractCFDateTime
— TypeAbstractCFDateTime{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.
Time encoding and decoding
CFTime.timedecode
— Functiondt = 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.
Calendar | Type (prefer_datetime=true) | Type (prefer_datetime=false) |
---|---|---|
standard , gregorian | DateTime | DateTimeStandard |
proleptic_gregorian | DateTime | DateTimeProlepticGregorian |
julian | DateTime | DateTimeJulian |
noleap , 365_day | DateTimeNoLeap | DateTimeNoLeap |
all_leap , 366_day | DateTimeAllLeap | DateTimeAllLeap |
360_day | DateTime360Day | DateTime360Day |
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
CFTime.timeencode
— Functiondata = 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.]
Accessor Functions
Dates.year
— MethodDates.year(dt::AbstractCFDateTime) -> Int64
Extract the year part of an AbstractCFDateTime
as an Int64
.
Dates.month
— MethodDates.month(dt::AbstractCFDateTime) -> Int64
Extract the month part of an AbstractCFDateTime
as an Int64
.
Dates.day
— MethodDates.day(dt::AbstractCFDateTime) -> Int64
Extract the day part of an AbstractCFDateTime
as an Int64
.
Dates.hour
— MethodDates.hour(dt::AbstractCFDateTime) -> Int64
Extract the hour part of an AbstractCFDateTime
as an Int64
.
Dates.minute
— MethodDates.minute(dt::AbstractCFDateTime) -> Int64
Extract the minute part of an AbstractCFDateTime
as an Int64
.
Dates.second
— MethodDates.second(dt::AbstractCFDateTime) -> Int64
Extract the second part of an AbstractCFDateTime
as an Int64
.
Dates.millisecond
— MethodDates.millisecond(dt::AbstractCFDateTime) -> Int64
Extract the millisecond part of an AbstractCFDateTime
as an Int64
.
Dates.microsecond
— MethodDates.microsecond(dt::AbstractCFDateTime) -> Int64
Extract the microsecond part of an AbstractCFDateTime
as an Int64
.
Dates.nanosecond
— MethodDates.nanosecond(dt::AbstractCFDateTime) -> Int64
Extract the nanosecond part of an AbstractCFDateTime
as an Int64
.
CFTime.picosecond
— MethodCFTime.picosecond(dt::AbstractCFDateTime) -> Int64
Extract the picosecond part of an AbstractCFDateTime
as an Int64
.
CFTime.femtosecond
— MethodCFTime.femtosecond(dt::AbstractCFDateTime) -> Int64
Extract the femtosecond part of an AbstractCFDateTime
as an Int64
.
CFTime.attosecond
— MethodCFTime.attosecond(dt::AbstractCFDateTime) -> Int64
Extract the attosecond part of an AbstractCFDateTime
as an Int64
.
Query Functions
Dates.daysinmonth
— Functionmonthlength = 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
monthlength = daysinmonth(t)
Returns the number of days in a month containing the date t
Example
julia> daysinmonth(DateTimeAllLeap(2001,2,1))
29
Dates.daysinyear
— Functionyearlength = 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
yearlength = daysinyear(t)
Returns the number of days in a year containing the date t
Example
julia> daysinyear(DateTimeAllLeap(2001,2,1))
366
Dates.yearmonthday
— Functionyearmonthday(dt::AbstractCFDateTime)
Simultaneously return the year, month and day parts of dt
.
Dates.yearmonth
— Functionyearmonth(dt::AbstractCFDateTime)
Simultaneously return the year and month parts of dt
.
Dates.monthday
— Functionmonthday(dt::AbstractCFDateTime)
Simultaneously return the month and day parts of dt
.
Dates.firstdayofyear
— Functionfirstdayofyear(dt::AbstractCFDateTime)
Return the first day of the year including the date dt
Dates.dayofyear
— Functiondayofyear(dt::AbstractCFDateTime)
Return the day of the year for dt with January 1st being day 1.
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.convert
— Methoddt2 = 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
.
Base.reinterpret
— Functiondt2 = 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
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)
unit | time 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.