aboutsummaryrefslogtreecommitdiff
path: root/stdlib/datetime.c
diff options
context:
space:
mode:
Diffstat (limited to 'stdlib/datetime.c')
-rw-r--r--stdlib/datetime.c107
1 files changed, 81 insertions, 26 deletions
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),