TMDATE(2)                                               TMDATE(2)

     NAME
          tmnow, tzload, tmtime, tmparse, tmfmt, tmnorm - convert date
          and time

     SYNOPSIS
          #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);

     DESCRIPTION
          This family of functions handles simple date and time manip-
          ulation.

          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.

     Page 1                       Plan 9            (printed 12/22/24)

     TMDATE(2)                                               TMDATE(2)

          Tzload loads a timezone by name. The returned timezone is
          cached for the lifetime of the program, 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 timezone.  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 case-
          insensitive

          The format argument contains zero or more of the following
          components:

          Y, YY, YYYY
               Represents the year.  YY prints the year in 2 digit
               form.

          M, MM, MMM, MMMM
               The month of the year, in unpadded numeric, padded
               numeric, short name, or long name, respectively.

          D, DD
               The day of month in unpadded or padded numeric form,
               respectively.

          W, WW, WWW
               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

     Page 2                       Plan 9            (printed 12/22/24)

     TMDATE(2)                                               TMDATE(2)

          t, tt, ttt
               The milliseconds in unpadded and padded form, respec-
               tively.

          u, uu, uuu, uuuu
               The microseconds in unpadded. padded form modulo mil-
               liseconds, or unpadded, padded forms of the complete
               value, respectively.

          n, nn, nnn, nnnn, nnnnn, nnnnnn
               The nanoseconds in unpadded and padded form modulo mil-
               liseconds, the unpadded and padded form modulo
               microseconds, and the unpadded and padded complete
               value, respectively.

          Z, ZZ, ZZZ
               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.  Otherwise, we will attempt to use the named
               zones listed in RFC5322.

          a, A Lower and uppercase 'am' and 'pm' specifiers, respec-
               tively.

          o    Ordinal day of month suffix specifier.

          [...]
               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 for-
               mat and specifier characters combined. When For exam-
               ple, __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 ignored.

          ~    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.

          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

     Page 3                       Plan 9            (printed 12/22/24)

     TMDATE(2)                                               TMDATE(2)

          identical to tmparse.

          When parsing, any amount of whitespace is treated as a sin-
          gle token.  All string matches are case insensitive, and
          zero padding is optional.

          Tmnorm takes a manually adjusted Tm structure, and normal-
          izes it, returning the absolute timestamp that the date rep-
          resents.  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 absolute time.  All out of
          range values are wrapped.  For example, December 32 will
          roll over to Jan 1 of the following year.

          Tmfmtinstall installs a time format specifier %τ. The time
          format behaves as in tmfmt

     EXAMPLES
          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 format.

               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, *est, *pst;

               pst = "Tue Dec 10 12:36:00 PST 2019";
               est = "Tue Dec 10 15:36:00 EST 2019";
               if(tmparse(&a, Fmt, pst, nil, &e) == nil)
                    sysfatal("failed to parse: %r");
               if(*e != '\0')

     Page 4                       Plan 9            (printed 12/22/24)

     TMDATE(2)                                               TMDATE(2)

                    sysfatal("trailing junk %s", e);
               if(tmparse(&b, Fmt, est, nil, &e) == nil)
                    sysfatal("failed to parse: %r");
               if(*e != '\0')
                    sysfatal("trailing junk %s", e);
               if(tmnorm(a) == tmnorm(b) && a.nsec == b.nsec)
                    print("same\n");
               else
                    print("different\n");

          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 parse");
               t.mday++;
               tmnorm(&t);
               print("%τ", tmfmt(&t, nil)); /*  Mon Nov 3 13:11:11 PST 2019 */

     BUGS
          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

     Page 5                       Plan 9            (printed 12/22/24)

     TMDATE(2)                                               TMDATE(2)

          seconds.

          We provide no way to manipulate timezones.

     Page 6                       Plan 9            (printed 12/22/24)