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
open! Import
module Span = Span_ns
type underlying = Int63.t
type t = Span.t [@@deriving typerep]
include (Span : Robustly_comparable.S with type t := t)
let to_parts t = Span.to_parts t
let start_of_day : t = Span.zero
let start_of_next_day : t = Span.day
let approximate_end_of_day = Span.( - ) start_of_next_day Span.nanosecond
let to_span_since_start_of_day t = t
let[@cold] input_out_of_bounds span =
raise_s
[%message
"Time_ns.Ofday.of_span_since_start_of_day_exn: input out of bounds"
~_:(span : Span.t)]
;;
let[@inline always] is_invalid span =
Span.( < ) span start_of_day || Span.( > ) span start_of_next_day
;;
let span_since_start_of_day_is_valid span = not (is_invalid span)
let of_span_since_start_of_day_unchecked span = span
let of_span_since_start_of_day_exn span =
if is_invalid span then input_out_of_bounds span else span
;;
let of_span_since_start_of_day_opt span = if is_invalid span then None else Some span
let add_exn t span = of_span_since_start_of_day_exn (Span.( + ) t span)
let sub_exn t span = of_span_since_start_of_day_exn (Span.( - ) t span)
let add t span = of_span_since_start_of_day_opt (Span.( + ) t span)
let sub t span = of_span_since_start_of_day_opt (Span.( - ) t span)
let next t = of_span_since_start_of_day_opt (Span.next t)
let prev t = of_span_since_start_of_day_opt (Span.prev t)
let diff t u = Span.( - ) t u
let create ?hr ?min ?sec ?ms ?us ?ns () =
let ms, us, ns =
match sec with
| Some 60 -> Some 0, Some 0, Some 0
| _ -> ms, us, ns
in
of_span_since_start_of_day_exn (Span.create ?hr ?min ?sec ?ms ?us ?ns ())
;;
module Stable = struct
module Option = struct end
module Zoned = struct end
module V1 = struct
type t = Span.Stable.V2.t [@@deriving bin_io, compare, equal, hash, stable_witness]
include (
Span.Stable.V2 :
Comparator.S
with type t := t
and type comparator_witness = Span.Stable.V2.comparator_witness)
let to_string_with_unit =
let ( / ) = Int63.( / ) in
let ( mod ) = Int63.rem in
let ( ! ) = Int63.of_int in
let i = Int63.to_int_exn in
fun t ~unit ->
if Span.( < ) t start_of_day || Span.( < ) start_of_next_day t
then "Incorrect day"
else (
let sixty = !60 in
let thousand = !1000 in
let ns = Span.to_int63_ns t in
let us = ns / thousand in
let ns = ns mod thousand |> i in
let ms = us / thousand in
let us = us mod thousand |> i in
let s = ms / thousand in
let ms = ms mod thousand |> i in
let m = s / sixty in
let s = s mod sixty |> i in
let h = m / sixty |> i in
let m = m mod sixty |> i in
let unit =
match unit with
| (`Nanosecond | `Microsecond | `Millisecond | `Second) as unit -> unit
| `Minute_or_less ->
if ns <> 0
then `Nanosecond
else if us <> 0
then `Microsecond
else if ms <> 0
then `Millisecond
else if s <> 0
then `Second
else `Minute
in
let len =
match unit with
| `Minute -> 5
| `Second -> 8
| `Millisecond -> 12
| `Microsecond -> 15
| `Nanosecond -> 18
in
let str = Bytes.create len in
Digit_string_helpers.write_2_digit_int str ~pos:0 h;
Bytes.set str 2 ':';
Digit_string_helpers.write_2_digit_int str ~pos:3 m;
(match unit with
| `Minute -> ()
| (`Second | `Millisecond | `Microsecond | `Nanosecond) as unit ->
Bytes.set str 5 ':';
Digit_string_helpers.write_2_digit_int str ~pos:6 s;
(match unit with
| `Second -> ()
| (`Millisecond | `Microsecond | `Nanosecond) as unit ->
Bytes.set str 8 '.';
Digit_string_helpers.write_3_digit_int str ~pos:9 ms;
(match unit with
| `Millisecond -> ()
| (`Microsecond | `Nanosecond) as unit ->
Digit_string_helpers.write_3_digit_int str ~pos:12 us;
(match unit with
| `Microsecond -> ()
| `Nanosecond -> Digit_string_helpers.write_3_digit_int str ~pos:15 ns))));
Bytes.unsafe_to_string ~no_mutation_while_string_reachable:str)
;;
let parse_nanoseconds string ~pos ~until =
let open Int.O in
let digits = ref 0 in
let num_digits = ref 0 in
let pos = ref pos in
while !pos < until && !num_digits < 10 do
let c = string.[!pos] in
if Char.is_digit c
then (
incr num_digits;
if !num_digits < 10
then digits := (!digits * 10) + Char.get_digit_exn c
else if Char.get_digit_exn c >= 5
then incr digits
else ());
incr pos
done;
if !num_digits < 9 then digits := !digits * Int.pow 10 (9 - !num_digits);
!digits
;;
let create_from_parsed string ~hr ~min ~sec ~subsec_pos ~subsec_len =
let nanoseconds =
if Int.equal subsec_len 0
then 0
else
parse_nanoseconds string ~pos:(subsec_pos + 1) ~until:(subsec_pos + subsec_len)
in
Span.of_int63_ns (Int63.of_int nanoseconds)
|> Span.( + ) (Span.scale_int Span.second sec)
|> Span.( + ) (Span.scale_int Span.minute min)
|> Span.( + ) (Span.scale_int Span.hour hr)
|> of_span_since_start_of_day_exn
;;
let of_string string = Ofday_helpers.parse string ~f:create_from_parsed
let t_of_sexp sexp : t =
match sexp with
| Sexp.List _ -> of_sexp_error "expected an atom" sexp
| Sexp.Atom s ->
(try of_string s with
| exn -> of_sexp_error_exn exn sexp)
;;
let t_sexp_grammar =
let open Sexplib in
Sexp_grammar.tag
(Sexp_grammar.coerce String.t_sexp_grammar : t Sexp_grammar.t)
~key:Sexp_grammar.type_name_tag
~value:(Atom "Core.Time_ns.Ofday.t")
;;
let to_string (t : t) = to_string_with_unit t ~unit:`Nanosecond
let sexp_of_t (t : t) = Sexp.Atom (to_string t)
let to_int63 t = Span_ns.Stable.V2.to_int63 t
let of_int63_exn t = of_span_since_start_of_day_exn (Span_ns.Stable.V2.of_int63_exn t)
end
end
include (
Stable.V1 :
Comparator.S
with type t := t
and type comparator_witness = Stable.V1.comparator_witness)
include Identifiable.Make_using_comparator (struct
type t = Stable.V1.t [@@deriving bin_io, compare, hash, sexp]
include (
Stable.V1 :
Comparator.S
with type t := t
and type comparator_witness = Stable.V1.comparator_witness)
include (Stable.V1 : Stringable.S with type t := t)
let module_name = "Core.Time_ns.Ofday"
end)
let t_sexp_grammar = Sexplib.Sexp_grammar.coerce Stable.V1.t_sexp_grammar
let to_microsecond_string t = Stable.V1.to_string_with_unit t ~unit:`Microsecond
let to_millisecond_string t = Stable.V1.to_string_with_unit t ~unit:`Millisecond
let to_sec_string t = Stable.V1.to_string_with_unit t ~unit:`Second
let to_string_trimmed t = Stable.V1.to_string_with_unit t ~unit:`Minute_or_less
let of_string_iso8601_extended ?pos ?len str =
try
Ofday_helpers.parse_iso8601_extended ?pos ?len str ~f:Stable.V1.create_from_parsed
with
| exn ->
raise_s
[%message
"Time_ns.Ofday.of_string_iso8601_extended: cannot parse string"
~_:(String.subo str ?pos ?len : string)
~_:(exn : exn)]
;;
let every =
let rec every_valid_ofday_span span ~start ~stop ~acc =
let acc = start :: acc in
let start = Span.( + ) start span in
if Span.( > ) start stop
then List.rev acc
else every_valid_ofday_span span ~start ~stop ~acc
in
let every span ~start ~stop =
if Span.( > ) start stop
then
Or_error.error_s
[%message
"[Time_ns.Ofday.every] called with [start] > [stop]" (start : t) (stop : t)]
else if Span.( <= ) span Span.zero
then
Or_error.error_s
[%message "[Time_ns.Ofday.every] called with negative span" ~_:(span : Span.t)]
else if is_invalid span
then Ok [ start ]
else Ok (every_valid_ofday_span span ~start ~stop ~acc:[])
in
every
;;
let small_diff =
let hour = Span.to_int63_ns Span.hour in
fun ofday1 ofday2 ->
let open Int63.O in
let ofday1 = Span.to_int63_ns (to_span_since_start_of_day ofday1) in
let ofday2 = Span.to_int63_ns (to_span_since_start_of_day ofday2) in
let diff = ofday1 - ofday2 in
let d1 = Int63.rem diff hour in
let d2 = Int63.rem (d1 + hour) hour in
let d = if d2 > hour / Int63.of_int 2 then d2 - hour else d2 in
Span.of_int63_ns d
;;
let%expect_test "small_diff" =
let test x y =
let diff = small_diff x y in
printf !"small_diff %s %s = %s\n" (to_string x) (to_string y) (Span.to_string diff)
in
let examples =
List.map
~f:(fun (x, y) -> of_string x, of_string y)
[ "12:00", "12:05"; "12:58", "13:02"; "00:52", "23:19"; "00:00", "24:00" ]
in
List.iter examples ~f:(fun (x, y) ->
test x y;
test y x);
[%expect
{|
small_diff 12:00:00.000000000 12:05:00.000000000 = -5m
small_diff 12:05:00.000000000 12:00:00.000000000 = 5m
small_diff 12:58:00.000000000 13:02:00.000000000 = -4m
small_diff 13:02:00.000000000 12:58:00.000000000 = 4m
small_diff 00:52:00.000000000 23:19:00.000000000 = -27m
small_diff 23:19:00.000000000 00:52:00.000000000 = 27m
small_diff 00:00:00.000000000 24:00:00.000000000 = 0s
small_diff 24:00:00.000000000 00:00:00.000000000 = 0s |}]
;;
let gen_incl = Span.gen_incl
let gen_uniform_incl = Span.gen_uniform_incl
let quickcheck_generator = gen_incl start_of_day start_of_next_day
let quickcheck_observer = Span.quickcheck_observer
let quickcheck_shrinker = Quickcheck.Shrinker.empty ()
include (Span : Comparisons.S with type t := t)
let of_span_since_start_of_day = of_span_since_start_of_day_exn
let to_millisec_string = to_millisecond_string
let arg_type = `Use_Time_ns_unix
let now = `Use_Time_ns_unix
let of_ofday_float_round_nearest = `Use_Time_ns_unix
let of_ofday_float_round_nearest_microsecond = `Use_Time_ns_unix
let to_ofday_float_round_nearest = `Use_Time_ns_unix
let to_ofday_float_round_nearest_microsecond = `Use_Time_ns_unix
module Option = struct end
module Zoned = struct end