Mercurial > vim
view src/time.c @ 21370:a652dd15819a
Added tag v8.2.1235 for changeset e00467b9f5de84ca3a906ae66c1551c780cb10fe
author | Bram Moolenaar <Bram@vim.org> |
---|---|
date | Sat, 18 Jul 2020 13:00:06 +0200 |
parents | 2e7acc208a1f |
children | ba81f5e300b7 |
line wrap: on
line source
/* vi:set ts=8 sts=4 sw=4 noet: * * VIM - Vi IMproved by Bram Moolenaar * * Do ":help uganda" in Vim to read copying and usage conditions. * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ /* * time.c: functions related to time and timers */ #include "vim.h" /* * Cache of the current timezone name as retrieved from TZ, or an empty string * where unset, up to 64 octets long including trailing null byte. */ #if defined(HAVE_LOCALTIME_R) && defined(HAVE_TZSET) static char tz_cache[64]; #endif #define FOR_ALL_TIMERS(t) \ for ((t) = first_timer; (t) != NULL; (t) = (t)->tr_next) /* * Call either localtime(3) or localtime_r(3) from POSIX libc time.h, with the * latter version preferred for reentrancy. * * If we use localtime_r(3) and we have tzset(3) available, check to see if the * environment variable TZ has changed since the last run, and call tzset(3) to * update the global timezone variables if it has. This is because the POSIX * standard doesn't require localtime_r(3) implementations to do that as it * does with localtime(3), and we don't want to call tzset(3) every time. */ static struct tm * vim_localtime( const time_t *timep, // timestamp for local representation struct tm *result UNUSED) // pointer to caller return buffer { #ifdef HAVE_LOCALTIME_R # ifdef HAVE_TZSET char *tz; // pointer for TZ environment var tz = (char *)mch_getenv((char_u *)"TZ"); if (tz == NULL) tz = ""; if (STRNCMP(tz_cache, tz, sizeof(tz_cache) - 1) != 0) { tzset(); vim_strncpy((char_u *)tz_cache, (char_u *)tz, sizeof(tz_cache) - 1); } # endif // HAVE_TZSET return localtime_r(timep, result); #else return localtime(timep); #endif // HAVE_LOCALTIME_R } /* * Return the current time in seconds. Calls time(), unless test_settime() * was used. */ time_T vim_time(void) { # ifdef FEAT_EVAL return time_for_testing == 0 ? time(NULL) : time_for_testing; # else return time(NULL); # endif } /* * Replacement for ctime(), which is not safe to use. * Requires strftime(), otherwise returns "(unknown)". * If "thetime" is invalid returns "(invalid)". Never returns NULL. * When "add_newline" is TRUE add a newline like ctime() does. * Uses a static buffer. */ char * get_ctime(time_t thetime, int add_newline) { static char buf[50]; #ifdef HAVE_STRFTIME struct tm tmval; struct tm *curtime; curtime = vim_localtime(&thetime, &tmval); // MSVC returns NULL for an invalid value of seconds. if (curtime == NULL) vim_strncpy((char_u *)buf, (char_u *)_("(Invalid)"), sizeof(buf) - 1); else { (void)strftime(buf, sizeof(buf) - 1, _("%a %b %d %H:%M:%S %Y"), curtime); # ifdef MSWIN if (enc_codepage >= 0 && (int)GetACP() != enc_codepage) { char_u *to_free = NULL; int len; acp_to_enc((char_u *)buf, (int)strlen(buf), &to_free, &len); if (to_free != NULL) { STRCPY(buf, to_free); vim_free(to_free); } } # endif } #else STRCPY(buf, "(unknown)"); #endif if (add_newline) STRCAT(buf, "\n"); return buf; } #if defined(FEAT_EVAL) || defined(PROTO) #if defined(MACOS_X) # include <time.h> // for time_t #endif /* * "localtime()" function */ void f_localtime(typval_T *argvars UNUSED, typval_T *rettv) { rettv->vval.v_number = (varnumber_T)time(NULL); } # if defined(FEAT_RELTIME) /* * Convert a List to proftime_T. * Return FAIL when there is something wrong. */ static int list2proftime(typval_T *arg, proftime_T *tm) { long n1, n2; int error = FALSE; if (arg->v_type != VAR_LIST || arg->vval.v_list == NULL || arg->vval.v_list->lv_len != 2) return FAIL; n1 = list_find_nr(arg->vval.v_list, 0L, &error); n2 = list_find_nr(arg->vval.v_list, 1L, &error); # ifdef MSWIN tm->HighPart = n1; tm->LowPart = n2; # else tm->tv_sec = n1; tm->tv_usec = n2; # endif return error ? FAIL : OK; } # endif // FEAT_RELTIME /* * "reltime()" function */ void f_reltime(typval_T *argvars UNUSED, typval_T *rettv UNUSED) { # ifdef FEAT_RELTIME proftime_T res; proftime_T start; if (argvars[0].v_type == VAR_UNKNOWN) { // No arguments: get current time. profile_start(&res); } else if (argvars[1].v_type == VAR_UNKNOWN) { if (list2proftime(&argvars[0], &res) == FAIL) return; profile_end(&res); } else { // Two arguments: compute the difference. if (list2proftime(&argvars[0], &start) == FAIL || list2proftime(&argvars[1], &res) == FAIL) return; profile_sub(&res, &start); } if (rettv_list_alloc(rettv) == OK) { long n1, n2; # ifdef MSWIN n1 = res.HighPart; n2 = res.LowPart; # else n1 = res.tv_sec; n2 = res.tv_usec; # endif list_append_number(rettv->vval.v_list, (varnumber_T)n1); list_append_number(rettv->vval.v_list, (varnumber_T)n2); } # endif } # ifdef FEAT_FLOAT /* * "reltimefloat()" function */ void f_reltimefloat(typval_T *argvars UNUSED, typval_T *rettv) { # ifdef FEAT_RELTIME proftime_T tm; # endif rettv->v_type = VAR_FLOAT; rettv->vval.v_float = 0; # ifdef FEAT_RELTIME if (list2proftime(&argvars[0], &tm) == OK) rettv->vval.v_float = profile_float(&tm); # endif } # endif /* * "reltimestr()" function */ void f_reltimestr(typval_T *argvars UNUSED, typval_T *rettv) { # ifdef FEAT_RELTIME proftime_T tm; # endif rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; # ifdef FEAT_RELTIME if (list2proftime(&argvars[0], &tm) == OK) rettv->vval.v_string = vim_strsave((char_u *)profile_msg(&tm)); # endif } # if defined(HAVE_STRFTIME) || defined(PROTO) /* * "strftime({format}[, {time}])" function */ void f_strftime(typval_T *argvars, typval_T *rettv) { char_u result_buf[256]; struct tm tmval; struct tm *curtime; time_t seconds; char_u *p; rettv->v_type = VAR_STRING; p = tv_get_string(&argvars[0]); if (argvars[1].v_type == VAR_UNKNOWN) seconds = time(NULL); else seconds = (time_t)tv_get_number(&argvars[1]); curtime = vim_localtime(&seconds, &tmval); // MSVC returns NULL for an invalid value of seconds. if (curtime == NULL) rettv->vval.v_string = vim_strsave((char_u *)_("(Invalid)")); else { vimconv_T conv; char_u *enc; conv.vc_type = CONV_NONE; enc = enc_locale(); convert_setup(&conv, p_enc, enc); if (conv.vc_type != CONV_NONE) p = string_convert(&conv, p, NULL); if (p != NULL) (void)strftime((char *)result_buf, sizeof(result_buf), (char *)p, curtime); else result_buf[0] = NUL; if (conv.vc_type != CONV_NONE) vim_free(p); convert_setup(&conv, enc, p_enc); if (conv.vc_type != CONV_NONE) rettv->vval.v_string = string_convert(&conv, result_buf, NULL); else rettv->vval.v_string = vim_strsave(result_buf); // Release conversion descriptors convert_setup(&conv, NULL, NULL); vim_free(enc); } } # endif # if defined(HAVE_STRPTIME) || defined(PROTO) /* * "strptime({format}, {timestring})" function */ void f_strptime(typval_T *argvars, typval_T *rettv) { struct tm tmval; char_u *fmt; char_u *str; vimconv_T conv; char_u *enc; CLEAR_FIELD(tmval); tmval.tm_isdst = -1; fmt = tv_get_string(&argvars[0]); str = tv_get_string(&argvars[1]); conv.vc_type = CONV_NONE; enc = enc_locale(); convert_setup(&conv, p_enc, enc); if (conv.vc_type != CONV_NONE) fmt = string_convert(&conv, fmt, NULL); if (fmt == NULL || strptime((char *)str, (char *)fmt, &tmval) == NULL || (rettv->vval.v_number = mktime(&tmval)) == -1) rettv->vval.v_number = 0; if (conv.vc_type != CONV_NONE) vim_free(fmt); convert_setup(&conv, NULL, NULL); vim_free(enc); } # endif # if defined(FEAT_TIMERS) || defined(PROTO) static timer_T *first_timer = NULL; static long last_timer_id = 0; /* * Return time left until "due". Negative if past "due". */ long proftime_time_left(proftime_T *due, proftime_T *now) { # ifdef MSWIN LARGE_INTEGER fr; if (now->QuadPart > due->QuadPart) return 0; QueryPerformanceFrequency(&fr); return (long)(((double)(due->QuadPart - now->QuadPart) / (double)fr.QuadPart) * 1000); # else if (now->tv_sec > due->tv_sec) return 0; return (due->tv_sec - now->tv_sec) * 1000 + (due->tv_usec - now->tv_usec) / 1000; # endif } /* * Insert a timer in the list of timers. */ static void insert_timer(timer_T *timer) { timer->tr_next = first_timer; timer->tr_prev = NULL; if (first_timer != NULL) first_timer->tr_prev = timer; first_timer = timer; did_add_timer = TRUE; } /* * Take a timer out of the list of timers. */ static void remove_timer(timer_T *timer) { if (timer->tr_prev == NULL) first_timer = timer->tr_next; else timer->tr_prev->tr_next = timer->tr_next; if (timer->tr_next != NULL) timer->tr_next->tr_prev = timer->tr_prev; } static void free_timer(timer_T *timer) { free_callback(&timer->tr_callback); vim_free(timer); } /* * Create a timer and return it. NULL if out of memory. * Caller should set the callback. */ timer_T * create_timer(long msec, int repeat) { timer_T *timer = ALLOC_CLEAR_ONE(timer_T); long prev_id = last_timer_id; if (timer == NULL) return NULL; if (++last_timer_id <= prev_id) // Overflow! Might cause duplicates... last_timer_id = 0; timer->tr_id = last_timer_id; insert_timer(timer); if (repeat != 0) timer->tr_repeat = repeat - 1; timer->tr_interval = msec; profile_setlimit(msec, &timer->tr_due); return timer; } /* * Invoke the callback of "timer". */ static void timer_callback(timer_T *timer) { typval_T rettv; typval_T argv[2]; argv[0].v_type = VAR_NUMBER; argv[0].vval.v_number = (varnumber_T)timer->tr_id; argv[1].v_type = VAR_UNKNOWN; call_callback(&timer->tr_callback, -1, &rettv, 1, argv); clear_tv(&rettv); } /* * Call timers that are due. * Return the time in msec until the next timer is due. * Returns -1 if there are no pending timers. */ long check_due_timer(void) { timer_T *timer; timer_T *timer_next; long this_due; long next_due = -1; proftime_T now; int did_one = FALSE; int need_update_screen = FALSE; long current_id = last_timer_id; // Don't run any timers while exiting or dealing with an error. if (exiting || aborting()) return next_due; profile_start(&now); for (timer = first_timer; timer != NULL && !got_int; timer = timer_next) { timer_next = timer->tr_next; if (timer->tr_id == -1 || timer->tr_firing || timer->tr_paused) continue; this_due = proftime_time_left(&timer->tr_due, &now); if (this_due <= 1) { // Save and restore a lot of flags, because the timer fires while // waiting for a character, which might be halfway a command. int save_timer_busy = timer_busy; int save_vgetc_busy = vgetc_busy; int save_did_emsg = did_emsg; int save_called_emsg = called_emsg; int save_must_redraw = must_redraw; int save_trylevel = trylevel; int save_did_throw = did_throw; int save_ex_pressedreturn = get_pressedreturn(); int save_may_garbage_collect = may_garbage_collect; except_T *save_current_exception = current_exception; vimvars_save_T vvsave; // Create a scope for running the timer callback, ignoring most of // the current scope, such as being inside a try/catch. timer_busy = timer_busy > 0 || vgetc_busy > 0; vgetc_busy = 0; called_emsg = 0; did_emsg = FALSE; did_uncaught_emsg = FALSE; must_redraw = 0; trylevel = 0; did_throw = FALSE; current_exception = NULL; may_garbage_collect = FALSE; save_vimvars(&vvsave); timer->tr_firing = TRUE; timer_callback(timer); timer->tr_firing = FALSE; timer_next = timer->tr_next; did_one = TRUE; timer_busy = save_timer_busy; vgetc_busy = save_vgetc_busy; if (did_uncaught_emsg) ++timer->tr_emsg_count; did_emsg = save_did_emsg; called_emsg = save_called_emsg; trylevel = save_trylevel; did_throw = save_did_throw; current_exception = save_current_exception; restore_vimvars(&vvsave); if (must_redraw != 0) need_update_screen = TRUE; must_redraw = must_redraw > save_must_redraw ? must_redraw : save_must_redraw; set_pressedreturn(save_ex_pressedreturn); may_garbage_collect = save_may_garbage_collect; // Only fire the timer again if it repeats and stop_timer() wasn't // called while inside the callback (tr_id == -1). if (timer->tr_repeat != 0 && timer->tr_id != -1 && timer->tr_emsg_count < 3) { profile_setlimit(timer->tr_interval, &timer->tr_due); this_due = proftime_time_left(&timer->tr_due, &now); if (this_due < 1) this_due = 1; if (timer->tr_repeat > 0) --timer->tr_repeat; } else { this_due = -1; remove_timer(timer); free_timer(timer); } } if (this_due > 0 && (next_due == -1 || next_due > this_due)) next_due = this_due; } if (did_one) redraw_after_callback(need_update_screen); #ifdef FEAT_BEVAL_TERM if (bevalexpr_due_set) { this_due = proftime_time_left(&bevalexpr_due, &now); if (this_due <= 1) { bevalexpr_due_set = FALSE; if (balloonEval == NULL) { balloonEval = ALLOC_CLEAR_ONE(BalloonEval); balloonEvalForTerm = TRUE; } if (balloonEval != NULL) { general_beval_cb(balloonEval, 0); setcursor(); out_flush(); } } else if (next_due == -1 || next_due > this_due) next_due = this_due; } #endif #ifdef FEAT_TERMINAL // Some terminal windows may need their buffer updated. next_due = term_check_timers(next_due, &now); #endif return current_id != last_timer_id ? 1 : next_due; } /* * Find a timer by ID. Returns NULL if not found; */ static timer_T * find_timer(long id) { timer_T *timer; if (id >= 0) { FOR_ALL_TIMERS(timer) if (timer->tr_id == id) return timer; } return NULL; } /* * Stop a timer and delete it. */ void stop_timer(timer_T *timer) { if (timer->tr_firing) // Free the timer after the callback returns. timer->tr_id = -1; else { remove_timer(timer); free_timer(timer); } } static void stop_all_timers(void) { timer_T *timer; timer_T *timer_next; for (timer = first_timer; timer != NULL; timer = timer_next) { timer_next = timer->tr_next; stop_timer(timer); } } static void add_timer_info(typval_T *rettv, timer_T *timer) { list_T *list = rettv->vval.v_list; dict_T *dict = dict_alloc(); dictitem_T *di; long remaining; proftime_T now; if (dict == NULL) return; list_append_dict(list, dict); dict_add_number(dict, "id", timer->tr_id); dict_add_number(dict, "time", (long)timer->tr_interval); profile_start(&now); remaining = proftime_time_left(&timer->tr_due, &now); dict_add_number(dict, "remaining", (long)remaining); dict_add_number(dict, "repeat", (long)(timer->tr_repeat < 0 ? -1 : timer->tr_repeat + 1)); dict_add_number(dict, "paused", (long)(timer->tr_paused)); di = dictitem_alloc((char_u *)"callback"); if (di != NULL) { if (dict_add(dict, di) == FAIL) vim_free(di); else put_callback(&timer->tr_callback, &di->di_tv); } } static void add_timer_info_all(typval_T *rettv) { timer_T *timer; FOR_ALL_TIMERS(timer) if (timer->tr_id != -1) add_timer_info(rettv, timer); } /* * Mark references in partials of timers. */ int set_ref_in_timer(int copyID) { int abort = FALSE; timer_T *timer; typval_T tv; for (timer = first_timer; !abort && timer != NULL; timer = timer->tr_next) { if (timer->tr_callback.cb_partial != NULL) { tv.v_type = VAR_PARTIAL; tv.vval.v_partial = timer->tr_callback.cb_partial; } else { tv.v_type = VAR_FUNC; tv.vval.v_string = timer->tr_callback.cb_name; } abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL); } return abort; } # if defined(EXITFREE) || defined(PROTO) void timer_free_all() { timer_T *timer; while (first_timer != NULL) { timer = first_timer; remove_timer(timer); free_timer(timer); } } # endif /* * "timer_info([timer])" function */ void f_timer_info(typval_T *argvars, typval_T *rettv) { timer_T *timer = NULL; if (rettv_list_alloc(rettv) != OK) return; if (argvars[0].v_type != VAR_UNKNOWN) { if (argvars[0].v_type != VAR_NUMBER) emsg(_(e_number_exp)); else { timer = find_timer((int)tv_get_number(&argvars[0])); if (timer != NULL) add_timer_info(rettv, timer); } } else add_timer_info_all(rettv); } /* * "timer_pause(timer, paused)" function */ void f_timer_pause(typval_T *argvars, typval_T *rettv UNUSED) { timer_T *timer = NULL; int paused = (int)tv_get_number(&argvars[1]); if (argvars[0].v_type != VAR_NUMBER) emsg(_(e_number_exp)); else { timer = find_timer((int)tv_get_number(&argvars[0])); if (timer != NULL) timer->tr_paused = paused; } } /* * "timer_start(time, callback [, options])" function */ void f_timer_start(typval_T *argvars, typval_T *rettv) { long msec = (long)tv_get_number(&argvars[0]); timer_T *timer; int repeat = 0; callback_T callback; dict_T *dict; rettv->vval.v_number = -1; if (check_secure()) return; if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_DICT || (dict = argvars[2].vval.v_dict) == NULL) { semsg(_(e_invarg2), tv_get_string(&argvars[2])); return; } if (dict_find(dict, (char_u *)"repeat", -1) != NULL) repeat = dict_get_number(dict, (char_u *)"repeat"); } callback = get_callback(&argvars[1]); if (callback.cb_name == NULL) return; timer = create_timer(msec, repeat); if (timer == NULL) free_callback(&callback); else { set_callback(&timer->tr_callback, &callback); rettv->vval.v_number = (varnumber_T)timer->tr_id; } } /* * "timer_stop(timer)" function */ void f_timer_stop(typval_T *argvars, typval_T *rettv UNUSED) { timer_T *timer; if (argvars[0].v_type != VAR_NUMBER) { emsg(_(e_number_exp)); return; } timer = find_timer((int)tv_get_number(&argvars[0])); if (timer != NULL) stop_timer(timer); } /* * "timer_stopall()" function */ void f_timer_stopall(typval_T *argvars UNUSED, typval_T *rettv UNUSED) { stop_all_timers(); } # endif // FEAT_TIMERS # if defined(STARTUPTIME) || defined(PROTO) static struct timeval prev_timeval; # ifdef MSWIN /* * Windows doesn't have gettimeofday(), although it does have struct timeval. */ static int gettimeofday(struct timeval *tv, char *dummy UNUSED) { long t = clock(); tv->tv_sec = t / CLOCKS_PER_SEC; tv->tv_usec = (t - tv->tv_sec * CLOCKS_PER_SEC) * 1000000 / CLOCKS_PER_SEC; return 0; } # endif /* * Save the previous time before doing something that could nest. * set "*tv_rel" to the time elapsed so far. */ void time_push(void *tv_rel, void *tv_start) { *((struct timeval *)tv_rel) = prev_timeval; gettimeofday(&prev_timeval, NULL); ((struct timeval *)tv_rel)->tv_usec = prev_timeval.tv_usec - ((struct timeval *)tv_rel)->tv_usec; ((struct timeval *)tv_rel)->tv_sec = prev_timeval.tv_sec - ((struct timeval *)tv_rel)->tv_sec; if (((struct timeval *)tv_rel)->tv_usec < 0) { ((struct timeval *)tv_rel)->tv_usec += 1000000; --((struct timeval *)tv_rel)->tv_sec; } *(struct timeval *)tv_start = prev_timeval; } /* * Compute the previous time after doing something that could nest. * Subtract "*tp" from prev_timeval; * Note: The arguments are (void *) to avoid trouble with systems that don't * have struct timeval. */ void time_pop( void *tp) // actually (struct timeval *) { prev_timeval.tv_usec -= ((struct timeval *)tp)->tv_usec; prev_timeval.tv_sec -= ((struct timeval *)tp)->tv_sec; if (prev_timeval.tv_usec < 0) { prev_timeval.tv_usec += 1000000; --prev_timeval.tv_sec; } } static void time_diff(struct timeval *then, struct timeval *now) { long usec; long msec; usec = now->tv_usec - then->tv_usec; msec = (now->tv_sec - then->tv_sec) * 1000L + usec / 1000L, usec = usec % 1000L; fprintf(time_fd, "%03ld.%03ld", msec, usec >= 0 ? usec : usec + 1000L); } void time_msg( char *mesg, void *tv_start) // only for do_source: start time; actually // (struct timeval *) { static struct timeval start; struct timeval now; if (time_fd != NULL) { if (strstr(mesg, "STARTING") != NULL) { gettimeofday(&start, NULL); prev_timeval = start; fprintf(time_fd, "\n\ntimes in msec\n"); fprintf(time_fd, " clock self+sourced self: sourced script\n"); fprintf(time_fd, " clock elapsed: other lines\n\n"); } gettimeofday(&now, NULL); time_diff(&start, &now); if (((struct timeval *)tv_start) != NULL) { fprintf(time_fd, " "); time_diff(((struct timeval *)tv_start), &now); } fprintf(time_fd, " "); time_diff(&prev_timeval, &now); prev_timeval = now; fprintf(time_fd, ": %s\n", mesg); } } # endif // STARTUPTIME #endif // FEAT_EVAL #if defined(FEAT_SPELL) || defined(FEAT_PERSISTENT_UNDO) || defined(PROTO) /* * Read 8 bytes from "fd" and turn them into a time_T, MSB first. * Returns -1 when encountering EOF. */ time_T get8ctime(FILE *fd) { int c; time_T n = 0; int i; for (i = 0; i < 8; ++i) { c = getc(fd); if (c == EOF) return -1; n = (n << 8) + c; } return n; } #ifdef _MSC_VER # if (_MSC_VER <= 1200) // This line is required for VC6 without the service pack. Also see the // matching #pragma below. # pragma optimize("", off) # endif #endif /* * Write time_T to file "fd" in 8 bytes. * Returns FAIL when the write failed. */ int put_time(FILE *fd, time_T the_time) { char_u buf[8]; time_to_bytes(the_time, buf); return fwrite(buf, (size_t)8, (size_t)1, fd) == 1 ? OK : FAIL; } /* * Write time_T to "buf[8]". */ void time_to_bytes(time_T the_time, char_u *buf) { int c; int i; int bi = 0; time_T wtime = the_time; // time_T can be up to 8 bytes in size, more than long_u, thus we // can't use put_bytes() here. // Another problem is that ">>" may do an arithmetic shift that keeps the // sign. This happens for large values of wtime. A cast to long_u may // truncate if time_T is 8 bytes. So only use a cast when it is 4 bytes, // it's safe to assume that long_u is 4 bytes or more and when using 8 // bytes the top bit won't be set. for (i = 7; i >= 0; --i) { if (i + 1 > (int)sizeof(time_T)) // ">>" doesn't work well when shifting more bits than avail buf[bi++] = 0; else { #if defined(SIZEOF_TIME_T) && SIZEOF_TIME_T > 4 c = (int)(wtime >> (i * 8)); #else c = (int)((long_u)wtime >> (i * 8)); #endif buf[bi++] = c; } } } #ifdef _MSC_VER # if (_MSC_VER <= 1200) # pragma optimize("", on) # endif #endif #endif /* * Put timestamp "tt" in "buf[buflen]" in a nice format. */ void add_time(char_u *buf, size_t buflen, time_t tt) { #ifdef HAVE_STRFTIME struct tm tmval; struct tm *curtime; if (vim_time() - tt >= 100) { curtime = vim_localtime(&tt, &tmval); if (vim_time() - tt < (60L * 60L * 12L)) // within 12 hours (void)strftime((char *)buf, buflen, "%H:%M:%S", curtime); else // longer ago (void)strftime((char *)buf, buflen, "%Y/%m/%d %H:%M:%S", curtime); } else #endif { long seconds = (long)(vim_time() - tt); vim_snprintf((char *)buf, buflen, NGETTEXT("%ld second ago", "%ld seconds ago", seconds), seconds); } }