From 394f3a47e464b67b17e2cb7166df066829250e88 Mon Sep 17 00:00:00 2001 From: dana Date: Wed, 20 Jun 2018 17:29:56 -0500 Subject: 43075: Support nanosecond-precision time formatting * Teach ztrftime() %9. and %N for nanoseconds * Update prompt expansion to pass sub-second times for time formatting * Update zsh/stat to pass sub-second times for atime/mtime/ctime Patch heavily based on Oliver's earlier work @ workers/24059 --- ChangeLog | 6 ++++++ Doc/Zsh/mod_stat.yo | 6 +++++- Doc/Zsh/prompt.yo | 12 ++++++++---- Src/Modules/datetime.c | 46 +++++----------------------------------------- Src/Modules/stat.c | 22 +++++++++++++++++----- Src/compat.c | 33 +++++++++++++++++++++++++++++++++ Src/prompt.c | 9 ++++----- Src/utils.c | 22 ++++++++++++++-------- Src/zsh_system.h | 8 ++++++++ Test/D01prompt.ztst | 8 ++++++++ configure.ac | 8 ++++++++ 11 files changed, 116 insertions(+), 64 deletions(-) diff --git a/ChangeLog b/ChangeLog index fe91372f9..34b6f7ebb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,6 +5,12 @@ 2018-06-20 dana + * 43075 (based on 24059): Doc/Zsh/mod_stat.yo, Doc/Zsh/prompt.yo, + Src/Modules/datetime.c, Src/Modules/stat.c, Src/compat.c, + Src/prompt.c, Src/utils.c, Src/zsh_system.h, Test/D01prompt.ztst, + configure.ac: Support nanosecond precision in zsh/stat file times + and time formats generally (%9./%N) + * 43061: Completion/Darwin/Command/_open, Completion/Unix/Command/_webbrowser: Improve open completion, adjust _webbrowser wording 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 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/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/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/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/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/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/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/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 +#endif +], struct timespec) + dnl Check for utmp structures, for watch zsh_TYPE_EXISTS([ #ifdef HAVE_SYS_TYPES_H -- cgit 1.4.1