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
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
let jd_to_date jd =
let a = jd + 32044 in
let b = (4 * a + 3) / 146097 in
let c = a - ((146097 * b) / 4) in
let d = (4 * c + 3) / 1461 in
let e = c - ((1461 * d) / 4) in
let m = (5 * e + 2) / 153 in
let day = e - ((153 * m + 2) / 5) + 1 in
let month = m + 3 - (12 * (m / 10)) in
let year = 100 * b + d - 4800 + (m / 10) in
(year, month, day)
let jd_to_year jd =
let a = jd + 32044 in
let b = (4 * a + 3) / 146097 in
let c = a - ((146097 * b) / 4) in
let d = (4 * c + 3) / 1461 in
let e = c - ((1461 * d) / 4) in
let m = (5 * e + 2) / 153 in
100 * b + d - 4800 + (m / 10)
let jd_of_date (year, month, day) =
let a = (14 - month) / 12 in
let y = year + 4800 - a in
let m = month + 12 * a - 3 in
day + ((153 * m) + 2)/ 5 + 365 * y +
(y / 4) - (y / 100) + (y / 400) - 32045
let jd_posix_epoch = 2_440_588
let jd_ptime_min = 1_721_060
let jd_ptime_max = 5_373_484
type t = int * int64
let ps_count_in_ps = 1L
let ps_count_in_ns = 1_000L
let ps_count_in_100ns = 100_000L
let ps_count_in_us = 1_000_000L
let ps_count_in_100us = 100_000_000L
let ps_count_in_ms = 1_000_000_000L
let ps_count_in_100ms = 100_000_000_000L
let ps_count_in_s = 1_000_000_000_000L
let ps_count_in_min = 60_000_000_000_000L
let ps_count_in_hour = 3600_000_000_000_000L
let ps_count_in_day = 86_400_000_000_000_000L
let ps_day_max = 86_399_999_999_999_999L
let day_min = jd_ptime_min - jd_posix_epoch
let day_max = jd_ptime_max - jd_posix_epoch
let epoch = (0, 0L)
let min = (day_min, 0L)
let max = (day_max, ps_day_max)
type span = t
module Span = struct
let stdlib_abs = abs
let neg = function
| (d, 0L) -> (-d, 0L)
| (d, ps) -> (-(d + 1), Int64.sub ps_count_in_day ps)
let add (d0, ps0) (d1, ps1) =
let d = d0 + d1 in
let ps = Int64.add ps0 ps1 in
let ps_clamp = Int64.rem ps ps_count_in_day in
let d = d + Int64.compare ps ps_clamp in
d, ps_clamp
let sub s0 s1 = add s0 (neg s1)
let abs (d, _ as s) = if d < 0 then neg s else s
type t = span
let zero = (0, 0L)
let v (d, ps as s) =
if ps < 0L || ps > ps_day_max
then invalid_arg (Format.sprintf "illegal ptime time span: (%d,%Ld)" d ps)
else s
let of_d_ps (d, ps as s) = if ps < 0L || ps > ps_day_max then None else Some s
let unsafe_of_d_ps s = s
let unsafe_of_d_ps_option s = s
let to_d_ps s = s
let of_int_s secs =
let d = stdlib_abs secs in
let s = (d / 86_400, Int64.(mul (of_int (d mod 86_400)) ps_count_in_s)) in
if secs < 0 then neg s else s
let day_int_min = min_int / 86_400
let day_int_max = max_int / 86_400
let to_int_s (d, ps) =
if d < day_int_min || d > day_int_max then None else
let days_s = d * 86_400 in
let day_s = Int64.(to_int (div ps ps_count_in_s)) in
let secs = days_s + day_s in
if secs < days_s then None else Some secs
let min_int_float = float min_int
let max_int_float = float max_int
let of_float_s secs =
if secs <> secs then None else
let days = floor (secs /. 86_400.) in
if days < min_int_float || days > max_int_float then None else
let rem_s = mod_float secs 86_400. in
let rem_s = if rem_s < 0. then 86_400. +. rem_s else rem_s in
if rem_s >= 86_400. then
let days = days +. 1. in
if days > max_int_float then None else
Some (int_of_float days, 0L)
else
let frac_s, rem_s = modf rem_s in
let rem_ps = Int64.(mul (of_float rem_s) ps_count_in_s) in
let frac_ps = Int64.(of_float (frac_s *. 1e12)) in
Some (int_of_float days, (Int64.add rem_ps frac_ps))
let to_float_s (d, ps) =
let days_s = (float d) *. 86_400. in
let day_s = Int64.(to_float (div ps ps_count_in_s)) in
let day_rem_ps = Int64.(to_float (rem ps ps_count_in_s)) in
days_s +. day_s +. (day_rem_ps *. 1e-12)
let equal (d0, ps0) (d1, ps1) =
(compare : int -> int -> int) d0 d1 = 0 &&
Int64.compare ps0 ps1 = 0
let compare (d0, ps0) (d1, ps1) =
let c = (compare : int -> int -> int) d0 d1 in
if c <> 0 then c else (compare : int64 -> int64 -> int) ps0 ps1
let round_div a b =
if a = 0L then 0L else
Int64.(div (add a (div b 2L)) b)
let frac_div = [| 1_000_000_000_000L;
100_000_000_000L;
10_000_000_000L;
1_000_000_000L;
100_000_000L;
10_000_000L;
1_000_000L;
100_000L;
10_000L;
1_000L;
100L;
10L;
1L; |]
let round ~frac_s:frac (sign, _ as t) =
let frac = if frac < 0 then 0 else (if frac > 12 then 12 else frac) in
let (d, ps) = if sign < 0 then neg t else t in
let rps = Int64.mul (round_div ps frac_div.(frac)) frac_div.(frac) in
let t = if rps > ps_day_max then (d + 1, 0L) else (d, rps) in
if sign < 0 then neg t else t
let truncate ~frac_s:frac (sign, _ as t) =
let frac = if frac < 0 then 0 else (if frac > 12 then 12 else frac) in
let (d, ps) = if sign < 0 then neg t else t in
let tps = Int64.(sub ps (rem ps frac_div.(frac))) in
if sign < 0 then neg (d, tps) else (d, tps)
let truncate_down ~frac_s:frac (d, ps) =
(d, Int64.(sub ps (rem ps frac_div.(frac ))))
let dump ppf (d, ps) = Format.fprintf ppf "@[<1>(%d,@,%Ld)@]" d ps
let divide_ps ~carry ps hi lo =
let hi_d = Int64.(to_int (div ps hi)) in
let rem_ps = Int64.rem ps hi in
let lo_d = Int64.to_int (round_div rem_ps lo) in
if lo_d = carry then hi_d + 1, 0 else hi_d, lo_d
let pp_y_d ppf ~neg d ps =
let y, rem_d =
let max_d = max_int / 4 in
if d > max_d then d / 365, d mod 365 else
let y = (d * 4) / 1461 in
y, d - (y * 1461) / 4
in
let days = rem_d + Int64.to_int (round_div ps ps_count_in_day) in
let y, days = if days = 366 then y + 1, 1 else y, days in
let y = if neg then -y else y in
Format.fprintf ppf "%dy" y;
if days <> 0 then Format.fprintf ppf "%dd" days;
()
let pp_d_h ppf ~neg d ps =
let h, _ = divide_ps ~carry:1 ps ps_count_in_hour ps_count_in_hour in
let d, h = if h = 24 then d + 1, 0 else d, h in
if d = 366 then Format.fprintf ppf "%dy1d" (if neg then -1 else 1) else
if d = 365 && h >= 6
then Format.fprintf ppf "%dy" (if neg then -1 else 1) else
let d = if neg then -d else d in
Format.fprintf ppf "%dd" d;
if h <> 0 then Format.fprintf ppf "%dh" h;
()
let pp_h_m ppf ~neg ps =
let h, m = divide_ps ~carry:60 ps ps_count_in_hour ps_count_in_min in
if h = 24 then Format.fprintf ppf "%dd" (if neg then -1 else 1) else
let h = if neg then -h else h in
Format.fprintf ppf "%dh" h;
if m <> 0 then Format.fprintf ppf "%dmin" m;
()
let pp_m_s ppf ~neg ps =
let m, s = divide_ps ~carry:60 ps ps_count_in_min ps_count_in_s in
if m = 60 then Format.fprintf ppf "%dh" (if neg then -1 else 1) else
let m = if neg then -m else m in
Format.fprintf ppf "%dmin" m;
if s <> 0 then Format.fprintf ppf "%ds" s;
()
let pp_s ppf ~neg ps =
let s, ms = divide_ps ~carry:1000 ps ps_count_in_s ps_count_in_ms in
if s = 60 then Format.fprintf ppf "%dmin" (if neg then -1 else 1) else
let s = if neg then -s else s in
if ms <> 0 then Format.fprintf ppf "%d.%03ds" s ms else
Format.fprintf ppf "%ds" s
let pp_unit higher_str hi hi_str frac_limit lo ppf ~neg ps =
let pp_unit_integral ppf ~neg h =
if h = 1000
then Format.fprintf ppf "%d%s" (if neg then -1 else 1) higher_str
else Format.fprintf ppf "%d%s" (if neg then -h else h) hi_str
in
if ps < frac_limit then begin
let h, l = divide_ps ~carry:1000 ps hi lo in
if h >= 100 || l = 0 then pp_unit_integral ppf ~neg h else
let h = if neg then -h else h in
Format.fprintf ppf "%d.%03d%s" h l hi_str
end else begin
let ms, _ = divide_ps ~carry:1 ps hi hi in
pp_unit_integral ppf ~neg ms
end
let pp_ms =
pp_unit "s" ps_count_in_ms "ms" ps_count_in_100ms ps_count_in_us
let pp_us =
pp_unit "ms" ps_count_in_us "us" ps_count_in_100us ps_count_in_ns
let pp_ns =
pp_unit "us" ps_count_in_ns "ns" ps_count_in_100ns ps_count_in_ps
let pp_ps ppf ~neg ps =
let ps = Int64.to_int ps in
Format.fprintf ppf "%dps" (if neg then -ps else ps)
let pp ppf (sign, _ as s) =
let neg = sign < 0 in
match (abs s) with
| (0, ps) ->
if ps >= ps_count_in_hour then pp_h_m ppf ~neg ps else
if ps >= ps_count_in_min then pp_m_s ppf ~neg ps else
if ps >= ps_count_in_s then pp_s ppf ~neg ps else
if ps >= ps_count_in_ms then pp_ms ppf ~neg ps else
if ps >= ps_count_in_us then pp_us ppf ~neg ps else
if ps >= ps_count_in_ns then pp_ns ppf ~neg ps else
pp_ps ppf ~neg ps
| (d, ps) ->
if d > 365 then pp_y_d ppf ~neg d ps else
pp_d_h ppf ~neg d ps
end
let v (d, ps as s) =
if (ps < 0L || ps > ps_day_max || d < day_min || d > day_max)
then invalid_arg (Format.sprintf "illegal ptime timestamp: (%d,%Ld)" d ps)
else s
let unsafe_of_d_ps s = s
let of_span (d, _ as span) =
if d < day_min || d > day_max then None else Some span
let to_span t = t
let of_float_s secs = match Span.of_float_s secs with
| None -> None
| Some d -> of_span d
let to_float_s = Span.to_float_s
let truncate = Span.truncate_down
let frac_s (_, ps) = (0, Int64.(rem ps ps_count_in_s))
let equal = Span.equal
let compare = Span.compare
let is_earlier t ~than = compare t than = -1
let is_later t ~than = compare t than = 1
let add_span t d = of_span (Span.add t d)
let sub_span t d = of_span (Span.sub t d)
let diff t1 t0 = Span.sub t1 t0
type tz_offset_s = int
type date = (int * int * int)
type time = (int * int * int) * tz_offset_s
let max_month_day =
let is_leap_year y = (y mod 4 = 0) && (y mod 100 <> 0 || y mod 400 = 0) in
let mlen = [|31; 28 ; 31; 30; 31; 30; 31; 31; 30; 31; 30; 31|] in
fun y m -> if (m = 2 && is_leap_year y) then 29 else mlen.(m - 1)
let is_date_valid (y, m, d) =
0 <= y && y <= 9999 &&
1 <= m && m <= 12 &&
1 <= d && d <= max_month_day y m
let is_time_valid ((hh, mm, ss), _) =
0 <= hh && hh <= 23 &&
0 <= mm && mm <= 59 &&
0 <= ss && ss <= 60
let of_date_time (date, ((hh, mm, ss), tz_offset_s as t)) =
if not (is_date_valid date && is_time_valid t) then None else
let d = jd_of_date date - jd_posix_epoch in
let hh_ps = Int64.(mul (of_int hh) ps_count_in_hour) in
let mm_ps = Int64.(mul (of_int mm) ps_count_in_min) in
let ss_ps = Int64.(mul (of_int ss) ps_count_in_s) in
let ps = Int64.(add hh_ps (add mm_ps ss_ps)) in
sub_span (d, ps) (Span.of_int_s tz_offset_s)
let to_date_time ?(tz_offset_s = 0) t =
let (d, ps), tz_offset_s = match add_span t (Span.of_int_s tz_offset_s) with
| None -> t, 0
| Some local -> local, tz_offset_s
in
let jd = d + jd_posix_epoch in
let date = jd_to_date jd in
let hh = Int64.(to_int (div ps ps_count_in_hour)) in
let hh_rem = Int64.rem ps ps_count_in_hour in
let mm = Int64.(to_int (div hh_rem ps_count_in_min)) in
let mm_rem = Int64.rem hh_rem ps_count_in_min in
let ss = Int64.(to_int (div mm_rem ps_count_in_s)) in
date, ((hh, mm, ss), tz_offset_s)
let of_date ?tz_offset_s:(tz = 0) date = of_date_time (date, ((00, 00, 00), tz))
let to_date ?tz_offset_s t = fst (to_date_time ?tz_offset_s t)
let of_year ?tz_offset_s y = of_date ?tz_offset_s (y, 01, 01)
let to_year ?(tz_offset_s = 0) t =
let d = match add_span t (Span.of_int_s tz_offset_s) with
| None -> fst t | Some (local_d, _) -> local_d
in
jd_to_year (d + jd_posix_epoch)
type weekday = [ `Sun | `Mon | `Tue | `Wed | `Thu | `Fri | `Sat ]
let weekday_num ?(tz_offset_s = 0) t =
let (d, _) = Span.add t (Span.of_int_s tz_offset_s) in
let i = (d + 4 ) mod 7 in
if i < 0 then 7 + i else i
let weekday =
let wday = [| `Sun; `Mon; `Tue; `Wed; `Thu; `Fri; `Sat; |] in
fun ?tz_offset_s t -> wday.(weekday_num ?tz_offset_s t)
type error_range = int * int
type rfc3339_error =
[ `Invalid_stamp | `Eoi | `Exp_chars of char list | `Trailing_input ]
let pp_rfc3339_error ppf = function
| `Invalid_stamp -> Format.fprintf ppf "@[invalid@ time@ stamp@]"
| `Eoi -> Format.fprintf ppf "@[unexpected@ end@ of@ input@]"
| `Trailing_input -> Format.fprintf ppf "@[trailing@ input@]"
| `Exp_chars cs ->
let rec pp_chars ppf = function
| c :: cs -> Format.fprintf ppf "@ %C" c; pp_chars ppf cs
| [] -> ()
in
Format.fprintf ppf "@[expected@ a@ character@ in:%a@]" pp_chars cs
let pp_range ppf (s, e) =
if s = e then Format.pp_print_int ppf s else Format.fprintf ppf "%d-%d" s e
let _rfc3339_error_to_string (r, err) =
Format.asprintf "@[<h>%a: %a@]" pp_range r pp_rfc3339_error err
let rfc3339_string_error = function
| Ok _ as v -> v | Error (`RFC3339 e) -> Error (_rfc3339_error_to_string e)
let rfc3339_error_to_msg = function
| Ok _ as v -> v | Error (`RFC3339 e) ->
Error (`Msg (_rfc3339_error_to_string e))
exception RFC3339 of (int * int) * rfc3339_error
let error r e = raise (RFC3339 (r, e))
let error_pos p e = error (p, p) e
let error_exp_digit p =
error_pos p (`Exp_chars ['0'; '1'; '2'; '3'; '4'; '5'; '6'; '7'; '8'; '9'])
let is_digit = function '0' .. '9' -> true | _ -> false
let parse_digits ~count pos max s =
let stop = pos + count - 1 in
if stop > max then error_pos max `Eoi else
let rec loop k acc =
if k > stop then acc else
if is_digit s.[k] then loop (k+1) (acc * 10 + Char.code s.[k] - 0x30) else
error_exp_digit k
in
loop pos 0
let parse_char c pos max s =
if pos > max then error_pos max `Eoi else
if s.[pos] = c then () else error_pos pos (`Exp_chars [c])
let parse_dt_sep ~strict pos max s =
let is_dt_sep = function
| 'T' -> true
| 't' | ' ' when not strict -> true
| _ -> false
in
if pos > max then error_pos max `Eoi else
if is_dt_sep s.[pos] then () else
error_pos pos (`Exp_chars (['T'] @ if strict then [] else ['t'; ' ']))
let decide_frac_or_tz ~strict pos max s =
if pos > max then error_pos max `Eoi else
match s.[pos] with
| '.' -> `Frac
| '+' | '-' | 'Z' -> `Tz
| 'z' when not strict -> `Tz
| c ->
let chars = ['.'; '+'; '-'; 'Z'] @ if strict then [] else ['z'] in
error_pos pos (`Exp_chars chars)
let parse_frac_ps pos max s =
if pos > max then error_pos max `Eoi else
if not (is_digit s.[pos]) then error_exp_digit pos else
let rec loop k acc pow =
if k > max then error_pos max `Eoi else
if not (is_digit s.[k]) then (Some acc), k else
let count = k - pos + 1 in
if count > 12 then loop (k + 1) acc pow else
let pow = Int64.div pow 10L in
let acc = Int64.(add acc (mul (of_int (Char.code s.[k] - 0x30)) pow)) in
loop (k + 1) acc pow
in
loop pos 0L ps_count_in_s
let parse_tz_s ~strict pos max s =
let parse_tz_mag sign pos =
let hh_pos = pos in
let hh = parse_digits ~count:2 hh_pos max s in
let mm, mm_pos = match strict with
| true ->
let mm_pos = hh_pos + 3 in
parse_char ':' (mm_pos - 1) max s;
parse_digits ~count:2 mm_pos max s, mm_pos
| false ->
let next = hh_pos + 2 in
if next > max || not (s.[next] = ':' || is_digit s.[next])
then (0, hh_pos )
else
let mm_pos = if s.[next] = ':' then hh_pos + 3 else hh_pos + 2 in
parse_digits ~count:2 mm_pos max s, mm_pos
in
if hh > 23 then error (hh_pos, hh_pos + 1) `Invalid_stamp else
if mm > 59 then error (mm_pos, mm_pos + 1) `Invalid_stamp else
let secs = hh * 3600 + mm * 60 in
let tz_s = match secs = 0 && sign = -1 with
| true -> None
| false -> Some (sign * secs)
in
tz_s, mm_pos + 1
in
if pos > max then error_pos max `Eoi else
match s.[pos] with
| 'Z' -> Some 0, pos
| 'z' when not strict -> Some 0, pos
| '+' -> parse_tz_mag ( 1) (pos + 1)
| '-' -> parse_tz_mag (-1) (pos + 1)
| c ->
let chars = ['+'; '-'; 'Z'] @ if strict then [] else ['z'] in
error_pos pos (`Exp_chars chars)
let of_rfc3339 ?(strict = false) ?(sub = false) ?(start = 0) s =
try
let s_len = String.length s in
let max = s_len - 1 in
if s_len = 0 || start < 0 || start > max then error_pos start `Eoi else
let y_pos = start in
let m_pos = y_pos + 5 in
let d_pos = m_pos + 3 in
let hh_pos = d_pos + 3 in
let mm_pos = hh_pos + 3 in
let ss_pos = mm_pos + 3 in
let decide_pos = ss_pos + 2 in
let y = parse_digits ~count:4 y_pos max s in
parse_char '-' (m_pos - 1) max s;
let m = parse_digits ~count:2 m_pos max s in
parse_char '-' (d_pos - 1) max s;
let d = parse_digits ~count:2 d_pos max s in
parse_dt_sep ~strict (hh_pos - 1) max s;
let hh = parse_digits ~count:2 hh_pos max s in
parse_char ':' (mm_pos - 1) max s;
let mm = parse_digits ~count:2 mm_pos max s in
parse_char ':' (ss_pos - 1) max s;
let ss = parse_digits ~count:2 ss_pos max s in
let frac, tz_pos = match decide_frac_or_tz ~strict decide_pos max s with
| `Frac -> parse_frac_ps (decide_pos + 1) max s
| `Tz -> None, decide_pos
in
let tz_s_opt, last_pos = parse_tz_s ~strict tz_pos max s in
let tz_s = match tz_s_opt with None -> 0 | Some s -> s in
match of_date_time ((y, m, d), ((hh, mm, ss), tz_s)) with
| None -> error (start, last_pos) `Invalid_stamp
| Some t ->
let t, tz_s = match frac with
| None | Some 0L -> t, tz_s
| Some frac ->
match add_span t (0, frac) with
| None -> error (start, last_pos) `Invalid_stamp
| Some t -> t, tz_s
in
if not sub && last_pos <> max
then error_pos (last_pos + 1) `Trailing_input
else Ok (t, tz_s_opt, last_pos - start + 1)
with RFC3339 (r, e) -> Error (`RFC3339 (r, e))
let rfc3339_adjust_tz_offset tz_offset_s =
let min = -86340 in
let max = +86340 in
if min <= tz_offset_s && tz_offset_s <= max && tz_offset_s mod 60 = 0
then tz_offset_s, false
else 0 , true
let s_frac_of_ps frac ps =
Int64.(div (rem ps ps_count_in_s) Span.frac_div.(frac))
let to_rfc3339 ?(space = false) ?frac_s:(frac = 0) ?tz_offset_s (_, ps as t) =
let buf = Buffer.create 255 in
let tz_offset_s, tz_unknown = match tz_offset_s with
| Some tz -> rfc3339_adjust_tz_offset tz
| None -> 0, true
in
let (y, m, d), ((hh, ss, mm), tz_offset_s) = to_date_time ~tz_offset_s t in
let dt_sep = if space then ' ' else 'T' in
Printf.bprintf buf "%04d-%02d-%02d%c%02d:%02d:%02d" y m d dt_sep hh ss mm;
let frac = if frac < 0 then 0 else (if frac > 12 then 12 else frac) in
if frac <> 0 then Printf.bprintf buf ".%0*Ld" frac (s_frac_of_ps frac ps);
if tz_offset_s = 0 && not tz_unknown then Printf.bprintf buf "Z" else
begin
let tz_sign = if tz_offset_s < 0 || tz_unknown then '-' else '+' in
let tz_min = abs (tz_offset_s / 60) in
let tz_hh = tz_min / 60 in
let tz_mm = tz_min mod 60 in
Printf.bprintf buf "%c%02d:%02d" tz_sign tz_hh tz_mm;
end;
Buffer.contents buf
let pp_rfc3339 ?space ?frac_s ?tz_offset_s () ppf t =
Format.fprintf ppf "%s" (to_rfc3339 ?space ?frac_s ?tz_offset_s t)
let pp_human ?frac_s:(frac = 0) ?tz_offset_s () ppf (_, ps as t) =
let tz_offset_s, tz_unknown = match tz_offset_s with
| Some tz -> rfc3339_adjust_tz_offset tz
| None -> 0, true
in
let (y, m, d), ((hh, ss, mm), tz_offset_s) = to_date_time ~tz_offset_s t in
Format.fprintf ppf "%04d-%02d-%02d %02d:%02d:%02d" y m d hh ss mm;
let frac = if frac < 0 then 0 else (if frac > 12 then 12 else frac) in
if frac <> 0 then Format.fprintf ppf ".%0*Ld" frac (s_frac_of_ps frac ps);
let tz_sign = if tz_offset_s < 0 || tz_unknown then '-' else '+' in
let tz_min = abs (tz_offset_s / 60) in
let tz_hh = tz_min / 60 in
let tz_mm = tz_min mod 60 in
Format.fprintf ppf " %c%02d:%02d" tz_sign tz_hh tz_mm;
()
let pp = pp_human ~tz_offset_s:0 ()
let dump = Span.dump