Source file temporal.ml
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
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
let seconds_per_day = 86_400L
let ns_per_day = 86_400_000_000_000L
let ps_per_day = 86_400_000_000_000_000L
let min_year = 1
let max_year = 9999
let ( let* ) = Option.bind
let pow10 n =
let rec go acc = function 0 -> acc | k -> go (acc * 10) (k - 1) in
go 1 n
let floor_div_rem n d =
let q = Int64.div n d in
let r = Int64.rem n d in
if r < 0L then (Int64.sub q 1L, Int64.add r d) else (q, r)
let days_from_civil (y, m, d) =
let y = if m <= 2 then y - 1 else y in
let era = if y >= 0 then y / 400 else (y - 399) / 400 in
let yoe = y - (era * 400) in
let doy = (((153 * if m > 2 then m - 3 else m + 9) + 2) / 5) + d - 1 in
let doe = (yoe * 365) + (yoe / 4) - (yoe / 100) + doy in
(era * 146097) + doe - 719468
let civil_from_days z =
let z = z + 719468 in
let era = if z >= 0 then z / 146097 else (z - 146096) / 146097 in
let doe = z - (era * 146097) in
let yoe = (doe - (doe / 1460) + (doe / 36524) - (doe / 146096)) / 365 in
let y = yoe + (era * 400) in
let doy = doe - ((365 * yoe) + (yoe / 4) - (yoe / 100)) in
let mp = ((5 * doy) + 2) / 153 in
let d = doy - (((153 * mp) + 2) / 5) + 1 in
let m = if mp < 10 then mp + 3 else mp - 9 in
let y = if m <= 2 then y + 1 else y in
(y, m, d)
let days_in_month (y, m) =
match m with
| 1 | 3 | 5 | 7 | 8 | 10 | 12 -> 31
| 4 | 6 | 9 | 11 -> 30
| 2 -> if (y mod 4 = 0 && y mod 100 <> 0) || y mod 400 = 0 then 29 else 28
| _ -> invalid_arg "days_in_month"
let epoch_ordinal = 719163
type tz = Offset of int | Zone_name of string
let tz_to_string = function
| Offset offset ->
let sign = if offset < 0 then "-" else "+" in
let abs = abs offset in
Printf.sprintf "%s%02d:%02d" sign (abs / 3600) (abs mod 3600 / 60)
| Zone_name name -> name
type date = int
module Date = struct
type t = date
let of_days days = days
let to_days t = t
let of_ymd (y, m, d) =
if y < min_year || y > max_year || m < 1 || m > 12 || d < 1 || d > days_in_month (y, m) then
None
else Some (of_days (days_from_civil (y, m, d)))
let to_ymd t = civil_from_days t
let to_ordinal t = t + epoch_ordinal
let of_ordinal ordinal = of_days (ordinal - epoch_ordinal)
let add_days n t = t + n
let diff_days a b = a - b
let compare a b = compare a b
let equal a b = a = b
let add_months n t =
let y, m, d = to_ymd t in
let total = (y * 12) + (m - 1) + n in
let y' = total / 12 in
let m' = (total mod 12) + 1 in
if y' < min_year || y' > max_year then None else of_ymd (y', m', min d (days_in_month (y', m')))
let to_iso8601 t =
let y, m, d = to_ymd t in
Printf.sprintf "%04d-%02d-%02d" y m d
let of_iso8601 s =
match String.split_on_char '-' s with
| [ y; m; d ] -> (
match (int_of_string_opt y, int_of_string_opt m, int_of_string_opt d) with
| Some y, Some m, Some d -> of_ymd (y, m, d)
| _ -> None)
| _ -> None
let to_string = to_iso8601
end
type time = { ticks : int64; tz_offset_seconds : int option }
module Time = struct
type t = time
let of_ticks ?tz_offset_seconds ticks = { ticks; tz_offset_seconds }
let to_ticks t = t.ticks
let tz_offset_seconds t = t.tz_offset_seconds
let of_hms_ns ?tz_offset_seconds h m s ns =
if h < 0 || h > 23 || m < 0 || m > 59 || s < 0 || s > 59 || ns < 0 || ns > 999_999_999 then None
else
let ticks =
Int64.add
(Int64.mul 3_600_000_000_000L (Int64.of_int h))
(Int64.add
(Int64.mul 60_000_000_000L (Int64.of_int m))
(Int64.add (Int64.mul 1_000_000_000L (Int64.of_int s)) (Int64.of_int ns)))
in
Some { ticks; tz_offset_seconds }
let to_hms_ns t =
let ns = Int64.to_int (Int64.rem t.ticks 1_000_000_000L) in
let s = Int64.to_int (Int64.rem (Int64.div t.ticks 1_000_000_000L) 60L) in
let m = Int64.to_int (Int64.rem (Int64.div t.ticks 60_000_000_000L) 60L) in
let h = Int64.to_int (Int64.div t.ticks 3_600_000_000_000L) in
(h, m, s, ns)
let add nanoseconds t = { t with ticks = Int64.add t.ticks nanoseconds }
let sub nanoseconds t = { t with ticks = Int64.sub t.ticks nanoseconds }
let compare a b = compare a.ticks b.ticks
let equal a b = a.ticks = b.ticks && a.tz_offset_seconds = b.tz_offset_seconds
let to_iso8601 t =
let h, m, s, ns = to_hms_ns t in
let base = Printf.sprintf "%02d:%02d:%02d" h m s in
let frac = if ns = 0 then "" else Printf.sprintf ".%09d" ns in
match t.tz_offset_seconds with
| None -> base ^ frac
| Some offset -> base ^ frac ^ tz_to_string (Offset offset)
let parse_time_of_day s =
let len = String.length s in
if len < 8 || s.[2] <> ':' || s.[5] <> ':' then None
else
let int_sub lo hi = int_of_string_opt (String.sub s lo (hi - lo)) in
match (int_sub 0 2, int_sub 3 5, int_sub 6 8) with
| Some h, Some m, Some sec -> (
let i = ref 8 in
let ns = ref 0 in
if !i < len && s.[!i] = '.' then begin
incr i;
let digits = ref 0 in
while !i < len && s.[!i] >= '0' && s.[!i] <= '9' do
if !digits < 9 then begin
ns := (!ns * 10) + (Char.code s.[!i] - Char.code '0');
incr digits
end;
incr i
done;
ns := !ns * pow10 (9 - !digits)
end;
let tz =
if !i < len then
match s.[!i] with
| 'Z' ->
incr i;
Some (Some (Offset 0))
| '+' | '-' ->
if !i + 5 < len && s.[!i + 3] = ':' then
match (int_sub (!i + 1) (!i + 3), int_sub (!i + 4) (!i + 6)) with
| Some oh, Some om ->
incr i;
let offset = (oh * 3600) + (om * 60) in
Some (Some (Offset (if s.[!i - 1] = '-' then -offset else offset)))
| _ -> None
else None
| _ -> None
else Some None
in
match tz with
| None -> None
| Some tz -> (
match
of_hms_ns
?tz_offset_seconds:(match tz with Some (Offset o) -> Some o | _ -> None)
h m sec !ns
with
| Some t -> Some (t, tz)
| None -> None))
| _ -> None
let of_iso8601 s = match parse_time_of_day s with Some (t, _) -> Some t | None -> None
let to_string = to_iso8601
end
type duration = { months : int; days : int; seconds : int64; nanoseconds : int }
module Duration = struct
type t = duration
let of_fields ~months ~days ~seconds ~nanoseconds = { months; days; seconds; nanoseconds }
let to_fields t = (t.months, t.days, t.seconds, t.nanoseconds)
let neg t =
{
months = -t.months;
days = -t.days;
seconds = Int64.neg t.seconds;
nanoseconds = -t.nanoseconds;
}
let add a b =
let seconds = Int64.add a.seconds b.seconds in
let nanoseconds = a.nanoseconds + b.nanoseconds in
let seconds, nanoseconds =
if nanoseconds >= 1_000_000_000 then (Int64.add seconds 1L, nanoseconds - 1_000_000_000)
else if nanoseconds < 0 then (Int64.sub seconds 1L, nanoseconds + 1_000_000_000)
else (seconds, nanoseconds)
in
{ months = a.months + b.months; days = a.days + b.days; seconds; nanoseconds }
let sub a b = add a (neg b)
let compare a b =
compare (a.months, a.days, a.seconds, a.nanoseconds) (b.months, b.days, b.seconds, b.nanoseconds)
let equal a b = compare a b = 0
let to_total_seconds t =
Int64.add (Int64.mul (Int64.of_int ((t.months * 30) + t.days)) seconds_per_day) t.seconds
let to_iso8601 t =
let negative =
t.months < 0 || t.days < 0 || Int64.compare t.seconds 0L < 0 || t.nanoseconds < 0
in
let t = if negative then neg t else t in
let years = t.months / 12 in
let months = t.months mod 12 in
let hours = Int64.div t.seconds 3_600L in
let minutes = Int64.div (Int64.rem t.seconds 3_600L) 60L in
let seconds = Int64.rem t.seconds 60L in
let buffer = Buffer.create 16 in
if negative then Buffer.add_char buffer '-';
Buffer.add_char buffer 'P';
if years > 0 then Printf.bprintf buffer "%dY" years;
if months > 0 then Printf.bprintf buffer "%dM" months;
if t.days > 0 then Printf.bprintf buffer "%dD" t.days;
if hours > 0L || minutes > 0L || seconds > 0L || t.nanoseconds > 0 then begin
Buffer.add_char buffer 'T';
if hours > 0L then Printf.bprintf buffer "%LdH" hours;
if minutes > 0L then Printf.bprintf buffer "%LdM" minutes;
if seconds > 0L || t.nanoseconds > 0 then begin
Printf.bprintf buffer "%Ld" seconds;
if t.nanoseconds > 0 then Printf.bprintf buffer ".%09d" t.nanoseconds;
Buffer.add_char buffer 'S'
end
end;
if Buffer.length buffer = 1 + if negative then 1 else 0 then Buffer.add_string buffer "T0S";
Buffer.contents buffer
let of_iso8601 s =
let len = String.length s in
let i = ref 0 in
let negative = !i < len && s.[!i] = '-' in
if negative then incr i;
if !i >= len || s.[!i] <> 'P' then None
else begin
incr i;
let months = ref 0 in
let days = ref 0 in
let seconds = ref 0L in
let nanoseconds = ref 0 in
let time_part = ref false in
let ok = ref true in
let read_number () =
let start = !i in
while !i < len && s.[!i] >= '0' && s.[!i] <= '9' do
incr i
done;
if !i = start then None else int_of_string_opt (String.sub s start (!i - start))
in
while !ok && !i < len do
if s.[!i] = 'T' then begin
incr i;
time_part := true
end
else
match read_number () with
| None -> ok := false
| Some n ->
if !i < len && s.[!i] = '.' then begin
incr i;
let frac = ref 0 in
let digits = ref 0 in
while !i < len && s.[!i] >= '0' && s.[!i] <= '9' do
if !digits < 9 then begin
frac := (!frac * 10) + (Char.code s.[!i] - Char.code '0');
incr digits
end;
incr i
done;
if !i < len && s.[!i] = 'S' && !time_part then begin
incr i;
seconds := Int64.add !seconds (Int64.of_int n);
nanoseconds := !frac * pow10 (9 - !digits)
end
else ok := false
end
else if !i < len then
match s.[!i] with
| 'Y' when not !time_part ->
incr i;
months := !months + (n * 12)
| 'M' when not !time_part ->
incr i;
months := !months + n
| 'D' when not !time_part ->
incr i;
days := !days + n
| 'H' when !time_part ->
incr i;
seconds := Int64.add !seconds (Int64.mul (Int64.of_int n) 3_600L)
| 'M' when !time_part ->
incr i;
seconds := Int64.add !seconds (Int64.mul (Int64.of_int n) 60L)
| 'S' when !time_part ->
incr i;
seconds := Int64.add !seconds (Int64.of_int n)
| _ -> ok := false
else ok := false
done;
if not !ok then None
else
Some
{
months = (if negative then - !months else !months);
days = (if negative then - !days else !days);
seconds = (if negative then Int64.neg !seconds else !seconds);
nanoseconds = (if negative then - !nanoseconds else !nanoseconds);
}
end
let to_span t =
if t.months <> 0 then None
else
let total_ps =
Int64.add
(Int64.mul t.seconds 1_000_000_000_000L)
(Int64.mul (Int64.of_int t.nanoseconds) 1_000L)
in
let days, ps = floor_div_rem total_ps ps_per_day in
Ptime.Span.of_d_ps (Int64.to_int days, ps)
let of_span span =
let days, ps = Ptime.Span.to_d_ps span in
let seconds = Int64.div ps 1_000_000_000_000L in
let nanoseconds = Int64.to_int (Int64.div (Int64.rem ps 1_000_000_000_000L) 1_000L) in
{ months = 0; days; seconds; nanoseconds }
let to_string = to_iso8601
end
type datetime = { epoch_seconds : int64; nanoseconds : int; tz : tz option }
module DateTime = struct
type t = datetime
let of_epoch_seconds ?tz epoch_seconds nanoseconds = { epoch_seconds; nanoseconds; tz }
let to_epoch_seconds t = (t.epoch_seconds, t.nanoseconds)
let tz t = t.tz
let wall_seconds t =
match t.tz with
| Some (Offset offset) -> Int64.add t.epoch_seconds (Int64.of_int offset)
| _ -> t.epoch_seconds
let canonical_zone = function
| "Canada/East-Saskatchewan" -> "America/Regina"
| "US/Pacific-New" -> "America/Los_Angeles"
| "SystemV/AST4ADT" -> "America/Halifax"
| "SystemV/CST6CDT" -> "America/Chicago"
| "SystemV/EST5EDT" -> "America/New_York"
| "SystemV/MST7MDT" -> "America/Denver"
| "SystemV/PST8PDT" -> "America/Los_Angeles"
| "SystemV/YST9YDT" -> "America/Anchorage"
| "SystemV/AST4" -> "Etc/GMT-4"
| "SystemV/CST6" -> "Etc/GMT-6"
| "SystemV/EST5" -> "Etc/GMT-5"
| "SystemV/HST10" -> "Etc/GMT-10"
| "SystemV/MST7" -> "Etc/GMT-7"
| "SystemV/PST8" -> "Etc/GMT-8"
| "SystemV/YST9" -> "Etc/GMT-9"
| z -> z
let lmt_offset name = Tz_lmt.find (canonical_zone name)
let timedesc_of_epoch name t =
match Timedesc.Time_zone.make (canonical_zone name) with
| None -> None
| Some zone ->
Timedesc.of_timestamp ~tz_of_date_time:zone
(Timedesc.Span.make ~s:t.epoch_seconds ~ns:t.nanoseconds ())
let zone_offset name t =
if t.epoch_seconds < 0L then lmt_offset name
else
match timedesc_of_epoch name t with
| None -> None
| Some dt -> (
match Timedesc.offset_from_utc dt with
| `Single span | `Ambiguous (span, _) ->
Some (Int64.to_int (fst (Timedesc.Span.to_s_ns span))))
let offset_seconds t =
match t.tz with
| Some (Offset offset) -> Some offset
| Some (Zone_name name) -> zone_offset name t
| None -> None
let to_ymd_hms t =
match t.tz with
| Some (Zone_name name) when t.epoch_seconds < 0L -> (
match lmt_offset name with
| Some offset ->
let days, rem =
floor_div_rem (Int64.add t.epoch_seconds (Int64.of_int offset)) seconds_per_day
in
let days = Int64.to_int days in
let h = Int64.to_int (Int64.div rem 3_600L) in
let m = Int64.to_int (Int64.div (Int64.rem rem 3_600L) 60L) in
let s = Int64.to_int (Int64.rem rem 60L) in
(civil_from_days days, (h, m, s), t.nanoseconds)
| None -> ((1970, 1, 1), (0, 0, 0), 0))
| Some (Zone_name name) -> (
match timedesc_of_epoch name t with
| Some dt ->
( (Timedesc.year dt, Timedesc.month dt, Timedesc.day dt),
(Timedesc.hour dt, Timedesc.minute dt, Timedesc.second dt),
t.nanoseconds )
| None -> ((1970, 1, 1), (0, 0, 0), 0))
| _ ->
let days, rem = floor_div_rem (wall_seconds t) seconds_per_day in
let days = Int64.to_int days in
let h = Int64.to_int (Int64.div rem 3_600L) in
let m = Int64.to_int (Int64.div (Int64.rem rem 3_600L) 60L) in
let s = Int64.to_int (Int64.rem rem 60L) in
(civil_from_days days, (h, m, s), t.nanoseconds)
let of_zone_timedesc name (y, mo, d) (h, m, s) ns =
let* zone = Timedesc.Time_zone.make (canonical_zone name) in
let* dt =
Result.to_option
(Timedesc.make ~tz:zone ~year:y ~month:mo ~day:d ~hour:h ~minute:m ~second:s ~ns ())
in
let span = match Timedesc.to_timestamp dt with `Single s | `Ambiguous (s, _) -> s in
Some
{
epoch_seconds = fst (Timedesc.Span.to_s_ns span);
nanoseconds = ns;
tz = Some (Zone_name name);
}
let of_zone name (y, mo, d) (h, m, s) ns =
if y < 1970 then
match lmt_offset name with
| Some offset ->
let wall =
Int64.add
(Int64.mul (Int64.of_int (days_from_civil (y, mo, d))) seconds_per_day)
(Int64.of_int ((h * 3600) + (m * 60) + s))
in
Some
{
epoch_seconds = Int64.sub wall (Int64.of_int offset);
nanoseconds = ns;
tz = Some (Zone_name name);
}
| None -> of_zone_timedesc name (y, mo, d) (h, m, s) ns
else of_zone_timedesc name (y, mo, d) (h, m, s) ns
let of_offset_or_naive tz (y, mo, d) (h, m, s) ns =
let wall =
Int64.add
(Int64.mul (Int64.of_int (days_from_civil (y, mo, d))) seconds_per_day)
(Int64.of_int ((h * 3600) + (m * 60) + s))
in
let epoch =
match tz with Some (Offset offset) -> Int64.sub wall (Int64.of_int offset) | _ -> wall
in
Some { epoch_seconds = epoch; nanoseconds = ns; tz }
let of_ymd_hms ?tz (y, mo, d) (h, m, s) ns =
if h < 0 || h > 23 || m < 0 || m > 59 || s < 0 || s > 59 || ns < 0 || ns > 999_999_999 then None
else
match (Date.of_ymd (y, mo, d), tz) with
| None, _ -> None
| Some _, Some (Zone_name name) -> of_zone name (y, mo, d) (h, m, s) ns
| Some _, _ -> of_offset_or_naive tz (y, mo, d) (h, m, s) ns
let compare a b =
match Int64.compare a.epoch_seconds b.epoch_seconds with
| 0 -> compare a.nanoseconds b.nanoseconds
| c -> c
let equal a b = compare a b = 0
let normalize_time (h, m, s, ns) add_seconds add_ns =
let total =
Int64.add
(Int64.mul 3_600_000_000_000L (Int64.of_int h))
(Int64.add
(Int64.mul 60_000_000_000L (Int64.of_int m))
(Int64.add (Int64.mul 1_000_000_000L (Int64.of_int s)) (Int64.of_int ns)))
in
let total =
Int64.add total (Int64.add (Int64.mul add_seconds 1_000_000_000L) (Int64.of_int add_ns))
in
let days, rem = floor_div_rem total ns_per_day in
let h' = Int64.to_int (Int64.div rem 3_600_000_000_000L) in
let m' = Int64.to_int (Int64.div (Int64.rem rem 3_600_000_000_000L) 60_000_000_000L) in
let s' = Int64.to_int (Int64.div (Int64.rem rem 60_000_000_000L) 1_000_000_000L) in
let ns' = Int64.to_int (Int64.rem rem 1_000_000_000L) in
(Int64.to_int days, (h', m', s', ns'))
let add (d : Duration.t) t =
match t.tz with
| Some (Zone_name _) -> None
| tz -> (
let (y, mo, day), (h, m, s), ns = to_ymd_hms t in
let carry, (h', m', s', ns') = normalize_time (h, m, s, ns) d.seconds d.nanoseconds in
match Date.add_months d.months (Date.of_days (days_from_civil (y, mo, day))) with
| None -> None
| Some date ->
let date = Date.add_days (d.days + carry) date in
let y', mo', d' = civil_from_days (Date.to_days date) in
of_ymd_hms ?tz (y', mo', d') (h', m', s') ns')
let sub (d : Duration.t) t =
add
{
months = -d.months;
days = -d.days;
seconds = Int64.neg d.seconds;
nanoseconds = -d.nanoseconds;
}
t
let diff a b =
match (a.tz, b.tz) with
| Some (Zone_name _), _ | _, Some (Zone_name _) -> None
| _ ->
let seconds = Int64.sub (wall_seconds a) (wall_seconds b) in
let nanoseconds = a.nanoseconds - b.nanoseconds in
let seconds, nanoseconds =
if nanoseconds < 0 then (Int64.sub seconds 1L, nanoseconds + 1_000_000_000)
else (seconds, nanoseconds)
in
let days = Int64.div seconds seconds_per_day in
let seconds = Int64.rem seconds seconds_per_day in
Some { months = 0; days = Int64.to_int days; seconds; nanoseconds }
let to_iso8601 t =
let (y, mo, d), (h, m, s), ns = to_ymd_hms t in
let date = Printf.sprintf "%04d-%02d-%02dT%02d:%02d:%02d" y mo d h m s in
let frac = if ns = 0 then "" else Printf.sprintf ".%09d" ns in
let suffix =
match t.tz with
| Some (Offset offset) -> tz_to_string (Offset offset)
| Some (Zone_name _) | None -> ""
in
date ^ frac ^ suffix
let of_iso8601 s =
let sep =
match String.index_opt s 'T' with Some i -> Some i | None -> String.index_opt s ' '
in
match sep with
| None -> None
| Some i -> (
let date_s = String.sub s 0 i in
let time_s = String.sub s (i + 1) (String.length s - i - 1) in
match (Date.of_iso8601 date_s, Time.parse_time_of_day time_s) with
| Some date, Some (time, tz) ->
let y, mo, d = Date.to_ymd date in
let h, m, sec, ns = Time.to_hms_ns time in
of_ymd_hms ?tz (y, mo, d) (h, m, sec) ns
| _ -> None)
let to_ptime t =
match t.tz with
| Some (Zone_name _) -> None
| _ -> (
let (y, mo, d), (h, m, s), ns = to_ymd_hms t in
let tz_offset_s = match t.tz with Some (Offset o) -> o | _ -> 0 in
match Ptime.of_date_time ((y, mo, d), ((h, m, s), tz_offset_s)) with
| None -> None
| Some ptime ->
let ns_span =
match Ptime.Span.of_d_ps (0, Int64.mul (Int64.of_int ns) 1_000L) with
| Some span -> span
| None -> Ptime.Span.zero
in
Ptime.add_span ptime ns_span)
let of_ptime ?tz ptime =
let (y, mo, d), ((h, m, s), _) = Ptime.to_date_time ptime in
let _, ps = Ptime.Span.to_d_ps (Ptime.to_span ptime) in
let nanoseconds = Int64.to_int (Int64.rem (Int64.div ps 1_000L) 1_000_000_000L) in
let epoch_seconds =
Int64.add
(Int64.mul (Int64.of_int (days_from_civil (y, mo, d))) seconds_per_day)
(Int64.of_int ((h * 3600) + (m * 60) + s))
in
{ epoch_seconds; nanoseconds; tz }
let to_string = to_iso8601
end