diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2024-09-30 13:55:55 -0400 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2024-09-30 13:55:55 -0400 |
| commit | ec0606091bdcc8a8473ba909fb8ca2d873ce4a59 (patch) | |
| tree | 4b490161bf0a0b91e24c7af6bb1e84d439dd5951 | |
| parent | 45425b77e40da59552cc800313aa80aac88430d4 (diff) | |
Add datetime literal and tests
| -rw-r--r-- | ast.h | 4 | ||||
| -rw-r--r-- | compile.c | 4 | ||||
| -rw-r--r-- | parse.c | 59 | ||||
| -rw-r--r-- | test/datetime.tm | 51 | ||||
| -rw-r--r-- | typecheck.c | 1 |
5 files changed, 117 insertions, 2 deletions
@@ -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; @@ -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) @@ -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); |
