A library for handling dates and times across arbitrary calendar systems
-
Large range and high precision.
Calends understands dates 262 seconds into the future or past, in units as small as 10-45 seconds – that's over 146 billion years into the past or future (146 138 512 313 years, 169 days, 10 hours, 5 minutes, and 28 seconds from CE 1970 Jan 01 00:00:00 TAI, Gregorian), at resolutions smaller than Planck Time (54x10−45 seconds, and the smallest meaningful duration even on the quantum scale). That encompasses well beyond the expected lifespan of the Universe, at resolutions enough to represent quantum events.
-
Supports parsing, formatting, and calculating offsets between date (and time) values in multiple calendar systems.
Supported out of the box are the following (all systems are proleptic [extrapolated beyond the officially-defined limits] unless specified otherwise):
-
Unix time: A count of the number of seconds since CE 1970 Jan 01 00:00:00 UTC
-
TAI64: Essentially Unix time plus 262, but using TAI seconds instead of UTC seconds, so times can be converted unambiguously (UTC uses leap seconds to keep the solar zenith at noon, while TAI is a simple, unadjusted count). Calends supports an extended version of this spec, with three more components, to encode out to 45 places instead of just 18; this is also actually the internal time scale used by Calends itself, which is how it can support such a broad range of dates at such a high resolution.
-
Automatic updates for handling leap second insertions
-
Estimation of undefined past and future leap second insertions
-
-
Gregorian: The current international standard calendar system
-
Julian: The previous version of the Gregorian calendar
-
Julian Day Count: A count of days since BCE 4713 Jan 01 12:00:00 UTC on the proleptic Julian Calendar
-
Hebrew: ...
-
Persian: ...
-
Chinese: Several variants
-
Meso-American: Commonly called Mayan, but used by several cultures in the region
-
Discordian: ...
-
Stardate: Yes, the ones from Star Trek™; several variants exist
-
-
Encodes both time spans (
start != end
,duration != 0
) and instants (start == end
,duration == 0
) in a single interface.The library treats the time values it encodes as
[start, end)
sets (that is, thestart
point is included in the range, as is every point betweenstart
andend
, but theend
point itself is not included in the range). This allowsduration
to accurately beend - start
in all cases. (And yes, that also means you can create spans withduration < 0
.) -
Supports calculations and comparisons on spans and instants.
Addition, subtraction, intersection, combination, gap calculation, overlap detection, and similar operations are all supported directly on Calends values.
-
Conversion to/from native date/time types.
While this is possible by using a string representation as an intermediary, in either direction, some data and precision is lost in such a conversion. Instead, Calends supports conversion to and from such types directly, preserving as much data and accuracy as each native type provides.
-
Geo-temporally aware.
The library provides methods for passing a location instead of a calendar system, and selecting an appropriate calendar based on which was most common in that location at that point in time. (Some guess work is involved in this process when parsing dates, so it is still preferred to supply the calendar system, if known, when parsing.)
-
Well-defined interfaces for extending the library.
Add more calendar systems, type conversions, or geo-temporal relationships without forking/modifying the library itself.
The steps here will vary based on which programming language(s) you're using.
For Golang, simply run go get github.com/danhunsaker/calends
, and then place
"github.com/danhunsaker/calends"
in the import
wherever you intend use it.
Other languages will use Calends through a language-specific wrapper around the
compiled Golang lib. So for PHP, as an example, you'd install the calends
extension, probably via PECL.
Calends exposes a very small handful of things for use outside the library
itself. One is the Calends
class, which should be the only interface users of
the library ever need to touch. Another is the TAI64NAXURTime
class, used to
store and manipulate the instants of time which make up a Calends
instance.
The rest are interfaces for extending the library's functionality.
Calends
objects are immutable - all methods return a new Calends
object
where they might otherwise alter the current one. This is true even of the
Set*()
methods. This has the side effect of using more memory to perform
manipulations than updating values on an existing object would. It makes many
operations safer, though, than mutable objects would allow.
Language-specific documentation is available, and may give a more concrete idea of how to use Calends in a given language/environment, but the general usage information given here should be valid for all of them.
-
calends.Create(value, calendar, format)
Creates a new
Calends
object, usingcalendar
to select a calendar system, andformat
to parse the contents ofvalue
into theCalends
object's internal instants. The contents ofvalue
can vary based on the calendar system itself, but generally speaking can always be a string. In any case, the value can always be a string→value map (associative array, hash, or whatever your language of choice prefers to call it), where the keys are any two ofstart
,end
, andduration
. If all three are provided,duration
is ignored in favor of calculating it directly. If only one is provided,value
is passed to the calendar system itself unchanged. The calendar system then convertsvalue
to aTAI64TAI64NAXURTime
instant, which theCalends
object sets to the appropriate internal value.
-
c.Date(calendar, format)
/c.EndDate(calendar, format)
Retrieves the start or end date of the
Calends
objectc
as a string. The value is generated by the calendar system given incalendar
, according to the format string informat
. -
c.Duration()
Retrieves the duration of the
Calends
objectc
as an arbitrary-precision floating point number. This value will be0
if theCalends
object is an instant.
-
c.SetDate(value, calendar, format)
/c.SetEndDate(value, calendar, format)
Sets the start or end date of a
Calends
object, based on theCalends
objectc
. The inputs are the same as forCreate()
, above, except the string→value map option isn't available, since you're already specifically setting the start or end value explicitly, depending on which method you call. -
c.SetDuration(duration, calendar)
/c.SetDurationFromEnd(duration, calendar)
Sets the duration of a
Calends
object, adjusting the end or start point accordingly, based on theCalends
objectc
. Theduration
value is interpreted by the calendar system given incalendar
, so is subject to any of its rules.SetDurationFromEnd()
will adjust the start point, using the end as the anchor for the duration.
-
c.Add(offset, calendar)
/c.AddFromEnd(offset, calendar)
Increases the corresponding date in the
Calends
objectc
byoffset
, as interpreted by the calendar system given incalendar
. -
c.Subtract(offset, calendar)
/c.SubtractFromEnd(offset, calendar)
Works the same as
Add()
/AddFromEnd()
, except it decreases the corresponding date, rather than increasing it. -
c.Next(offset, calendar)
/c.Previous(offset, calendar)
Returns a
Calends
object ofoffset
duration (as interpreted by the calendar system given incalendar
), which abuts theCalends
objectc
. Ifoffset
is empty,calendar
is ignored, and the duration ofc
is used instead.
-
c1.Merge(c2)
Returns a
Calends
object spanning from the earliest start date to the latest end date betweenCalends
objectsc1
andc2
. -
c1.Intersect(c2)
Returns a
Calends
object spanning the overlap betweenCalends
objectsc1
andc2
. Ifc1
andc2
don't overlap, returns an error. -
c1.Gap(c2)
Returns a
Calends
object spanning the gap betweenCalends
objectsc1
andc2
. Ifc1
andc2
overlap (and there is, therefore, no gap to return), returns an error.
-
c1.Difference(c2, mode)
Returns the difference of
Calends
objectc1
minusc2
, usingmode
to select which values to use in the calculation. Validmode
s include:start
- use the start date of bothc1
andc2
duration
- use the duration of bothc1
andc2
end
- use the end date of bothc1
andc2
start-end
- use the start ofc1
, and the end ofc2
end-start
- use the end ofc1
, and the start ofc2
-
c1.Compare(c2, mode)
Returns
-1
ifCalends
objectc1
is less thanCalends
objectc2
,0
if they are equal, and1
ifc1
is greater thanc2
, usingmode
to select which values to use in the comparison. Valid modes are the same as forDifference()
, above. -
c1.Contains(c2)
Returns a boolean value indicating whether
Calends
objectc1
contains all ofCalends
objectc2
. -
c1.Overlaps(c2)
Returns a boolean value indicating whether
Calends
objectc1
overlaps withCalends
objectc2
. -
c1.Abuts(c2)
Returns a boolean value indicating whether
Calends
objectc1
abutsCalends
objectc2
(that is, whether one begins at the same instant the other ends). -
c1.IsSame(c2)
Returns a boolean value indicating whether
Calends
objectc1
covers the same span of time asCalends
objectc2
. -
c1.IsShorter(c2)
/c1.IsSameDuration(c2)
/c1.IsLonger(c2)
Returns a boolean comparing the duration of
Calends
objectsc1
andc2
. -
c1.IsBefore(c2)
/c1.StartsBefore(c2)
/c1.EndsBefore(c2)
Returns a boolean comparing
Calends
objectc1
with the start date ofCalends
objectc2
.IsBefore
compares the entirety ofc1
withc2
;StartsBefore
compares only the start date ofc1
;EndsBefore
compares only the end date ofc1
. -
c1.IsDuring(c2)
/c1.StartsDuring(c2)
/c1.EndsDuring(c2)
Returns a boolean indicating whether
Calends
objectc1
lies between the start and end dates ofCalends
objectc2
.IsDuring
compares the entirety ofc1
withc2
;StartsDuring
compares only the start date ofc1
;EndsDuring
compares only the end date ofc1
. -
c1.IsAfter(c2)
/c1.StartsAfter(c2)
/c1.EndsAfter(c2)
Returns a boolean comparing
Calends
objectc1
with the end date ofCalends
objectc2
.IsAfter
compares the entirety ofc1
withc2
;StartsAfter
compares only the start date ofc1
;EndsAfter
compares only the end date ofc1
.
Currently supported calendar systems, and the options available for each, are listed below. Formats in bold are the default format for that calendar.
-
tai64
- Formats
- Calends
TAI64NAXURTime
object (input only) - string with TAI instant representation in one of the following layouts:
decimal
- number of seconds since 1970.01.01 00:00:00 TAItai64
- TAI64 External Representation; the hexadecimal version ofdecimal
plus 2^62, with no fractional seconds (16 hexits total)tai64n
- TAI64N External Representation;tai64
with 9 decimal places encoded as 8 additional hexadecimal digits (24 hexits total)tai64na
- TAI64NA External Representation;tai64n
with 9 more decimal places (18 total) encoded as 8 additional hexadecimal digits (32 hexits total)tai64nax
- TAI64NAX External Representation;tai64na
with 9 more decimal places (27 total) encoded as 8 additional hexadecimal digits (40 hexits total)tai64naxu
- TAI64NAXU External Representation;tai64nax
with 9 more decimal places (36 total) encoded as 8 additional hexadecimal digits (48 hexits total)tai64naxur
- TAI64NAXUR External Representation;tai64naxu
with 9 more decimal places (45 total) encoded as 8 additional hexadecimal digits (56 hexits total)
- Calends
- Offsets
- Calends
TAI64NAXURTime
object - arbitrary-precision floating point number of seconds
- string with
decimal
format layout (above)
- Calends
- Formats
-
unix
- Formats
- number of seconds since 1970-01-01 00:00:00 UTC
- input can be integer or float, in either numeric or string representation
- output uses Golang
fmt.Print()
conventions
- Offsets
- number of seconds
- same input formatting as above
- number of seconds
- Formats
-
gregorian
- Formats
- Golang
time.Time
object (input only) - Golang
time
package format strings (RFC1123 layout) - C-style
strptime()
/strftime()
format strings
- Golang
- Offsets
- Golang
time.Duration
object - string with Gregorian time units
- must be relative times
- use full words instead of abbreviations for time units (such as
seconds
instead of justs
)
- Golang
- Formats
Pull requests are always welcome! That said, please be open to discussing the PR content, and possibly revising it if requested. Not all requests can be merged, and not all changes are desired.
Report all security-related issues to dan (dot) hunsaker (plus) calends (at) gmail, and use PGP or GPG protections on your message. Security issues will be addressed internally before making any vulnerability announcements.