aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/os/time.c
blob: 873302a27debc9940ee0e8f36e5f916cfaf0852f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <uv.h>

#include "auto/config.h"
#include "nvim/event/loop.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/log.h"
#include "nvim/macros.h"
#include "nvim/main.h"
#include "nvim/memory.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"

struct tm;

static uv_mutex_t delay_mutex;
static uv_cond_t delay_cond;

#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/time.c.generated.h"  // IWYU pragma: export
#endif

/// Initializes the time module
void time_init(void)
{
  uv_mutex_init(&delay_mutex);
  uv_cond_init(&delay_cond);
}

/// Gets a high-resolution (nanosecond), monotonically-increasing time relative
/// to an arbitrary time in the past.
///
/// Not related to the time of day and therefore not subject to clock drift.
///
/// @return Relative time value with nanosecond precision.
uint64_t os_hrtime(void)
  FUNC_ATTR_WARN_UNUSED_RESULT
{
  return uv_hrtime();
}

/// Gets a millisecond-resolution, monotonically-increasing time relative to an
/// arbitrary time in the past.
///
/// Not related to the time of day and therefore not subject to clock drift.
/// The value is cached by the loop, it will not change until the next
/// loop-tick (unless uv_update_time is called).
///
/// @return Relative time value with millisecond precision.
uint64_t os_now(void)
  FUNC_ATTR_WARN_UNUSED_RESULT
{
  return uv_now(&main_loop.uv);
}

/// Sleeps for `ms` milliseconds.
///
/// @see uv_sleep() (libuv v1.34.0)
///
/// @param ms          Number of milliseconds to sleep
/// @param ignoreinput If true, only SIGINT (CTRL-C) can interrupt.
void os_delay(uint64_t ms, bool ignoreinput)
{
  DLOG("%" PRIu64 " ms", ms);
  if (ignoreinput) {
    if (ms > INT_MAX) {
      ms = INT_MAX;
    }
    LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, (int)ms, got_int);
  } else {
    os_microdelay(ms * 1000U, ignoreinput);
  }
}

/// Sleeps for `us` microseconds.
///
/// @see uv_sleep() (libuv v1.34.0)
///
/// @param us          Number of microseconds to sleep.
/// @param ignoreinput If true, ignore all input (including SIGINT/CTRL-C).
///                    If false, waiting is aborted on any input.
void os_microdelay(uint64_t us, bool ignoreinput)
{
  uint64_t elapsed = 0U;
  uint64_t base = uv_hrtime();
  // Convert microseconds to nanoseconds, or UINT64_MAX on overflow.
  const uint64_t ns = (us < UINT64_MAX / 1000U) ? us * 1000U : UINT64_MAX;

  uv_mutex_lock(&delay_mutex);

  while (elapsed < ns) {
    // If ignoring input, we simply wait the full delay.
    // Else we check for input in ~100ms intervals.
    const uint64_t ns_delta = ignoreinput
                              ? ns - elapsed
                              : MIN(ns - elapsed, 100000000U);  // 100ms

    const int rv = uv_cond_timedwait(&delay_cond, &delay_mutex, ns_delta);
    if (0 != rv && UV_ETIMEDOUT != rv) {
      abort();
      break;
    }  // Else: Timeout proceeded normally.

    if (!ignoreinput && os_char_avail()) {
      break;
    }

    const uint64_t now = uv_hrtime();
    elapsed += now - base;
    base = now;
  }

  uv_mutex_unlock(&delay_mutex);
}

// 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.
static char tz_cache[64];

/// Portable version of POSIX localtime_r()
///
/// @return NULL in case of error
struct tm *os_localtime_r(const time_t *restrict clock,
                          struct tm *restrict result) FUNC_ATTR_NONNULL_ALL
{
#ifdef UNIX
  // POSIX provides localtime_r() as a thread-safe version of localtime().
  //
  // Check to see if the environment variable TZ has changed since the last run.
  // Call tzset(3) to update the global timezone variables if it has.
  // POSIX standard doesn't require localtime_r() implementations to do that
  // as it does with localtime(), and we don't want to call tzset() every time.
  const char *tz = os_getenv("TZ");
  if (tz == NULL) {
    tz = "";
  }
  if (strncmp(tz_cache, tz, sizeof(tz_cache) - 1) != 0) {
    tzset();
    xstrlcpy(tz_cache, tz, sizeof(tz_cache));
  }
  return localtime_r(clock, result);  // NOLINT(runtime/threadsafe_fn)
#else
  // Windows version of localtime() is thread-safe.
  // See http://msdn.microsoft.com/en-us/library/bf12f0hc%28VS.80%29.aspx
  struct tm *local_time = localtime(clock);  // NOLINT(runtime/threadsafe_fn)
  if (!local_time) {
    return NULL;
  }
  *result = *local_time;
  return result;
#endif
}

/// Gets the current Unix timestamp and adjusts it to local time.
///
/// @param result Pointer to a 'struct tm' where the result should be placed
/// @return A pointer to a 'struct tm' in the current time zone (the 'result'
///         argument) or NULL in case of error
struct tm *os_localtime(struct tm *result) FUNC_ATTR_NONNULL_ALL
{
  time_t rawtime = time(NULL);
  return os_localtime_r(&rawtime, result);
}

/// Portable version of POSIX ctime_r()
///
/// @param clock[in]
/// @param result[out] Pointer to a 'char' where the result should be placed
/// @param result_len length of result buffer
/// @return human-readable string of current local time
char *os_ctime_r(const time_t *restrict clock, char *restrict result, size_t result_len,
                 bool add_newline)
  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
  struct tm clock_local;
  struct tm *clock_local_ptr = os_localtime_r(clock, &clock_local);
  // MSVC returns NULL for an invalid value of seconds.
  if (clock_local_ptr == NULL) {
    xstrlcpy(result, _("(Invalid)"), result_len - 1);
  } else {
    // xgettext:no-c-format
    if (strftime(result, result_len - 1, _("%a %b %d %H:%M:%S %Y"), clock_local_ptr) == 0) {
      // Quoting "man strftime":
      // > If the length of the result string (including the terminating
      // > null byte) would exceed max bytes, then strftime() returns 0,
      // > and the contents of the array are undefined.
      xstrlcpy(result, _("(Invalid)"), result_len - 1);
    }
  }
  if (add_newline) {
    xstrlcat(result, "\n", result_len);
  }
  return result;
}

/// Gets the current Unix timestamp and adjusts it to local time.
///
/// @param result[out] Pointer to a 'char' where the result should be placed
/// @param result_len length of result buffer
/// @return human-readable string of current local time
char *os_ctime(char *result, size_t result_len, bool add_newline)
  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
  time_t rawtime = time(NULL);
  return os_ctime_r(&rawtime, result, result_len, add_newline);
}

/// Portable version of POSIX strptime()
///
/// @param str[in]  string to convert
/// @param format[in]  format to parse "str"
/// @param tm[out]  time representation of "str"
/// @return Pointer to first unprocessed character or NULL
char *os_strptime(const char *str, const char *format, struct tm *tm)
  FUNC_ATTR_NONNULL_ALL
{
#ifdef HAVE_STRPTIME
  return strptime(str, format, tm);
#else
  return NULL;
#endif
}

/// Obtains the current Unix timestamp.
///
/// @return Seconds since epoch.
Timestamp os_time(void)
  FUNC_ATTR_WARN_UNUSED_RESULT
{
  return (Timestamp)time(NULL);
}