zsh-workers
 help / color / mirror / code / Atom feed
* zsh/stat: output atime/mtime/ctime with nanoseconds
@ 2018-06-19 14:18 Vincent Lefevre
  2018-06-19 17:32 ` dana
  2018-06-19 19:17 ` Oliver Kiddle
  0 siblings, 2 replies; 4+ messages in thread
From: Vincent Lefevre @ 2018-06-19 14:18 UTC (permalink / raw)
  To: zsh-workers

Under Linux, the stat utility from GNU Coreutils outputs
atime/mtime/ctime with nanoseconds.

IMHO, zsh should do the same.

-- 
Vincent Lefèvre <vincent@vinc17.net> - Web: <https://www.vinc17.net/>
100% accessible validated (X)HTML - Blog: <https://www.vinc17.net/blog/>
Work: CR INRIA - computer arithmetic / AriC project (LIP, ENS-Lyon)


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: zsh/stat: output atime/mtime/ctime with nanoseconds
  2018-06-19 14:18 zsh/stat: output atime/mtime/ctime with nanoseconds Vincent Lefevre
@ 2018-06-19 17:32 ` dana
  2018-06-19 19:17 ` Oliver Kiddle
  1 sibling, 0 replies; 4+ messages in thread
From: dana @ 2018-06-19 17:32 UTC (permalink / raw)
  To: Vincent Lefevre; +Cc: zsh-workers

On 19 Jun 2018, at 09:18, Vincent Lefevre <vincent@vinc17.net> wrote:
>Under Linux, the stat utility from GNU Coreutils outputs
>atime/mtime/ctime with nanoseconds.
>
>IMHO, zsh should do the same.

The GNU tools that support nanoseconds in time strings tend to use Gnulib's
nstrftime(), which provides their %N extension to the standard strftime(3)
stuff.

zsh has a similar function, ztrftime(), which zsh/stat uses. The first problem
is that the module doesn't pass the file's sub-second time information to the
function, and the second problem is that, even if it did, ztrftime() only
supports microsecond granularity. I think it would be very easy to change both
of those things, but there may be some portability issues with getting the
nanosecond information itself.

(Also, not all file systems even record times to the nanosecond — HFS+ only has
whole-second granularity for example — but that's another thing.)

If nobody has any objections i might look into this later; there've been a few
times i wanted '%9.' myself.

dana


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: zsh/stat: output atime/mtime/ctime with nanoseconds
  2018-06-19 14:18 zsh/stat: output atime/mtime/ctime with nanoseconds Vincent Lefevre
  2018-06-19 17:32 ` dana
@ 2018-06-19 19:17 ` Oliver Kiddle
  2018-06-20  5:12   ` dana
  1 sibling, 1 reply; 4+ messages in thread
From: Oliver Kiddle @ 2018-06-19 19:17 UTC (permalink / raw)
  To: zsh-workers

Vincent Lefevre wrote:
> Under Linux, the stat utility from GNU Coreutils outputs
> atime/mtime/ctime with nanoseconds.

I was sure this was added at the same time that nanosecond timestamp
support was added for the [[ file1 -nt file2 ]] condition tests in
24050.

Checking back, it seems the patch in workers/24059 was never applied.

Oliver


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: zsh/stat: output atime/mtime/ctime with nanoseconds
  2018-06-19 19:17 ` Oliver Kiddle
@ 2018-06-20  5:12   ` dana
  0 siblings, 0 replies; 4+ messages in thread
From: dana @ 2018-06-20  5:12 UTC (permalink / raw)
  To: Oliver Kiddle; +Cc: zsh-workers

On 19 Jun 2018, at 14:17, Oliver Kiddle <okiddle@yahoo.co.uk> 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


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2018-06-20  5:12 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-06-19 14:18 zsh/stat: output atime/mtime/ctime with nanoseconds Vincent Lefevre
2018-06-19 17:32 ` dana
2018-06-19 19:17 ` Oliver Kiddle
2018-06-20  5:12   ` dana

Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/zsh/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).