by Peter McGoron (steward, corresponding author);
John Cowan (original design), Daphne Preston-Kendal (design and specification)
and Arvydas Silanskas (implementation)
This SRFI is currently in draft status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-285@nospamsrfi.schemers.org. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.
This SRFI details an API for storing and manipulating dates and times. This SRFI decomposes dates and times into dates (a count of days in the proleptic Gregorian calendar), clock times (time of day, independent of time zone), moments (instants in TAI), timezones (a possibly dynamic offset from UTC), timestamps (a date, clock time,and timezone), and time deltas (measurement of elapsed time). By doing this, a Scheme program can operate on days in different calendars and timestamps in different timezones.
@400000000000000000000000) is midnight in TAI on 1 January 1970;
another claims it’s 00:00:10 in TAI on 1 January 1970.moment->timestamp
and
timestamp->moment use?Representing time in computers is a very difficult task for something that people generally expect to work without issue. End users may wish to see non-Gregorian calendars, and may wish to calculate the occurrences of religious festivals on a computer. Beyond cultural reasons, the definition of time and the definition of timezones may shift around due to laws, “revolts, invasions, [and] new constitutions.”
The desirable properties of a date and time library are:
It should make doing the right thing easy, and attempts to do wrong things should look obviously wrong.
It should be modelled on existing international standards for representing and communicating dates and times, both machine-readably (ISO 8601) and internationally (CLDR).
It should allow measuring time very precisely. In particular, it should
not lose precision as time moves further away from some particular
epoch. (R7RS’s current-second procedure gets less precise
the further away from 1970 we get.)
It should allow representation of dates and times in the very distant past: specific dates are known to within a single day going back at least 2,500 years. It should also allow representation of dates and times in the very distant future. There should be no limit on the range of dates and times representable which any application, including those dealing with far-past historical events and those dealing with far-future projections, is ever likely to encounter.
It should not be specific to any calendar system, even though the Gregorian calendar (and, in industrial applications, the derived ISO week calendar) is now effectively a worldwide standard. Fortunately this is not too hard, because humans have been accurately counting the number of sunsets and sunrises for thousands of years, and their methods of organizing their counts of these events into more manageable periods of time usually follow strict arithmetic rules. A count of days from a known epoch, and knowledge of the arithmetic rules of each calendar system, is thus sufficient for representing different calendar systems.
It should be possible to use the date and time library to represent time values from sources including operating system calls, network requests, and file formats both ancient and modern.
It should be possible to accurately perform date and time arithmetic using rules that make sense to the majority of humans despite the irregularity of calendar systems. E.g. adding 2 years to March 1st, 2023 should result in an answer of March 1st, 2025, notwithstanding that the year 2024 was one day longer than a common year. Adding 1 month to January 31st should probably result in February 28th, February 29th, or maybe March 1st, because there is no year with a February 30th (except in Sweden in 1712) or February 31st.
This library does not handle any locale information: it is intended that a library that formats dates according to desired formats can be based off of this library.
This library does not account for the effects of special and general relativity [Einstein 1907].
This SRFI’s specification makes use of a TAI (International Atomic Time)-based calendar, defined as follows. A date in this TAI-based calendar consists of an integer count of days from an arbitrary epoch, and a real count of seconds in the range [0, 86400).
At midnight UTC on January 1st, 1972, TAI was exactly 10 seconds ahead of UTC. Since then, additional leap seconds have been added, increasing this difference. However, the TAI-based calendar has incremented monotonically by one second for every TAI second, rolling over and incrementing the count of days every 86,400 TAI seconds, without observing leap seconds.
Implementations of this SRFI must perform correct conversions between TAI and UTC, respecting leap seconds, for all times beginning at this epoch. Before this epoch, the number of seconds added and removed in TAI to UTC and UTC to TAI conversions is unspecified, but must always be less than or equal to 10, and round-trip conversions should produce an identical time value to their initial input.
Note: The difference between TAI and UTC was not fixed prior to 1972, and there is no computable relationship between the two, although there is some historical data available [RS/PC n.d.]. Most computer timekeeping takes UTC to be a timescale where TAI−UTC is proleptically assumed to be 10 for all times prior to 1972.
Every time a leap second [Bizouard 2026] is added or the rules for time zones change, the correspondence between TAI and UTC, and between UTC and local time in a particular location, is changed.
In order to handle changes in the leap second set and in time zone rules, implementations should use the time zone database provided by the operating system where possible. The method by which time zone database information is found is implementation-specified. Furthermore, in case of a long-running program, they should reload the database at least once every 24 hours in order to pick up any upgrades, and should use the reloaded version for all subsequent conversions between TAI and UTC or UTC-derived time zones, including for objects created before the reload.
On most Linux systems, the timezone database is stored in
/usr/share/zoneinfo, and leap second information is stored
in /usr/share/zoneinfo/leap-seconds.list.
If this is not possible, implementations may use their own database of
time zones and leap seconds.
The leap seconds list can be obtained from the IERS [IERS 2026]
and the zoneinfo database can be obtained from the
IETF [IETF 2026].
This SRFI refers at times to various calendar types.
The Rata Die is a simple count of days beginning from a standard epoch of January 1st in the year AD 1 of the proleptic Gregorian calendar, which is Rata Die 1. This is the ‘fixed’ type used consistently as a lingua franca between calendar systems by Reingold and Dershowitz.
The Gregorian calendar is used here for both its proleptic and its current era, and indeed for any potential future era in which the Gregorian calendar is superseded.
The Modified Julian Day is a simple count of days beginning from a standard epoch of November 17th, 1858 in the Gregorian calendar, which is Modified Julian Day 0.
It is an error if the following arguments do not satisfy the predicates on the right part of the table, defining
(define (exact-rational? obj) (and (rational? obj) (exact? obj)))
| Argument | Predicate |
|---|---|
| date | date? |
| ei | exact-integer? |
| er | exact-rational? |
| clock-time | clock-time? |
| moment | moment? |
| timezone | timezone? |
| timestamp | timestamp? |
| time-delta | time-delta? |
| year | exact-integer? |
| month | exact-integer? |
| day | exact-integer? |
| weekday | exact-integer? |
| hour | exact-integer? |
| minute | exact-integer? |
| second | exact-rational? |
If year, month, and day do not reference a valid Gregorian date, an error is signaled. Years are interpreted in the sense of ISO 8601 (so that 1 BC is year 0). Months are numbered starting from 1 for January, and days for the first day of each month. This means that month is always in [1,12] and that day is always positive.
If weekday is not in [1,7], an error is signaled.
If hour is not in [0,24), or if minute is not in [0,60), or if second is not in [0,60], then an error is signaled.
All libraries except (srfi 285 current-time), and
(srfi 285 tzdb) are required.
The (srfi 285) composite library exports all identifiers
that are supported by the implementation.
Date objects represent a simple count of days from some unspecified epoch. They are independent of any time zone. The same date may, depending on the location, refer to a period of 24 hours which does not overlap at all the 24 hours to which that date refers in another location. (This is because some time zones in the Pacific Ocean are more than 12 hours offset from the meridian.)
Rationale: Many events are defined as taking place on a certain date, regardless of what time zone an observer happens to be in. April 20th 2025 is Easter in 2025, regardless of whether one celebrates it in Hawaii at UTC−10:00 or 2,000 km further south in Kiribati at UTC+14:00, which do not overlap at all. Other such events include birthdays and anniversaries, as well as national holidays in countries which span more than one time zone: the fête nationale française is celebrated annually on July 14th in UTC−10:00 in French Polynesia, and in UTC+12:00 in Wallis and Futuna, which overlap by only two hours. The date object is intended to represent the dates of such events.
The date procedures are defined with reference to Reingold and Dershowitz 2018 (hereafter RD); each procedure which has a corresponding formula in that book names that formula within its specification. In RD, dates are represented by the ‘fixed’ data type, which in their Common Lisp implementation is simply an exact integer.
The procedures and procedure names here assume use of the Gregorian calendar as described above. However, this need not be intrinsic in the representation nor the use of date objects: those who wish to provide procedures for other calendar systems should use standard date objects, using one of the procedures which converts a date object to a count of days from an epoch and applying their own calendar system’s arithmetic to it, with a constructor which uses one of the procedures to convert a count of days to a date object.
In principle there is no reason for the range of dates supported by an implementation to be limited at all: a simple count of days can fit in a bignum. Implementations should support a practically unlimited range of dates. However, implementations must support all dates within the current Julian period (4713 BC to AD 3267 inclusive). Assuming a rata die-based representation, this requires at least a 22 bit signed integer to represent the count of days.
A date object corresponds to a complete representation of calendar date in ISO 8601 (section 5.2.2.1 of ISO 8601:2019). Accessors are also provided for complete representations of week dates (section 5.2.4.1).
(make-date year month day)
Returns a new date object for the date which is represented in the Gregorian calendar by the given year, month, and day.
Note:
This corresponds to fixed-from-gregorian
in RD.
(date? obj)
Returns #t if obj is a date object, or #f otherwise.
(date-ymd date)
Returns three values, the year, month, and day numbers respectively of
the date represented by the given date. The numbering convention is
identical to that used by make-date, so that the following procedure
yields an object which is date=? to d for all date object values of
d:
(lambda (d)
(call-with-values
(lambda () (date-ymd d))
make-date)))
Note:
This corresponds to gregorian-from-fixed in RD.
Returns, respectively, the first, second, and third values that would
result from applying the date-ymd procedure to their arguments.
Note: The date-year procedure
is from RD. The
other two are conveniences for the corresponding values from date-ymd,
and using some internal representations of the data datatype cannot be calculated independently of one another.
Returns a newly-allocated string representing the date from the given date object in Gregorian-based ISO 8601 format: the year formatted to at least 4 places, with a leading U+002D for years prior to 1 BC; U+002D; the month formatted to 2 places; a U+002D; and the day of the month formatted to 2 places. The year, month, and day are printed in decimal with leading 0s if needed for padding.
Note that some consumers and protocols use profiles of ISO 8601 which do not accept years before 1 BC or after AD 9999.
(date->iso-8601 (make-date 1975 12 22)) ⇒ "1975-12-22"
Note: No inverse of this or any other ISO 8601 formatting procedure is provided, because in practice the complete ISO 8601 specification offers very many possible formatting variants and usually only a subset or profile of these is used in any specific protocol. Future SRFIs, or non-SRFI libraries, should provide parsers for these profiles.
Returns the ISO week number for the date represented by the given date object.
(date-iso-week (make-date 1975 12 22)) ⇒ 51
Note: This procedure corresponds
to week from RD’s iso-from-fixed.
Returns the year number in the ISO week calendar for the date represented by the given date object.
Note: This procedure corresponds
to year from RD’s iso-from-fixed.
(date-iso-weekday date)
Returns the day of the week for the date represented by the given date object as an exact integer in the range [1, 7] where 1 is Monday.
Note: This procedure corresponds
to code from RD’s iso-from-fixed.
(date->mjd date)
Returns the Modified Julian Day number corresponding to the date represented by the given date object, as an exact integer.
Note: This procedure corresponds
to mjd-from-fixed in RD.
(mjd->date ei)
Returns a date object for the date represented by the Modified Julian Day number mjd.
Note: This procedure corresponds
to fixed-from-mjd in RD.
(date->rata-die date)
Returns the Rata Die number corresponding to the date represented by the given date object, as an exact integer.
(rata-die->date ei)
Returns a date object for the date represented by the Rata Die number ei.
;;; Calculate the occurence of Gregorian Easter. This is the
;;; date of Easter in Roman Catholicism and Protestantism.
(define (first-sunday-after date)
(let ((weekday (date-iso-weekday date)))
(rata-die->date
(+ (date->rata-die date)
(if (= weekday 7)
7
(- 7 weekday))))))
(define (gregorian-easter year)
(let* ((century (+ (floor-quotient year 100) 1))
(epact (modulo (+ 14
(* 11 (modulo year 19))
(- (floor-quotient (* 3 century) 4))
(floor-quotient (+ 5 (* 8 century)) 25))
30))
(epact (if (or (zero? epact)
(and (= epact 1) (< 10 (modulo year 19))))
(+ epact 1)
epact))
(paschal-moon (rata-die->date
(- (date->rata-die (make-date year 4 19))
epact))))
(first-sunday-after paschal-moon)))
(date=?
date1
date2
date3 …)(date<?
date1
date2
date3 …)(date<=?
date1
date2
date3 …)(date>?
date1
date2
date3 …)(date>=?
date1
date2
date3 …)These procedures measure whether one date is before, the same as, or
after another. The date=? procedure returns #t
if the date objects it is given refer to the same date and #f
otherwise. The date<? returns #t if the
each date is strictly before the next dates in the argument list. The
other procedures are defined in the usual way accordingly.
These implementation operations specific to the Gregorian calendar.
Returns #t if ei is a Gregorian leap year,
i.e., a year such that February 29th is a valid date.
It is an error if symbol is not one of
monday
tuesday
wednesday
thursday
friday
saturday
or
sunday.
Convert the symbol to the appropriate ISO weekday number.
Rationale: This procedure allows the programmer to use the common English names for the weekdays instead of the numeric equivalent. These are not meant for display, which is why only English versions are used.
Returns the number of days in month in the Gregorian year year. On leap years, the number of days in February is 29, and in non-leap-years, the number of days is 28.
Returns the day of the month that the kth appearance of the
weekday (interpreted as an ISO weekday; see
date-iso-weekday).
If there is no such appearance, returns #f.
;;; Calculates the number of complete weeks in a Gregorian month.
(define (complete-weeks year month)
(let*-values (((first-sunday) (nth-in-month year month 7 1))
((sundays rem) (floor/ (- (days-in-month year month)
first-sunday
1)
7)))
(+ sundays (if (= rem 6) 1 0))))
;;; Calculate the date of American Thanksgiving.
(define (thanksgiving year)
(make-date year 11 (nth-in-month year 11 4)))
;;; Calculate the timestamp of the American DST transition.
(define (american-dst year standard-timezone)
(date+clock-time->timestamp
(make-date year 3 (nth-in-month year 3 7 2))
(make-clock-time 2 0 0)
standard-timezone))
Returns the day of the month that the last appearance of the
weekday (interpreted as an ISO weekday; see
date-iso-weekday).
A clock time represents the time of day independently of any time zone. As with dates, some events are scheduled at certain times of day no matter which time zone the observer of that event happens to be in. No particular accommodation is made for alternative clock systems, though it should be relatively easy to convert from any astronomical notion of hours to this variant.
A clock time object has three components, respectively the hour of the day, minute of the hour, and second of the minute. The hour is in the range [0, 24); the minute in the range [0, 60), and the second in the range [0, 61). The hour and minute are stored and returned as exact integers, and the second as an exact rational.
A clock time object corresponds to a complete representation of a local time of day in ISO 8601 (section 5.3.1.2 of ISO 8601:2019), potentially with a fractional part of a second (section 5.3.1.2.a of ISO 8601:2019).
Because clock time is periodic (is 11:59 PM earlier or later than 12:01 AM?) there are no comparison procedures for clock times.
(make-clock-time hour minute second)
Returns a clock time object with the given hour, minute, and second values.
(clock-time? obj)
Returns #t if obj is a clock time object, or #f otherwise.
(clock-time-hms clock-time)
Returns three values: the hour, month, and second respectively of the clock time object clock-time.
(clock-time-hour clock-time)(clock-time-minute clock-time)(clock-time-second clock-time)Convenience procedures for retrieving the hour, minute, and second from clock-time respectively.
Moments are instants within the TAI timescale, represented using the TAI calendar — that is, they can be decomposed into a date and a count of seconds, although (as of writing, and for the foreseeable future) that count of seconds will appear to be going slightly fast compared to UTC-based civil time (for which see next section).
The count of seconds is a nonnegative exact rational in the range [0, 86,400). Moments can therefore be measured with arbitrary precision, although computer time-keeping is rarely done with more than nanosecond precision, and is rarely accurate to more than a few milliseconds.
In order to discourage mistaken use of moments to display time values intended to be civil times, direct accessors for the Gregorian calendar values of year, month, and day are not provided.
Note: ISO 8601 does not define a serialization form for dates on the TAI timescale, because TAI is officially a count of seconds with no notion of minutes, hours, or days.
Returns a moment corresponding to seconds seconds into the given date in the TAI calendar.
Returns #t if obj is a moment object, or #f otherwise.
Returns a date object for the day in the TAI calendar (as defined above) represented by the given moment object.
Returns an exact rational in the range [0, 86400) representing the second of the day in the TAI calendar which the given moment object represents.
(moment=?
moment1
moment2
moment3 …)(moment<?
moment1
moment2
moment3 …)(moment<=?
moment1
moment2
moment3 …)(moment>?
moment1
moment2
moment3 …)(moment>=?
moment1
moment2
moment3 …)These procedures measure whether one moment is before, the same as, or
after another. The moment=? procedure returns #t if the moment objects
represent exactly the same date and second measurement in
the TAI calendar. The moment<? procedure returns #t if either the date measurement
in moment1 is the less than the date measurement in moment2, or if
the date measurement in moment1 is the same as the date measurement in
moment2 but the seconds measurement in moment1 is less than the
seconds measurement in moment2, and so on. The other procedures are defined in
the usual way, accordingly, and are required to be transitive.
Timestamps are what humans usually think of as dates and times. They are instants within a specific local timescale, which, for the sake of convenience, is taken to be derived from TAI by way of UTC. (In some places, the official local time zone is defined by reference to mean solar time, but computer time-keeping invariably ignores the distinction.)
Every timestamp corresponds unambiguously to a single moment. Representationally, timestamps are a combination of a date with an hour in the range [0, 24), a minute in the range [0, 60), and a second in the range [0, 61), plus a time zone. The time zone may be interpreted in two ways to yield an unambiguous moment: either as a simple offset from UTC applicable at the corresponding moment of the timestamp; or as a dynamic set of rules applicable at different times.
Although procedures to compare the corresponding moments of two timestamps
are theoretically unambiguous, they are not directly provided because
of the unclarity of the hypothetical timestamp=? procedure: should it consider two
timestamps the same if they have the same corresponding moment even
if their time zones are different? Considered in the context of the
less-than or greater-than operators, it seems so; but strictly, those
timestamps are not the same as one another: adding two time-deltas
to these timestamps may result in different moments. So users must explicitly
convert to moments and use the moment comparison procedures.
A timestamp object corresponds to a complete representation for calendar date and time of day in ISO 8601 (section 5.4.2.1 of ISO 8601:2019); a timestamp always includes a time zone component, which, as an extension to ISO 8601, may consist of information about the periods of validity of time shift designators for producers or consumers of date and time information.
If not given, fold defaults to 0.
Constructs a timezone object with the corresponding fields.
For information about fold, see
timestamp-fold.
If the constructed timestamp does not refer to any time on the TAI scale, an error is signaled.
Note: It is possible for a seemingly valid timestamp to not be valid in certain timezones. For example, the times 2:00 to 2:59 did not exist on March 8th, 2026 in Eastern Time, because at 2:00 EST on March 8th, 2026, the Eastern United States did its annual switch to Eastern Daylight Time. These sort of clock changes may be unpredictable.
Equivalent to
make-timestamp, using the
appropriate accessors for date and clock-time.
(timestamp? obj)
Returns #t if obj is a timestamp object,
and #f if obj is any other object.
(moment->timestamp
moment timezone)
Returns a timestamp object reflecting the civil time in the time zone timezone at the instant represented by moment.
(timestamp->moment
timestamp)
Returns a moment object corresponding to the instant when timestamp was observed in its time zone.
(timestamp-in-timezone
timestamp timezone)
Perform a time zone conversion on a timestamp, returning a timestamp which reflects the same corresponding moment but with a different time zone. Equivalent to:
(moment->timestamp (timestamp->moment timestamp) timezone)
(posix-time->utc-timestamp
posix-seconds [nanoseconds])
Returns a timestamp in the UTC time zone corresponding to the given POSIX time (a count of seconds since 1 January 1970, minus leap seconds).
If nanoseconds is not given and if posix-seconds is not an exact or inexact rational, then an error is signaled. If nanoseconds is given, and if either posix-seconds or nanoseconds is not an exact integer, then an error is signaled.
If the POSIX time corresponds to a number that does not have a TAI equivalent (which can only happen if negative leap seconds are introduced), then an error is signalled.
(timestamp->utc-posix-time
timestamp)
Returns an exact rational representing the timestamp as a count of seconds since midnight UTC on 1 January 1970, minus leap seconds. The timestamp object may be in any time zone. If the timestamp corresponds to a moment that is within a leap second (i.e. if the result of the timestamp-second procedure is greater than or equal to 60), one second is subtracted from the timestamp before the conversion takes place.
Note:
The fact that the names of this procedure and of posix-time->utc-timestamp
are not perfect inverses is intended to reflect the fact that the two do
not round-trip losslessly because of the leap second problem with POSIX
time. Thus, doing a wrong thing (attempting a round trip conversion in
the expectation of losslessness) will look wrong.
(timestamp-date
timestamp)
Returns a date object for the date represented by the timestamp object. The
returned date object is not guaranteed to be the same as any other date
object in the sense of eqv? or eq?, including date objects returned
by previous invocations of this procedure on the same timestamp object.
(timestamp-ymd
timestamp)(timestamp-year
timestamp)(timestamp-month
timestamp)(timestamp-day
timestamp)
Convenience procedures which are respectively the equivalents of:
(date-ymd (timestamp-date timestamp)) (date-year (timestamp-date timestamp)) (date-month (timestamp-date timestamp)) (date-day (timestamp-date timestamp))
(timestamp-clock-time
timestamp)
Returns a clock-time object representing the hour, minute, and second of
the date represented by the timestamp. The returned date object is not
guaranteed to be the same as any other clock-time object in the sense
of eqv? or eq?, including clock-time objects returned by previous
invocations of this procedure on the same timestamp object.
(timestamp-hms
timestamp)(timestamp-hour
timestamp)(timestamp-minute
timestamp)(timestamp-second
timestamp)
Convenience procedures which are respectively the equivalents of:
(clock-time-hms (timestamp-clock-time timestamp)) (clock-time-hour (timestamp-clock-time timestamp)) (clock-time-minute (timestamp-clock-time timestamp)) (clock-time-second (timestamp-clock-time timestamp))
Returns the time zone object which is used to calculate the corresponding
moment. Unlike timestamp-date
and timestamp-clock-time, the returned
object is guaranteed to be the same in the sense of eqv? as the time
zone object that was passed to the procedure which originally constructed
the timestamp object.
Returns an exact integer 0 or 1 representing the number of times this timestamp has repeated within the time zone (considered as a time zone object, not as an offset) at the corresponding moment. This integer is almost always 0 except immediately following a switch from summer time to winter time. In particular, if the time zone object used in the timestamp consists only of a fixed offset, this procedure always returns 0.
Returns a time delta object representing the offset from UTC in effect in the timestamp’s timezone at the corresponding moment.
Returns a string that is an abbreviation of the name of the timezone, or
#f if no abbreviation is available.
Note: The returned string corresponds to the “time zone designation” in an RFC9636 TZif database file. Note that the returned time zone designation may be ambiguous: for example, IST can refer to India Standard Time, Ireland Standard Time, or Israel Standard Time.
Returns #t if the timezone is considered to
be in daylight savings time, and #f otherwise.
Note: Many locations do not observe daylight savings time. Some US states, such as Florida and Washington, want to switch to permanent daylight savings time. The value of this flag should be taken from the appropriate field in the timezone database.
Returns a newly-allocated string containing the date and time specified by the given timestamp together with the offset from UTC in hours, minutes, and possibly seconds.
The first part of the returned string consists of the ISO 8601 formatted
date for the timestamp, exactly as if
date->iso-8601 had been called
on the result of applying timestamp-date to the timestamp object. Then
follows U+0054 (T); the hour formatted to 2 places;
U+003A (-); the minute formatted to two places; U+003A (-);
and the integral part of the second formatted to two places.
If the second is not an exact integer, there follows a U+002E
(.)
followed by the fractional part of the second. Because
seconds in a timestamp are represented as an exact rational, it may
not be possible to convert them exactly to a terminating decimal
number. This procedure must therefore format seconds to at
most nanosecond precision. If the fractional part of the second can
be precisely represented in fewer than 9 places, there must be no
trailing zeroes.
Then follows the time zone part, which is formatted as follows. If the
time delta object resulting from calling
timestamp-timezone-offset on
the timestamp object is equivalent to a duration of zero seconds, the time
zone part must be a single U+005A (Z). If the time
delta object contains a seconds part or a part for any time unit greater
than or equal to 24 hours, an error is signaled. If the
time delta is negative, there follows a U+002D (-), otherwise a
U+002B (+); then the hours part of the time delta formatted to two
places; a U+003A (:); and the minutes part of the time delta formatted
to two places.
Note:
The formatting rules described here are compatible with the IETF
and W3C date-time formats, except that, as noted under
date->iso-8601,
these profiles of ISO 8601 do not support BC or far-future AD dates.
While
some historical time zones do include a seconds part, these may not be
represented even in the full ISO 8601 format.
See the remark under date->iso-8601 on why no
inverse of this procedure is provided.
A time zone is a set of rules for calculating local clock-times in some specific location. It is usually an offset from UTC and a dynamic set of rules for determining when a location changes from one offset to another for its local time, either according to standard rules (as for daylight savings/summer time in most of the world) or according to one-off political changes (as for summer time in the United Kingdom before the 1990s, or for more permanent alterations, such as countries in the Pacific Ocean deciding to hop to the other side of the International Date Line).
A time zone object is modelled on entries from the Olson/ICANN tz database, but on operating systems which provide some other means of finding time zone change information, it should be possible to provide a useful implementation of timestamps and time zones with the information they provide.
Note: When converting between
timestamps, which are based on UTC, and moments, which are based on
TAI, time zone data from the Olson/ICANN tz database should be taken
from the right variant of each time zone, not the default
posix variant. Note that some operating systems do not
include the right versions of the time zone data; in this
case, the leapseconds file should be provided anyway, and will
have to be combined with the default posix data.
Returns #t if obj is a time zone object and #f otherwise.
(utc-timezone)
A time zone which adds leap seconds to TAI only.
(utc-offset-timezone
time-delta)
Returns a time zone object which is consistently offset from UTC by
the amount of time specified by the time-delta object time-delta. The new
time zone object is guaranteed to be distinct from all existing time
zone objects in the sense of the eqv? procedure.
It is an error if time-delta does not have zero components for everything except the hour and the minute.
Rationale: This procedure is intended to be used only in unparsing dates from formats such as ISO 8601, which include an offset from UTC but not location information. Note that any date and time arithmetic done on a timestamp created this way is not guaranteed to correctly reflect local time in the location of the person who generated the time string, because the offset from UTC in that location might be different at the moment for the calculated timestamp than at the moment when the timestamp was created.
(timezone-earliest
timezone)
Returns the moment that is the earliest entry in the timezones transition list. Moments prior to this are based off of projection of the earliest entry, and may be less accurate due to a lack of historical data.
If the timezone is definitionally accurate for all previous times
(for example, a fixed offset from UTC), then this procedure returns
#f.
(timezone-latest
timezone)
Returns the moment that is the latest entry in the timezones transition list. Moments subsequent to this are based off of projection from the latest entry and known regular timezone changes (such as daylight savings time), and may not be accurate due to unpredictable changes such as new laws.
If a timezone is a fixed offset from UTC, then the latest moment is the expiry of the leap second table.
If the timezone is definitionally accurate for all subsequent times
(for example, a fixed offset from UTC when leap seconds have been
abolished), then this procedure returns
#f.
(reload-timezone-data!)
Force-reload all relevant timezone data, such as the current system timezone and the timezone database.
Note: If the implementation does not read timezone data from the system, this procedure probably does nothing. As stated above, the implementation should do the equivalent of calling this procedure once every 24 hours to update the timezone database.
(system-timezone)
Returns a time zone object corresponding to the current operating system or user time zone setting.
This setting should read from the TZ environment variable.
(tz-timezone string)
Returns a time zone object corresponding to the Olson/ICANN tz database entry for string. An error is signaled if the time zone string is not known to the system.
(tz-timezones)
Returns a list of the names of all Olson/ICANN tz database entries known
to the system as strings, i.e. all strings that would be valid argument
to the tz-timezone procedure, in an unspecified order.
An implementation should consult the environment variable
TZPATH for the location of the timezone database.
A time-delta represents a measurement of an amount of time, although they do not necessarily represent an actual duration of time on the absolute TAI or UTC time scale, except maybe in an abstract sense. (A time-delta used to represent a time zone offset could be interpreted as meaning ‘when it is midnight in UTC, you will have to wait minus this time delta amount of time before it is midnight in that time zone’, but this is abstruse.)
A time-delta is a product type whose components are years, months, weeks, days, hours, minutes, and seconds. All the components are exact integers, except for seconds, which is an exact rational. If the value of one of these components is 0, the time-delta object may also be described as not having that component.
A time-delta object corresponds to a duration in ISO 8601 (section 5.5.2 of ISO 8601:2019).
Note: Versions of ISO 8601 prior to the 2019 revision did not have the concept of a negative duration.
(time-delta? obj)
Returns #t if obj is a time-delta object,
and #f otherwise.
(years-delta ei)(months-delta ei)(weeks-delta ei)(days-delta ei)(hours-delta ei)(seconds-delta er)Returns a time-delta object with the corresponding number of the unit within the name of the procedure.
(time-delta-years time-delta)(time-delta-months time-delta)(time-delta-weeks time-delta)(time-delta-days time-delta)(time-delta-hours time-delta)(time-delta-seconds time-delta)Returns the value of the corresponding component in time-delta.
(time-delta+
time-delta1
time-delta2)
Returns a time-delta object which is the sum of the given time-delta arguments. Specifically, each component is summed; no overflow of smaller units to larger ones is calculated.
(time-delta-negate
time-delta)
Returns a time-delta object which has all its components negated, yielding a time-delta of equivalent magnitude but opposite direction.
(date+
date
time-delta)
It is an error if time-delta is has hours, minutes, or seconds components.
Calculate the date that results from adding the time-delta to the date object date and returns a new date object representing that date. The calculation is performed according to Annex D of the ISO 8601-2:2019 specification.
(timestamp+
timestamp
time-delta)
Calculate a timestamp that results from adding the time-delta to the timestamp object timestamp, returning a new timestamp object containing the result. The calculation is performed according to Annex D of the ISO 8601-2:2019 specification.
(current-moment)
Returns the current time as a moment object.
Note:
The R7RS provides only the
current-second procedure, which provides the current time as
an inexact number. Implementations of this SRFI should use exact sources
of the current time when possible.
(current-utc-timestamp)
Equivalent to (moment->timestamp (current-moment) utc-timezone).
(current-system-timestamp)
Equivalent to (moment->timestamp (current-moment) (system-timezone)).
The following Scheme code demonstrates the use of the epoch procedures on dates to implement constructors for a non-Gregorian calendar; specifically, the French republican calendar in its later, purely arithmetic form. (Implementing the astronomical form is an exercise for the reader — a long, but not particularly intellectually challenging one, for anyone armed with a copy of RD.)
(define proclamation-de-la-république (make-date 1792 9 22))
(define (an-bisextil? an)
(and (= (modulo an 4) 0)
(not (memv (modulo an 4) '(100 200 300)))
(not (= (modulo an 4000) 0))))
(define (construire-date an mois jour)
(rata-die->date
(+ (date->rata-die proclamation-de-la-république)
-1
(* 365 (- an 1))
(floor-quotient (- an 1) 4)
(- (floor-quotient (- an 1) 100))
(floor-quotient (- an 1) 400)
(- (floor-quotient (- an 1) 4000))
(* 30 (- mois 1))
jour)))
(define (date-amj date)
(let* ((rd (date->rata-die date))
(proclamation
(date->rata-die proclamation-de-la-république))
(approx
(+ (floor (* 4000/1460969
(+ (- rd proclamation) 2)))
1))
(an
(if (date<? date (construire-date approx 1 1))
(- approx 1)
approx))
(mois
(+ 1 (floor (* 1/30 (- rd (date->rata-die
(construire-date an 1 1)))))))
(jour
(+ 1 rd (- (date->rata-die (construire-date an mois 1))))))
(values an mois jour)))
(define (date-an date)
(let-values (((an mois jour) (date-amj date))) an))
(define (date-mois date)
(let-values (((an mois jour) (date-amj date))) mois))
(define (date-jour date)
(let-values (((an mois jour) (date-amj date))) jour))
A program or library which uses these procedures to construct and access the day, month, and year of native date objects can interoperate transparently with code which works with other calendar systems.
A sample implementation written in R7RS with a test suite is included in the SRFI repository. It uses a proleptic Gregorian date for the representation of date objects, and uses SRFI 170 for access to the filesystem. It does not refresh timezone data periodically.
The sample implementation checks the TZ environment variable
and the /etc/localtime file for the system timezone.
It checks TZPATH and (by default) /usr/share/zoneinfo
for the timezone database and the leap seconds table.
The code is not written with performance in mind, although it caches timezones until refresh. The code theoretically handles negative leap seconds, but you should not count on that.
The implementation has been tested with Gauche 0.9.15 and Chibi Scheme 0.11.0.
Daphne Preston-Kendal wrote most of this proposal, and Arvydas Silanskas wrote most of the sample implementation. This proposal is posted with their permissions.
This SRFI is derived, at some length, from an original proposal by John Cowan.
Arvydas Silanskas also found numerous mistakes and omissions.
Thanks to the following people for suggesting new features in the specification: Arthur Gleckler (clock-time type) and Antero Mejr (ability to create timestamp/moment conversions independently of the standard implementation, and explicit note that the method of finding time zone database information is implementation-specific).
Thanks to the following people for finding errors in the specification: Vincent Manis and Marc Nieper-Wißkirchen.
Thanks to the following people for general encouragement: Alex Shinn and Wolfgang Corcoran-Mathe.
leap-seconds.txt. Retrieved from
https://hpiers.obspm.fr/iers/bul/bulc/ntp/leap-seconds.list
on September 5th, 2026.tai-utc.dat. U.S. Naval Observatory. Retrieved from
https://maia.usno.navy.mil/ser7/tai-utc.dat on September 13th, 2026.© 2026 Peter McGoron, Daphne Preston-Kendal, Arvydas Silanskas (implementation)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.