logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/
commit: 3b2cdee6bea740511c3b2c61997ad98b3bf2d5de
parent b4a83c313b6f36d6823fcf8fa370fad5c697dc8a
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Mon, 24 Feb 2025 13:56:49 +0100

lib/iso_parse: Add support for asctime() datetimes

Diffstat:

Mlib/iso_parse.c105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mlib/iso_parse.mdoc5+++++
Mtest-cmd/date.sh5++++-
3 files changed, 112 insertions(+), 3 deletions(-)

diff --git a/lib/iso_parse.c b/lib/iso_parse.c @@ -19,7 +19,8 @@ #include <time.h> /* strptime, tm */ static const char *short_weekday_name[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; -static const char *short_month_name[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; +static const char *short_month_name[12] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; // Parses [+|-]HH:?MM timezone offsets static char * @@ -225,6 +226,98 @@ email_date_parse(char *arg, struct tm *time, const char **errstr) return arg; } +// For iso_parse function +// Sets *errstr to NULL when it isn't an email date-time +// +// Check if it could be asctime() format: Thu Nov 24 18:22:48 1986 +static char * +asctime_date_parse(char *arg, struct tm *time, const char **errstr) +{ + // Kept free of strptime() due to update/overriding being undefined and + // requiring custom parsing, notably locale-free, which strptime() can't handle + + // Change `time` only right before returning in case datetime is invalid + struct tm tmp_time = *time; + tmp_time.tm_isdst = -1; + tmp_time.tm_wday = -1; + + // asctime() doesn't gives any timezone information, assume UTC + tmp_time.tm_isdst = 0; + tmp_time.tm_gmtoff = 0; + tmp_time.tm_zone = "UTC"; + + errno = 0; + int parsed = 0; + char month_name[4] = ""; + char weekday_name[4] = ""; + if(sscanf(arg, + "%3s %3s %d %2d:%2d:%2d %d%n", + weekday_name, + month_name, + &tmp_time.tm_mday, + &tmp_time.tm_hour, + &tmp_time.tm_min, + &tmp_time.tm_sec, + &tmp_time.tm_year, + &parsed) < 7) + { + if(errno == 0 || errno == EINVAL) + { + *errstr = NULL; + } + else + { + *errstr = strerror(errno); + errno = 0; + } + return NULL; + } + + arg += parsed; + + tmp_time.tm_year -= 1900; + + tmp_time.tm_wday = -1; + // Because %a/%A is locale-dependent + for(size_t i = 0; i < 7; i++) + { + if(memcmp(weekday_name, short_weekday_name[i], 3) == 0) + { + tmp_time.tm_wday = i; + break; + } + } + if(tmp_time.tm_wday < 0) + { + *errstr = "Failed parsing short weekday name"; + errno = 0; + return NULL; + } + + tmp_time.tm_mon = -1; + // Because %b/%B is locale-dependent + for(size_t i = 0; i < 12; i++) + { + if(memcmp(month_name, short_month_name[i], 3) == 0) + { + tmp_time.tm_mon = i; + break; + } + } + if(tmp_time.tm_mon < 0) + { + *errstr = "Failed parsing short month name"; + errno = 0; + return NULL; + } + + for(; isspace(arg[0]); arg++) + ; + + memcpy(time, &tmp_time, sizeof(tmp_time)); + return arg; +} + // Sets errstr on failure // YYYY-MM-DD[T ]hh:mm:SS([,\.]frac)?(Z|[+\-]hh:?mm)? char * @@ -251,7 +344,15 @@ iso_parse(char *arg, struct tm *time, long *nsec, const char **errstr) return endptr; } - char *ret = email_date_parse(arg, time, errstr); + char *ret = NULL; + + ret = email_date_parse(arg, time, errstr); + if(ret != NULL || *errstr != NULL) + { + return ret; + } + + ret = asctime_date_parse(arg, time, errstr); if(ret != NULL || *errstr != NULL) { return ret; diff --git a/lib/iso_parse.mdoc b/lib/iso_parse.mdoc @@ -20,6 +20,11 @@ Or as Email / "Internet Message Format" (RFC5322, RFC2822, RFC822), for example: .Ql 21 Nov 97 09:55:06 GMT .El .Pp +Or an +.Xr asctime 3 Ns -like +format, for example: +.Ql Sun Sep 16 01:03:52 1973 +.Pp Or as RFC3339 which looks like .Ql YYYY-MM-DDThh:mm:SS[frac][tz] , where: diff --git a/test-cmd/date.sh b/test-cmd/date.sh @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MPL-2.0 target="$(dirname "$0")/../cmd/date" -plans=31 +plans=32 . "$(dirname "$0")/tap.sh" . "$(dirname "$0")/init_env.sh" @@ -94,6 +94,9 @@ t_args 'email RFC2822-RFC5322-obsolete-dates' '1997-11-21 09:55:06+0000 t_args 'email Y2K' '2017-11-21 09:55:06+0000 ' -u -d '21 Nov 17 09:55:06 GMT' '+%F %T%z' +t_args 'asctime' '1973-09-16 01:03:52+0000 +' -u -d 'Sun Sep 16 01:03:52 1973' '+%F %T%z' + #usage="\ #date [-uR] [-d datetime] [+format] #date [-uR] -f now_format now [+format]