Add DateTime

This commit is contained in:
Bruce Hill 2024-09-29 20:06:09 -04:00
parent 8f7f66d7c8
commit 05515d8645
14 changed files with 771 additions and 25 deletions

View File

@ -31,7 +31,7 @@ LDLIBS=-lgc -lcord -lm -lunistring -lgmp -ldl
BUILTIN_OBJS=stdlib/siphash.o stdlib/arrays.o stdlib/bools.o stdlib/bytes.o stdlib/channels.o stdlib/nums.o stdlib/integers.o \
stdlib/pointers.o stdlib/memory.o stdlib/text.o stdlib/threads.o stdlib/c_strings.o stdlib/tables.o \
stdlib/types.o stdlib/util.o stdlib/files.o stdlib/ranges.o stdlib/shell.o stdlib/paths.o \
stdlib/optionals.o stdlib/patterns.o stdlib/metamethods.o stdlib/functiontype.o stdlib/stdlib.o
stdlib/optionals.o stdlib/patterns.o stdlib/metamethods.o stdlib/functiontype.o stdlib/stdlib.o stdlib/datetime.o
TESTS=$(patsubst %.tm,%.tm.testresult,$(wildcard test/*.tm))
all: libtomo.so tomo

View File

@ -211,6 +211,7 @@ CORD compile_type(type_t *t)
case BoolType: return "Bool_t";
case ByteType: return "Byte_t";
case CStringType: return "char*";
case DateTimeType: return "DateTime_t";
case BigIntType: return "Int_t";
case IntType: return CORD_asprintf("Int%ld_t", Match(t, IntType)->bits);
case NumType: return Match(t, NumType)->bits == TYPE_NBITS64 ? "Num_t" : CORD_asprintf("Num%ld_t", Match(t, NumType)->bits);
@ -257,7 +258,7 @@ CORD compile_type(type_t *t)
case PointerType: case EnumType: case ChannelType:
return compile_type(nonnull);
case IntType: case BigIntType: case TextType: case NumType: case BoolType: case ByteType:
case ArrayType: case TableType: case SetType:
case ArrayType: case TableType: case SetType: case DateTimeType:
return CORD_all("Optional", compile_type(nonnull));
case StructType: {
if (nonnull == THREAD_TYPE)
@ -384,6 +385,8 @@ static CORD check_null(type_t *t, CORD value)
return CORD_all("(", value, ").is_null");
else if (t->tag == EnumType)
return CORD_all("((", value, ").tag == 0)");
else if (t->tag == DateTimeType)
return CORD_all("((", value, ").tv_usec < 0)");
errx(1, "Optional check not implemented for: %T", t);
}
@ -1443,6 +1446,7 @@ CORD expr_as_text(env_t *env, CORD expr, type_t *t, CORD color)
// NOTE: this cannot use stack(), since bools may actually be bit fields:
return CORD_asprintf("Bool$as_text((Bool_t[1]){%r}, %r, &Bool$info)", expr, color);
case CStringType: return CORD_asprintf("CString$as_text(stack(%r), %r, &CString$info)", expr, color);
case DateTimeType: return CORD_asprintf("DateTime$as_text(stack(%r), %r, &DateTime$info)", expr, color);
case BigIntType: case IntType: case ByteType: case NumType: {
CORD name = type_to_cord(t);
return CORD_asprintf("%r$as_text(stack(%r), %r, &%r$info)", name, expr, color, name);
@ -1808,6 +1812,7 @@ CORD compile_null(type_t *t)
case ChannelType: return "NULL";
case TextType: return "NULL_TEXT";
case CStringType: return "NULL";
case DateTimeType: return "NULL_DATETIME";
case PointerType: return CORD_all("((", compile_type(t), ")NULL)");
case ClosureType: return "NULL_CLOSURE";
case NumType: return "nan(\"null\")";
@ -3397,7 +3402,7 @@ CORD compile_type_info(env_t *env, type_t *t)
else if (t == RANGE_TYPE) return "&Range$info";
switch (t->tag) {
case BoolType: case ByteType: case IntType: case BigIntType: case NumType: case CStringType:
case BoolType: case ByteType: case IntType: case BigIntType: case NumType: case CStringType: case DateTimeType:
return CORD_all("&", type_to_cord(t), "$info");
case TextType: {
auto text = Match(t, TextType);

435
docs/datetime.md Normal file
View File

@ -0,0 +1,435 @@
# DateTime
Tomo has a builtin datatype for representing a specific single point in time:
`DateTime`. A DateTime object is internally represented using a UNIX epoch in
seconds and a number of nanoseconds to represent sub-second times (in C, the
equivalent of `struct timeval`). DateTime values do not represent calendar
dates or clock times, they represent an exact moment in time, such as the
moment when a file was last modified on the filesystem or the current moment
(`DateTime.now()`).
## Time Zones
Because humans are not able to easily understand UNIX timestamps, the default
textual representation of `DateTime` objects uses the current locale's
preferred representation of the DateTime in the current time zone:
```tomo
>> DateTime.now()
= Sun Sep 29 18:20:12 2024
```
For various methods, it is assumed by default that users wish to perform
calculations and specify datetimes using the local time zone and daylight
savings time rules. For example, if a program is running in New York and it is
currently 11pm on February 28th, 2023 (the last day of the month) in local
time, it is assumed that "one month from now" refers to 11pm on March 28th,
2024 in local time, rather than referring to one month from the current UTC
time. In that example, the initial time would be 3am March 1, 2023 in UTC, so
one month later would be 3am April 1, 2023 in UTC, which is which is 11am March
31st in local time. Most users would be unpleasantly surprised to find out that
when it's February 28th in local time, one month later is March 28th until 8pm,
at which point it becomes March 31st! For functions where this matters, there
is an extra `local_time` argument that is `yes` by default.
## DateTime Methods
### `after`
**Description:**
Returns a DateTime that occurs after the specified time differences. Time
differences may be either positive or negative.
**Note:** time offsets for days, months, weeks, and years do not refer to fixed
time intervals, but are relative to which date they are applied to. For
example, one year from January 1, 2024 is January 1, 2025, which is 366 days
later because 2024 is a leap year. Similarly, adding one month may add anywhere
from 28 to 31 days, depending on the starting month. Days and weeks are
affected by leap seconds. For this reason, `after()` takes an argument,
`local_time` which is used to determine whether time offsets should be
calculated using the current local time or UTC.
**Usage:**
```markdown
datetime:after(seconds : Num = 0.0, minutes : Num = 0.0, hours : Num = 0.0, days : Int = 0, weeks : Int = 0, months : Int = 0, years : Int = 0, local_time : Bool = yes) -> DateTime
```
**Parameters:**
- `seconds`: An amount of seconds to offset the datetime (default: 0).
- `minutes`: An amount of minutes to offset the datetime (default: 0).
- `hours`: An amount of hours to offset the datetime (default: 0).
- `days`: An amount of days to offset the datetime (default: 0).
- `weeks`: An amount of weeks to offset the datetime (default: 0).
- `months`: An amount of months to offset the datetime (default: 0).
- `years`: An amount of years to offset the datetime (default: 0).
- `local_time`: Whether to perform the calculations in local time (default: `yes`) or, if not, in UTC time.
**Returns:**
A new `DateTime` offset by the given amount.
**Example:**
```markdown
>> DateTime.new(2024, 9, 29, hour=19):after(days=1, minutes=30)
= Mon Sep 30 19:30:00 2024
```
---
### `date`
**Description:**
Return a text representation of the datetime using the `"%F"` format
specifier, which gives the date in `YYYY-MM-DD` form.
**Usage:**
```markdown
datetime:date(local_time : Bool = yes) -> Text
```
**Parameters:**
- `local_time`: Whether to use local time (default: `yes`) or UTC.
**Returns:**
The date in `YYYY-MM-DD` format.
**Example:**
```markdown
>> DateTime.new(2024, 9, 29):format("%A")
= "2024-09-29"
```
---
### `format`
**Description:**
Using the C-style [`strftime`](https://linux.die.net/man/3/strftime) format
options, return a text representation of the given date in the given format. If
`local_time` is `no`, then use UTC instead of the current locale's timezone.
**Usage:**
```markdown
datetime:format(format: Text = "%c", local_time : Bool = yes) -> Text
```
**Parameters:**
- `path`: The path of the file to append to.
- `bytes`: The bytes to append to the file.
- `permissions` (optional): The permissions to set on the file if it is being created (default is `0o644`).
**Returns:**
Nothing.
**Example:**
```markdown
>> DateTime.new(2024, 9, 29):format("%A")
= "Sunday"
```
---
### `from_unix_timestamp`
**Description:**
Return a datetime object that represents the same moment in time as
the given UNIX epoch timestamp (seconds since January 1, 1970 UTC).
**Usage:**
```markdown
DateTime.from_unix_timestamp(timestamp: Int64) -> DateTime
```
**Parameters:**
- `timestamp`: The UNIX timestamp.
**Returns:**
A `DateTime` object representing the same moment as the given UNIX timestamp.
**Example:**
```markdown
# In the New York timezone:
>> DateTime.from_unix_timestamp(0)
= Wed Dec 31 19:00:00 1969
```
---
### `get`
**Description:**
Get various components of the given datetime object and store them in the
provided optional fields.
**Usage:**
```markdown
datetime:get(year : &Int? = !&Int, month : &Int? = !&Int, day : &Int? = !&Int, hour : &Int? = !&Int, minute : &Int? = !&Int, second : &Int? = !&Int, nanosecond : &Int? = !&Int, weekday : &Int? = !&Int, local_time=yes) -> Void
```
**Parameters:**
- `year`: If non-null, store the year here.
- `month`: If non-null, store the month here (1-12).
- `day`: If non-null, store the day of the month here (1-31).
- `hour`: If non-null, store the hour of the day here (0-23).
- `minute`: If non-null, store the minute of the hour here (0-59).
- `second`: If non-null, store the second of the minute here (0-59).
- `nanosecond`: If non-null, store the nanosecond of the second here (0-1,000,000,000).
- `weekday`: If non-null, store the day of the week here (sunday=1, saturday=7)
- `local_time`: Whether to use the local timezone (default: `yes`) or UTC.
**Returns:**
Nothing.
**Example:**
```markdown
dt := DateTime.new(2024, 9, 29)
month := 0
dt:get(month=&month)
>> month
= 9
```
---
### `hours_till`
**Description:**
Return the number of hours until a given datetime.
**Usage:**
```markdown
datetime:hours_till(then:DateTime) -> Num
```
**Parameters:**
- `then`: Another datetime that we want to calculate the time offset from (in hours).
**Returns:**
The number of hours (possibly fractional, possibly negative) until the given time.
**Example:**
```markdown
the_future := now():after(hours=1, minutes=30)
>> now():hours_till(the_future)
= 1.5
```
---
### `minutes_till`
**Description:**
Return the number of minutes until a given datetime.
**Usage:**
```markdown
datetime:minutes_till(then:DateTime) -> Num
```
**Parameters:**
- `then`: Another datetime that we want to calculate the time offset from (in minutes).
**Returns:**
The number of minutes (possibly fractional, possibly negative) until the given time.
**Example:**
```markdown
the_future := now():after(minutes=1, seconds=30)
>> now():minutes_till(the_future)
= 1.5
```
---
### `new`
**Description:**
Return a new `DateTime` object representing the given time parameters expressed
in local time.
**Usage:**
```markdown
DateTime.new(year : Int, month : Int, day : Int, hour : Int = 0, minute : Int = 0, second : Num = 0.0) -> DateTime
```
**Parameters:**
- `year`: The year.
- `month`: The month of the year (1-12).
- `day`: The day of the month (1-31).
- `hour`: The hour of the day (0-23) (default: 0).
- `minute`: The minute of the hour (0-59) (default: 0).
- `second`: The second of the minute (0-59) (default: 0.0).
**Returns:**
A `DateTime` representing the given information in local time. If the given
parameters exceed reasonable bounds, the time values will wrap around. For
example, `DateTime.new(..., hour=3, minute=65)` is the same as
`DateTime.new(..., hour=4, minute=5)`. If any arguments cannot fit in a 32-bit
integer, an error will be raised.
**Example:**
```markdown
>> DateTime.new(2024, 9, 29)
= Mon Sep 30 00:00:00 2024
```
---
### `parse`
**Description:**
Return a new `DateTime` object parsed from the given string in the given format,
or a null value if the value could not be successfully parsed.
**Usage:**
```markdown
DateTime.parse(text: Text, format: Text = "%c") -> DateTime?
```
**Parameters:**
- `text`: The text to parse.
- `format`: The date format of the text being parsed (see:
[strptime](https://linux.die.net/man/3/strptime) for more info on this format) (default: `"%c"`).
**Returns:**
If the text was successfully parsed according to the given format, return a
`DateTime` representing that information. Otherwise, return a null value.
**Example:**
```markdown
>> DateTime.parse("2024-09-29", "%Y-%m-%d")!
= Sun Sep 29 00:00:00 2024
>> DateTime.parse("???", "%Y-%m-%d")
= !DateTime
```
---
### `relative`
**Description:**
Return a plain English textual representation of the approximate time difference
between two `DateTime`s. For example: `5 minutes ago` or `1 day later`
**Usage:**
```markdown
datetime:relative(relative_to : DateTime = DateTime.now(), local_time : Bool = yes) -> Text
```
**Parameters:**
- `relative_to` (optional): The time against which the relative time is calculated (default: `DateTime.now()`).
- `local_time` (optional): Whether or not to perform calculations in local time (default: `yes`).
**Returns:**
Return a plain English textual representation of the approximate time
difference between two `DateTime`s. For example: `5 minutes ago` or `1 day
later`. Return values are approximate and use only one significant unit of
measure with one significant digit, so a difference of 1.6 days will be
represented as `2 days later`. Datetimes in the past will have the suffix `"
ago"`, while datetimes in the future will have the suffix `" later"`.
**Example:**
```markdown
>> now():after(days=2):relative()
= "2 days later"
>> now():after(minutes=-65):relative()
= "1 hour ago"
```
---
### `seconds_till`
**Description:**
Return the number of seconds until a given datetime.
**Usage:**
```markdown
datetime:seconds_till(then:DateTime) -> Num
```
**Parameters:**
- `then`: Another datetime that we want to calculate the time offset from (in seconds).
**Returns:**
The number of seconds (possibly fractional, possibly negative) until the given time.
**Example:**
```markdown
the_future := now():after(seconds=1)
>> now():seconds_till(the_future)
= 1
```
---
### `time`
**Description:**
Return a text representation of the time component of the given datetime.
**Usage:**
```markdown
datetime:time(seconds : Bool = no, am_pm : Bool = yes, local_time : Bool = yes) -> Text
```
**Parameters:**
- `seconds`: Whether to include seconds in the time (default: `no`).
- `am_pm`: Whether to use am/pm in the representation or use a 24-hour clock (default: `yes`).
- `local_time`: Whether to use local time (default: `yes`) or UTC.
**Returns:**
A text representation of the time component of the datetime.
**Example:**
```markdown
dt := DateTime.new(2024, 9, 29, hours=13, minutes=59, seconds=30)
>> dt:time()
= "1:59pm"
>> dt:time(am_pm=no)
= "13:59"
>> dt:time(seconds=yes)
= "1:59:30pm"
```
---
### `unix_timestamp`
**Description:**
Get the UNIX timestamp of the given datetime (seconds since the UNIX epoch:
January 1, 1970 UTC).
**Usage:**
```markdown
datetime:unix_timestamp() -> Int64
```
**Parameters:**
None.
**Returns:**
A 64-bit integer representation of the UNIX timestamp.
**Example:**
```markdown
>> now():unix_timestamp()
= 1727654730[64]
```

View File

@ -50,6 +50,7 @@ env_t *new_compilation_unit(CORD libname)
.default_val=FakeAST(Int, .bits=IBITS32, .str="1"))), .ret=Type(AbortType))}},
{"fail", {.code="fail", .type=Type(FunctionType, .args=new(arg_t, .name="message", .type=Type(CStringType)), .ret=Type(AbortType))}},
{"sleep", {.code="sleep_num", .type=Type(FunctionType, .args=new(arg_t, .name="seconds", .type=Type(NumType, .bits=TYPE_NBITS64)), .ret=Type(VoidType))}},
{"now", {.code="DateTime$now", .type=Type(FunctionType, .args=NULL, .ret=Type(DateTimeType))}},
{"USE_COLOR", {.code="USE_COLOR", .type=Type(BoolType)}},
};
@ -77,7 +78,6 @@ env_t *new_compilation_unit(CORD libname)
THREAD_TYPE = Type(StructType, .name="Thread", .env=thread_env, .opaque=true);
}
struct {
const char *name;
type_t *type;
@ -261,6 +261,24 @@ env_t *new_compilation_unit(CORD libname)
{"escape_int", "Int$value_as_text", "func(i:Int)->Pattern"},
{"escape_text", "Pattern$escape_text", "func(text:Text)->Pattern"},
)},
{"DateTime", Type(DateTimeType), "DateTime_t", "DateTime", TypedArray(ns_entry_t,
// Used as a default for functions below:
{"now", "DateTime$now", "func()->DateTime"},
{"after", "DateTime$after", "func(dt:DateTime,seconds=0.0,minutes=0.0,hours=0.0,days=0,weeks=0,months=0,years=0,local_time=yes)->DateTime"},
{"date", "DateTime$date", "func(dt:DateTime,local_time=yes)->Text"},
{"format", "DateTime$format", "func(dt:DateTime,format=\"%c\",local_time=yes)->Text"},
{"from_unix_timestamp", "DateTime$from_unix_timestamp", "func(timestamp:Int64)->DateTime"},
{"get", "DateTime$get", "func(dt:DateTime,year=!&Int,month=!&Int,day=!&Int,hour=!&Int,minute=!&Int,second=!&Int,nanosecond=!&Int,weekday=!&Int, local_time=yes)"},
{"hours_till", "DateTime$hours_till", "func(now:DateTime,then:DateTime)->Num"},
{"minutes_till", "DateTime$minutes_till", "func(now:DateTime,then:DateTime)->Num"},
{"new", "DateTime$new", "func(year:Int,month:Int,day:Int,hour=0,minute=0,second=0.0)->DateTime"},
{"parse", "DateTime$parse", "func(text:Text, format=\"%c\")->DateTime?"},
{"relative", "DateTime$relative", "func(dt:DateTime,relative_to=DateTime.now(),local_time=yes)->Text"},
{"seconds_till", "DateTime$seconds_till", "func(now:DateTime,then:DateTime)->Num"},
{"time", "DateTime$time", "func(dt:DateTime,seconds=no,am_pm=yes,local_time=yes)->Text"},
{"unix_timestamp", "DateTime$unix_timestamp", "func(dt:DateTime)->Int64"},
)},
{"Path", Type(TextType, .lang="Path", .env=namespace_env(env, "Path")), "Text_t", "Text$info", TypedArray(ns_entry_t,
{"append", "Path$append", "func(path:Path, text:Text, permissions=0o644[32])"},
{"append_bytes", "Path$append_bytes", "func(path:Path, bytes:[Byte], permissions=0o644[32])"},
@ -292,6 +310,10 @@ env_t *new_compilation_unit(CORD libname)
{"write_unique", "Path$write_unique", "func(path:Path, text:Text)->Path"},
{"write_unique_bytes", "Path$write_unique_bytes", "func(path:Path, bytes:[Byte])->Path"},
{"modified", "Path$modified", "func(path:Path, follow_symlinks=yes)->DateTime?"},
{"accessed", "Path$accessed", "func(path:Path, follow_symlinks=yes)->DateTime?"},
{"changed", "Path$changed", "func(path:Path, follow_symlinks=yes)->DateTime?"},
// Text methods:
{"ends_with", "Text$ends_with", "func(path:Path, suffix:Text)->Bool"},
{"has", "Text$has", "func(path:Path, pattern:Pattern)->Bool"},
@ -380,7 +402,6 @@ env_t *new_compilation_unit(CORD libname)
}
}
set_binding(namespace_env(env, "Shell"), "without_escaping",
new(binding_t, .type=Type(FunctionType, .args=new(arg_t, .name="text", .type=TEXT_TYPE),
.ret=Type(TextType, .lang="Shell", .env=namespace_env(env, "Shell"))),
@ -579,7 +600,7 @@ binding_t *get_namespace_binding(env_t *env, ast_t *self, const char *name)
switch (cls_type->tag) {
case ArrayType: return NULL;
case TableType: return NULL;
case CStringType:
case CStringType: case DateTimeType:
case BoolType: case IntType: case BigIntType: case NumType: {
binding_t *b = get_binding(env, CORD_to_const_char_star(type_to_cord(cls_type)));
assert(b);

View File

@ -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

216
stdlib/datetime.c Normal file
View File

@ -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

32
stdlib/datetime.h Normal file
View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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"

View File

@ -25,6 +25,7 @@ CORD type_to_cord(type_t *t) {
case BoolType: return "Bool";
case ByteType: return "Byte";
case CStringType: return "CString";
case DateTimeType: return "DateTime";
case TextType: return Match(t, TextType)->lang ? Match(t, TextType)->lang : "Text";
case BigIntType: return "Int";
case IntType: return CORD_asprintf("Int%d", Match(t, IntType)->bits);
@ -405,6 +406,7 @@ PUREFUNC size_t type_size(type_t *t)
case BoolType: return sizeof(bool);
case ByteType: return sizeof(uint8_t);
case CStringType: return sizeof(char*);
case DateTimeType: return sizeof(DateTime_t);
case BigIntType: return sizeof(Int_t);
case IntType: {
switch (Match(t, IntType)->bits) {
@ -484,6 +486,7 @@ PUREFUNC size_t type_align(type_t *t)
case BoolType: return __alignof__(bool);
case ByteType: return __alignof__(uint8_t);
case CStringType: return __alignof__(char*);
case DateTimeType: return __alignof__(DateTime_t);
case BigIntType: return __alignof__(Int_t);
case IntType: {
switch (Match(t, IntType)->bits) {

View File

@ -48,6 +48,7 @@ struct type_s {
IntType,
NumType,
CStringType,
DateTimeType,
TextType,
ArrayType,
ChannelType,
@ -77,7 +78,7 @@ struct type_s {
struct {
enum { TYPE_NBITS32=32, TYPE_NBITS64=64 } bits;
} NumType;
struct {} CStringType;
struct {} CStringType, DateTimeType;
struct {
const char *lang;
struct env_s *env;