2024-11-17 11:49:03 -08:00
|
|
|
// Moment methods/type info
|
2024-09-29 17:06:09 -07:00
|
|
|
#include <ctype.h>
|
|
|
|
#include <gc.h>
|
|
|
|
#include <err.h>
|
2024-11-29 15:09:12 -08:00
|
|
|
#include <stdio.h>
|
2024-09-29 17:06:09 -07:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <time.h>
|
2024-09-29 22:53:39 -07:00
|
|
|
#include <unistd.h>
|
2024-09-29 17:06:09 -07:00
|
|
|
|
|
|
|
#include "datatypes.h"
|
2024-11-17 11:49:03 -08:00
|
|
|
#include "moments.h"
|
2024-09-29 17:06:09 -07:00
|
|
|
#include "optionals.h"
|
|
|
|
#include "patterns.h"
|
|
|
|
#include "stdlib.h"
|
|
|
|
#include "text.h"
|
|
|
|
#include "util.h"
|
|
|
|
|
2024-11-24 13:18:21 -08:00
|
|
|
static OptionalText_t _local_timezone = NONE_TEXT;
|
2024-09-29 22:53:39 -07:00
|
|
|
|
2024-09-29 23:03:03 -07:00
|
|
|
#define WITH_TIMEZONE(tz, body) ({ if (tz.length >= 0) { \
|
|
|
|
OptionalText_t old_timezone = _local_timezone; \
|
2024-11-17 11:49:03 -08:00
|
|
|
Moment$set_local_timezone(tz); \
|
2024-09-29 23:03:03 -07:00
|
|
|
body; \
|
2024-11-17 11:49:03 -08:00
|
|
|
Moment$set_local_timezone(old_timezone); \
|
2024-09-29 23:03:03 -07:00
|
|
|
} else { \
|
|
|
|
body; \
|
|
|
|
}})
|
|
|
|
|
2024-11-29 09:55:14 -08:00
|
|
|
public Text_t Moment$as_text(const void *moment, bool colorize, const TypeInfo_t*)
|
2024-09-29 17:06:09 -07:00
|
|
|
{
|
2024-11-17 11:49:03 -08:00
|
|
|
if (!moment)
|
|
|
|
return Text("Moment");
|
2024-09-29 17:06:09 -07:00
|
|
|
|
|
|
|
struct tm info;
|
2024-11-29 09:55:14 -08:00
|
|
|
struct tm *final_info = localtime_r(&((Moment_t*)moment)->tv_sec, &info);
|
2024-09-29 17:06:09 -07:00
|
|
|
static char buf[256];
|
2024-09-29 20:09:37 -07:00
|
|
|
size_t len = strftime(buf, sizeof(buf), "%c %Z", final_info);
|
2024-09-29 17:06:09 -07:00
|
|
|
Text_t text = Text$format("%.*s", (int)len, buf);
|
|
|
|
if (colorize)
|
|
|
|
text = Text$concat(Text("\x1b[36m"), text, Text("\x1b[m"));
|
|
|
|
return text;
|
|
|
|
}
|
|
|
|
|
2024-11-29 09:55:14 -08:00
|
|
|
PUREFUNC public int32_t Moment$compare(const void *va, const void *vb, const TypeInfo_t*)
|
2024-09-29 17:06:09 -07:00
|
|
|
{
|
2024-11-29 09:55:14 -08:00
|
|
|
Moment_t *a = (Moment_t*)va, *b = (Moment_t*)vb;
|
2024-09-29 17:06:09 -07:00
|
|
|
if (a->tv_sec != b->tv_sec)
|
|
|
|
return (a->tv_sec > b->tv_sec) - (a->tv_sec < b->tv_sec);
|
|
|
|
return (a->tv_usec > b->tv_usec) - (a->tv_usec < b->tv_usec);
|
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
public Moment_t Moment$now(void)
|
2024-09-29 17:06:09 -07:00
|
|
|
{
|
|
|
|
struct timespec ts;
|
|
|
|
if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
|
|
|
|
fail("Couldn't get the time!");
|
2024-11-17 11:49:03 -08:00
|
|
|
return (Moment_t){.tv_sec=ts.tv_sec, .tv_usec=ts.tv_nsec};
|
2024-09-29 17:06:09 -07:00
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
public Moment_t Moment$new(Int_t year, Int_t month, Int_t day, Int_t hour, Int_t minute, double second, OptionalText_t timezone)
|
2024-09-29 17:06:09 -07:00
|
|
|
{
|
|
|
|
struct tm info = {
|
|
|
|
.tm_min=Int_to_Int32(minute, false),
|
|
|
|
.tm_hour=Int_to_Int32(hour, false),
|
|
|
|
.tm_mday=Int_to_Int32(day, false),
|
|
|
|
.tm_mon=Int_to_Int32(month, false) - 1,
|
|
|
|
.tm_year=Int_to_Int32(year, false) - 1900,
|
|
|
|
.tm_isdst=-1,
|
|
|
|
};
|
2024-09-29 22:55:24 -07:00
|
|
|
|
|
|
|
time_t t;
|
2024-09-29 23:03:03 -07:00
|
|
|
WITH_TIMEZONE(timezone, t = mktime(&info));
|
2024-11-17 11:49:03 -08:00
|
|
|
return (Moment_t){.tv_sec=t + (time_t)second, .tv_usec=(suseconds_t)(fmod(second, 1.0) * 1e9)};
|
2024-09-29 17:06:09 -07:00
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
public Moment_t Moment$after(Moment_t moment, double seconds, double minutes, double hours, Int_t days, Int_t weeks, Int_t months, Int_t years, OptionalText_t timezone)
|
2024-09-29 17:06:09 -07:00
|
|
|
{
|
|
|
|
double offset = seconds + 60.*minutes + 3600.*hours;
|
2024-11-17 11:49:03 -08:00
|
|
|
moment.tv_sec += (time_t)offset;
|
2024-09-29 17:06:09 -07:00
|
|
|
|
|
|
|
struct tm info = {};
|
2024-11-17 11:49:03 -08:00
|
|
|
WITH_TIMEZONE(timezone, localtime_r(&moment.tv_sec, &info));
|
2024-09-29 22:53:39 -07:00
|
|
|
|
2024-09-29 17:06:09 -07:00
|
|
|
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);
|
|
|
|
|
|
|
|
time_t t = mktime(&info);
|
2024-11-17 11:49:03 -08:00
|
|
|
return (Moment_t){
|
2024-09-29 17:06:09 -07:00
|
|
|
.tv_sec=t,
|
2024-11-17 11:49:03 -08:00
|
|
|
.tv_usec=moment.tv_usec + (suseconds_t)(fmod(offset, 1.0) * 1e9),
|
2024-09-29 17:06:09 -07:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
CONSTFUNC public double Moment$seconds_till(Moment_t now, Moment_t then)
|
2024-09-29 17:06:09 -07:00
|
|
|
{
|
|
|
|
return (double)(then.tv_sec - now.tv_sec) + 1e-9*(double)(then.tv_usec - now.tv_usec);
|
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
CONSTFUNC public double Moment$minutes_till(Moment_t now, Moment_t then)
|
2024-09-29 17:06:09 -07:00
|
|
|
{
|
2024-11-17 11:49:03 -08:00
|
|
|
return Moment$seconds_till(now, then)/60.;
|
2024-09-29 17:06:09 -07:00
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
CONSTFUNC public double Moment$hours_till(Moment_t now, Moment_t then)
|
2024-09-29 17:06:09 -07:00
|
|
|
{
|
2024-11-17 11:49:03 -08:00
|
|
|
return Moment$seconds_till(now, then)/3600.;
|
2024-09-29 17:06:09 -07:00
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
public void Moment$get(
|
|
|
|
Moment_t moment, Int_t *year, Int_t *month, Int_t *day, Int_t *hour, Int_t *minute, Int_t *second,
|
2024-09-29 22:53:39 -07:00
|
|
|
Int_t *nanosecond, Int_t *weekday, OptionalText_t timezone)
|
2024-09-29 17:06:09 -07:00
|
|
|
{
|
|
|
|
struct tm info = {};
|
2024-11-17 11:49:03 -08:00
|
|
|
WITH_TIMEZONE(timezone, localtime_r(&moment.tv_sec, &info));
|
2024-09-29 17:06:09 -07:00
|
|
|
|
|
|
|
if (year) *year = I(info.tm_year + 1900);
|
|
|
|
if (month) *month = I(info.tm_mon + 1);
|
|
|
|
if (day) *day = I(info.tm_mday);
|
|
|
|
if (hour) *hour = I(info.tm_hour);
|
|
|
|
if (minute) *minute = I(info.tm_min);
|
|
|
|
if (second) *second = I(info.tm_sec);
|
2024-11-17 11:49:03 -08:00
|
|
|
if (nanosecond) *nanosecond = I(moment.tv_usec);
|
2024-09-29 17:06:09 -07:00
|
|
|
if (weekday) *weekday = I(info.tm_wday + 1);
|
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
public Int_t Moment$year(Moment_t moment, OptionalText_t timezone)
|
2024-10-27 15:41:00 -07:00
|
|
|
{
|
|
|
|
struct tm info = {};
|
2024-11-17 11:49:03 -08:00
|
|
|
WITH_TIMEZONE(timezone, localtime_r(&moment.tv_sec, &info));
|
2024-10-27 15:41:00 -07:00
|
|
|
return I(info.tm_year + 1900);
|
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
public Int_t Moment$month(Moment_t moment, OptionalText_t timezone)
|
2024-10-27 15:41:00 -07:00
|
|
|
{
|
|
|
|
struct tm info = {};
|
2024-11-17 11:49:03 -08:00
|
|
|
WITH_TIMEZONE(timezone, localtime_r(&moment.tv_sec, &info));
|
2024-10-27 15:41:00 -07:00
|
|
|
return I(info.tm_mon + 1);
|
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
public Int_t Moment$day_of_week(Moment_t moment, OptionalText_t timezone)
|
2024-10-27 15:41:00 -07:00
|
|
|
{
|
|
|
|
struct tm info = {};
|
2024-11-17 11:49:03 -08:00
|
|
|
WITH_TIMEZONE(timezone, localtime_r(&moment.tv_sec, &info));
|
2024-10-27 15:41:00 -07:00
|
|
|
return I(info.tm_wday + 1);
|
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
public Int_t Moment$day_of_month(Moment_t moment, OptionalText_t timezone)
|
2024-10-27 15:41:00 -07:00
|
|
|
{
|
|
|
|
struct tm info = {};
|
2024-11-17 11:49:03 -08:00
|
|
|
WITH_TIMEZONE(timezone, localtime_r(&moment.tv_sec, &info));
|
2024-10-27 15:41:00 -07:00
|
|
|
return I(info.tm_mday);
|
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
public Int_t Moment$day_of_year(Moment_t moment, OptionalText_t timezone)
|
2024-10-27 15:41:00 -07:00
|
|
|
{
|
|
|
|
struct tm info = {};
|
2024-11-17 11:49:03 -08:00
|
|
|
WITH_TIMEZONE(timezone, localtime_r(&moment.tv_sec, &info));
|
2024-10-27 15:41:00 -07:00
|
|
|
return I(info.tm_yday);
|
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
public Int_t Moment$hour(Moment_t moment, OptionalText_t timezone)
|
2024-10-27 15:41:00 -07:00
|
|
|
{
|
|
|
|
struct tm info = {};
|
2024-11-17 11:49:03 -08:00
|
|
|
WITH_TIMEZONE(timezone, localtime_r(&moment.tv_sec, &info));
|
2024-10-27 15:41:00 -07:00
|
|
|
return I(info.tm_hour);
|
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
public Int_t Moment$minute(Moment_t moment, OptionalText_t timezone)
|
2024-10-27 15:41:00 -07:00
|
|
|
{
|
|
|
|
struct tm info = {};
|
2024-11-17 11:49:03 -08:00
|
|
|
WITH_TIMEZONE(timezone, localtime_r(&moment.tv_sec, &info));
|
2024-10-27 15:41:00 -07:00
|
|
|
return I(info.tm_min);
|
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
public Int_t Moment$second(Moment_t moment, OptionalText_t timezone)
|
2024-10-27 15:41:00 -07:00
|
|
|
{
|
|
|
|
struct tm info = {};
|
2024-11-17 11:49:03 -08:00
|
|
|
WITH_TIMEZONE(timezone, localtime_r(&moment.tv_sec, &info));
|
2024-10-27 15:41:00 -07:00
|
|
|
return I(info.tm_sec);
|
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
public Int_t Moment$nanosecond(Moment_t moment, OptionalText_t timezone)
|
2024-10-27 15:41:00 -07:00
|
|
|
{
|
|
|
|
(void)timezone;
|
2024-11-17 11:49:03 -08:00
|
|
|
return I(moment.tv_usec);
|
2024-10-27 15:41:00 -07:00
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
public Text_t Moment$format(Moment_t moment, Text_t fmt, OptionalText_t timezone)
|
2024-09-29 17:06:09 -07:00
|
|
|
{
|
|
|
|
struct tm info;
|
2024-11-17 11:49:03 -08:00
|
|
|
WITH_TIMEZONE(timezone, localtime_r(&moment.tv_sec, &info));
|
2024-09-29 17:06:09 -07:00
|
|
|
static char buf[256];
|
2024-09-29 22:53:39 -07:00
|
|
|
size_t len = strftime(buf, sizeof(buf), Text$as_c_string(fmt), &info);
|
2024-09-29 17:06:09 -07:00
|
|
|
return Text$format("%.*s", (int)len, buf);
|
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
public Text_t Moment$date(Moment_t moment, OptionalText_t timezone)
|
2024-09-29 17:06:09 -07:00
|
|
|
{
|
2024-11-17 11:49:03 -08:00
|
|
|
return Moment$format(moment, Text("%F"), timezone);
|
2024-09-29 17:06:09 -07:00
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
public Text_t Moment$time(Moment_t moment, bool seconds, bool am_pm, OptionalText_t timezone)
|
2024-09-29 17:06:09 -07:00
|
|
|
{
|
|
|
|
Text_t text;
|
|
|
|
if (seconds)
|
2024-11-17 11:49:03 -08:00
|
|
|
text = Moment$format(moment, am_pm ? Text("%l:%M:%S%P") : Text("%T"), timezone);
|
2024-09-29 17:06:09 -07:00
|
|
|
else
|
2024-11-17 11:49:03 -08:00
|
|
|
text = Moment$format(moment, am_pm ? Text("%l:%M%P") : Text("%H:%M"), timezone);
|
2024-09-29 17:06:09 -07:00
|
|
|
return Text$trim(text, Pattern(" "), true, true);
|
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
public OptionalMoment_t Moment$parse(Text_t text, Text_t format)
|
2024-09-29 17:06:09 -07:00
|
|
|
{
|
|
|
|
struct tm info = {.tm_isdst=-1};
|
2024-09-29 20:09:37 -07:00
|
|
|
const char *str = Text$as_c_string(text);
|
|
|
|
const char *fmt = Text$as_c_string(format);
|
|
|
|
if (strstr(fmt, "%Z"))
|
|
|
|
fail("The %%Z specifier is not supported for time parsing!");
|
|
|
|
|
|
|
|
char *invalid = strptime(str, fmt, &info);
|
2024-09-29 17:06:09 -07:00
|
|
|
if (!invalid || invalid[0] != '\0')
|
2024-11-24 13:18:21 -08:00
|
|
|
return NONE_MOMENT;
|
2024-09-29 17:06:09 -07:00
|
|
|
|
2024-09-29 20:09:37 -07:00
|
|
|
long offset = info.tm_gmtoff; // Need to cache this because mktime() mutates it to local timezone >:(
|
2024-09-29 17:06:09 -07:00
|
|
|
time_t t = mktime(&info);
|
2024-11-17 11:49:03 -08:00
|
|
|
return (Moment_t){.tv_sec=t + offset - info.tm_gmtoff};
|
2024-09-29 17:06:09 -07:00
|
|
|
}
|
|
|
|
|
2024-10-29 20:14:31 -07:00
|
|
|
static INLINE Text_t num_format(long n, const char *unit)
|
2024-09-29 17:06:09 -07:00
|
|
|
{
|
|
|
|
if (n == 0)
|
|
|
|
return Text("now");
|
|
|
|
return Text$format((n == 1 || n == -1) ? "%ld %s %s" : "%ld %ss %s", n < 0 ? -n : n, unit, n < 0 ? "ago" : "later");
|
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
public Text_t Moment$relative(Moment_t moment, Moment_t relative_to, OptionalText_t timezone)
|
2024-09-29 17:06:09 -07:00
|
|
|
{
|
|
|
|
struct tm info = {};
|
|
|
|
struct tm relative_info = {};
|
2024-09-29 23:03:03 -07:00
|
|
|
WITH_TIMEZONE(timezone, {
|
2024-11-17 11:49:03 -08:00
|
|
|
localtime_r(&moment.tv_sec, &info);
|
2024-09-29 22:53:39 -07:00
|
|
|
localtime_r(&relative_to.tv_sec, &relative_info);
|
2024-09-29 23:03:03 -07:00
|
|
|
});
|
2024-09-29 17:06:09 -07:00
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
double second_diff = Moment$seconds_till(relative_to, moment);
|
2024-09-29 17:06:09 -07:00
|
|
|
if (info.tm_year != relative_info.tm_year && fabs(second_diff) > 365.*24.*60.*60.)
|
|
|
|
return num_format((long)info.tm_year - (long)relative_info.tm_year, "year");
|
|
|
|
else if (info.tm_mon != relative_info.tm_mon && fabs(second_diff) > 31.*24.*60.*60.)
|
|
|
|
return num_format(12*((long)info.tm_year - (long)relative_info.tm_year) + (long)info.tm_mon - (long)relative_info.tm_mon, "month");
|
|
|
|
else if (info.tm_yday != relative_info.tm_yday && fabs(second_diff) > 24.*60.*60.)
|
|
|
|
return num_format(round(second_diff/(24.*60.*60.)), "day");
|
|
|
|
else if (info.tm_hour != relative_info.tm_hour && fabs(second_diff) > 60.*60.)
|
|
|
|
return num_format(round(second_diff/(60.*60.)), "hour");
|
|
|
|
else if (info.tm_min != relative_info.tm_min && fabs(second_diff) > 60.)
|
|
|
|
return num_format(round(second_diff/(60.)), "minute");
|
|
|
|
else {
|
|
|
|
if (fabs(second_diff) < 1e-6)
|
|
|
|
return num_format((long)(second_diff*1e9), "nanosecond");
|
|
|
|
else if (fabs(second_diff) < 1e-3)
|
|
|
|
return num_format((long)(second_diff*1e6), "microsecond");
|
|
|
|
else if (fabs(second_diff) < 1.0)
|
|
|
|
return num_format((long)(second_diff*1e3), "millisecond");
|
|
|
|
else
|
|
|
|
return num_format((long)(second_diff), "second");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
CONSTFUNC public Int64_t Moment$unix_timestamp(Moment_t moment)
|
2024-09-29 17:06:09 -07:00
|
|
|
{
|
2024-11-17 11:49:03 -08:00
|
|
|
return (Int64_t)moment.tv_sec;
|
2024-09-29 17:06:09 -07:00
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
CONSTFUNC public Moment_t Moment$from_unix_timestamp(Int64_t timestamp)
|
2024-09-29 17:06:09 -07:00
|
|
|
{
|
2024-11-17 11:49:03 -08:00
|
|
|
return (Moment_t){.tv_sec=(time_t)timestamp};
|
2024-09-29 17:06:09 -07:00
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
public void Moment$set_local_timezone(OptionalText_t timezone)
|
2024-09-29 22:53:39 -07:00
|
|
|
{
|
|
|
|
if (timezone.length >= 0) {
|
|
|
|
setenv("TZ", Text$as_c_string(timezone), 1);
|
|
|
|
} else {
|
|
|
|
unsetenv("TZ");
|
|
|
|
}
|
|
|
|
_local_timezone = timezone;
|
|
|
|
tzset();
|
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
public Text_t Moment$get_local_timezone(void)
|
2024-09-29 22:53:39 -07:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-11-17 11:49:03 -08:00
|
|
|
public const TypeInfo_t Moment$info = {
|
|
|
|
.size=sizeof(Moment_t),
|
|
|
|
.align=__alignof__(Moment_t),
|
2024-11-29 09:55:14 -08:00
|
|
|
.metamethods={
|
|
|
|
.as_text=Moment$as_text,
|
|
|
|
.compare=Moment$compare,
|
2024-09-29 17:06:09 -07:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
|