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.
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); }#| 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." |#@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); }