summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--README.md92
-rw-r--r--lib.c121
-rw-r--r--tests/custom/03_stdlib/51_localtime36
-rw-r--r--tests/custom/03_stdlib/52_gmtime32
-rw-r--r--tests/custom/03_stdlib/53_timelocal58
-rw-r--r--tests/custom/03_stdlib/54_timegm54
-rw-r--r--tests/custom/03_stdlib/55_clock33
7 files changed, 426 insertions, 0 deletions
diff --git a/README.md b/README.md
index 33e9bd1..7ae085a 100644
--- a/README.md
+++ b/README.md
@@ -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 ]
+```
diff --git a/lib.c b/lib.c
index 8fdf2ec..30eb29b 100644
--- a/lib.c
+++ b/lib.c
@@ -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 --