diff --git a/ChangeLog b/ChangeLog
index a58002d71..989cc0aa3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2024-12-26  dana  <dana@dana.is>
+
+	* 53257: Doc/Zsh/params.yo, Doc/Zsh/prompt.yo,
+	Src/Modules/zftp.c, Src/Modules/zprof.c, Src/compat.c,
+	Src/exec.c, Src/hist.c, Src/init.c, Src/jobs.c, Src/params.c,
+	Src/prompt.c, Src/signals.c, Src/utils.c, Src/zsh.h,
+	Test/A08time.ztst, Test/D01prompt.ztst, Test/D04parameter.ztst:
+	use monotonic clock where appropriate
+
 2024-12-16  dana  <dana@dana.is>
 
 	* 53251: Completion/Unix/Command/_man: fix page completion on
diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo
index 02ce796a9..69298855f 100644
--- a/Doc/Zsh/params.yo
+++ b/Doc/Zsh/params.yo
@@ -926,7 +926,9 @@ referenced or seeded in the parent shell in between subshell invocations.
 )
 vindex(SECONDS)
 item(tt(SECONDS) <S>)(
-The number of seconds since shell invocation.  If this parameter
+The number of seconds since shell invocation.  On most platforms, this
+is a monotonic value, so it is not affected by NTP time jumps or other
+clock changes (though it may be affected by slewing).  If this parameter
 is assigned a value, then the value returned upon reference
 will be the value that was assigned plus the number of seconds
 since the assignment.
@@ -936,8 +938,10 @@ be changed using the tt(typeset) command.  The type may be changed only
 to one of the floating point types or back to integer.  For example,
 `tt(typeset -F SECONDS)'
 causes the value to be reported as a floating point number.  The
-value is available to microsecond accuracy, although the shell may
-show more or fewer digits depending on the use of tt(typeset).  See
+value is nominally available to nanosecond precision, although this
+varies by platform (and probably isn't accurate to 1 ns regardless),
+and the shell may show more or fewer digits depending on the
+use of tt(typeset).  See
 the documentation for the builtin tt(typeset) in
 ifzman(zmanref(zshbuiltins))\
 ifnzman(noderef(Shell Builtin Commands)) for more details.
@@ -1735,8 +1739,12 @@ A star may be inserted between the percent sign and flags printing time
 (e.g., `tt(%*E)'); this causes the time to be printed in
 `var(hh)tt(:)var(mm)tt(:)var(ss)tt(.)var(ttt)'
 format (hours and minutes are only printed if they are not zero).
-Alternatively, `tt(m)' or `tt(u)' may be used (e.g., `tt(%mE)') to produce
-time output in milliseconds or microseconds, respectively.
+Alternatively, `tt(m)', `tt(u)', or `tt(n)' may be used (e.g.,
+`tt(%mE)') to produce time output in milliseconds, microseconds, or
+nanoseconds, respectively. Note that some timings on some platforms
+are not actually nanosecond-precise (nor accurate to 1 ns when
+they are); in fact on many systems user and kernel times are not
+even microsecond-precise.
 )
 vindex(TMOUT)
 item(tt(TMOUT))(
diff --git a/Doc/Zsh/prompt.yo b/Doc/Zsh/prompt.yo
index de988ab7c..108cb62e5 100644
--- a/Doc/Zsh/prompt.yo
+++ b/Doc/Zsh/prompt.yo
@@ -195,7 +195,8 @@ sitem(tt(%K))(the hour of the day on the 24-hour clock)
 sitem(tt(%L))(the hour of the day on the 12-hour clock)
 endsitem()
 
-In addition, if the system supports the POSIX tt(gettimeofday) system
+In addition, if the system supports the POSIX tt(clock_gettime)
+or 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 9 may be given following the tt(%); hence tt(%6.)
diff --git a/Src/Modules/zftp.c b/Src/Modules/zftp.c
index b60e5bf31..230ad86f6 100644
--- a/Src/Modules/zftp.c
+++ b/Src/Modules/zftp.c
@@ -397,7 +397,7 @@ zfalarm(int tmout)
     signal(SIGALRM, zfhandler);
     oalremain = alarm(tmout);
     if (oalremain)
-	oaltime = time(NULL);
+	oaltime = zmonotime(NULL);
     /*
      * We'll leave sigtrapped, sigfuncs and TRAPXXX as they are; since the
      * shell's handler doesn't get the signal, they don't matter.
@@ -431,7 +431,7 @@ zfunalarm(void)
 	 * I love the way alarm() uses unsigned int while time_t
 	 * is probably something completely different.
 	 */
-	unsigned int tdiff = time(NULL) - oaltime;
+	time_t tdiff = zmonotime(NULL) - oaltime;
 	alarm(oalremain < tdiff ? 1 : oalremain - tdiff);
     } else
 	alarm(0);
diff --git a/Src/Modules/zprof.c b/Src/Modules/zprof.c
index 171a15b90..f5a50effc 100644
--- a/Src/Modules/zprof.c
+++ b/Src/Modules/zprof.c
@@ -239,8 +239,7 @@ zprof_wrapper(Eprog prog, FuncWrap w, char *name)
     struct sfunc sf, *sp;
     Pfunc f = NULL;
     Parc a = NULL;
-    struct timeval tv;
-    struct timezone dummy;
+    struct timespec ts;
     double prev = 0, now;
     char *name_for_lookups;
 
@@ -278,19 +277,19 @@ zprof_wrapper(Eprog prog, FuncWrap w, char *name)
         stack = &sf;
 
         f->calls++;
-        tv.tv_sec = tv.tv_usec = 0;
-        gettimeofday(&tv, &dummy);
-        sf.beg = prev = ((((double) tv.tv_sec) * 1000.0) +
-                         (((double) tv.tv_usec) / 1000.0));
+        ts.tv_sec = ts.tv_nsec = 0;
+        zgettime_monotonic_if_available(&ts);
+        sf.beg = prev = ((((double) ts.tv_sec) * 1000.0) +
+                         (((double) ts.tv_nsec) / 1000000.0));
     }
     runshfunc(prog, w, name);
     if (active) {
         if (zprof_module && !(zprof_module->node.flags & MOD_UNLOAD)) {
-            tv.tv_sec = tv.tv_usec = 0;
-            gettimeofday(&tv, &dummy);
+            ts.tv_sec = ts.tv_nsec = 0;
+            zgettime_monotonic_if_available(&ts);
 
-            now = ((((double) tv.tv_sec) * 1000.0) +
-                   (((double) tv.tv_usec) / 1000.0));
+            now = ((((double) ts.tv_sec) * 1000.0) +
+                   (((double) ts.tv_nsec) / 1000000.0));
             f->self += now - sf.beg;
             for (sp = sf.prev; sp && sp->p != f; sp = sp->prev);
             if (!sp)
diff --git a/Src/compat.c b/Src/compat.c
index 8b31ad9f4..918d98e69 100644
--- a/Src/compat.c
+++ b/Src/compat.c
@@ -136,7 +136,19 @@ zgettime_monotonic_if_available(struct timespec *ts)
 
 #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
     struct timespec dts;
+
+/*
+ * On at least some versions of macOS it appears that CLOCK_MONOTONIC is not
+ * actually monotonic -- there are reports that it can go backwards.
+ * CLOCK_MONOTONIC_RAW does not have this problem. On top of that, it is faster
+ * to read and it has nanosecond precision. We could use it on other systems
+ * too, but on Linux at least it seems that CLOCK_MONOTONIC is preferred
+ */
+#if defined(__APPLE__) && defined(CLOCK_MONOTONIC_RAW)
+    if (clock_gettime(CLOCK_MONOTONIC_RAW, &dts) < 0) {
+#else
     if (clock_gettime(CLOCK_MONOTONIC, &dts) < 0) {
+#endif
 	zwarn("unable to retrieve CLOCK_MONOTONIC time: %e", errno);
 	ret--;
     } else {
diff --git a/Src/exec.c b/Src/exec.c
index bc07e8c39..874ff41f7 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -348,10 +348,9 @@ setlimits(char *nam)
 
 /**/
 static pid_t
-zfork(struct timeval *tv)
+zfork(struct timespec *ts)
 {
     pid_t pid;
-    struct timezone dummy_tz;
 
     /*
      * Is anybody willing to explain this test?
@@ -360,8 +359,8 @@ zfork(struct timeval *tv)
 	zerr("job table full");
 	return -1;
     }
-    if (tv)
-	gettimeofday(tv, &dummy_tz);
+    if (ts)
+	zgettime_monotonic_if_available(ts);
     /*
      * Queueing signals is necessary on Linux because fork()
      * manipulates mutexes, leading to deadlock in memory
@@ -460,7 +459,7 @@ zfork(struct timeval *tv)
 int list_pipe = 0, simple_pline = 0;
 
 static pid_t list_pipe_pid;
-static struct timeval list_pipe_start;
+static struct timespec list_pipe_start;
 static int nowait, pline_level = 0;
 static int list_pipe_child = 0, list_pipe_job;
 static char list_pipe_text[JOBTEXTSIZE];
@@ -1863,7 +1862,7 @@ execpline(Estate state, wordcode slcode, int how, int last1)
 		      (jobtab[list_pipe_job].stat & STAT_STOPPED)))) {
 		    pid_t pid = 0;
 		    int synch[2];
-		    struct timeval bgtime;
+		    struct timespec bgtime;
 
 		    /*
 		     * A pipeline with the shell handling the right
@@ -2284,7 +2283,7 @@ closemn(struct multio **mfds, int fd, int type)
 	char buf[TCBUFSIZE];
 	int len, i;
 	pid_t pid;
-	struct timeval bgtime;
+	struct timespec bgtime;
 
 	/*
 	 * We need to block SIGCHLD in case the process
@@ -2829,7 +2828,7 @@ execcmd_fork(Estate state, int how, int type, Wordcode varspc,
     pid_t pid;
     int synch[2], flags;
     struct entersubsh_ret esret;
-    struct timeval bgtime;
+    struct timespec bgtime;
 
     child_block();
     esret.gleader = -1;
@@ -2947,7 +2946,7 @@ execcmd_exec(Estate state, Execcmd_params eparams,
      * for the "time" keyword
      */
     child_times_t shti, chti;
-    struct timeval then;
+    struct timespec then;
     if (how & Z_TIMED)
 	shelltime(&shti, &chti, &then, 0);
 
@@ -5060,7 +5059,7 @@ getproc(char *cmd, char **eptr)
     int out = *cmd == Inang;
     char *pnam;
     pid_t pid;
-    struct timeval bgtime;
+    struct timespec bgtime;
 
 #ifndef PATH_DEV_FD
     int fd;
@@ -5149,7 +5148,7 @@ getpipe(char *cmd, int nullexec)
     Eprog prog;
     int pipes[2], out = *cmd == Inang;
     pid_t pid;
-    struct timeval bgtime;
+    struct timespec bgtime;
     char *ends;
 
     if (!(prog = parsecmd(cmd, &ends)))
diff --git a/Src/hist.c b/Src/hist.c
index 1a00c30ed..d0960a284 100644
--- a/Src/hist.c
+++ b/Src/hist.c
@@ -2891,9 +2891,9 @@ flockhistfile(char *fn, int keep_trying)
     /*
      * Timeout is ten seconds.
      */
-    end_time = time(NULL) + (time_t)10;
+    end_time = zmonotime(NULL) + (time_t)10;
     while (fcntl(flock_fd, F_SETLKW, &lck) == -1) {
-	if (!keep_trying || time(NULL) >= end_time ||
+	if (!keep_trying || zmonotime(NULL) >= end_time ||
 	    /*
 	     * Randomise wait to minimise clashes with shells exiting at
 	     * the same time.
@@ -3137,7 +3137,7 @@ static int lockhistct;
 static int
 checklocktime(char *lockfile, long *sleep_usp, time_t then)
 {
-    time_t now = time(NULL);
+    time_t now = zmonotime(NULL);
 
     if (now + 10 < then) {
 	/* File is more than 10 seconds in the future? */
diff --git a/Src/init.c b/Src/init.c
index 61f759ded..378aee348 100644
--- a/Src/init.c
+++ b/Src/init.c
@@ -1022,7 +1022,6 @@ setupvals(char *cmd, char *runscript, char *zsh_name)
 #ifdef USE_GETPWUID
     struct passwd *pswd;
 #endif
-    struct timezone dummy_tz;
     char *ptr;
     int i, j;
 #if defined(SITEFPATH_DIR) || defined(FPATH_DIR) || defined (ADDITIONAL_FPATH) || defined(FIXED_FPATH_DIR)
@@ -1109,8 +1108,8 @@ setupvals(char *cmd, char *runscript, char *zsh_name)
     hatchar = '^';
     termflags = TERM_UNKNOWN;
     curjob = prevjob = coprocin = coprocout = -1;
-    gettimeofday(&shtimer, &dummy_tz);	/* init $SECONDS */
-    srand((unsigned int)(shtimer.tv_sec + shtimer.tv_usec)); /* seed $RANDOM */
+    zgettime_monotonic_if_available(&shtimer);	/* init $SECONDS */
+    srand((unsigned int)(shtimer.tv_sec + shtimer.tv_nsec)); /* seed $RANDOM */
 
     /* Set default path */
     path    = (char **) zalloc(sizeof(*path) * 5);
@@ -1297,7 +1296,7 @@ setupvals(char *cmd, char *runscript, char *zsh_name)
 #endif
 
     breaks = loops = 0;
-    lastmailcheck = time(NULL);
+    lastmailcheck = zmonotime(NULL);
     locallevel = sourcelevel = 0;
     sfcontext = SFC_NONE;
     trap_return = 0;
diff --git a/Src/jobs.c b/Src/jobs.c
index 39c664388..ad14f6312 100644
--- a/Src/jobs.c
+++ b/Src/jobs.c
@@ -136,7 +136,7 @@ int numpipestats, pipestats[MAX_PIPESTATS];
 
 /**/
 static struct timeval *
-dtime(struct timeval *dt, struct timeval *t1, struct timeval *t2)
+dtime_tv(struct timeval *dt, struct timeval *t1, struct timeval *t2)
 {
     dt->tv_sec = t2->tv_sec - t1->tv_sec;
     dt->tv_usec = t2->tv_usec - t1->tv_usec;
@@ -147,6 +147,21 @@ dtime(struct timeval *dt, struct timeval *t1, struct timeval *t2)
     return dt;
 }
 
+/* As above, but with timespecs */
+
+/**/
+static struct timespec *
+dtime_ts(struct timespec *dt, struct timespec *t1, struct timespec *t2)
+{
+    dt->tv_sec = t2->tv_sec - t1->tv_sec;
+    dt->tv_nsec = t2->tv_nsec - t1->tv_nsec;
+    if (dt->tv_nsec < 0) {
+	dt->tv_nsec += 1000000000.0;
+	dt->tv_sec -= 1.0;
+    }
+    return dt;
+}
+
 /* change job table entry from stopped to running */
 
 /**/
@@ -349,7 +364,6 @@ get_usage(void)
 void
 update_process(Process pn, int status)
 {
-    struct timezone dummy_tz;
 #ifdef HAVE_GETRUSAGE
     struct timeval childs = child_usage.ru_stime;
     struct timeval childu = child_usage.ru_utime;
@@ -360,12 +374,12 @@ update_process(Process pn, int status)
 
     /* get time-accounting info          */
     get_usage();
-    gettimeofday(&pn->endtime, &dummy_tz);  /* record time process exited        */
+    zgettime_monotonic_if_available(&pn->endtime); /* record time process exited */
 
     pn->status = status;                    /* save the status returned by WAIT  */
 #ifdef HAVE_GETRUSAGE
-    dtime(&pn->ti.ru_stime, &childs, &child_usage.ru_stime);
-    dtime(&pn->ti.ru_utime, &childu, &child_usage.ru_utime);
+    dtime_tv(&pn->ti.ru_stime, &childs, &child_usage.ru_stime);
+    dtime_tv(&pn->ti.ru_utime, &childu, &child_usage.ru_utime);
 #else
     pn->ti.st  = shtms.tms_cstime - childs; /* compute process system space time */
     pn->ti.ut  = shtms.tms_cutime - childu; /* compute process user space time   */
@@ -753,7 +767,7 @@ printhhmmss(double secs)
 }
 
 static void
-printtime(struct timeval *real, child_times_t *ti, char *desc)
+printtime(struct timespec *real, child_times_t *ti, char *desc)
 {
     char *s;
     double elapsed_time, user_time, system_time;
@@ -774,21 +788,21 @@ printtime(struct timeval *real, child_times_t *ti, char *desc)
     }
 
     /* go ahead and compute these, since almost every TIMEFMT will have them */
-    elapsed_time = real->tv_sec + real->tv_usec / 1000000.0;
+    elapsed_time = real->tv_sec + real->tv_nsec / 1000000000.0;
 
 #ifdef HAVE_GETRUSAGE
     user_time = ti->ru_utime.tv_sec + ti->ru_utime.tv_usec / 1000000.0;
     system_time = ti->ru_stime.tv_sec + ti->ru_stime.tv_usec / 1000000.0;
     total_time = user_time + system_time;
     percent = 100.0 * total_time
-	/ (real->tv_sec + real->tv_usec / 1000000.0);
+	/ (real->tv_sec + real->tv_nsec / 1000000000.0);
 #else
     {
 	long clktck = get_clktck();
 	user_time    = ti->ut / (double) clktck;
 	system_time  = ti->st / (double) clktck;
 	percent      =  100.0 * (ti->ut + ti->st)
-	    / (clktck * real->tv_sec + clktck * real->tv_usec / 1000000.0);
+	    / (clktck * real->tv_sec + clktck * real->tv_nsec / 1000000000.0);
     }
 #endif
 
@@ -844,6 +858,23 @@ printtime(struct timeval *real, child_times_t *ti, char *desc)
 		    break;
 		}
 		break;
+	    case 'n':
+		switch (*++s) {
+		case 'E':
+		    fprintf(stderr, "%0.fns", elapsed_time * 1000000000.0);
+		    break;
+		case 'U':
+		    fprintf(stderr, "%0.fns", user_time * 1000000000.0);
+		    break;
+		case 'S':
+		    fprintf(stderr, "%0.fns", system_time * 1000000000.0);
+		    break;
+		default:
+		    fprintf(stderr, "%%n");
+		    s--;
+		    break;
+		}
+		break;
 	    case '*':
 		switch (*++s) {
 		case 'E':
@@ -991,12 +1022,12 @@ static void
 dumptime(Job jn)
 {
     Process pn;
-    struct timeval dtimeval;
+    struct timespec dtimespec;
 
     if (!jn->procs)
 	return;
     for (pn = jn->procs; pn; pn = pn->next)
-	printtime(dtime(&dtimeval, &pn->bgtime, &pn->endtime), &pn->ti,
+	printtime(dtime_ts(&dtimespec, &pn->bgtime, &pn->endtime), &pn->ti,
 		  pn->text);
 }
 
@@ -1506,7 +1537,7 @@ deletejob(Job jn, int disowning)
 
 /**/
 void
-addproc(pid_t pid, char *text, int aux, struct timeval *bgtime,
+addproc(pid_t pid, char *text, int aux, struct timespec *bgtime,
 	int gleader, int list_pipe_job_used)
 {
     Process pn, *pnlist;
@@ -1894,16 +1925,15 @@ spawnjob(void)
 
 /**/
 void
-shelltime(child_times_t *shell, child_times_t *kids, struct timeval *then, int delta)
+shelltime(child_times_t *shell, child_times_t *kids, struct timespec *then, int delta)
 {
-    struct timezone dummy_tz;
-    struct timeval dtimeval, now;
+    struct timespec dtimespec, now;
     child_times_t ti;
 #ifndef HAVE_GETRUSAGE
     struct tms buf;
 #endif
 
-    gettimeofday(&now, &dummy_tz);
+    zgettime_monotonic_if_available(&now);
 
 #ifdef HAVE_GETRUSAGE
     getrusage(RUSAGE_SELF, &ti);
@@ -1916,8 +1946,8 @@ shelltime(child_times_t *shell, child_times_t *kids, struct timeval *then, int d
     if (shell) {
 	if (delta) {
 #ifdef HAVE_GETRUSAGE
-	    dtime(&ti.ru_utime, &shell->ru_utime, &ti.ru_utime);
-	    dtime(&ti.ru_stime, &shell->ru_stime, &ti.ru_stime);
+	    dtime_tv(&ti.ru_utime, &shell->ru_utime, &ti.ru_utime);
+	    dtime_tv(&ti.ru_stime, &shell->ru_stime, &ti.ru_stime);
 #else
 	    ti.ut -= shell->ut;
 	    ti.st -= shell->st;
@@ -1926,15 +1956,15 @@ shelltime(child_times_t *shell, child_times_t *kids, struct timeval *then, int d
 	    *shell = ti;
     }
     if (delta)
-	dtime(&dtimeval, then, &now);
+	dtime_ts(&dtimespec, then, &now);
     else {
 	if (then)
 	    *then = now;
-	dtime(&dtimeval, &shtimer, &now);
+	dtime_ts(&dtimespec, &shtimer, &now);
     }
 
     if (!delta == !shell)
-	printtime(&dtimeval, &ti, "shell");
+	printtime(&dtimespec, &ti, "shell");
 
 #ifdef HAVE_GETRUSAGE
     getrusage(RUSAGE_CHILDREN, &ti);
@@ -1945,8 +1975,8 @@ shelltime(child_times_t *shell, child_times_t *kids, struct timeval *then, int d
     if (kids) {
 	if (delta) {
 #ifdef HAVE_GETRUSAGE
-	    dtime(&ti.ru_utime, &kids->ru_utime, &ti.ru_utime);
-	    dtime(&ti.ru_stime, &kids->ru_stime, &ti.ru_stime);
+	    dtime_tv(&ti.ru_utime, &kids->ru_utime, &ti.ru_utime);
+	    dtime_tv(&ti.ru_stime, &kids->ru_stime, &ti.ru_stime);
 #else
 	    ti.ut -= shell->ut;
 	    ti.st -= shell->st;
@@ -1955,7 +1985,7 @@ shelltime(child_times_t *shell, child_times_t *kids, struct timeval *then, int d
 	    *kids = ti;
     }
     if (!delta == !kids)
-	printtime(&dtimeval, &ti, "children");
+	printtime(&dtimespec, &ti, "children");
 }
 
 /* see if jobs need printing */
diff --git a/Src/params.c b/Src/params.c
index 6f137585b..d1c06b893 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -137,11 +137,11 @@ unsigned char hatchar, hashchar;
 unsigned char keyboardhackchar = '\0';
  
 /* $SECONDS = now.tv_sec - shtimer.tv_sec
- *          + (now.tv_usec - shtimer.tv_usec) / 1000000.0
+ *          + (now.tv_nsec - shtimer.tv_nsec) / 1000000000.0
  * (rounded to an integer if the parameter is not set to float) */
  
 /**/
-struct timeval shtimer;
+struct timespec shtimer;
  
 /* 0 if this $TERM setup is usable, otherwise it contains TERM_* flags */
 
@@ -4496,13 +4496,12 @@ randomsetfn(UNUSED(Param pm), zlong v)
 zlong
 intsecondsgetfn(UNUSED(Param pm))
 {
-    struct timeval now;
-    struct timezone dummy_tz;
+    struct timespec now;
 
-    gettimeofday(&now, &dummy_tz);
+    zgettime_monotonic_if_available(&now);
 
     return (zlong)(now.tv_sec - shtimer.tv_sec -
-		  (now.tv_usec < shtimer.tv_usec ? 1 : 0));
+		  (now.tv_nsec < shtimer.tv_nsec ? 1 : 0));
 }
 
 /* Function to set value of special parameter `SECONDS' */
@@ -4511,48 +4510,47 @@ intsecondsgetfn(UNUSED(Param pm))
 void
 intsecondssetfn(UNUSED(Param pm), zlong x)
 {
-    struct timeval now;
-    struct timezone dummy_tz;
+    struct timespec now;
     zlong diff;
 
-    gettimeofday(&now, &dummy_tz);
+    zgettime_monotonic_if_available(&now);
+
     diff = (zlong)now.tv_sec - x;
     shtimer.tv_sec = diff;
     if ((zlong)shtimer.tv_sec != diff)
 	zwarn("SECONDS truncated on assignment");
-    shtimer.tv_usec = now.tv_usec;
+    shtimer.tv_nsec = now.tv_nsec;
 }
 
 /**/
 double
 floatsecondsgetfn(UNUSED(Param pm))
 {
-    struct timeval now;
-    struct timezone dummy_tz;
+    struct timespec now;
 
-    gettimeofday(&now, &dummy_tz);
+    zgettime_monotonic_if_available(&now);
 
     return (double)(now.tv_sec - shtimer.tv_sec) +
-	(double)(now.tv_usec - shtimer.tv_usec) / 1000000.0;
+	(double)(now.tv_nsec - shtimer.tv_nsec) / 1000000000.0;
 }
 
 /**/
 void
 floatsecondssetfn(UNUSED(Param pm), double x)
 {
-    struct timeval now;
-    struct timezone dummy_tz;
+    struct timespec now;
+
+    zgettime_monotonic_if_available(&now);
 
-    gettimeofday(&now, &dummy_tz);
     shtimer.tv_sec = now.tv_sec - (zlong)x;
-    shtimer.tv_usec = now.tv_usec - (zlong)((x - (zlong)x) * 1000000.0);
+    shtimer.tv_nsec = now.tv_nsec - (zlong)((x - (zlong)x) * 1000000000.0);
 }
 
 /**/
 double
 getrawseconds(void)
 {
-    return (double)shtimer.tv_sec + (double)shtimer.tv_usec / 1000000.0;
+    return (double)shtimer.tv_sec + (double)shtimer.tv_nsec / 1000000000.0;
 }
 
 /**/
@@ -4560,7 +4558,7 @@ void
 setrawseconds(double x)
 {
     shtimer.tv_sec = (zlong)x;
-    shtimer.tv_usec = (zlong)((x - (zlong)x) * 1000000.0);
+    shtimer.tv_nsec = (zlong)((x - (zlong)x) * 1000000000.0);
 }
 
 /**/
diff --git a/Src/prompt.c b/Src/prompt.c
index e10b05215..f36aba9d3 100644
--- a/Src/prompt.c
+++ b/Src/prompt.c
@@ -469,7 +469,7 @@ putpromptchar(int doprint, int endchar)
 			test = 1;
 		    break;
 		case 'S':
-		    if (time(NULL) - shtimer.tv_sec >= arg)
+		    if (zmonotime(NULL) - shtimer.tv_sec >= arg)
 			test = 1;
 		    break;
 		case 'v':
diff --git a/Src/signals.c b/Src/signals.c
index 86f1a49f6..de42f302d 100644
--- a/Src/signals.c
+++ b/Src/signals.c
@@ -342,8 +342,7 @@ wait_for_processes(void)
 		zwarn("job can't be suspended");
 	    } else {
 #if defined(HAVE_WAIT3) && defined(HAVE_GETRUSAGE)
-		struct timezone dummy_tz;
-		gettimeofday(&pn->endtime, &dummy_tz);
+		zgettime_monotonic_if_available(&pn->endtime);
 #ifdef WIFCONTINUED
 		if (WIFCONTINUED(status))
 		    pn->status = SP_RUNNING;
diff --git a/Src/utils.c b/Src/utils.c
index ce4e875fd..5c91dfcda 100644
--- a/Src/utils.c
+++ b/Src/utils.c
@@ -1570,14 +1570,14 @@ preprompt(void)
     /* If 1) the parameter PERIOD exists, 2) a hook function for    *
      * "periodic" exists, 3) it's been greater than PERIOD since we *
      * executed any such hook, then execute it now.                 */
-    if (period && ((zlong)time(NULL) > (zlong)lastperiodic + period) &&
+    if (period && ((zlong)zmonotime(NULL) > (zlong)lastperiodic + period) &&
 	!callhookfunc("periodic", NULL, 1, NULL))
-	lastperiodic = time(NULL);
+	lastperiodic = zmonotime(NULL);
     if (errflag)
 	return;
 
     /* Check mail */
-    currentmailcheck = time(NULL);
+    currentmailcheck = zmonotime(NULL);
     if (mailcheck &&
 	(zlong) difftime(currentmailcheck, lastmailcheck) > mailcheck) {
 	char *mailfile;
@@ -2761,6 +2761,19 @@ timespec_diff_us(const struct timespec *t1, const struct timespec *t2)
     return (reverse ? LONG_MIN : LONG_MAX);
 }
 
+/* Like time(), but uses the monotonic clock */
+
+/**/
+mod_export int
+zmonotime(time_t *tloc)
+{
+    struct timespec ts;
+    zgettime_monotonic_if_available(&ts);
+    if (tloc)
+	*tloc = ts.tv_sec;
+    return ts.tv_sec;
+}
+
 /*
  * Sleep for the given number of microseconds --- must be within
  * range of a long at the moment, but this is only used for
@@ -2794,7 +2807,9 @@ zsleep(long us)
 
 /**
  * Sleep for time (fairly) randomly up to max_us microseconds.
- * Don't let the wallclock time extend beyond end_time.
+ * Don't let the time extend beyond end_time. end_time is compared to
+ * the current *monotonic* clock time, so do NOT base it on e.g. time(2);
+ * use zmonotime() or zgettime_monotonic_if_available().
  * Return 1 if that seemed to work, else 0.
  *
  * For best results max_us should be a multiple of 2**16 or large
@@ -2806,7 +2821,7 @@ int
 zsleep_random(long max_us, time_t end_time)
 {
     long r;
-    time_t now = time(NULL);
+    time_t now = zmonotime(NULL);
 
     /*
      * Randomish backoff.  Doesn't need to be fundamentally
diff --git a/Src/zsh.h b/Src/zsh.h
index 090abf8f5..85b5c9bdc 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -1115,8 +1115,8 @@ struct process {
     char text[JOBTEXTSIZE];	/* text to print when 'jobs' is run */
     int status;			/* return code from waitpid/wait3() */
     child_times_t ti;
-    struct timeval bgtime;	/* time job was spawned             */
-    struct timeval endtime;	/* time job exited                  */
+    struct timespec bgtime;	/* time job was spawned             */
+    struct timespec endtime;	/* time job exited                  */
 };
 
 struct execstack {
diff --git a/Test/A08time.ztst b/Test/A08time.ztst
index 22a460f5e..4a41cc76a 100644
--- a/Test/A08time.ztst
+++ b/Test/A08time.ztst
@@ -11,9 +11,44 @@
   (time cat) >&/dev/null
 0:`time' keyword (status only)
 
-  ( TIMEFMT='%E %mE %uE %* %m%mm %u%uu'; time (:) )
+  ( TIMEFMT='%E %mE %uE %nE %* %m%mm %u%uu %n%nn'; time (:) )
 0:`time' keyword with custom TIMEFMT
-*?[0-9]##.[0-9](#c2)s [0-9]##ms [0-9]##us %\* %m%mm %u%uu
+*?[0-9]##.[0-9](#c2)s [0-9]##ms [0-9]##us [0-9]##ns %\* %m%mm %u%uu %n%nn
+
+  ( TIMEFMT='x %U %S %E'; time (:) )
+0:TIMEFMT %[USE] use centisecond precision
+*?x( <0-9>.<00-99>s)(#c3)
+
+  ( TIMEFMT='x %*U %*S %*E'; time (:) )
+0:TIMEFMT %*[USE] use millisecond precision
+*?x( <0-9>.<000-999>)(#c3)
+
+  ( TIMEFMT='%nU %nS'; time (read -k3 -t0.1) )
+1:TIMEFMT %nU and %nS are limited to microsecond precision
+*?[1-9][0-9]#000ns [1-9][0-9]#000ns
+
+# SECONDS (after - before) must be greater than the elapsed time, but not much
+# greater. 25% was picked arbitrarily as something that hopefully will prevent
+# the test from failing on slow machines
+  (
+    typeset -F SECONDS
+    TIMEFMT=%nE
+    a=$SECONDS
+    t=$( (time (read -k3 -t0.1)) 2>&1 )
+    b=$SECONDS
+    s=$(( b - a ))
+    t=$(( ${t%ns}.0 / 10**9 ))
+    echo $s $t $(( s > t )) $(( t > s - (s * 0.25) ))
+  )
+0:`time' elapsed time matches SECONDS
+*>[0-9.]## [0-9.]## 1 1
+
+# Again, the wide range here is an attempt to prevent this test from failing on
+# slow machines. We don't care about the exact time, just that it's vaguely sane
+# and that each representation has the same basis
+  ( TIMEFMT='%E %mE %uE %nE %*E'; time (read -k3 -t0.1) )
+1:TIMEFMT elapsed time values
+*?0.<10-50>s <10-500>ms <100000-500000>us <100000000-500000000>ns 0.<100-500>
 
   time x=1
 0:`time' simple assignment
diff --git a/Test/D01prompt.ztst b/Test/D01prompt.ztst
index 55861cca1..f42e19714 100644
--- a/Test/D01prompt.ztst
+++ b/Test/D01prompt.ztst
@@ -68,6 +68,13 @@
   print -P '%(?.true.false)'
 0:ternary prompt escapes
 >true
+>false
+
+  sec=$SECONDS
+  eval "print -P '%(${sec}S.true.false)'"
+  eval "print -P '%($((sec+30))S.true.false)'"
+0:ternary prompt escape with test character S
+>true
 >false
 
   print -P 'start %10<...<truncated at 10%<< Not truncated%3< ...<Not shown'
diff --git a/Test/D04parameter.ztst b/Test/D04parameter.ztst
index 7953827d6..ed25fd7a9 100644
--- a/Test/D04parameter.ztst
+++ b/Test/D04parameter.ztst
@@ -1550,6 +1550,28 @@
 >1
 >1
 
+  # Integer
+  a=$SECONDS
+  sleep 1
+  b=$SECONDS
+  print -r - $a $b $(( b > a ))
+  # Float
+  typeset -F SECONDS
+  a=$SECONDS
+  repeat 10 :
+  b=$SECONDS
+  print -r - $a $b $(( b > a ))
+  # Assignment
+  a=$SECONDS
+  SECONDS=8888
+  repeat 10 :
+  b=$SECONDS
+  print -r - $(( a < 8888 )) $(( b > 8888 ))
+0:SECONDS
+*>[0-9]## [0-9]## 1
+*>[0-9]##.[0-9]## [0-9]##.[0-9]## 1
+*>1 1
+
   foo=("|" "?")
   [[ "|" = ${(j.|.)foo} ]] && print yes || print no
   [[ "|" = ${(j.|.)~foo} ]] && print yes || print no