Add datetime literal and tests
This commit is contained in:
parent
45425b77e4
commit
ec0606091b
4
ast.h
4
ast.h
@ -142,6 +142,7 @@ typedef enum {
|
||||
Extern,
|
||||
StructDef, EnumDef, LangDef,
|
||||
Index, FieldAccess, Optional, NonOptional,
|
||||
DateTime,
|
||||
DocTest,
|
||||
Use,
|
||||
InlineCCode,
|
||||
@ -305,6 +306,9 @@ struct ast_s {
|
||||
struct {
|
||||
ast_t *value;
|
||||
} Optional, NonOptional;
|
||||
struct {
|
||||
DateTime_t dt;
|
||||
} DateTime;
|
||||
struct {
|
||||
ast_t *expr;
|
||||
const char *output;
|
||||
|
@ -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)
|
||||
|
59
parse.c
59
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))
|
||||
|
51
test/datetime.tm
Normal file
51
test/datetime.tm
Normal file
@ -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()
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user