aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2024-09-30 01:53:39 -0400
committerBruce Hill <bruce@bruce-hill.com>2024-09-30 01:53:39 -0400
commit793717729ae46bd026eff882f7d4da819dec32e5 (patch)
treecb35ac60f2821188bf86f3864ca99ad23f95944e
parent37780cb3234441d693dcb0f2d1d4efbd6632a0a6 (diff)
Parameterize with timezones
-rw-r--r--docs/datetime.md123
-rw-r--r--environment.c16
-rw-r--r--stdlib/datetime.c107
-rw-r--r--stdlib/datetime.h17
4 files changed, 197 insertions, 66 deletions
diff --git a/docs/datetime.md b/docs/datetime.md
index 7e528def..7fbd8ff7 100644
--- a/docs/datetime.md
+++ b/docs/datetime.md
@@ -1,13 +1,20 @@
# DateTime
Tomo has a builtin datatype for representing a specific single point in time:
-`DateTime`. A DateTime object is internally represented using a UNIX epoch in
-seconds and a number of nanoseconds to represent sub-second times (in C, the
+`DateTime`. A DateTime object is internally represented using a UNIX timestamp
+in seconds and a number of nanoseconds to represent sub-second times (in C, the
equivalent of `struct timeval`). DateTime values do not represent calendar
dates or clock times, they represent an exact moment in time, such as the
moment when a file was last modified on the filesystem or the current moment
(`DateTime.now()`).
+⚠️⚠️⚠️ **WARNING** ⚠️⚠️⚠️ Dates and times are deeply counterintuitive and you should
+be extremely cautious when writing code that deals with dates and times. Effort
+has been made to ensure that Tomo's `DateTime` code uses standard libraries and
+is as correct as possible, but counterintuitive behaviors around time zones,
+daylight savings time, leap seconds, and other anomalous time situations can
+still cause bugs if you're not extremely careful.
+
## Time Zones
Because humans are not able to easily understand UNIX timestamps, the default
@@ -29,8 +36,12 @@ time. In that example, the initial time would be 3am March 1, 2023 in UTC, so
one month later would be 3am April 1, 2023 in UTC, which is which is 11am March
31st in local time. Most users would be unpleasantly surprised to find out that
when it's February 28th in local time, one month later is March 28th until 8pm,
-at which point it becomes March 31st! For functions where this matters, there
-is an extra `local_time` argument that is `yes` by default.
+at which point it becomes March 31st!
+
+For various functions where time zones matter, there is an optional `timezone`
+argument that, if set, will override the timezone when performing calculations.
+If unspecified, it is assumed that the current local timezone should be used.
+Time zones are specified by name, such as `America/New_York` or `EDT`.
## DateTime Methods
@@ -46,24 +57,25 @@ example, one year from January 1, 2024 is January 1, 2025, which is 366 days
later because 2024 is a leap year. Similarly, adding one month may add anywhere
from 28 to 31 days, depending on the starting month. Days and weeks are
affected by leap seconds. For this reason, `after()` takes an argument,
-`local_time` which is used to determine whether time offsets should be
-calculated using the current local time or UTC.
+`timezone` which is used to determine in which timezone the offsets should be
+calculated.
**Usage:**
```markdown
-datetime:after(seconds : Num = 0.0, minutes : Num = 0.0, hours : Num = 0.0, days : Int = 0, weeks : Int = 0, months : Int = 0, years : Int = 0, local_time : Bool = yes) -> DateTime
+datetime:after(seconds : Num = 0.0, minutes : Num = 0.0, hours : Num = 0.0, days : Int = 0, weeks : Int = 0, months : Int = 0, years : Int = 0, timezone : Text? = !Text) -> DateTime
```
**Parameters:**
-- `seconds`: An amount of seconds to offset the datetime (default: 0).
-- `minutes`: An amount of minutes to offset the datetime (default: 0).
-- `hours`: An amount of hours to offset the datetime (default: 0).
-- `days`: An amount of days to offset the datetime (default: 0).
-- `weeks`: An amount of weeks to offset the datetime (default: 0).
-- `months`: An amount of months to offset the datetime (default: 0).
-- `years`: An amount of years to offset the datetime (default: 0).
-- `local_time`: Whether to perform the calculations in local time (default: `yes`) or, if not, in UTC time.
+- `seconds` (optional): An amount of seconds to offset the datetime (default: 0).
+- `minutes` (optional): An amount of minutes to offset the datetime (default: 0).
+- `hours` (optional): An amount of hours to offset the datetime (default: 0).
+- `days` (optional): An amount of days to offset the datetime (default: 0).
+- `weeks` (optional): An amount of weeks to offset the datetime (default: 0).
+- `months` (optional): An amount of months to offset the datetime (default: 0).
+- `years` (optional): An amount of years to offset the datetime (default: 0).
+- `timezone` (optional): If specified, perform perform the calculations in the
+ given timezone. If unspecified, the current local timezone will be used.
**Returns:**
A new `DateTime` offset by the given amount.
@@ -84,12 +96,12 @@ specifier, which gives the date in `YYYY-MM-DD` form.
**Usage:**
```markdown
-datetime:date(local_time : Bool = yes) -> Text
+datetime:date(timezone : Text? = !Text) -> Text
```
**Parameters:**
-- `local_time`: Whether to use local time (default: `yes`) or UTC.
+- `timezone` (optional): If specified, give the date in the given timezone (otherwise, use the current local timezone).
**Returns:**
The date in `YYYY-MM-DD` format.
@@ -107,17 +119,18 @@ The date in `YYYY-MM-DD` format.
**Description:**
Using the C-style [`strftime`](https://linux.die.net/man/3/strftime) format
options, return a text representation of the given date in the given format. If
-`local_time` is `no`, then use UTC instead of the current locale's timezone.
+`timezone` is specified, use that timezone instead of the current local
+timezone.
**Usage:**
```markdown
-datetime:format(format: Text = "%Y-%m-%dT%H:%M:%S%z", local_time : Bool = yes) -> Text
+datetime:format(format: Text = "%Y-%m-%dT%H:%M:%S%z", timezone : Text? = !Text) -> Text
```
**Parameters:**
- `format`: The `strftime` format to use (default: `"%Y-%m-%dT%H:%M:%S%z"`).
-- `local_time`: Whether to use local time (default: `yes`) or UTC.
+- `timezone` (optional): If specified, use the given timezone (otherwise, use the current local timezone).
**Returns:**
Nothing.
@@ -165,7 +178,7 @@ provided optional fields.
**Usage:**
```markdown
-datetime:get(year : &Int? = !&Int, month : &Int? = !&Int, day : &Int? = !&Int, hour : &Int? = !&Int, minute : &Int? = !&Int, second : &Int? = !&Int, nanosecond : &Int? = !&Int, weekday : &Int? = !&Int, local_time=yes) -> Void
+datetime:get(year : &Int? = !&Int, month : &Int? = !&Int, day : &Int? = !&Int, hour : &Int? = !&Int, minute : &Int? = !&Int, second : &Int? = !&Int, nanosecond : &Int? = !&Int, weekday : &Int? = !&Int, timezone : Text? = !Text) -> Void
```
**Parameters:**
@@ -178,7 +191,7 @@ datetime:get(year : &Int? = !&Int, month : &Int? = !&Int, day : &Int? = !&Int, h
- `second`: If non-null, store the second of the minute here (0-59).
- `nanosecond`: If non-null, store the nanosecond of the second here (0-1,000,000,000).
- `weekday`: If non-null, store the day of the week here (sunday=1, saturday=7)
-- `local_time`: Whether to use the local timezone (default: `yes`) or UTC.
+- `timezone` (optional): If specified, give values in the given timezone (otherwise, use the current local timezone).
**Returns:**
Nothing.
@@ -194,6 +207,33 @@ dt:get(month=&month)
---
+### `get_local_timezone`
+
+**Description:**
+Get the local timezone's name (e.g. `America/New_York` or `UTC`. By default,
+this value is read from `/etc/localtime`, however, this can be overridden by
+calling `DateTime.set_local_timezone(...)`.
+
+**Usage:**
+```markdown
+DateTime.get_local_timezone() -> Text
+```
+
+**Parameters:**
+
+None.
+
+**Returns:**
+The name of the current local timezone.
+
+**Example:**
+```markdown
+>> DateTime.get_local_timezone()
+= "America/New_York"
+```
+
+---
+
### `hours_till`
**Description:**
@@ -353,13 +393,14 @@ between two `DateTime`s. For example: `5 minutes ago` or `1 day later`
**Usage:**
```markdown
-datetime:relative(relative_to : DateTime = DateTime.now(), local_time : Bool = yes) -> Text
+datetime:relative(relative_to : DateTime = DateTime.now(), timezone : Text? = !Text) -> Text
```
**Parameters:**
- `relative_to` (optional): The time against which the relative time is calculated (default: `DateTime.now()`).
-- `local_time` (optional): Whether or not to perform calculations in local time (default: `yes`).
+- `timezone` (optional): If specified, perform calculations in the given
+ timezone (otherwise, use the current local timezone).
**Returns:**
Return a plain English textual representation of the approximate time
@@ -406,6 +447,36 @@ the_future := now():after(seconds=1)
---
+### `set_local_timezone`
+
+**Description:**
+Set the current local timezone to a given value by name (e.g.
+`America/New_York` or `UTC`). The local timezone is used as the default
+timezone for performing calculations and constructing `DateTime` objects from
+component parts. It's also used as the default way that `DateTime` objects are
+converted to text.
+
+**Usage:**
+```markdown
+DateTime.set_local_timezone(timezone : Text? = !Text) -> Void
+```
+
+**Parameters:**
+
+- `timezone` (optional): if specified, set the current local timezone to the
+ timezone with the given name. If null, reset the current local timezone to
+ the system default (the value referenced in `/etc/localtime`).
+
+**Returns:**
+Nothing.
+
+**Example:**
+```markdown
+DateTime.set_local_timezone("America/Los_Angeles")
+```
+
+---
+
### `time`
**Description:**
@@ -413,14 +484,14 @@ Return a text representation of the time component of the given datetime.
**Usage:**
```markdown
-datetime:time(seconds : Bool = no, am_pm : Bool = yes, local_time : Bool = yes) -> Text
+datetime:time(seconds : Bool = no, am_pm : Bool = yes, timezone : Text? = !Text) -> Text
```
**Parameters:**
- `seconds`: Whether to include seconds in the time (default: `no`).
- `am_pm`: Whether to use am/pm in the representation or use a 24-hour clock (default: `yes`).
-- `local_time`: Whether to use local time (default: `yes`) or UTC.
+- `timezone` (optional): If specified, give the time in the given timezone (otherwise, use the current local timezone).
**Returns:**
A text representation of the time component of the datetime.
diff --git a/environment.c b/environment.c
index 69710b41..d5b8190c 100644
--- a/environment.c
+++ b/environment.c
@@ -265,18 +265,20 @@ env_t *new_compilation_unit(CORD libname)
// Used as a default for functions below:
{"now", "DateTime$now", "func()->DateTime"},
- {"after", "DateTime$after", "func(dt:DateTime,seconds=0.0,minutes=0.0,hours=0.0,days=0,weeks=0,months=0,years=0,local_time=yes)->DateTime"},
- {"date", "DateTime$date", "func(dt:DateTime,local_time=yes)->Text"},
- {"format", "DateTime$format", "func(dt:DateTime,format=\"%Y-%m-%dT%H:%M:%S%z\",local_time=yes)->Text"},
+ {"after", "DateTime$after", "func(dt:DateTime,seconds=0.0,minutes=0.0,hours=0.0,days=0,weeks=0,months=0,years=0,timezone=!Text)->DateTime"},
+ {"date", "DateTime$date", "func(dt:DateTime,timezone=!Text)->Text"},
+ {"format", "DateTime$format", "func(dt:DateTime,format=\"%Y-%m-%dT%H:%M:%S%z\",timezone=!Text)->Text"},
{"from_unix_timestamp", "DateTime$from_unix_timestamp", "func(timestamp:Int64)->DateTime"},
- {"get", "DateTime$get", "func(dt:DateTime,year=!&Int,month=!&Int,day=!&Int,hour=!&Int,minute=!&Int,second=!&Int,nanosecond=!&Int,weekday=!&Int, local_time=yes)"},
+ {"get", "DateTime$get", "func(dt:DateTime,year=!&Int,month=!&Int,day=!&Int,hour=!&Int,minute=!&Int,second=!&Int,nanosecond=!&Int,weekday=!&Int, timezone=!Text)"},
+ {"get_local_timezone", "DateTime$get_local_timezone", "func()->Text"},
{"hours_till", "DateTime$hours_till", "func(now:DateTime,then:DateTime)->Num"},
{"minutes_till", "DateTime$minutes_till", "func(now:DateTime,then:DateTime)->Num"},
- {"new", "DateTime$new", "func(year:Int,month:Int,day:Int,hour=0,minute=0,second=0.0)->DateTime"},
+ {"new", "DateTime$new", "func(year:Int,month:Int,day:Int,hour=0,minute=0,second=0.0,timezone=!Text)->DateTime"},
{"parse", "DateTime$parse", "func(text:Text, format=\"%Y-%m-%dT%H:%M:%S%z\")->DateTime?"},
- {"relative", "DateTime$relative", "func(dt:DateTime,relative_to=DateTime.now(),local_time=yes)->Text"},
+ {"relative", "DateTime$relative", "func(dt:DateTime,relative_to=DateTime.now(),timezone=!Text)->Text"},
{"seconds_till", "DateTime$seconds_till", "func(now:DateTime,then:DateTime)->Num"},
- {"time", "DateTime$time", "func(dt:DateTime,seconds=no,am_pm=yes,local_time=yes)->Text"},
+ {"set_local_timezone", "DateTime$set_local_timezone", "func(timezone=!Text)"},
+ {"time", "DateTime$time", "func(dt:DateTime,seconds=no,am_pm=yes,timezone=!Text)->Text"},
{"unix_timestamp", "DateTime$unix_timestamp", "func(dt:DateTime)->Int64"},
)},
{"Path", Type(TextType, .lang="Path", .env=namespace_env(env, "Path")), "Text_t", "Text$info", TypedArray(ns_entry_t,
diff --git a/stdlib/datetime.c b/stdlib/datetime.c
index bc1d88e7..6ad4966f 100644
--- a/stdlib/datetime.c
+++ b/stdlib/datetime.c
@@ -4,14 +4,18 @@
#include <err.h>
#include <stdlib.h>
#include <time.h>
+#include <unistd.h>
#include "datatypes.h"
+#include "datetime.h"
#include "optionals.h"
#include "patterns.h"
#include "stdlib.h"
#include "text.h"
#include "util.h"
+static OptionalText_t _local_timezone = NULL_TEXT;
+
public Text_t DateTime$as_text(const DateTime_t *dt, bool colorize, const TypeInfo *type)
{
(void)type;
@@ -44,8 +48,10 @@ public DateTime_t DateTime$now(void)
return (DateTime_t){.tv_sec=ts.tv_sec, .tv_usec=ts.tv_nsec};
}
-public DateTime_t DateTime$new(Int_t year, Int_t month, Int_t day, Int_t hour, Int_t minute, double second)
+public DateTime_t DateTime$new(Int_t year, Int_t month, Int_t day, Int_t hour, Int_t minute, double second, OptionalText_t timezone)
{
+ if (timezone.length >= 0)
+ DateTime$set_local_timezone(timezone);
struct tm info = {
.tm_min=Int_to_Int32(minute, false),
.tm_hour=Int_to_Int32(hour, false),
@@ -55,19 +61,26 @@ public DateTime_t DateTime$new(Int_t year, Int_t month, Int_t day, Int_t hour, I
.tm_isdst=-1,
};
time_t t = mktime(&info);
+ if (timezone.length >= 0)
+ DateTime$set_local_timezone(_local_timezone);
return (DateTime_t){.tv_sec=t + (time_t)second, .tv_usec=(suseconds_t)(fmod(second, 1.0) * 1e9)};
}
-public DateTime_t DateTime$after(DateTime_t dt, double seconds, double minutes, double hours, Int_t days, Int_t weeks, Int_t months, Int_t years, bool local_time)
+public DateTime_t DateTime$after(DateTime_t dt, double seconds, double minutes, double hours, Int_t days, Int_t weeks, Int_t months, Int_t years, OptionalText_t timezone)
{
double offset = seconds + 60.*minutes + 3600.*hours;
dt.tv_sec += (time_t)offset;
struct tm info = {};
- if (local_time)
+ if (timezone.length >= 0) {
+ OptionalText_t old_timezone = _local_timezone;
+ DateTime$set_local_timezone(timezone);
localtime_r(&dt.tv_sec, &info);
- else
- gmtime_r(&dt.tv_sec, &info);
+ DateTime$set_local_timezone(old_timezone);
+ } else {
+ localtime_r(&dt.tv_sec, &info);
+ }
+
info.tm_mday += Int_to_Int32(days, false) + 7*Int_to_Int32(weeks, false);
info.tm_mon += Int_to_Int32(months, false);
info.tm_year += Int_to_Int32(years, false);
@@ -96,13 +109,18 @@ CONSTFUNC public double DateTime$hours_till(DateTime_t now, DateTime_t then)
public void DateTime$get(
DateTime_t dt, Int_t *year, Int_t *month, Int_t *day, Int_t *hour, Int_t *minute, Int_t *second,
- Int_t *nanosecond, Int_t *weekday, bool local_time)
+ Int_t *nanosecond, Int_t *weekday, OptionalText_t timezone)
{
struct tm info = {};
- if (local_time)
+
+ if (timezone.length >= 0) {
+ OptionalText_t old_timezone = _local_timezone;
+ DateTime$set_local_timezone(timezone);
localtime_r(&dt.tv_sec, &info);
- else
- gmtime_r(&dt.tv_sec, &info);
+ DateTime$set_local_timezone(old_timezone);
+ } else {
+ localtime_r(&dt.tv_sec, &info);
+ }
if (year) *year = I(info.tm_year + 1900);
if (month) *month = I(info.tm_mon + 1);
@@ -114,27 +132,34 @@ public void DateTime$get(
if (weekday) *weekday = I(info.tm_wday + 1);
}
-public Text_t DateTime$format(DateTime_t dt, Text_t fmt, bool local_time)
+public Text_t DateTime$format(DateTime_t dt, Text_t fmt, OptionalText_t timezone)
{
struct tm info;
- struct tm *final_info = local_time ? localtime_r(&dt.tv_sec, &info) : gmtime_r(&dt.tv_sec, &info);
+ if (timezone.length >= 0) {
+ OptionalText_t old_timezone = _local_timezone;
+ DateTime$set_local_timezone(timezone);
+ localtime_r(&dt.tv_sec, &info);
+ DateTime$set_local_timezone(old_timezone);
+ } else {
+ localtime_r(&dt.tv_sec, &info);
+ }
static char buf[256];
- size_t len = strftime(buf, sizeof(buf), Text$as_c_string(fmt), final_info);
+ size_t len = strftime(buf, sizeof(buf), Text$as_c_string(fmt), &info);
return Text$format("%.*s", (int)len, buf);
}
-public Text_t DateTime$date(DateTime_t dt, bool local_time)
+public Text_t DateTime$date(DateTime_t dt, OptionalText_t timezone)
{
- return DateTime$format(dt, Text("%F"), local_time);
+ return DateTime$format(dt, Text("%F"), timezone);
}
-public Text_t DateTime$time(DateTime_t dt, bool seconds, bool am_pm, bool local_time)
+public Text_t DateTime$time(DateTime_t dt, bool seconds, bool am_pm, OptionalText_t timezone)
{
Text_t text;
if (seconds)
- text = DateTime$format(dt, am_pm ? Text("%l:%M:%S%P") : Text("%T"), local_time);
+ text = DateTime$format(dt, am_pm ? Text("%l:%M:%S%P") : Text("%T"), timezone);
else
- text = DateTime$format(dt, am_pm ? Text("%l:%M%P") : Text("%H:%M"), local_time);
+ text = DateTime$format(dt, am_pm ? Text("%l:%M%P") : Text("%H:%M"), timezone);
return Text$trim(text, Pattern(" "), true, true);
}
@@ -162,19 +187,21 @@ static inline Text_t num_format(long n, const char *unit)
return Text$format((n == 1 || n == -1) ? "%ld %s %s" : "%ld %ss %s", n < 0 ? -n : n, unit, n < 0 ? "ago" : "later");
}
-public Text_t DateTime$relative(DateTime_t dt, DateTime_t relative_to, bool local_time)
+public Text_t DateTime$relative(DateTime_t dt, DateTime_t relative_to, OptionalText_t timezone)
{
struct tm info = {};
- if (local_time)
- localtime_r(&dt.tv_sec, &info);
- else
- gmtime_r(&dt.tv_sec, &info);
-
struct tm relative_info = {};
- if (local_time)
+
+ if (timezone.length >= 0) {
+ OptionalText_t old_timezone = _local_timezone;
+ DateTime$set_local_timezone(timezone);
+ localtime_r(&dt.tv_sec, &info);
localtime_r(&relative_to.tv_sec, &relative_info);
- else
- gmtime_r(&relative_to.tv_sec, &relative_info);
+ DateTime$set_local_timezone(old_timezone);
+ } else {
+ localtime_r(&dt.tv_sec, &info);
+ localtime_r(&relative_to.tv_sec, &relative_info);
+ }
double second_diff = DateTime$seconds_till(relative_to, dt);
if (info.tm_year != relative_info.tm_year && fabs(second_diff) > 365.*24.*60.*60.)
@@ -209,6 +236,34 @@ CONSTFUNC public DateTime_t DateTime$from_unix_timestamp(Int64_t timestamp)
return (DateTime_t){.tv_sec=(time_t)timestamp};
}
+public void DateTime$set_local_timezone(OptionalText_t timezone)
+{
+ if (timezone.length >= 0) {
+ setenv("TZ", Text$as_c_string(timezone), 1);
+ } else {
+ unsetenv("TZ");
+ }
+ _local_timezone = timezone;
+ tzset();
+}
+
+public Text_t DateTime$get_local_timezone(void)
+{
+ if (_local_timezone.length < 0) {
+ static char buf[PATH_MAX];
+ ssize_t len = readlink("/etc/localtime", buf, sizeof(buf));
+ if (len < 0)
+ fail("Could not get local timezone!");
+
+ char *zoneinfo = strstr(buf, "/zoneinfo/");
+ if (zoneinfo)
+ _local_timezone = Text$from_str(zoneinfo + strlen("/zoneinfo/"));
+ else
+ fail("Could not resolve local timezone!");
+ }
+ return _local_timezone;
+}
+
public const TypeInfo DateTime$info = {
.size=sizeof(DateTime_t),
.align=__alignof__(DateTime_t),
diff --git a/stdlib/datetime.h b/stdlib/datetime.h
index 6f9c7fb0..ecce6c74 100644
--- a/stdlib/datetime.h
+++ b/stdlib/datetime.h
@@ -6,25 +6,28 @@
#include "datatypes.h"
#include "integers.h"
+#include "optionals.h"
#include "types.h"
#include "util.h"
Text_t DateTime$as_text(const DateTime_t *dt, bool colorize, const TypeInfo *type);
PUREFUNC int32_t DateTime$compare(const DateTime_t *a, const DateTime_t *b, const TypeInfo *type);
DateTime_t DateTime$now(void);
-DateTime_t DateTime$new(Int_t year, Int_t month, Int_t day, Int_t hour, Int_t minute, double second);
-DateTime_t DateTime$after(DateTime_t dt, double seconds, double minutes, double hours, Int_t days, Int_t weeks, Int_t months, Int_t years, bool local_time);
+DateTime_t DateTime$new(Int_t year, Int_t month, Int_t day, Int_t hour, Int_t minute, double second, OptionalText_t timezone);
+DateTime_t DateTime$after(DateTime_t dt, double seconds, double minutes, double hours, Int_t days, Int_t weeks, Int_t months, Int_t years, OptionalText_t timezone);
CONSTFUNC double DateTime$seconds_till(DateTime_t now, DateTime_t then);
CONSTFUNC double DateTime$minutes_till(DateTime_t now, DateTime_t then);
CONSTFUNC double DateTime$hours_till(DateTime_t now, DateTime_t then);
-void DateTime$get(DateTime_t dt, Int_t *year, Int_t *month, Int_t *day, Int_t *hour, Int_t *minute, Int_t *second, Int_t *nanosecond, Int_t *weekday, bool local_time);
-Text_t DateTime$format(DateTime_t dt, Text_t fmt, bool local_time);
-Text_t DateTime$date(DateTime_t dt, bool local_time);
-Text_t DateTime$time(DateTime_t dt, bool seconds, bool am_pm, bool local_time);
+void DateTime$get(DateTime_t dt, Int_t *year, Int_t *month, Int_t *day, Int_t *hour, Int_t *minute, Int_t *second, Int_t *nanosecond, Int_t *weekday, OptionalText_t timezone);
+Text_t DateTime$format(DateTime_t dt, Text_t fmt, OptionalText_t timezone);
+Text_t DateTime$date(DateTime_t dt, OptionalText_t timezone);
+Text_t DateTime$time(DateTime_t dt, bool seconds, bool am_pm, OptionalText_t timezone);
OptionalDateTime_t DateTime$parse(Text_t text, Text_t format);
-Text_t DateTime$relative(DateTime_t dt, DateTime_t relative_to, bool local_time);
+Text_t DateTime$relative(DateTime_t dt, DateTime_t relative_to, OptionalText_t timezone);
CONSTFUNC Int64_t DateTime$unix_timestamp(DateTime_t dt);
CONSTFUNC DateTime_t DateTime$from_unix_timestamp(Int64_t timestamp);
+void DateTime$set_local_timezone(OptionalText_t timezone);
+Text_t DateTime$get_local_timezone(void);
extern const TypeInfo DateTime$info;