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:
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]