Formatted Dates

February 21, 2020

Here is my awk function to return a date-formatted string, which calls the Unix system date command to do the work (it took several tries to get the quoting right):

function today(fmt,    year, month, day,    date, str) {
    if (0 < year + 0 && year + 0 < 10000 &&
        0 < month + 0 && month + 0 <= 12 &&
        0 < day + 0 && day + 0 <= 31) {
            str = "-d \"" month "/" day "/" year "\"" }
    "date +\"" fmt "\" " str | getline date
    return date }

Called with just a fmt parameter, it uses the current date; alternately, the user may specify the date in the three optional parameters. (Yes, awk can handle variadic parameter lists.)

I have to say that I quite enjoy programming with awk, even if it’s very different from Scheme. I also have to say that I don’t feel particularly constrained by using Posix awk rather than Gnu awk; today is a strong example that most things can be done using either flavor of awk.

You can see the program at https://ideone.com/oI3ky7, but you can’t run it because it shells out to the date command.

Pages: 1 2

3 Responses to “Formatted Dates”

  1. matthew said

    Let’s display the Modified Julian Date. Some C:

    #include <time.h>
    #include <stdio.h>
    
    int main() {
      time_t t = time(0);
      printf("MJD %.5f\n", double(t)/86400 + 40587);
    }
    
    $ g++ -Wall date.cpp -o date
    $ ./date
    MJD 58900.62108
    
  2. Pascal Bourguignon said
    
    
    #|
    
    In Common Lisp, dates are represented as universal-time, ie. an
    non-negative integer number of seconds since 1900-01-01 00:00:00 UTC.
    CL provides functions to convert between this universal-time integer,
    and seconds, minutes, hours, day, month and year (given a timezone).
    
    universal time n. time, represented as a non-negative integer number
    of seconds. Absolute universal time is measured as an offset from the
    beginning of the year 1900 (ignoring leap seconds). See Section
    25.1.4.2 (Universal Time).
    
    (decode-universal-time 0 0)
    --> 0
        0
        0
        1
        1
        1900
        0
        nil
        0
    
    Note, that for timezones west of GMT, decode-universal-time can give
    dates before 1900-01-01 (GMT-x) (also, note that since CL was
    standardized by Americans, timezones such as GMT-x are represented by
    x, while timezones such as GMT+x are represneted by -x).
    
    (decode-universal-time 0 8)
    --> 0
        0
        16
        31
        12
        1899
        6
        nil
        8
    
    Implementations could have the extensions of accepting a negative
    integer as universal-time, but:
    
    1- this would be an extension that has to be documented, or else, it
       would be non-conforming to the standard, and,
    
    2- this doesn't make much sense anyway, since the conversion functions
       assume the Gregorian calendar, which wasn't accepted by some big
       countries before 1900.  Even Saudi Arabia didn't use it before 2016!!!
    
       https://en.wikipedia.org/wiki/Gregorian_calendar#Adoption_of_the_Gregorian_Calendar
    
    The problem of (historical) past dates is a difficult relativistic time-space problem.
    
    
    (get-universal-time) --> 3791288667 ; now
    
    (decode-universal-time 3791288667) ; IN LOCAL TIMEZONE
    27
    44
    16
    21
    2
    2020
    4
    nil
    -1
    
    (encode-universal-time 27 44 16 21 2 2020)
    --> 3791288667
    
    
    
    Anyways, back to our question.  Given this easy access to the date and
    time components, and given the rich CL:FORMAT specifiers, CL doesn't
    provide any specific date formating function.
    
    (multiple-value-bind (se mi ho da mo ye dow dst tz) (decode-universal-time (get-universal-time))
       (format nil "~4,'0D-~2,'0D-~2,'0D ~2,'0D:~2,'0D:~2,'0D ~[Mon~;Tue~;Wed~;Thi~;Fri~;Sat~;Sun~] ~:[not DST~;DST~] GMT~:[-~;+~]~A"
                  ye mo da ho se mi
                  dow dst (minusp tz) (abs tz)))
    --> "2020-02-21 14:34:27 Fri not DST GMT-3/2"
    
    (multiple-value-bind (se mi ho da mo ye dow dst tz) (decode-universal-time (get-universal-time) 0)
       (format nil "~4,'0D~2,'0D~2,'0DT~2,'0D~2,'0D~2,'0DZ"
                  ye mo da ho se mi))
    --> "20200221T152258Z"
    
    
    Note however that once we enter the territory of localized date
    formatting, the variants in the "spelling" of dates and numbers are
    such that it's difficult to use a mere format control string however
    sophisticated it is, to cover all the languages.
    
    For example, in English we add "st", "nd", "rd" or "th" after the day
    number, but in French we add "er" or "e".  I hear that Polish has even
    more sophisticated a rule there.  This selection needs to be computed
    algorithmically.
    
    In CL, we could use the ~/ format specifier which allows to hook in
    function calls.  Therefore we could provide a set of localized
    functions to format dates:
    
    |#
    
    
    (defun cl-user::iso8601-timestamp (stream universal-time colon at &rest parameters)
      (declare (ignore colon at parameters))
      (multiple-value-bind (se mi ho da mo ye) (decode-universal-time universal-time 0)
        (format stream "~4,'0D~2,'0D~2,'0DT~2,'0D~2,'0D~2,'0DZ"
                ye mo da ho se mi)))
    
    (defun english-ordinal-suffix (integer)
      (cond
        ((< 10 integer 20) "th")
        (t (case (mod integer 10)
             (1         "st")
             (2         "nd")
             (3         "rd")
             (otherwise "th")))))
    
    (defun cl-user::english-date (stream universal-time colon at &rest parameters)
      (declare (ignore colon at parameters))
      (multiple-value-bind (se mi ho da mo ye dow) (decode-universal-time universal-time 0)
        (declare (ignore se mi ho))
        (format stream "~[Monday~;Tuesday~;Wednesday~;Thirsday~;Friday~;Saturday~;Sunday~], ~
                        ~[~;January~;February~;March~;April~;May~;June~;July~;August~;September~;October~;November~;December~] ~
                        ~D~A, ~D"
                dow mo da (english-ordinal-suffix da) ye)))
    
    (defun cl-user::french-date (stream universal-time colon at &rest parameters)
      (declare (ignore colon at parameters))
      (multiple-value-bind (se mi ho da mo ye dow) (decode-universal-time universal-time 0)
        (declare (ignore se mi ho))
        (format stream "~[Lundi~;Mardi~;Mercredi~;Jeudi~;Vendredi~;Samedi~;Dimanche~] ~D ~
                        ~[~;Janvier~;Février~;Mars~;Avril~;Mai~;Juin~;Juillet~;Août~;Septembre~;Octobre~;Novembre~;Decembre~] ~
                        ~D"
                dow da mo ye)))
    
    #|
    
    (format nil "Today is: ~/english-date/" (get-universal-time))
    --> "Today is: Friday, February 21st, 2020"
    
    (format nil "Aujourd'hui, ~/french-date/" (get-universal-time))
    --> "Aujourd'hui, Vendredi 21 Février 2020"
    
    
    (format nil "~/iso8601-timestamp/ Date Printed." (get-universal-time))
    --> "20200221T164241Z Date Printed."
    
    |#
    
    
    
  3. matthew said

    @Pascal: nice stuff. Common Lisp formatting is amazing.

    Here’s an improved version of my minimal approach that using the Posix clock_gettime function for a more accurate result. Also, I made the format precision a parameter – default is to show microdays, which are slightly less that a tenth of a second:

    #include <time.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    double mjd(const timespec &tp) {
      return (double(tp.tv_sec) + double(tp.tv_nsec)/1e9)/86400 + 40587;
    }
    
    int main(int argc, char *argv[]) {
      int prec = 6;
      if (argc > 1) prec = strtoul(argv[1],0,0);
      timespec tp;
      clock_gettime(CLOCK_REALTIME,&tp);
      double t = mjd(tp);
      printf("MJD %.*f\n",prec,t);
    }
    

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: