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

Re: zsh/stat: output atime/mtime/ctime with nanoseconds



On 19 Jun 2018, at 14:17, Oliver Kiddle <okiddle@xxxxxxxxxxx> wrote:
>Checking back, it seems the patch in workers/24059 was never applied.

How is this? Building on the earlier patch, it:

* Adds a zgettime() function to conveniently get nanosecond-precision clock
  time, or at least something approximating it (Peter had independently added ns
  precision to zsh/datetime between then and now, so i figured it was best to
  centralize that functionality)

* Updates ztrftime() to support %9. and %N

* Updates prompt expansion to format times with nanoseconds

* Updates zsh/datetime to use zgettime()

* Updates zsh/stat to format times with nanoseconds

* Updates documentation and tests

I'm not sure about the error-handling / fall-back stuff in zgettime(). Is an
error message necessary? I put one in because pws had one, but he wasn't falling
back to gettimeofday() like i am. Also the ret++/ret-- stuff may be a bit
strange; i couldn't think of anything cleaner.

One remaining (not new) issue is the fact that the strftime built-in doesn't
support sub-second times. That might not be feasible for `strftime -r`, but the
regular one could accept an optional third operand maybe?

dana


diff --git a/configure.ac b/configure.ac
index 7644ebe52..53c30c2cf 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1113,6 +1113,14 @@ zsh_TYPE_EXISTS([
 #endif
 ], struct timezone)
 
+dnl Check for struct timespec since POSIX only gained it in 2008
+zsh_TYPE_EXISTS([
+#define _GNU_SOURCE 1
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+], struct timespec)
+
 dnl Check for utmp structures, for watch
 zsh_TYPE_EXISTS([
 #ifdef HAVE_SYS_TYPES_H
diff --git a/Src/zsh_system.h b/Src/zsh_system.h
index 5339b496f..8289ee97c 100644
--- a/Src/zsh_system.h
+++ b/Src/zsh_system.h
@@ -250,6 +250,14 @@ struct timezone {
 };
 #endif
 
+/* Used to provide compatibility with clock_gettime() */
+#if !defined(HAVE_STRUCT_TIMESPEC) && !defined(ZSH_OOT_MODULE)
+struct timespec {
+    time_t tv_sec;
+    long tv_nsec;
+};
+#endif
+
 /* There's more than one non-standard way to get at this data */
 #if !defined(HAVE_STRUCT_DIRENT_D_INO) && defined(HAVE_STRUCT_DIRENT_D_STAT)
 # define d_ino d_stat.st_ino
diff --git a/Src/compat.c b/Src/compat.c
index a130d9264..7b5c4411c 100644
--- a/Src/compat.c
+++ b/Src/compat.c
@@ -94,6 +94,39 @@ gettimeofday(struct timeval *tv, struct timezone *tz)
 #endif
 
 
+/* Provide clock time with nanoseconds */
+
+/**/
+mod_export int
+zgettime(struct timespec *ts)
+{
+    int ret = -1;
+
+#ifdef HAVE_CLOCK_GETTIME
+    struct timespec dts;
+    if (clock_gettime(CLOCK_REALTIME, &dts) < 0) {
+	zwarn("unable to retrieve time: %e", errno);
+	ret--;
+    } else {
+	ret++;
+	ts->tv_sec = (time_t) dts.tv_sec;
+	ts->tv_nsec = (long) dts.tv_nsec;
+    }
+#endif
+
+    if (ret) {
+	struct timeval dtv;
+	struct timezone dtz;
+	gettimeofday(&dtv, &dtz);
+	ret++;
+	ts->tv_sec = (time_t) dtv.tv_sec;
+	ts->tv_nsec = (long) dtv.tv_usec * 1000;
+    }
+
+    return ret;
+}
+
+
 /* compute the difference between two calendar times */
 
 /**/

diff --git a/Src/utils.c b/Src/utils.c
index b41851700..ee2ad207f 100644
--- a/Src/utils.c
+++ b/Src/utils.c
@@ -3224,7 +3224,7 @@ ztrftimebuf(int *bufsizeptr, int decr)
 
 /**/
 mod_export int
-ztrftime(char *buf, int bufsize, char *fmt, struct tm *tm, long usec)
+ztrftime(char *buf, int bufsize, char *fmt, struct tm *tm, long nsec)
 {
     int hr12;
 #ifdef HAVE_STRFTIME
@@ -3299,15 +3299,15 @@ morefmt:
 	    case '.':
 		if (ztrftimebuf(&bufsize, digs))
 		    return -1;
-		if (digs > 6)
-		    digs = 6;
-		if (digs < 6) {
+		if (digs > 9)
+		    digs = 9;
+		if (digs < 9) {
 		    int trunc;
-		    for (trunc = 5 - digs; trunc; trunc--)
-			usec /= 10;
-		    usec  = (usec + 5) / 10;
+		    for (trunc = 8 - digs; trunc; trunc--)
+			nsec /= 10;
+		    nsec = (nsec + 8) / 10;
 		}
-		sprintf(buf, "%0*ld", digs, usec);
+		sprintf(buf, "%0*ld", digs, nsec);
 		buf += digs;
 		break;
 	    case '\0':
@@ -3369,6 +3369,12 @@ morefmt:
 		    *buf++ = '0' + tm->tm_min / 10;
 		*buf++ = '0' + tm->tm_min % 10;
 		break;
+	    case 'N':
+		if (ztrftimebuf(&bufsize, 9))
+		    return -1;
+		sprintf(buf, "%09ld", nsec);
+		buf += 9;
+		break;
 	    case 'S':
 		if (tm->tm_sec > 9 || !strip)
 		    *buf++ = '0' + tm->tm_sec / 10;

diff --git a/Src/prompt.c b/Src/prompt.c
index 95da52559..959ed8e3d 100644
--- a/Src/prompt.c
+++ b/Src/prompt.c
@@ -273,8 +273,7 @@ putpromptchar(int doprint, int endchar, unsigned int *txtchangep)
     char *ss, *hostnam;
     int t0, arg, test, sep, j, numjobs, len;
     struct tm *tm;
-    struct timezone dummy_tz;
-    struct timeval tv;
+    struct timespec ts;
     time_t timet;
     Nameddir nd;
 
@@ -664,8 +663,8 @@ putpromptchar(int doprint, int endchar, unsigned int *txtchangep)
 			tmfmt = "%l:%M%p";
 			break;
 		    }
-		    gettimeofday(&tv, &dummy_tz);
-		    tm = localtime(&tv.tv_sec);
+		    zgettime(&ts);
+		    tm = localtime(&ts.tv_sec);
 		    /*
 		     * Hack because strftime won't say how
 		     * much space it actually needs.  Try to add it
@@ -675,7 +674,7 @@ putpromptchar(int doprint, int endchar, unsigned int *txtchangep)
 		     */
 		    for(j = 0, t0 = strlen(tmfmt)*8; j < 3; j++, t0*=2) {
 			addbufspc(t0);
-			if ((len = ztrftime(bv->bp, t0, tmfmt, tm, tv.tv_usec))
+			if ((len = ztrftime(bv->bp, t0, tmfmt, tm, ts.tv_nsec))
 			    >= 0)
 			    break;
 		    }

diff --git a/Src/Modules/datetime.c b/Src/Modules/datetime.c
index 6e9047bc5..be378b347 100644
--- a/Src/Modules/datetime.c
+++ b/Src/Modules/datetime.c
@@ -180,66 +180,30 @@ getcurrentsecs(UNUSED(Param pm))
 }
 
 static double
-getcurrentrealtime(Param pm)
+getcurrentrealtime(UNUSED(Param pm))
 {
-#ifdef HAVE_CLOCK_GETTIME
     struct timespec now;
-
-    if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
-	zwarn("%s: unable to retrieve time: %e", pm->node.nam, errno);
-	return (double)0.0;
-    }
-
+    zgettime(&now);
     return (double)now.tv_sec + (double)now.tv_nsec * 1e-9;
-#else
-    struct timeval now;
-    struct timezone dummy_tz;
-
-    (void)pm;
-    gettimeofday(&now, &dummy_tz);
-
-    return (double)now.tv_sec + (double)now.tv_usec * 1e-6;
-#endif
 }
 
 static char **
-getcurrenttime(Param pm)
+getcurrenttime(UNUSED(Param pm))
 {
     char **arr;
     char buf[DIGBUFSIZE];
-
-#ifdef HAVE_CLOCK_GETTIME
     struct timespec now;
 
-    if (clock_gettime(CLOCK_REALTIME, &now) < 0) {
-	zwarn("%s: unable to retrieve time: %e", pm->node.nam, errno);
-	return NULL;
-    }
-
-    arr = (char **)zhalloc(3 * sizeof(*arr));
-    sprintf(buf, "%ld", (long)now.tv_sec);
-    arr[0] = dupstring(buf);
-    sprintf(buf, "%ld", now.tv_nsec);
-    arr[1] = dupstring(buf);
-    arr[2] = NULL;
-
-    return arr;
-#else
-    struct timeval now;
-    struct timezone dummy_tz;
-
-    (void)pm;
-    gettimeofday(&now, &dummy_tz);
+    zgettime(&now);
 
     arr = (char **)zhalloc(3 * sizeof(*arr));
     sprintf(buf, "%ld", (long)now.tv_sec);
     arr[0] = dupstring(buf);
-    sprintf(buf, "%ld", (long)now.tv_usec * 1000);
+    sprintf(buf, "%ld", (long)now.tv_nsec);
     arr[1] = dupstring(buf);
     arr[2] = NULL;
 
     return arr;
-#endif
 }
 
 static struct builtin bintab[] = {

diff --git a/Src/Modules/stat.c b/Src/Modules/stat.c
index 66baa1292..50a6a9bb2 100644
--- a/Src/Modules/stat.c
+++ b/Src/Modules/stat.c
@@ -188,7 +188,7 @@ static char *timefmt;
 
 /**/
 static void
-stattimeprint(time_t tim, char *outbuf, int flags)
+stattimeprint(time_t tim, long nsecs, char *outbuf, int flags)
 {
     if (flags & STF_RAW) {
 	sprintf(outbuf, "%ld", (unsigned long)tim);
@@ -199,7 +199,7 @@ stattimeprint(time_t tim, char *outbuf, int flags)
 	char *oend = outbuf + strlen(outbuf);
 	/* Where the heck does "40" come from? */
 	int len = ztrftime(oend, 40, timefmt, (flags & STF_GMT) ? gmtime(&tim) :
-			   localtime(&tim), 0L);
+			   localtime(&tim), nsecs);
 	if (len > 0)
 	    metafy(oend, len, META_NOALLOC);
 	if (flags & STF_RAW)
@@ -291,15 +291,27 @@ statprint(struct stat *sbuf, char *outbuf, char *fname, int iwhich, int flags)
 	break;
 
     case ST_ATIM:
-	stattimeprint(sbuf->st_atime, optr, flags);
+#ifdef GET_ST_ATIME_NSEC
+	stattimeprint(sbuf->st_atime, GET_ST_ATIME_NSEC(*sbuf), optr, flags);
+#else
+	stattimeprint(sbuf->st_atime, 0L, optr, flags);
+#endif
 	break;
 
     case ST_MTIM:
-	stattimeprint(sbuf->st_mtime, optr, flags);
+#ifdef GET_ST_MTIME_NSEC
+	stattimeprint(sbuf->st_mtime, GET_ST_MTIME_NSEC(*sbuf), optr, flags);
+#else
+	stattimeprint(sbuf->st_mtime, 0L, optr, flags);
+#endif
 	break;
 
     case ST_CTIM:
-	stattimeprint(sbuf->st_ctime, optr, flags);
+#ifdef GET_ST_CTIME_NSEC
+	stattimeprint(sbuf->st_ctime, GET_ST_CTIME_NSEC(*sbuf), optr, flags);
+#else
+	stattimeprint(sbuf->st_ctime, 0L, optr, flags);
+#endif
 	break;
 
     case ST_BLKSIZE:

diff --git a/Test/D01prompt.ztst b/Test/D01prompt.ztst
index 11f18dc71..56b7c294a 100644
--- a/Test/D01prompt.ztst
+++ b/Test/D01prompt.ztst
@@ -108,6 +108,14 @@
   if (( $date2[7,8] != $date3[1,2] )); then
     print "Years do not agree in $date2, $date3"
   fi
+  # These are somewhat questionable, but...
+  ns=( ${="$(print -P '%D{%9.} %D{%N}')"} )
+  if [[ $ns[1] != [0-9](#c9) ]] || [[ $ns[2] != [0-9](#c9) ]]; then
+    print "Nanosecond lengths/formats are not as expected in $ns[1], $ns[2]"
+  fi
+  if (( ($ns2[2] - $ns[1]) > 5000000 )); then
+    print "Nanoseconds differ too much in $ns[1], $ns[2]"
+  fi
 0:Dates produced by prompt escapes
 
   mkdir foo

diff --git a/Doc/Zsh/prompt.yo b/Doc/Zsh/prompt.yo
index 3c8f2a094..909012c8e 100644
--- a/Doc/Zsh/prompt.yo
+++ b/Doc/Zsh/prompt.yo
@@ -198,11 +198,15 @@ endsitem()
 In addition, if the system supports the POSIX tt(gettimeofday) system
 call, tt(%.) provides decimal fractions of a second since the epoch with
 leading zeroes.  By default three decimal places are provided, but a
-number of digits up to 6 may be given following the tt(%); hence tt(%6.)
-outputs microseconds.  A typical example of this is the format
-`tt(%D{%H:%M:%S.%.})'.
+number of digits up to 9 may be given following the tt(%); hence tt(%6.)
+outputs microseconds, and tt(%9.) outputs nanoseconds.  (The latter
+requires a nanosecond-precision tt(clock_gettime); systems lacking this
+will return a value multiplied by the appropriate power of 10.)  A typical
+example of this is the format `tt(%D{%H:%M:%S.%.})'.
 
-The GNU extension that a `tt(-)' between the tt(%) and the
+The GNU extension tt(%N) is handled as a synonym for tt(%9.).
+
+Additionally, the GNU extension that a `tt(-)' between the tt(%) and the
 format character causes a leading zero or space to be stripped
 is handled directly by the shell for the format characters tt(d), tt(f),
 tt(H), tt(k), tt(l), tt(m), tt(M), tt(S) and tt(y); any other format

diff --git a/Doc/Zsh/mod_stat.yo b/Doc/Zsh/mod_stat.yo
index 96349061e..9caed1e45 100644
--- a/Doc/Zsh/mod_stat.yo
+++ b/Doc/Zsh/mod_stat.yo
@@ -114,7 +114,11 @@ named files; no list of file names is allowed in this case.
 )
 item(tt(-F) var(fmt))(
 Supplies a tt(strftime) (see manref(strftime)(3)) string for the
-formatting of the time elements.  The tt(-s) option is implied.
+formatting of the time elements.  The format string supports all of the
+zsh extensions described in
+ifzman(the section EXPANSION OF PROMPT SEQUENCES in zmanref(zshmisc))\
+ifnzman(noderef(Prompt Expansion)).
+The tt(-s) option is implied.
 )
 item(tt(-g))(
 Show the time elements in the GMT time zone.  The



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