Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

[PATCH] Add nanosecond support to strftime (zsh/datetime)



This is a follow-up to workers/43075, which added nanosecond support to (AFAIK)
the rest of the shell.

The strftime built-in is changed as follows:

1. An optional third operand (nanoseconds) is accepted to specify the nanosecond
   value to pass to ztrftime(). This is essentially patterned after the
   $epochtime array, and lets you do things like this:

   start=( $epochtime )
   # do some stuff
   strftime 'Started at %T.%3.' $start
   strftime 'Finished at %T.%3.' $epochtime

   I considered having it accept a float instead (so you could do like
   `strftime %N $EPOCHREALTIME`), but it seemed like this might be easier to
   deal with? Let me know if the other way's better though.

   If the third operand is *not* provided, the nanosecond value is assumed to be
   0 as before.

2. If the second operand (epochtime) is not provided, the current system time
   (with nanoseconds) is used automatically. There didn't really seem to be a
   *need* to provide the epoch time explicitly, so i thought this would be a
   nice convenience.

   Obviously this does mean that if the operand is elided, it'll now silently
   fall back to the current time. That's the only potential issue i can think
   of.

dana


diff --git a/Doc/Zsh/mod_datetime.yo b/Doc/Zsh/mod_datetime.yo
index 27bc78157..da65a9bbd 100644
--- a/Doc/Zsh/mod_datetime.yo
+++ b/Doc/Zsh/mod_datetime.yo
@@ -6,9 +6,13 @@ The tt(zsh/datetime) module makes available one builtin command:
 startitem()
 findex(strftime)
 cindex(date string, printing)
-xitem(tt(strftime) [ tt(-s) var(scalar) ] var(format) var(epochtime) )
+xitem(tt(strftime) [ tt(-s) var(scalar) ] var(format) [ var(epochtime) [ var(nanoseconds) ] ] )
 item(tt(strftime) tt(-r) [ tt(-q) ] [ tt(-s) var(scalar) ] var(format) var(timestring) )(
-Output the date denoted by var(epochtime) in the var(format) specified.
+Output the date in the var(format) specified.  With no var(epochtime), the
+current system date/time is used; optionally, var(epochtime) may be used to
+specify the number of seconds since the epoch, and var(nanoseconds) may
+additionally be used to specify the number of nanoseconds past the second
+(otherwise that number is assumed to be 0).
 See manref(strftime)(3) for details.  The zsh extensions described in
 ifzman(the section EXPANSION OF PROMPT SEQUENCES in zmanref(zshmisc))\
 ifnzman(noderef(Prompt Expansion)) are also available.

diff --git a/Src/Modules/datetime.c b/Src/Modules/datetime.c
index be378b347..18c7fb58e 100644
--- a/Src/Modules/datetime.c
+++ b/Src/Modules/datetime.c
@@ -100,8 +100,8 @@ output_strftime(char *nam, char **argv, Options ops, UNUSED(int func))
 {
     int bufsize, x, len;
     char *endptr = NULL, *scalar = NULL, *buffer;
-    time_t secs;
-    struct tm *t;
+    struct tm *tm;
+    struct timespec ts;
 
     if (OPT_ISSET(ops,'s')) {
 	scalar = OPT_ARG(ops, 's');
@@ -110,30 +110,58 @@ output_strftime(char *nam, char **argv, Options ops, UNUSED(int func))
 	    return 1;
 	}
     }
-    if (OPT_ISSET(ops, 'r'))
+    if (OPT_ISSET(ops, 'r')) {
+	if (!argv[1]) {
+	    zwarnnam(nam, "timestring expected");
+	    return 1;
+	}
 	return reverse_strftime(nam, argv, scalar, OPT_ISSET(ops, 'q'));
-
-    errno = 0;
-    secs = (time_t)strtoul(argv[1], &endptr, 10);
-    if (errno != 0) {
-	zwarnnam(nam, "%s: %e", argv[1], errno);
-	return 1;
-    } else if (*endptr != '\0') {
-	zwarnnam(nam, "%s: invalid decimal number", argv[1]);
-	return 1;
     }
 
-    t = localtime(&secs);
-    if (!t) {
-	zwarnnam(nam, "%s: unable to convert to time", argv[1]);
-	return 1;
+    if (!argv[1]) {
+	zgettime(&ts);
+	tm = localtime(&ts.tv_sec);
+    } else {
+	errno = 0;
+
+	ts.tv_sec = (time_t)strtoul(argv[1], &endptr, 10);
+	if (errno != 0) {
+	    zwarnnam(nam, "%s: %e", argv[1], errno);
+	    return 1;
+	} else if (*argv[1] == '\0' || *endptr != '\0') {
+	    zwarnnam(nam, "%s: invalid decimal number", argv[1]);
+	    return 1;
+	}
+
+	tm = localtime(&ts.tv_sec);
+	if (!tm) {
+	    zwarnnam(nam, "%s: unable to convert to time", argv[1]);
+	    return 1;
+	}
+
+	ts.tv_nsec = 0L;
+	if (argv[2]) {
+	    ts.tv_nsec = (long)zstrtol(argv[2], &endptr, 10);
+	    if (errno != 0) {
+		zwarnnam(nam, "%s: %e", argv[2], errno);
+		return 1;
+	    } else if (*argv[2] == '\0' || *endptr != '\0') {
+		zwarnnam(nam, "%s: invalid decimal number", argv[2]);
+		return 1;
+	    } else if (ts.tv_nsec < 0) {
+		zwarnnam(nam, "%s: invalid nanosecond value", argv[2]);
+		return 1;
+	    }
+	}
     }
+
     bufsize = strlen(argv[0]) * 8;
     buffer = zalloc(bufsize);
 
     len = 0;
     for (x=0; x < 4; x++) {
-        if ((len = ztrftime(buffer, bufsize, argv[0], t, 0L)) >= 0 || x==3)
+        if ((len = ztrftime(buffer, bufsize, argv[0], tm, ts.tv_nsec)) >= 0 ||
+	    x==3)
 	    break;
 	buffer = zrealloc(buffer, bufsize *= 2);
     }
@@ -207,7 +235,7 @@ getcurrenttime(UNUSED(Param pm))
 }
 
 static struct builtin bintab[] = {
-    BUILTIN("strftime",    0, bin_strftime,    2,   2, 0, "qrs:", NULL),
+    BUILTIN("strftime",    0, bin_strftime,    1,   3, 0, "qrs:", NULL),
 };
 
 static const struct gsu_integer epochseconds_gsu =

diff --git a/Test/V09datetime.ztst b/Test/V09datetime.ztst
index ffad96c04..22d560750 100644
--- a/Test/V09datetime.ztst
+++ b/Test/V09datetime.ztst
@@ -82,3 +82,32 @@
 # The result can be '%@' (Linux), '@' (BSDs) or an error (Cygwin).
   [[ $(strftime '%@' 0 2> /dev/null) == (%|)@ || $? != 0 ]]
 0:bad format specifier
+
+# This test may fail at 23:59:59.xxx on New Year's Eve :/
+  [[ "$( strftime '%Y' )" == "$( strftime '%Y' "$EPOCHSECONDS" )" ]]
+0:epochtime optional
+
+  strftime '%Y-%m-%d %H:%M:%S.%3.' 1012615322
+  strftime '%Y-%m-%d %H:%M:%S.%3.' 1012615322 0
+  strftime '%Y-%m-%d %H:%M:%S.%3.' 1012615322 2
+  strftime '%Y-%m-%d %H:%M:%S.%3.' 1012615322 $(( 222 * (10 ** 9) ))
+0:optional nanoseconds
+>2002-02-02 02:02:02.000
+>2002-02-02 02:02:02.000
+>2002-02-02 02:02:02.000
+>2002-02-02 02:02:02.222
+
+  strftime '%Y' '' 2> /dev/null
+1:empty epochtime not allowed
+
+  strftime '%Y' 1012615322 '' 2> /dev/null
+1:empty nanoseconds not allowed
+
+  strftime '%N' 1012615322 ${(l<64><9>):-} 2> /dev/null
+1:overflowed nanoseconds not allowed
+
+  strftime '%N' 1012615322 -1 2> /dev/null
+1:negative nanoseconds not allowed
+
+  strftime -r '%Y' 2> /dev/null
+1:-r timestring not optional



Messages sorted by: Reverse Date, Date, Thread, Author