9fans - fans of the OS Plan 9 from Bell Labs
 help / color / mirror / Atom feed
From: ori@eigenstate.org
To: 9fans@9fans.net
Subject: libdate
Date: Sat, 25 Apr 2020 17:54:30 -0700	[thread overview]
Message-ID: <756B454F0747C231D31E8E3D3AFA3113@eigenstate.org> (raw)

Date handling on plan 9 is almost adequate today if you don't
have to parse dates or deal with timezones, and don't do
multithreading. Otherwise, it's difficult to get right, and
we often don't.

We've got a crappy home-rolled date parser in seconds(1),
a few in the upas source tree to deal with mail formats,
and git9 has a few hacks around this as well.

Out of tree, joe9 has been trying to write code that takes
stock information in one timezone and moves them to another,
and our APIs there are completely inadequate.

So, I tried to write a library that is adequate, without
being complicated.

The code lives here:

https://git.eigenstate.org/ori/date.git

I'll probably be merging in the changes between Tmd and Tm
soon, and committing to 9front, possibly even as part of libc.

Some additional work is probably going to be needed to convert
from IANA zoneinfo to actually bring our timezone data up to
date. We may also need some timezone info format changes to
handle political (and leap second) changes.


The manpage is attached below for review:

     TMDATE(2)                                               TMDATE(2)

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

     SYNOPSIS
          #include <u.h>
          #include <libc.h>

          typedef struct Tmd Tmd;
          struct Tmd {
                vlong    abs; /* seconds since Jan 1 1970, GMT */
                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;     /* year A.D. - 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 */
          };

          Tzone *tmgetzone(char *name);
          Tmd    *tmnow(Tmd *tm, char *tz);
          Tmd    *tmtime(Tmd *tm, vlong abs, Tzone *tz);
          Tmd    *tmstime(Tmd *tm, vlong sec, Tzone *tz);
          Tmd    *tmparse(char *fmt, char *tm, Tzone *zone, Tmd *dst);
          int   tmfmt(char *buf, usize nbuf, char *fmt, Tmd *tm);
          void tmnorm(Tmd *tm);
          void tmfmtinstall(char *fmt);

     DESCRIPTION
          This family of functions handles simple date and time manpu-
          lation.  Times are represented as an absolute instant in
          time, combined with a time zone.

          Time zones are loaded by as 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.

          Tmgetzone 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 millisecond-resolution timestamp 'abs'
          into a Tm struct in the requested timezone.

          Tmstime is identical to tmtime, but accepts the time in sec-
          onds.

          Tmparse parses a time from a string according to the format
          argument. The result is returned in the timezone requested.
          If there is a timezone in the date, then we tzshift to the
          local timezone.

          The format argument takes contains zero or more of the fol-
          lowing 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
               The day of week in short or long name form, respec-
               tively.

          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

          z, Z, ZZ
               The timezone in named, [+-]HHMM and [+-]HH:MM form,
               respectively

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

          [...]
               Quoted text, copied directly to the output.

          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,RFC-3339 and RFC-2822 dates.

          Tmfmt formats a Tm struct according to the format fmt. If
          fmt is nil, we format as in ctime(2). At most characters are
          written into buf, including the terminator.  The format is
          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.

          Tmrecalc takes a manually adjusted Tm structure, and recal-
          culates the absolute time from the year, mon, mday, hr, min
          and sec fields. Other fields are ignored.  This recalcula-
          tion respects the time zone stored in struct tm.  Out of
          range values are wrapped. For example, December 32nd becomes
          January 1st.

          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, GMT, and
          US_Pacific time. Print it using the default format.

               Tm t;
               Tzone *zl, *zp;
               if((zl = tmgetzone("local") == nil)
                    sysfatal("load zone: %r");
               if((zp = tmgetzone("US_Pacific") == nil)
                    sysfatal("load zone: %r");
               print("local: %τ\n", tmnow(&t, zl));
               print("gmt: %τ\n", tmnow(&t, nil));
               print("eastern: %τ\n", tmnow(&t, zp));

          Compare if two times are the same, regardless of timezone.

               Tm a, b;

               tmparse(&a, nil, "Tue Dec 10 12:36:00 PST 2019");
               tmparse(&b, nil, "Tue Dec 10 15:36:00 EST 2019");
               if(a.abs == b.abs)
                    print("same\n");
               else
                    print("different\n");

          Convert from one timezone to another.

               Tm here, there;
               Tzone *zl, *zp;
               if((zl = tmgetzone("local") == nil)
                    sysfatal("load zone: %r");
               if((zp = tmgetzone("US_Pacific") == nil)
                    sysfatal("load zone: %r");
               if(tmnow(&here, zl) == nil)
                    sysfatal("get time: %r");
               if(tmtime(&there, here.abs, zp) == nil)
                    sysfatal("shift time: %r");

          Add a day to two times. Because we picked daylight savings
          time to adjust over, only 23 hours are added.

          Tm t;
          tmparse(&t, "W MMM D hh:mm:ss z YYYY, "Sun Nov 2 13:11:11 PST 2019");
          tm.day++;
          tmrecalc(&t);
          print("%τ", &t); /*  Mon Nov 3 13:11:11 PST 2019 */


             reply	other threads:[~2020-04-26  0:54 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-04-26  0:54 ori [this message]
2020-04-26  1:54 ` [9fans] libdate tlaronde
2020-04-26  3:08   ` ori
2020-04-26  3:14 ` Calvin Morrison
2020-04-26  3:41 ` cinap_lenrek
2020-04-27  0:18 ` ori
2020-05-01  0:46   ` ori
2020-05-01  3:15     ` ori

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=756B454F0747C231D31E8E3D3AFA3113@eigenstate.org \
    --to=ori@eigenstate.org \
    --cc=9fans@9fans.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).