aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ast.h4
-rw-r--r--compile.c4
-rw-r--r--parse.c59
-rw-r--r--test/datetime.tm51
-rw-r--r--typecheck.c1
5 files changed, 117 insertions, 2 deletions
diff --git a/ast.h b/ast.h
index fbbdb702..378a797d 100644
--- a/ast.h
+++ b/ast.h
@@ -142,6 +142,7 @@ typedef enum {
Extern,
StructDef, EnumDef, LangDef,
Index, FieldAccess, Optional, NonOptional,
+ DateTime,
DocTest,
Use,
InlineCCode,
@@ -306,6 +307,9 @@ struct ast_s {
ast_t *value;
} Optional, NonOptional;
struct {
+ DateTime_t dt;
+ } DateTime;
+ struct {
ast_t *expr;
const char *output;
bool skip_source:1;
diff --git a/compile.c b/compile.c
index f1c27328..744ac95f 100644
--- a/compile.c
+++ b/compile.c
@@ -1850,6 +1850,10 @@ CORD compile(env_t *env, ast_t *ast)
return compile_null(t);
}
case Bool: return Match(ast, Bool)->b ? "yes" : "no";
+ case DateTime: {
+ auto dt = Match(ast, DateTime)->dt;
+ return CORD_asprintf("((DateTime_t){.tv_sec=%ld, .tv_usec=%ld})", dt.tv_sec, dt.tv_usec);
+ }
case Var: {
binding_t *b = get_binding(env, Match(ast, Var)->name);
if (b)
diff --git a/parse.c b/parse.c
index cad60f6d..3b71c405 100644
--- a/parse.c
+++ b/parse.c
@@ -110,6 +110,7 @@ static PARSER(parse_heap_alloc);
static PARSER(parse_if);
static PARSER(parse_inline_c);
static PARSER(parse_int);
+static PARSER(parse_datetime);
static PARSER(parse_lambda);
static PARSER(parse_lang_def);
static PARSER(parse_namespace);
@@ -514,6 +515,59 @@ PARSER(parse_int) {
return NewAST(ctx->file, start, pos, Int, .str=str, .bits=bits);
}
+PARSER(parse_datetime) {
+ const char *start = pos;
+ bool negative = match(&pos, "-");
+ if (!isdigit(*pos)) return NULL;
+
+ struct tm info = {.tm_isdst=-1};
+ char *after = strptime(pos, "%Y-%m-%d", &info);
+ if (!after) return NULL;
+ if (negative) info.tm_year = -(info.tm_year + 1900) - 1900;
+ pos = after;
+ if (match(&pos, "T") || spaces(&pos) >= 1) {
+ after = strptime(pos, "%H:%M", &info);
+ if (after) {
+ pos = after;
+ after = strptime(pos, ":%S", &info);
+ if (after) pos = after;
+ // TODO parse nanoseconds
+ }
+ }
+
+ const char *before_spaces = pos;
+ spaces(&pos);
+ DateTime_t dt;
+ if (match(&pos, "[")) {
+ size_t tz_len = strcspn(pos, "\r\n]");
+ const char *tz = heap_strf("%.*s", tz_len, pos);
+ // TODO: check that tz is a valid timezone
+ pos += tz_len;
+ expect_closing(ctx, &pos, "]", "I wasn't able to parse the rest of this datetime timezone");
+ const char *old_tz = getenv("TZ");
+ setenv("TZ", tz, 1);
+ tzset();
+ dt = (DateTime_t){.tv_sec=mktime(&info)};
+ if (old_tz) setenv("TZ", old_tz, 1);
+ else unsetenv("TZ");
+ } else if (*pos == 'Z' || *pos == '-' || *pos == '+') {
+ after = strptime(pos, "%z", &info);
+ if (after) {
+ pos = after;
+ long offset = info.tm_gmtoff; // Need to cache this because mktime() mutates it to local timezone >:(
+ time_t t = mktime(&info);
+ dt = (DateTime_t){.tv_sec=t + offset - info.tm_gmtoff};
+ } else {
+ dt = (DateTime_t){.tv_sec=mktime(&info)};
+ }
+ } else {
+ pos = before_spaces;
+ dt = (DateTime_t){.tv_sec=mktime(&info)};
+ }
+
+ return NewAST(ctx->file, start, pos, DateTime, .dt=dt);
+}
+
type_ast_t *parse_table_type(parse_ctx_t *ctx, const char *pos) {
const char *start = pos;
if (!match(&pos, "{")) return NULL;
@@ -1518,10 +1572,11 @@ PARSER(parse_term_no_suffix) {
ast_t *term = NULL;
(void)(
false
+ || (term=parse_datetime(ctx, pos)) // Must come before num/int
|| (term=parse_null(ctx, pos))
- || (term=parse_num(ctx, pos))
+ || (term=parse_num(ctx, pos)) // Must come before int
|| (term=parse_int(ctx, pos))
- || (term=parse_negative(ctx, pos))
+ || (term=parse_negative(ctx, pos)) // Must come after num/int
|| (term=parse_heap_alloc(ctx, pos))
|| (term=parse_stack_reference(ctx, pos))
|| (term=parse_bool(ctx, pos))
diff --git a/test/datetime.tm b/test/datetime.tm
new file mode 100644
index 00000000..37559996
--- /dev/null
+++ b/test/datetime.tm
@@ -0,0 +1,51 @@
+
+func main():
+ >> 2024-1-1 12:00[America/New_York] == 2024-1-1T09:00[America/Los_Angeles]
+ = yes
+ >> 2024-1-1 12:00[America/New_York] == DateTime(2024, 1, 1, hour=9, timezone="America/Los_Angeles")
+ = yes
+
+ >> t := 2024-1-2 13:45[America/New_York]
+ >> t:after(days=40) == 2024-2-11T13:45:00[America/New_York]
+ = yes
+ >> t:date(timezone="America/New_York")
+ = "2024-01-02"
+
+ >> t:time(timezone="America/New_York")
+ = "1:45pm"
+
+ >> t:time(am_pm=no, timezone="America/New_York")
+ = "13:45"
+
+ >> t:relative(relative_to=t:after(minutes=65))
+ = "1 hour ago"
+
+ >> t:seconds_till(t:after(minutes=2))
+ = 120
+
+ >> t:minutes_till(t:after(minutes=2))
+ = 2
+
+ >> t:hours_till(t:after(minutes=60))
+ = 1
+
+ weekday := 0
+ >> t:get(weekday=&weekday)
+ >> weekday # 1 = Sun, 2 = Mon, 3 = Tue
+ = 3
+
+ >> t:format("%A")
+ = "Tuesday"
+
+ >> t:unix_timestamp()
+ = 1704221100[64]
+ >> t == DateTime.from_unix_timestamp(1704221100[64])
+ = yes
+
+ >> t < t:after(minutes=1)
+ = yes
+
+ >> t < t:after(seconds=0.1)
+ = yes
+
+ >> now()
diff --git a/typecheck.c b/typecheck.c
index fa7ca19c..0e29dbbf 100644
--- a/typecheck.c
+++ b/typecheck.c
@@ -1271,6 +1271,7 @@ type_t *get_type(env_t *env, ast_t *ast)
type_ast_t *type_ast = inline_code->type_ast;
return type_ast ? parse_type_ast(env, type_ast) : Type(VoidType);
}
+ case DateTime: return Type(DateTimeType);
case Unknown: code_err(ast, "I can't figure out the type of: %W", ast);
}
code_err(ast, "I can't figure out the type of: %W", ast);