diff options
-rw-r--r-- | README.md | 92 | ||||
-rw-r--r-- | lib.c | 121 | ||||
-rw-r--r-- | tests/custom/03_stdlib/51_localtime | 36 | ||||
-rw-r--r-- | tests/custom/03_stdlib/52_gmtime | 32 | ||||
-rw-r--r-- | tests/custom/03_stdlib/53_timelocal | 58 | ||||
-rw-r--r-- | tests/custom/03_stdlib/54_timegm | 54 | ||||
-rw-r--r-- | tests/custom/03_stdlib/55_clock | 33 |
7 files changed, 426 insertions, 0 deletions
@@ -1268,3 +1268,95 @@ If a non-array argument is given, the function returns `null`. uniq([ 1, true, "foo", 2, true, "bar", "foo" ]); // [ 1, true, "foo", 2, "bar" ] uniq("test"); // null ``` + +#### 6.65. `localtime([epoch])` + +Return the given epoch timestamp (or now, if omitted) as a dictionary +containing broken-down date and time information according to the local +system timezone. + +The resulting dictionary contains the following fields: + + - `sec` Seconds (0-60) + - `min` Minutes (0-59) + - `hour` Hours (0-23) + - `mday` Day of month (1-31) + - `mon` Month (1-12) + - `year` Year (>= 1900) + - `wday` Day of the week (1-7, Sunday = 7) + - `yday` Day of the year (1-366, Jan 1st = 1) + - `isdst` Daylight saving time in effect (yes = 1) + +Note that in contrast to the underlying `localtime(3)` C library function, +the values for `mon`, `wday` and `yday` are 1-based and the `year` is +1900-based. + +```javascript +localtime(1647953502); +// { +// "sec": 42, +// "min": 51, +// "hour": 13, +// "mday": 22, +// "mon": 3, +// "year": 2022, +// "wday": 2, +// "yday": 81, +// "isdst": 0 +// } +``` + +#### 6.66. `gmtime([epoch])` + +Like `localtime()` but interpreting the given epoch value as UTC time. + +See `localtime()` for details on the return value. + +#### 6.67. `timelocal(datetimespec)` + +Performs the inverse operation of `localtime()` by taking a broken-down +date and time dictionary and transforming it into an epoch value according +to the local system timezone. + +The `wday` and `yday` fields of the given date time specification are +ignored. Field values outside of their valid range are internally normalized, +e.g. October 40th is interpreted as November 9th. + +Returns the resulting epoch value or null if the input date time dictionary +was invalid or if the date time specification cannot be represented as +epoch value. + +```javascript +timelocal({ "sec": 42, "min": 51, "hour": 13, "mday": 22, "mon": 3, "year": 2022, "isdst": 0 }) +// 1647953502 +``` + +#### 6.68. `timegm(datetimespec)` + +Like `timelocal()` but interpreting the given date time specification as +UTC time. + +See `timelocal()` for details. + +#### 6.69. `clock([monotonic])` + +Reads the current second and microsecond value of the system clock. + +By default, the realtime clock is queried which might skew forwards +or backwards due to NTP changes, system sleep modes etc. + +If a truish value is passed as argument, the monotonic system clock +is queried instead, which will return the monotonically increasing +time since some arbitrary point in the past (usually the system boot +time). + +Returns a two element array containing the full seconds as first and +the nanosecond fraction as second element. + +Returns `null` if a monotonic clock value is requested and the system +does not implement this clock type. + +```javascript +clock(); // [ 1647954926, 798269464 ] +clock(true); // [ 474751, 527959975 ] +``` @@ -3126,6 +3126,122 @@ uc_uniq(uc_vm_t *vm, size_t nargs) return uniq; } +static uc_value_t * +uc_gettime_common(uc_vm_t *vm, size_t nargs, bool local) +{ + uc_value_t *ts = uc_fn_arg(0), *res; + time_t t = ts ? (time_t)ucv_to_integer(ts) : time(NULL); + struct tm *tm = (local ? localtime : gmtime)(&t); + + if (!tm) + return NULL; + + res = ucv_object_new(vm); + + ucv_object_add(res, "sec", ucv_int64_new(tm->tm_sec)); + ucv_object_add(res, "min", ucv_int64_new(tm->tm_min)); + ucv_object_add(res, "hour", ucv_int64_new(tm->tm_hour)); + ucv_object_add(res, "mday", ucv_int64_new(tm->tm_mday)); + ucv_object_add(res, "mon", ucv_int64_new(tm->tm_mon + 1)); + ucv_object_add(res, "year", ucv_int64_new(tm->tm_year + 1900)); + ucv_object_add(res, "wday", ucv_int64_new(tm->tm_wday ? tm->tm_wday : 7)); + ucv_object_add(res, "yday", ucv_int64_new(tm->tm_yday + 1)); + ucv_object_add(res, "isdst", ucv_int64_new(tm->tm_isdst)); + + return res; +} + +static uc_value_t * +uc_localtime(uc_vm_t *vm, size_t nargs) +{ + return uc_gettime_common(vm, nargs, true); +} + +static uc_value_t * +uc_gmtime(uc_vm_t *vm, size_t nargs) +{ + return uc_gettime_common(vm, nargs, false); +} + +static uc_value_t * +uc_mktime_common(uc_vm_t *vm, size_t nargs, bool local) +{ +#define FIELD(name, required) \ + { #name, required, offsetof(struct tm, tm_##name) } + + const struct { + const char *name; + bool required; + size_t off; + } fields[] = { + FIELD(sec, false), + FIELD(min, false), + FIELD(hour, false), + FIELD(mday, true), + FIELD(mon, true), + FIELD(year, true), + FIELD(isdst, false) + }; + + uc_value_t *to = uc_fn_arg(0), *v; + struct tm tm = { 0 }; + bool exists; + time_t t; + size_t i; + + if (ucv_type(to) != UC_OBJECT) + return NULL; + + for (i = 0; i < ARRAY_SIZE(fields); i++) { + v = ucv_object_get(to, fields[i].name, &exists); + + if (!exists && fields[i].required) + return NULL; + + *(int *)((char *)&tm + fields[i].off) = (int)ucv_to_integer(v); + } + + if (tm.tm_mon > 0) + tm.tm_mon--; + + if (tm.tm_year >= 1900) + tm.tm_year -= 1900; + + t = (local ? mktime : timegm)(&tm); + + return (t != (time_t)-1) ? ucv_int64_new((int64_t)t) : NULL; +} + +static uc_value_t * +uc_timelocal(uc_vm_t *vm, size_t nargs) +{ + return uc_mktime_common(vm, nargs, true); +} + +static uc_value_t * +uc_timegm(uc_vm_t *vm, size_t nargs) +{ + return uc_mktime_common(vm, nargs, false); +} + +static uc_value_t * +uc_clock(uc_vm_t *vm, size_t nargs) +{ + clockid_t id = ucv_is_truish(uc_fn_arg(0)) ? CLOCK_MONOTONIC : CLOCK_REALTIME; + struct timespec ts; + uc_value_t *res; + + if (clock_gettime(id, &ts) == -1) + return NULL; + + res = ucv_array_new(vm); + + ucv_array_set(res, 0, ucv_int64_new((int64_t)ts.tv_sec)); + ucv_array_set(res, 1, ucv_int64_new((int64_t)ts.tv_nsec)); + + return res; +} + const uc_function_list_t uc_stdlib_functions[] = { { "chr", uc_chr }, @@ -3186,6 +3302,11 @@ const uc_function_list_t uc_stdlib_functions[] = { { "b64dec", uc_b64dec }, { "b64enc", uc_b64enc }, { "uniq", uc_uniq }, + { "localtime", uc_localtime }, + { "gmtime", uc_gmtime }, + { "timelocal", uc_timelocal }, + { "timegm", uc_timegm }, + { "clock", uc_clock }, }; diff --git a/tests/custom/03_stdlib/51_localtime b/tests/custom/03_stdlib/51_localtime new file mode 100644 index 0000000..d16f279 --- /dev/null +++ b/tests/custom/03_stdlib/51_localtime @@ -0,0 +1,36 @@ +The `localtime()` function returns the given epoch timestamp (or now, +if omitted) as a dictionary containing broken-down date and time +information according to the local system timezone. + +-- Testcase -- +{% + let t = time(); + let d1 = localtime(); + let d2 = localtime(1647953502); + + // assert that localtime without epoch returns the current time + let c = timelocal(d1); + assert(c >= t && c <= t + 5, "localtime() result does not match time()"); + + // dump fixed time and check expected output + printf("%.J\n", d2); +%} +-- End -- + +-- Vars -- +TZ=CET-1CEST,M3.5.0/2,M10.5.0/3 +-- End -- + +-- Expect stdout -- +{ + "sec": 42, + "min": 51, + "hour": 13, + "mday": 22, + "mon": 3, + "year": 2022, + "wday": 2, + "yday": 81, + "isdst": 0 +} +-- End -- diff --git a/tests/custom/03_stdlib/52_gmtime b/tests/custom/03_stdlib/52_gmtime new file mode 100644 index 0000000..2d73a12 --- /dev/null +++ b/tests/custom/03_stdlib/52_gmtime @@ -0,0 +1,32 @@ +The `gmtime()` function returns the given epoch timestamp (or now, +if omitted) as a dictionary containing broken-down date and time +information interpreted as UTC time. + +-- Testcase -- +{% + let t = time(); + let d1 = gmtime(); + let d2 = gmtime(1647953502); + + // assert that localtime without epoch returns the current time + let c = timegm(d1); + assert(c >= t && c <= t + 5, "gmtime() result does not match time()"); + + // dump fixed time and check expected output + printf("%.J\n", d2); +%} +-- End -- + +-- Expect stdout -- +{ + "sec": 42, + "min": 51, + "hour": 12, + "mday": 22, + "mon": 3, + "year": 2022, + "wday": 2, + "yday": 81, + "isdst": 0 +} +-- End -- diff --git a/tests/custom/03_stdlib/53_timelocal b/tests/custom/03_stdlib/53_timelocal new file mode 100644 index 0000000..e4d0db4 --- /dev/null +++ b/tests/custom/03_stdlib/53_timelocal @@ -0,0 +1,58 @@ +The `timelocal()` function performs the inverse operation of `localtime()` +by taking a broken-down date and time dictionary and transforming it into +an epoch value according to the local system timezone. + +-- Testcase -- +{% + // check expected epoch + let d1 = { + "sec": 42, + "min": 51, + "hour": 13, + "mday": 22, + "mon": 3, + "year": 2022, + "wday": 2, + "yday": 81, + "isdst": 0 + }; + + // check that out of range values are normalized + let d2 = { + "sec": 33, + "min": 22, + "hour": 11, + "mday": 40, + "mon": 10, + "year": 2022, + "wday": 2, + "yday": 81, + "isdst": 0 + }; + + // check that everything except mday, mon, year is optional + let d3 = { + "mday": 1, + "mon": 1, + "year": 2000 + }; + + printf("%.J\n", [ + timelocal(d1), + timelocal(d2), + timelocal(d3) + ]); +%} +-- End -- + +-- Vars -- +TZ=CET-1CEST,M3.5.0/2,M10.5.0/3 +-- End -- + +-- Expect stdout -- +[ + 1647953502, + 1667989353, + 946681200 +] +-- End -- diff --git a/tests/custom/03_stdlib/54_timegm b/tests/custom/03_stdlib/54_timegm new file mode 100644 index 0000000..9c0b59a --- /dev/null +++ b/tests/custom/03_stdlib/54_timegm @@ -0,0 +1,54 @@ +The `timegm()` function performs the inverse operation of `gmtime()` +by taking a broken-down date and time dictionary and transforming it into +an epoch value, assuming UTC time. + +-- Testcase -- +{% + // check expected epoch + let d1 = { + "sec": 42, + "min": 51, + "hour": 13, + "mday": 22, + "mon": 3, + "year": 2022, + "wday": 2, + "yday": 81, + "isdst": 0 + }; + + // check that out of range values are normalized + let d2 = { + "sec": 33, + "min": 22, + "hour": 11, + "mday": 40, + "mon": 10, + "year": 2022, + "wday": 2, + "yday": 81, + "isdst": 0 + }; + + // check that everything except mday, mon, year is optional + let d3 = { + "mday": 1, + "mon": 1, + "year": 2000 + }; + + printf("%.J\n", [ + timegm(d1), + timegm(d2), + timegm(d3) + ]); +%} +-- End -- + +-- Expect stdout -- +[ + 1647957102, + 1667992953, + 946684800 +] +-- End -- diff --git a/tests/custom/03_stdlib/55_clock b/tests/custom/03_stdlib/55_clock new file mode 100644 index 0000000..109ce21 --- /dev/null +++ b/tests/custom/03_stdlib/55_clock @@ -0,0 +1,33 @@ +The `clock()` function reads the current second and microsecond value of +the system clock, optionally using the monotonic clock instead of the +default realtime one. + +-- Testcase -- +{% + let t1 = clock(); + let t3 = clock(true); + + sleep(250); + + let t2 = clock(); + let t4 = clock(true); + + let delta1 = (t2[0] - t1[0]) * 1000000000 + (t2[1] - t1[1]); + let delta2 = (t4[0] - t3[0]) * 1000000000 + (t4[1] - t3[1]); + + assert(delta1 >= 0, "Realtime clock went backwards!"); + assert(delta2 >= 0, "Monotonic clock went backwards!"); + + printf("%.J\n", [ + (delta1 >= 240000000 && delta1 <= 260000000) ? true : "unexpected delta: " + delta1, + (delta2 >= 240000000 && delta2 <= 260000000) ? true : "unexpected delta: " + delta2 + ]); +%} +-- End -- + +-- Expect stdout -- +[ + true, + true +] +-- End -- |