TMDATE(2)                                                     TMDATE(2)

tmnow, tzload, tmtime, tmparse, tmfmt, tmnorm – convert date and time

#include <u.h>
#include <libc.h>
typedef struct Tm Tm;
typedef struct Tmfmt Tmfmt;
typedef struct Tzone Tzone;
struct Tm {
int       nsec;      /* nanoseconds (range 0..1e9) */
int       sec;       /* seconds (range 0..59) */
int       min;       /* minutes (0..59) */
int       hour;      /* hours (0..23) */
int       mday;      /* day of the month (1..31) */
int       mon;       /* month of the year (0..11) */
int       year;      /* C.E year – 1900 */
int       wday;      /* day of week (0..6, Sunday = 0) */
int       yday;      /* day of year (0..365) */
char      zone[];    /* time zone name */
int       tzoff;     /* time zone delta from GMT, seconds */
Tzone    *tz;        /* the time zone (optional) */
Tzone *tzload(char *name);
Tm      *tmnow(Tm *tm, Tzone *tz);
Tm      *tmtime(Tm *tm, vlong abs, Tzone *tz);
Tm      *tmtimens(Tm *tm, vlong abs, int ns, Tzone *tz);
Tm      *tmparse(Tm *dst, char *fmt, char *tm, Tzone *zone, char **ep);
vlong    tmnorm(Tm *tm);
Tmfmt    tmfmt(Tm *tm, char *fmt);
void     tmfmtinstall(void);

This family of functions handles simple date and time manipulation.
Time zones are loaded by name. They can be specified as the abbreviated timezone name, the full
timezone name, the path to a timezone file, or an absolute offset in the HHMM form.
When given as a timezone, any instant–dependent adjustments such as leap seconds and daylight
savings time will be applied to the derived fields of struct Tm, but will not affect the absolute time.
The time zone name local always refers to the time in /env/timezone. The nil timezone always
refers to GMT.
Tzload loads a timezone by name. The returned timezone is cached for the lifetime of the pro–
gram, and should not be freed. Loading a timezone repeatedly by name loads from the cache, and
does not leak.
Tmnow gets the current time of day in the requested time zone.
Tmtime converts the second resolution timestamp 'abs' into a Tm struct in the requested time–
zone. Tmtimens does the same, but with a nanosecond accuracy.
Tmtimens is identical to tmtime, but accepts a nanosecond argument.
Tmparse parses a time from a string according to the format argument. Leading whitespace is
ignored. The point at which the parsing stopped is returned in ep. If ep is nil, trailing garbage is
ignored. The result is returned in the timezone requested. If there is a timezone in the date, and
a timezone is provided when parsing, then the zone is shifted to the provided timezone. Parsing is
The format argument contains zero or more of the following components:
TMDATE(2)                                                     TMDATE(2)
Represents the year. YY prints the year in 2 digit form.
The month of the year, in unpadded numeric, padded numeric, short name, or long name,
The day of month in unpadded or padded numeric form, respectively.
The day of week in numeric, short or long name form, respectively.
h, hh
The hour in unpadded or padded form, respectively
m, mm
The minute in unpadded or padded form, respectively
s, ss
The second in unpadded or padded form, respectively
t, tt
The milliseconds in unpadded and padded form, respectively. u, uu, uuu, uuuu The
microseconds in unpadded. padded form modulo milliseconds, or unpadded, padded forms
of the complete value, respectively. n,    nn,    nnn,    nnnn,    nnnnn,    nnnnnn The
nanoseconds in unpadded and padded form modulo milliseconds, the unpadded and pad–
ded form modulo microseconds, and the unpadded and padded complete value, respec–
The timezone in [+–]HHMM and [+–]HH:MM, and named form, respectively. If the named
timezone matches the name of the local zone, then the local timezone will be used. Other–
wise, we will attempt to use the named zones listed in RFC5322.
a, ALower and uppercase 'am' and 'pm' specifiers, respectively.
Quoted text, copied directly to the output.
_     When formatting, this inserts padding into the date format. The padded width of a field is
the sum of format and specifier characters combined. When For example, __h will format to
a width of 3. When parsing, this acts as whitespace.
?     When parsing, all formats of the following argument are tried from most to least specific.
For example, ?M will match January, Jan, 01, and 1, in that order. When formatting, ? is
~     When parsing a date, this slackens range enforcement, accepting out of range values such
as January 32, which would get normalized to February 1st.
Any characters not specified above are copied directly to output, without modification.
If the format argument is nil, it makes an attempt to parse common human readable date formats.
These formats include ISO–8601, RFC3339 and RFC2822 dates.
Tmfmt produces a format description structure suitable for passing to fmtprint(2). If fmt is nil, we
default to the format used in ctime(2). The format of the format string is identical to tmparse.
When parsing, any amount of whitespace is treated as a single token. All string matches are case
insensitive, and zero padding is optional.
Tmnorm takes a manually adjusted Tm structure, and normalizes it, returning the absolute times–
tamp that the date represents. Normalizing recomputes the year, mon, mday, hr, min, sec and
tzoff fields. If tz is non–nil, then tzoff will be recomputed, taking into account daylight savings for
the absolute time. The values not used in the computation are recomputed for the resulting abso–
lute time. All out of range values are wrapped. For example, December 32 will roll over to Jan 1 of
TMDATE(2)                                                     TMDATE(2)
the following year.
Tmfmtinstall installs a time format specifier %τ. The time format behaves as in tmfmt

All examples assume tmfmtinstall has been called.
Get the current date in the local timezone, UTC, and US_Pacific time. Print it using the default for–
Tm t;
Tzone *zl, *zp;
if((zl = tzload("local") == nil)
sysfatal("load zone: %r");
if((zp = tzload("US_Pacific") == nil)
sysfatal("load zone: %r");
print("local: %τ\n", tmfmt(tmnow(&t, zl), nil));
print("gmt: %τ\n", tmfmt(tmnow(&t, nil), nil));
print("eastern: %τ\n", tmfmt(tmnow(&t, zp), nil));
Compare if two times are the same, regardless of timezone. Done with full, strict error checking.
#define Fmt "?WWW, ?MM ?DD hh:mm:ss ?Z YYYY"
Tm a, b;
char *e;
if(tmparse(&a, Fmt, "Tue Dec 10 12:36:00 PST 2019", &e) == nil)
sysfatal("failed to parse: %r");
if(*e != '')
sysfatal("trailing junk %s", e);
if(tmparse(&b, Fmt, "Tue Dec 10 15:36:00 EST 2019", &e) == nil)
sysfatal("failed to parse: %r");
if(*e != '')
sysfatal("trailing junk %s", e);
if(tmnorm(a) == tmnorm(b) && a.nsec == b.nsec)
Convert from one timezone to another.
Tm here, there;
Tzone *zl, *zp;
if((zl = tzload("local")) == nil)
sysfatal("load zone: %r");
if((zp = tzload("US_Pacific")) == nil)
sysfatal("load zone: %r");
if(tmnow(&here, zl) == nil)
sysfatal("get time: %r");
if(tmtime(&there, tmnorm(&here), zp) == nil)
sysfatal("shift time: %r");
Add a day. Because cross daylight savings, only 23 hours are added.
Tm t;
char *date = "Sun Nov 2 13:11:11 PST 2019";
if(tmparse(&t, "W MMM D hh:mm:ss z YYYY, date, nil) == nil)
print("failed to arse");
TMDATE(2)                                                     TMDATE(2);
print("%τ", &t); /*    Mon Nov 3 13:11:11 PST 2019 */

Checking the timezone name against the local timezone is a dirty hack. The same date string may
parse differently for people in different timezones.
Tmparse and ctime don't mix. Tmparse preserves timezone names, including names like '+0200'.
Ctime expects timezone names to be exactly three characters. Use the format character instead
of ctime.
The timezone information that we ship is out of date.
The Plan 9 timezone format has no way to express leap seconds.
We provide no way to manipulate timezones.