package core

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file ofday_float.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
open! Import
open Std_internal
open Digit_string_helpers
open! Int.Replace_polymorphic_compare
module Span = Span_float

(* Create an abstract type for Ofday to prevent us from confusing it with
   other floats.
*)
module Stable = struct
  module V1 = struct
    module T : sig
      type underlying = float
      type t = private underlying [@@deriving bin_io, hash, typerep]

      include Comparable.S_common with type t := t
      include Robustly_comparable with type t := t
      include Floatable with type t := t

      val add : t -> Span.t -> t option
      val sub : t -> Span.t -> t option
      val next : t -> t option
      val prev : t -> t option
      val diff : t -> t -> Span.t
      val of_span_since_start_of_day_exn : Span.t -> t
      val of_span_since_start_of_day_unchecked : Span.t -> t
      val span_since_start_of_day_is_valid : Span.t -> bool
      val to_span_since_start_of_day : t -> Span.t
      val start_of_day : t
      val start_of_next_day : t
    end = struct
      (* Number of seconds since midnight. *)
      type underlying = Float.t

      include (
      struct
        include Float

        let sign = sign_exn
      end :
      sig
        type t = underlying [@@deriving bin_io, hash, typerep]

        include Comparable.S_common with type t := t
        include Comparable.With_zero with type t := t
        include Robustly_comparable with type t := t
        include Floatable with type t := t
      end)

      (* IF THIS REPRESENTATION EVER CHANGES, ENSURE THAT EITHER
         (1) all values serialize the same way in both representations, or
         (2) you add a new Time.Ofday version to stable.ml *)

      (* due to precision limitations in float we can't expect better than microsecond
         precision *)
      include Float.Robust_compare.Make (struct
          let robust_comparison_tolerance = 1E-6
        end)

      let to_span_since_start_of_day t = Span.of_sec t

      (* Another reasonable choice would be only allowing Ofday.t to be < 24hr, but this
         choice was made early on and people became used to being able to easily call 24hr
         the end of the day.  It's a bit sad because it shares that moment with the
         beginning of the next day, and round trips oddly if passed through
         Time.to_date_ofday/Time.of_date_ofday.

         Note: [Schedule.t] requires that the end of day be representable, as it's the
         only way to write a schedule in terms of [Ofday.t]s that spans two weekdays. *)
      (* ofday must be >= 0 and <= 24h *)
      let is_valid (t : t) =
        let t = to_span_since_start_of_day t in
        Span.( <= ) Span.zero t && Span.( <= ) t Span.day
      ;;

      let of_span_since_start_of_day_unchecked span = Span.to_sec span

      let span_since_start_of_day_is_valid span =
        is_valid (of_span_since_start_of_day_unchecked span)
      ;;

      let of_span_since_start_of_day_exn span =
        let module C = Float.Class in
        let s = Span.to_sec span in
        match Float.classify s with
        | C.Infinite -> invalid_arg "Ofday.of_span_since_start_of_day_exn: infinite value"
        | C.Nan -> invalid_arg "Ofday.of_span_since_start_of_day_exn: NaN value"
        | C.Normal | C.Subnormal | C.Zero ->
          if not (is_valid s)
          then invalid_argf !"Ofday out of range: %{Span}" span ()
          else s
      ;;

      let start_of_day = 0.
      let start_of_next_day = of_span_since_start_of_day_exn Span.day

      let add (t : t) (span : Span.t) =
        let t = t +. Span.to_sec span in
        if is_valid t then Some t else None
      ;;

      let sub (t : t) (span : Span.t) =
        let t = t -. Span.to_sec span in
        if is_valid t then Some t else None
      ;;

      let next t =
        let candidate = Float.one_ulp `Up t in
        if is_valid candidate then Some candidate else None
      ;;

      let prev t =
        let candidate = Float.one_ulp `Down t in
        if is_valid candidate then Some candidate else None
      ;;

      let diff t1 t2 =
        Span.( - ) (to_span_since_start_of_day t1) (to_span_since_start_of_day t2)
      ;;
    end

    let approximate_end_of_day =
      Option.value_exn (T.sub T.start_of_next_day Span.microsecond)
    ;;

    (* [create] chops off any subsecond part when [sec = 60] to handle leap seconds. In
       particular it's trying to be generous about reading in times on things like fix
       messages that might include an extra unlikely second.

       Other ways of writing a time, like 1000ms, while mathematically valid, don't match
       ways that people actually write times down, so we didn't see the need to support
       them. That is, a clock might legitimately read 23:59:60 (or, with 60 seconds at
       times of day other than 23:59, depending on the time zone), but it doesn't seem
       reasonable for a clock to read "23:59:59 and 1000ms". *)
    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
      T.of_span_since_start_of_day_exn (Span.create ?hr ?min ?sec ?ms ?us ?ns ())
    ;;

    let to_parts t = Span.to_parts (T.to_span_since_start_of_day t)

    let to_string_gen ~drop_ms ~drop_us ~trim t =
      let ( / ) = Int63.( / ) in
      let ( ! ) = Int63.of_int in
      let ( mod ) = Int63.rem in
      let i = Int63.to_int_exn in
      assert (if drop_ms then drop_us else true);
      let float_sec = Span.to_sec (T.to_span_since_start_of_day t) in
      let us = Float.int63_round_nearest_exn (float_sec *. 1e6) in
      let ms, us = us / !1000, us mod !1000 |> i in
      let sec, ms = ms / !1000, ms mod !1000 |> i in
      let min, sec = sec / !60, sec mod !60 |> i in
      let hr, min = min / !60, min mod !60 |> i in
      let hr = i hr in
      let dont_print_us = drop_us || (trim && us = 0) in
      let dont_print_ms = drop_ms || (trim && ms = 0 && dont_print_us) in
      let dont_print_s = trim && sec = 0 && dont_print_ms in
      let len =
        if dont_print_s
        then 5
        else if dont_print_ms
        then 8
        else if dont_print_us
        then 12
        else 15
      in
      let buf = Bytes.create len in
      write_2_digit_int buf ~pos:0 hr;
      Bytes.set buf 2 ':';
      write_2_digit_int buf ~pos:3 min;
      if dont_print_s
      then ()
      else (
        Bytes.set buf 5 ':';
        write_2_digit_int buf ~pos:6 sec;
        if dont_print_ms
        then ()
        else (
          Bytes.set buf 8 '.';
          write_3_digit_int buf ~pos:9 ms;
          if dont_print_us then () else write_3_digit_int buf ~pos:12 us));
      Bytes.unsafe_to_string ~no_mutation_while_string_reachable:buf
    ;;

    let to_string_trimmed t = to_string_gen ~drop_ms:false ~drop_us:false ~trim:true t
    let to_sec_string t = to_string_gen ~drop_ms:true ~drop_us:true ~trim:false t
    let to_millisecond_string t = to_string_gen ~drop_ms:false ~drop_us:true ~trim:false t

    let small_diff =
      let hour = 3600. in
      fun ofday1 ofday2 ->
        let ofday1 = Span.to_sec (T.to_span_since_start_of_day ofday1) in
        let ofday2 = Span.to_sec (T.to_span_since_start_of_day ofday2) in
        let diff = ofday1 -. ofday2 in
        (*  d1 is in (-hour; hour) *)
        let d1 = Float.mod_float diff hour in
        (*  d2 is in (0;hour) *)
        let d2 = Float.mod_float (d1 +. hour) hour in
        let d = if Float.( > ) d2 (hour /. 2.) then d2 -. hour else d2 in
        Span.of_sec d
    ;;

    include T

    let to_string t = to_string_gen ~drop_ms:false ~drop_us:false ~trim:false t

    include Pretty_printer.Register (struct
        type nonrec t = t

        let to_string = to_string
        let module_name = "Core.Time.Ofday"
      end)

    let create_from_parsed string ~hr ~min ~sec ~subsec_pos ~subsec_len =
      let subsec =
        if Int.equal subsec_len 0
        then 0.
        else Float.of_string (String.sub string ~pos:subsec_pos ~len:subsec_len)
      in
      Float.of_int ((hr * 3600) + (min * 60) + sec) +. subsec
      |> Span.of_sec
      |> T.of_span_since_start_of_day_exn
    ;;

    let of_string s = Ofday_helpers.parse s ~f:create_from_parsed

    let t_of_sexp sexp =
      match sexp with
      | Sexp.Atom s ->
        (try of_string s with
         | Invalid_argument s -> of_sexp_error ("Ofday.t_of_sexp: " ^ s) sexp)
      | _ -> of_sexp_error "Ofday.t_of_sexp" sexp
    ;;

    let t_sexp_grammar = Sexplib.Sexp_grammar.coerce String.t_sexp_grammar
    let sexp_of_t span = Sexp.Atom (to_string span)

    let of_string_iso8601_extended ?pos ?len str =
      try Ofday_helpers.parse_iso8601_extended ?pos ?len str ~f:create_from_parsed with
      | exn ->
        invalid_argf
          "Ofday.of_string_iso8601_extended(%s): %s"
          (String.subo str ?pos ?len)
          (Exn.to_string exn)
          ()
    ;;
  end
end

include Stable.V1

let gen_incl lo hi =
  Span.gen_incl (to_span_since_start_of_day lo) (to_span_since_start_of_day hi)
  |> Quickcheck.Generator.map ~f:of_span_since_start_of_day_exn
;;

let gen_uniform_incl lo hi =
  Span.gen_uniform_incl (to_span_since_start_of_day lo) (to_span_since_start_of_day hi)
  |> Quickcheck.Generator.map ~f:of_span_since_start_of_day_exn
;;

let quickcheck_generator = gen_incl start_of_day start_of_next_day

let quickcheck_observer =
  Quickcheck.Observer.unmap Span.quickcheck_observer ~f:to_span_since_start_of_day
;;

let quickcheck_shrinker = Quickcheck.Shrinker.empty ()

include Hashable.Make_binable (struct
    type nonrec t = t [@@deriving bin_io, compare, hash, sexp_of]

    (* Previous versions rendered hash-based containers using float serialization rather
       than time serialization, so when reading hash-based containers in we accept either
       serialization. *)
    let t_of_sexp sexp =
      match Float.t_of_sexp sexp with
      | float -> of_float float
      | exception _ -> t_of_sexp sexp
    ;;
  end)

module C = struct
  type t = T.t [@@deriving bin_io]
  type comparator_witness = T.comparator_witness

  let comparator = T.comparator
  let compare = T.comparator.compare

  (* In 108.06a and earlier, ofdays in sexps of Maps and Sets were raw floats.  From
     108.07 through 109.13, the output format remained raw as before, but both the raw and
     pretty format were accepted as input.  From 109.14 on, the output format was changed
     from raw to pretty, while continuing to accept both formats.  Once we believe most
     programs are beyond 109.14, we will switch the input format to no longer accept
     raw. *)
  let sexp_of_t = sexp_of_t

  let t_of_sexp sexp =
    match Option.try_with (fun () -> T.of_float (Float.t_of_sexp sexp)) with
    | Some t -> t
    | None -> t_of_sexp sexp
  ;;
end

module Map = Map.Make_binable_using_comparator (C)
module Set = Set.Make_binable_using_comparator (C)
include Comparable.Validate (C)

let of_span_since_start_of_day = of_span_since_start_of_day_exn
let to_millisec_string = to_millisecond_string
OCaml

Innovation. Community. Security.