aboutsummaryrefslogtreecommitdiff
path: root/stdlib
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2024-09-29 20:06:09 -0400
committerBruce Hill <bruce@bruce-hill.com>2024-09-29 20:06:09 -0400
commit05515d8645326efa3db6311f6be1be776452c68a (patch)
tree15e58e72fc1aeec61017f02d8bf555e38a8dfda3 /stdlib
parent8f7f66d7c894544c7c7e8ffae5d74e3cc602256f (diff)
Add DateTime
Diffstat (limited to 'stdlib')
-rw-r--r--stdlib/datatypes.h8
-rw-r--r--stdlib/datetime.c216
-rw-r--r--stdlib/datetime.h32
-rw-r--r--stdlib/optionals.c5
-rw-r--r--stdlib/optionals.h1
-rw-r--r--stdlib/paths.c51
-rw-r--r--stdlib/paths.h3
-rw-r--r--stdlib/tomo.h1
8 files changed, 299 insertions, 18 deletions
diff --git a/stdlib/datatypes.h b/stdlib/datatypes.h
index 4bb6beb3..8d342d2e 100644
--- a/stdlib/datatypes.h
+++ b/stdlib/datatypes.h
@@ -3,9 +3,10 @@
// Common datastructures (arrays, tables, closures)
#include <gmp.h>
-#include <stdint.h>
-#include <stdbool.h>
#include <pthread.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <time.h>
#define ARRAY_LENGTH_BITS 42
#define ARRAY_FREE_BITS 6
@@ -89,4 +90,7 @@ typedef struct Text_s {
#define Pattern_t Text_t
#define OptionalPattern_t Text_t
+typedef struct timeval DateTime_t;
+#define OptionalDateTime_t DateTime_t
+
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
diff --git a/stdlib/datetime.c b/stdlib/datetime.c
new file mode 100644
index 00000000..a3bb42c1
--- /dev/null
+++ b/stdlib/datetime.c
@@ -0,0 +1,216 @@
+// DateTime methods/type info
+#include <ctype.h>
+#include <gc.h>
+#include <err.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "datatypes.h"
+#include "optionals.h"
+#include "patterns.h"
+#include "stdlib.h"
+#include "text.h"
+#include "util.h"
+
+public Text_t DateTime$as_text(const DateTime_t *dt, bool colorize, const TypeInfo *type)
+{
+ (void)type;
+ if (!dt)
+ return Text("DateTime");
+
+ struct tm info;
+ struct tm *final_info = localtime_r(&dt->tv_sec, &info);
+ static char buf[256];
+ size_t len = strftime(buf, sizeof(buf), "%c", final_info);
+ Text_t text = Text$format("%.*s", (int)len, buf);
+ if (colorize)
+ text = Text$concat(Text("\x1b[36m"), text, Text("\x1b[m"));
+ return text;
+}
+
+PUREFUNC public int32_t DateTime$compare(const DateTime_t *a, const DateTime_t *b, const TypeInfo *type)
+{
+ (void)type;
+ 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);
+}
+
+public DateTime_t DateTime$now(void)
+{
+ struct timespec ts;
+ if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
+ fail("Couldn't get the time!");
+ 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)
+{
+ 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,
+ };
+ time_t t = mktime(&info);
+ 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)
+{
+ double offset = seconds + 60.*minutes + 3600.*hours;
+ dt.tv_sec += (time_t)offset;
+
+ struct tm info = {};
+ if (local_time)
+ localtime_r(&dt.tv_sec, &info);
+ else
+ gmtime_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);
+
+ time_t t = mktime(&info);
+ return (DateTime_t){
+ .tv_sec=t,
+ .tv_usec=dt.tv_usec + (suseconds_t)(fmod(offset, 1.0) * 1e9),
+ };
+}
+
+CONSTFUNC public double DateTime$seconds_till(DateTime_t now, DateTime_t then)
+{
+ return (double)(then.tv_sec - now.tv_sec) + 1e-9*(double)(then.tv_usec - now.tv_usec);
+}
+
+CONSTFUNC public double DateTime$minutes_till(DateTime_t now, DateTime_t then)
+{
+ return DateTime$seconds_till(now, then)/60.;
+}
+
+CONSTFUNC public double DateTime$hours_till(DateTime_t now, DateTime_t then)
+{
+ return DateTime$seconds_till(now, then)/3600.;
+}
+
+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)
+{
+ struct tm info = {};
+ if (local_time)
+ localtime_r(&dt.tv_sec, &info);
+ else
+ gmtime_r(&dt.tv_sec, &info);
+
+ 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);
+ if (nanosecond) *nanosecond = I(dt.tv_usec);
+ if (weekday) *weekday = I(info.tm_wday + 1);
+}
+
+public Text_t DateTime$format(DateTime_t dt, Text_t fmt, bool local_time)
+{
+ struct tm info;
+ struct tm *final_info = local_time ? localtime_r(&dt.tv_sec, &info) : gmtime_r(&dt.tv_sec, &info);
+ static char buf[256];
+ size_t len = strftime(buf, sizeof(buf), Text$as_c_string(fmt), final_info);
+ return Text$format("%.*s", (int)len, buf);
+}
+
+public Text_t DateTime$date(DateTime_t dt, bool local_time)
+{
+ return DateTime$format(dt, Text("%F"), local_time);
+}
+
+public Text_t DateTime$time(DateTime_t dt, bool seconds, bool am_pm, bool local_time)
+{
+ Text_t text;
+ if (seconds)
+ text = DateTime$format(dt, am_pm ? Text("%l:%M:%S%P") : Text("%T"), local_time);
+ else
+ text = DateTime$format(dt, am_pm ? Text("%l:%M%P") : Text("%H:%M"), local_time);
+ return Text$trim(text, Pattern(" "), true, true);
+}
+
+public OptionalDateTime_t DateTime$parse(Text_t text, Text_t format)
+{
+ struct tm info = {.tm_isdst=-1};
+ char *invalid = strptime(Text$as_c_string(text), Text$as_c_string(format), &info);
+ if (!invalid || invalid[0] != '\0')
+ return NULL_DATETIME;
+
+ time_t t = mktime(&info);
+ return (DateTime_t){.tv_sec=t};
+}
+
+static inline Text_t num_format(long n, const char *unit)
+{
+ 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");
+}
+
+public Text_t DateTime$relative(DateTime_t dt, DateTime_t relative_to, bool local_time)
+{
+ 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)
+ localtime_r(&relative_to.tv_sec, &relative_info);
+ else
+ gmtime_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.)
+ 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");
+ }
+}
+
+CONSTFUNC public Int64_t DateTime$unix_timestamp(DateTime_t dt)
+{
+ return (Int64_t)(dt.tv_sec);
+}
+
+CONSTFUNC public DateTime_t DateTime$from_unix_timestamp(Int64_t timestamp)
+{
+ return (DateTime_t){.tv_sec=(time_t)timestamp};
+}
+
+public const TypeInfo DateTime$info = {
+ .size=sizeof(DateTime_t),
+ .align=__alignof__(DateTime_t),
+ .tag=CustomInfo,
+ .CustomInfo={
+ .as_text=(void*)DateTime$as_text,
+ .compare=(void*)DateTime$compare,
+ },
+};
+
+// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
diff --git a/stdlib/datetime.h b/stdlib/datetime.h
new file mode 100644
index 00000000..6f9c7fb0
--- /dev/null
+++ b/stdlib/datetime.h
@@ -0,0 +1,32 @@
+#pragma once
+
+// DateTime objects
+
+#include <stdint.h>
+
+#include "datatypes.h"
+#include "integers.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);
+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);
+OptionalDateTime_t DateTime$parse(Text_t text, Text_t format);
+Text_t DateTime$relative(DateTime_t dt, DateTime_t relative_to, bool local_time);
+CONSTFUNC Int64_t DateTime$unix_timestamp(DateTime_t dt);
+CONSTFUNC DateTime_t DateTime$from_unix_timestamp(Int64_t timestamp);
+
+extern const TypeInfo DateTime$info;
+
+// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0
+
diff --git a/stdlib/optionals.c b/stdlib/optionals.c
index 717a1e0c..15f93846 100644
--- a/stdlib/optionals.c
+++ b/stdlib/optionals.c
@@ -5,10 +5,11 @@
#include "bools.h"
#include "bytes.h"
#include "datatypes.h"
+#include "datetime.h"
#include "integers.h"
#include "metamethods.h"
-#include "threads.h"
#include "text.h"
+#include "threads.h"
#include "util.h"
public PUREFUNC bool is_null(const void *obj, const TypeInfo *non_optional_type)
@@ -31,6 +32,8 @@ public PUREFUNC bool is_null(const void *obj, const TypeInfo *non_optional_type)
return ((OptionalByte_t*)obj)->is_null;
else if (non_optional_type == &Thread$info)
return *(pthread_t**)obj == NULL;
+ else if (non_optional_type == &DateTime$info)
+ return ((OptionalDateTime_t*)obj)->tv_usec < 0;
switch (non_optional_type->tag) {
case ChannelInfo: return *(Channel_t**)obj == NULL;
diff --git a/stdlib/optionals.h b/stdlib/optionals.h
index 31fad710..563b5c30 100644
--- a/stdlib/optionals.h
+++ b/stdlib/optionals.h
@@ -22,6 +22,7 @@
#define NULL_TABLE ((OptionalTable_t){.entries.length=-1})
#define NULL_CLOSURE ((OptionalClosure_t){.fn=NULL})
#define NULL_TEXT ((OptionalText_t){.length=-1})
+#define NULL_DATETIME ((OptionalDateTime_t){.tv_usec=-1})
PUREFUNC bool is_null(const void *obj, const TypeInfo *non_optional_type);
Text_t Optional$as_text(const void *obj, bool colorize, const TypeInfo *type);
diff --git a/stdlib/paths.c b/stdlib/paths.c
index 0119dbf0..0519201d 100644
--- a/stdlib/paths.c
+++ b/stdlib/paths.c
@@ -157,56 +157,77 @@ public bool Path$exists(Path_t path)
return (stat(Text$as_c_string(path), &sb) == 0);
}
-public bool Path$is_file(Path_t path, bool follow_symlinks)
+static inline int path_stat(Path_t path, bool follow_symlinks, struct stat *sb)
{
path = Path$_expand_home(path);
- struct stat sb;
const char *path_str = Text$as_c_string(path);
- int status = follow_symlinks ? stat(path_str, &sb) : lstat(path_str, &sb);
+ return follow_symlinks ? stat(path_str, sb) : lstat(path_str, sb);
+}
+
+public bool Path$is_file(Path_t path, bool follow_symlinks)
+{
+ struct stat sb;
+ int status = path_stat(path, follow_symlinks, &sb);
if (status != 0) return false;
return (sb.st_mode & S_IFMT) == S_IFREG;
}
public bool Path$is_directory(Path_t path, bool follow_symlinks)
{
- path = Path$_expand_home(path);
struct stat sb;
- const char *path_str = Text$as_c_string(path);
- int status = follow_symlinks ? stat(path_str, &sb) : lstat(path_str, &sb);
+ int status = path_stat(path, follow_symlinks, &sb);
if (status != 0) return false;
return (sb.st_mode & S_IFMT) == S_IFDIR;
}
public bool Path$is_pipe(Path_t path, bool follow_symlinks)
{
- path = Path$_expand_home(path);
struct stat sb;
- const char *path_str = Text$as_c_string(path);
- int status = follow_symlinks ? stat(path_str, &sb) : lstat(path_str, &sb);
+ int status = path_stat(path, follow_symlinks, &sb);
if (status != 0) return false;
return (sb.st_mode & S_IFMT) == S_IFIFO;
}
public bool Path$is_socket(Path_t path, bool follow_symlinks)
{
- path = Path$_expand_home(path);
struct stat sb;
- const char *path_str = Text$as_c_string(path);
- int status = follow_symlinks ? stat(path_str, &sb) : lstat(path_str, &sb);
+ int status = path_stat(path, follow_symlinks, &sb);
if (status != 0) return false;
return (sb.st_mode & S_IFMT) == S_IFSOCK;
}
public bool Path$is_symlink(Path_t path)
{
- path = Path$_expand_home(path);
struct stat sb;
- const char *path_str = Text$as_c_string(path);
- int status = stat(path_str, &sb);
+ int status = path_stat(path, false, &sb);
if (status != 0) return false;
return (sb.st_mode & S_IFMT) == S_IFLNK;
}
+public OptionalDateTime_t Path$modified(Path_t path, bool follow_symlinks)
+{
+ struct stat sb;
+ int status = path_stat(path, follow_symlinks, &sb);
+ if (status != 0) return NULL_DATETIME;
+ return (DateTime_t){.tv_sec=sb.st_mtime};
+}
+
+public OptionalDateTime_t Path$accessed(Path_t path, bool follow_symlinks)
+{
+ struct stat sb;
+ int status = path_stat(path, follow_symlinks, &sb);
+ if (status != 0) return NULL_DATETIME;
+ return (DateTime_t){.tv_sec=sb.st_atime};
+}
+
+public OptionalDateTime_t Path$changed(Path_t path, bool follow_symlinks)
+{
+ struct stat sb;
+ int status = path_stat(path, follow_symlinks, &sb);
+ if (status != 0) return NULL_DATETIME;
+ return (DateTime_t){.tv_sec=sb.st_ctime};
+}
+
static void _write(Path_t path, Array_t bytes, int mode, int permissions)
{
path = Path$_expand_home(path);
diff --git a/stdlib/paths.h b/stdlib/paths.h
index dd6129ea..07ddfb27 100644
--- a/stdlib/paths.h
+++ b/stdlib/paths.h
@@ -26,6 +26,9 @@ bool Path$is_directory(Path_t path, bool follow_symlinks);
bool Path$is_pipe(Path_t path, bool follow_symlinks);
bool Path$is_socket(Path_t path, bool follow_symlinks);
bool Path$is_symlink(Path_t path);
+OptionalDateTime_t Path$modified(Path_t path, bool follow_symlinks);
+OptionalDateTime_t Path$accessed(Path_t path, bool follow_symlinks);
+OptionalDateTime_t Path$changed(Path_t path, bool follow_symlinks);
void Path$write(Path_t path, Text_t text, int permissions);
void Path$write_bytes(Path_t path, Array_t bytes, int permissions);
void Path$append(Path_t path, Text_t text, int permissions);
diff --git a/stdlib/tomo.h b/stdlib/tomo.h
index d1e61a5d..515bb8da 100644
--- a/stdlib/tomo.h
+++ b/stdlib/tomo.h
@@ -15,6 +15,7 @@
#include "c_strings.h"
#include "channels.h"
#include "datatypes.h"
+#include "datetime.h"
#include "functiontype.h"
#include "integers.h"
#include "memory.h"