aboutsummaryrefslogtreecommitdiff
path: root/docs/datetime.md
blob: 9d4e1e4667a3334a9aaf7adbe066d4741299a612 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
# DateTime

Tomo has a builtin datatype for representing a specific single point in time:
`DateTime`. A DateTime object is internally represented using a UNIX timestamp
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()`).

⚠️⚠️⚠️ **WARNING** ⚠️⚠️⚠️ Dates and times are deeply counterintuitive and you should
be extremely cautious when writing code that deals with dates and times. Effort
has been made to ensure that Tomo's `DateTime` code uses standard libraries and
is as correct as possible, but counterintuitive behaviors around time zones,
daylight savings time, leap seconds, and other anomalous time situations can
still cause bugs if you're not extremely careful.

## Syntax

DateTime literals can be specified using [ISO
8601](https://en.wikipedia.org/wiki/ISO_8601) syntax with an optional
square-bracket delimited time zone name afterwards. A space may be used instead
of a `T` in the ISO 8601 format for readability, and spaces may come before the
timezone.

```tomo
2024-09-30
2024-09-30T13:57
2024-09-30 13:57
2024-09-30 13:57:01
2024-09-30 13:57:01 +04:00
2024-09-30 13:57:01 [America/New_York]
```

## 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 EDT
```

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 various functions where time zones matter, there is an optional `timezone`
argument that, if set, will override the timezone when performing calculations.
If unspecified, it is assumed that the current local timezone should be used.
Time zones are specified by name, such as `America/New_York` or `UTC`.

## 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,
`timezone` which is used to determine in which timezone the offsets should be
calculated.

**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, timezone : Text? = !Text -> DateTime)
```

**Parameters:**

- `seconds` (optional): An amount of seconds to offset the datetime (default: 0).
- `minutes` (optional): An amount of minutes to offset the datetime (default: 0).
- `hours` (optional): An amount of hours to offset the datetime (default: 0).
- `days` (optional): An amount of days to offset the datetime (default: 0).
- `weeks` (optional): An amount of weeks to offset the datetime (default: 0).
- `months` (optional): An amount of months to offset the datetime (default: 0).
- `years` (optional): An amount of years to offset the datetime (default: 0).
- `timezone` (optional): If specified, perform perform the calculations in the
  given timezone. If unspecified, the current local timezone will be used.

**Returns:**  
A new `DateTime` offset by the given amount.

**Example:**  
```markdown
>> DateTime(2024, 9, 29, hour=19):after(days=1, minutes=30)
= Mon Sep 30 19:30:00 2024 EDT
```

---

### `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(timezone : Text? = !Text -> Text)
```

**Parameters:**

- `timezone` (optional): If specified, give the date in the given timezone (otherwise, use the current local timezone).

**Returns:**  
The date in `YYYY-MM-DD` format.

**Example:**  
```markdown
>> DateTime(2024, 9, 29):date()
= "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
`timezone` is specified, use that timezone instead of the current local
timezone.

**Usage:**  
```markdown
datetime:format(format: Text = "%Y-%m-%dT%H:%M:%S%z", timezone : Text? = !Text -> Text)
```

**Parameters:**

- `format`: The `strftime` format to use (default: `"%Y-%m-%dT%H:%M:%S%z"`).
- `timezone` (optional): If specified, use the given timezone (otherwise, use the current local timezone).

**Returns:**  
Nothing.

**Example:**  
```markdown
>> DateTime(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, timezone : Text? = !Text -> 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)
- `timezone` (optional): If specified, give values in the given timezone (otherwise, use the current local timezone).

**Returns:**
Nothing.

**Example:**  
```markdown
dt := DateTime(2024, 9, 29)
month := 0
dt:get(month=&month)
>> month
= 9
```

---

### `get_local_timezone`

**Description:**
Get the local timezone's name (e.g. `America/New_York` or `UTC`. By default,
this value is read from `/etc/localtime`, however, this can be overridden by
calling `DateTime.set_local_timezone(...)`.

**Usage:**  
```markdown
DateTime.get_local_timezone(->Text)
```

**Parameters:**

None.

**Returns:**
The name of the current local timezone.

**Example:**  
```markdown
>> DateTime.get_local_timezone()
= "America/New_York"
```

---

### `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. This function is the same as calling `DateTime` directly as a
constructor.

**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 EDT

# March 1642, 2020:
>> DateTime(2020, 4, 1643)
= Sat Sep 28 00:00:00 2024 EDT
```

---

### `now`

**Description:**
Get a `DateTime` object representing the current date and time. This function
is the same as the global function `now()`.

**Usage:**  
```markdown
DateTime.now(->DateTime)
```

**Parameters:**

None.

**Returns:**
Returns a `DateTime` object representing the current date and time.

**Example:**  
```markdown
>> DateTime.now()
= Sun Sep 29 20:22:48 2024 EDT
```

---

### `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 = "%Y-%m-%dT%H:%M:%S%z" -> 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: `"%Y-%m-%dT%H:%M:%S%z"`).

**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 EDT

>> 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(), timezone : Text? = !Text -> Text)
```

**Parameters:**

- `relative_to` (optional): The time against which the relative time is calculated (default: `DateTime.now()`).
- `timezone` (optional): If specified, perform calculations in the given
  timezone (otherwise, use the current local timezone).

**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
```

---

### `set_local_timezone`

**Description:**
Set the current local timezone to a given value by name (e.g.
`America/New_York` or `UTC`). The local timezone is used as the default
timezone for performing calculations and constructing `DateTime` objects from
component parts. It's also used as the default way that `DateTime` objects are
converted to text.

**Usage:**  
```markdown
DateTime.set_local_timezone(timezone : Text? = !Text -> Void)
```

**Parameters:**

- `timezone` (optional): if specified, set the current local timezone to the
  timezone with the given name. If null, reset the current local timezone to
  the system default (the value referenced in `/etc/localtime`).

**Returns:**
Nothing.

**Example:**  
```markdown
DateTime.set_local_timezone("America/Los_Angeles")
```

---

### `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, timezone : Text? = !Text -> 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`).
- `timezone` (optional): If specified, give the time in the given timezone (otherwise, use the current local timezone).

**Returns:**  
A text representation of the time component of the datetime.

**Example:**  
```markdown
dt := DateTime(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]
```