summary refs log tree commit diff
diff options
context:
space:
mode:
authordana <dana@dana.is>2018-11-13 13:01:01 -0600
committerdana <dana@dana.is>2018-11-13 13:01:01 -0600
commit5ad76492af8931added1ae9600309d915d1427a5 (patch)
tree61450721cfc810d4154c1875c3519bc5927f8dbb
parent0b3b52778bd7a4a2c97637c3d4739a7e66a695b1 (diff)
downloadzsh-5ad76492af8931added1ae9600309d915d1427a5.tar.gz
zsh-5ad76492af8931added1ae9600309d915d1427a5.tar.xz
zsh-5ad76492af8931added1ae9600309d915d1427a5.zip
43800: Add nanosecond support to strftime built-in
-rw-r--r--ChangeLog5
-rw-r--r--Doc/Zsh/mod_datetime.yo8
-rw-r--r--Src/Modules/datetime.c64
-rw-r--r--Test/V09datetime.ztst29
4 files changed, 86 insertions, 20 deletions
diff --git a/ChangeLog b/ChangeLog
index 7e69c5841..4cba03c71 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2018-11-13  dana  <dana@dana.is>
+
+	* 43800: Doc/Zsh/mod_datetime.yo, Src/Modules/datetime.c,
+	Test/V09datetime.ztst: Add nanosecond support to strftime built-in
+
 2018-11-12  Oliver Kiddle  <okiddle@yahoo.co.uk>
 
 	* 43819: Completion/Unix/Command/_adb: expand adb completion to
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