package timedesc

  1. Overview
  2. Docs

Source file printers.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
open Date_time_utils

let frac_s_1_divisor = Span.ns_count_in_s / 10

let frac_s_2_divisor = frac_s_1_divisor / 10

let frac_s_3_divisor = frac_s_2_divisor / 10

let frac_s_4_divisor = frac_s_3_divisor / 10

let frac_s_5_divisor = frac_s_4_divisor / 10

let frac_s_6_divisor = frac_s_5_divisor / 10

let frac_s_7_divisor = frac_s_6_divisor / 10

let frac_s_8_divisor = frac_s_7_divisor / 10

let frac_s_9_divisor = frac_s_8_divisor / 10

let get_divisor frac_s =
  match frac_s with
  | 1 -> frac_s_1_divisor
  | 2 -> frac_s_2_divisor
  | 3 -> frac_s_3_divisor
  | 4 -> frac_s_4_divisor
  | 5 -> frac_s_5_divisor
  | 6 -> frac_s_6_divisor
  | 7 -> frac_s_7_divisor
  | 8 -> frac_s_8_divisor
  | 9 -> frac_s_9_divisor
  | _ -> failwith "Unexpected case"

let deduce_smallest_lossless_frac_s ~ns =
  if ns = 0 then 0
  else if ns mod frac_s_1_divisor = 0 then 1
  else if ns mod frac_s_2_divisor = 0 then 2
  else if ns mod frac_s_3_divisor = 0 then 3
  else if ns mod frac_s_4_divisor = 0 then 4
  else if ns mod frac_s_5_divisor = 0 then 5
  else if ns mod frac_s_6_divisor = 0 then 6
  else if ns mod frac_s_7_divisor = 0 then 7
  else if ns mod frac_s_8_divisor = 0 then 8
  else 9

type case =
  | Upper
  | Lower

type size_and_casing =
  | Abbreviated of case * case * case
  | Full of case * case

let map_char_to_case (case : case) (c : char) =
  match case with
  | Upper -> Char.uppercase_ascii c
  | Lower -> Char.lowercase_ascii c

let map_string_to_size_and_casing (x : size_and_casing) (s : string) : string =
  match x with
  | Abbreviated (case1, case2, case3) ->
      let c1 = map_char_to_case case1 s.[0] in
      let c2 = map_char_to_case case2 s.[1] in
      let c3 = map_char_to_case case3 s.[2] in
      Printf.sprintf "%c%c%c" c1 c2 c3
  | Full (case1, case2) ->
      String.mapi
        (fun i c ->
          if i = 0 then map_char_to_case case1 c else map_char_to_case case2 c)
        s

let pad_int (c : char option) (x : int) : string =
  match c with
  | None -> string_of_int x
  | Some c -> if x < 10 then Printf.sprintf "%c%d" c x else string_of_int x

exception Date_time_cannot_deduce_offset_from_utc of Date_time.t

let string_of_s_frac ~sep ~frac_s ~ns =
  let ns = ns mod Span.ns_count_in_s in
  assert (0 <= frac_s && frac_s <= 9);
  if frac_s = 0 then ""
  else
    let divisor = get_divisor frac_s in
    Printf.sprintf "%c%0*d" sep frac_s (ns / divisor)

module Format_string_parsers = struct
  open MParser
  open Parser_components

  let case : (case, unit) t =
    attempt (char 'x' >> return Lower) <|> (char 'X' >> return Upper)

  let size_and_casing : (size_and_casing, unit) t =
    case
    >>= fun c1 ->
    case
    >>= fun c2 ->
    attempt (char '*' >> return (Full (c1, c2)))
    <|> (case >>= fun c3 -> return (Abbreviated (c1, c2, c3)))

  let padding : (char option, unit) t =
    attempt
      (satisfy (fun _ -> true)
      >>= fun padding -> char 'X' >> return (Some padding))
    <|> (char 'X' >> return None)

  let date_time_inner (date_time : Date_time.t) : (string, unit) t =
    let single_offset =
      match date_time.offset_from_utc with
      | `Single offset -> Some (Span.For_human'.view offset)
      | `Ambiguous _ -> None
    in
    let Date.Ymd_date.{ year; month; day } = Date_time.ymd_date date_time in
    let weekday = Date_time.weekday date_time in
    let Time.{ hour; minute; second; ns } = Date_time.time_view date_time in
    let smallest_lossless_frac_s = deduce_smallest_lossless_frac_s ~ns in
    choice
      [
        attempt (string "year") >> return (Printf.sprintf "%04d" year);
        attempt (string "mon:")
        >> (attempt size_and_casing
           >>= (fun x ->
                 return
                   (map_string_to_size_and_casing x
                      (CCOpt.get_exn_or "Expected valid month"
                      @@ full_string_of_month month)))
           <|> (padding >>= fun padding -> return (pad_int padding month)));
        (attempt (string "day:")
        >> padding
        >>= fun padding -> return (pad_int padding day));
        (attempt (string "wday:")
        >> size_and_casing
        >>= fun x ->
        return
          (map_string_to_size_and_casing x (full_string_of_weekday weekday)));
        (attempt (string "hour:")
        >> padding
        >>= fun padding -> return (pad_int padding hour));
        (attempt (string "12hour:")
        >> padding
        >>= fun padding ->
        let hour = if hour = 0 then 12 else hour mod 12 in
        return (pad_int padding hour));
        (attempt (string "min:")
        >> padding
        >>= fun padding -> return (pad_int padding minute));
        (attempt (string "sec:")
        >> padding
        >>= fun padding -> return (pad_int padding second));
        attempt (string "ns") >> return (string_of_int ns);
        (attempt (string "sec-frac:")
        >> any_char
        >>= fun sep ->
        opt smallest_lossless_frac_s nat_zero
        >>= fun frac_s ->
        if frac_s > 9 then
          fail "Number of digits after decimal separator cannot be > 9"
        else return (string_of_s_frac ~sep ~frac_s ~ns));
        (attempt (string "tzoff-sign")
        >>= fun _ ->
        match single_offset with
        | None -> raise (Date_time_cannot_deduce_offset_from_utc date_time)
        | Some offset -> (
            match offset.sign with `Pos -> return "+" | `Neg -> return "-"));
        (attempt (string "tzoff-hour:")
        >> padding
        >>= fun padding ->
        match single_offset with
        | None -> raise (Date_time_cannot_deduce_offset_from_utc date_time)
        | Some offset -> return (pad_int padding Span.For_human'.(offset.hours))
        );
        (attempt (string "tzoff-min:")
        >> padding
        >>= fun padding ->
        match single_offset with
        | None -> raise (Date_time_cannot_deduce_offset_from_utc date_time)
        | Some offset ->
            return (pad_int padding Span.For_human'.(offset.minutes)));
        (attempt (string "tzoff-sec:")
        >> padding
        >>= fun padding ->
        match single_offset with
        | None -> raise (Date_time_cannot_deduce_offset_from_utc date_time)
        | Some offset ->
            return (pad_int padding Span.For_human'.(offset.seconds)));
        (* string "unix"
         * >> return (Int64.to_string (Time.Date_time'.to_timestamp date_time)); *)
      ]

  let span_for_human_inner (view : Span.For_human'.view) : (string, unit) t =
    let smallest_lossless_frac_s =
      deduce_smallest_lossless_frac_s ~ns:view.ns
    in
    let string_of_number_and_unit ~empty_on_zero ~padding (x : int)
        (unit_str : string) =
      if empty_on_zero && x = 0 then ""
      else Printf.sprintf "%s%s" (pad_int padding x) unit_str
    in
    let single ~empty_on_zero ~handle_padding ~name ~number =
      attempt
        (string name >> if empty_on_zero then string "-nz:" else string ":")
      >> (if handle_padding then padding else return None)
      >>= fun padding ->
      non_curly_bracket_string
      >>= fun unit_str ->
      return (string_of_number_and_unit ~empty_on_zero ~padding number unit_str)
    in
    let sec_frac ~empty_on_zero ~ns =
      attempt
        (string "sec-frac"
        >> if empty_on_zero then string "-nz:" else string ":")
      >> any_char
      >>= fun sep ->
      opt smallest_lossless_frac_s nat_zero
      >>= fun frac_s ->
      if frac_s > 9 then
        fail "Number of digits after decimal separator cannot be > 9"
      else
        char 'X'
        >> non_curly_bracket_string
        >>= fun unit_str ->
        if empty_on_zero && ns = 0 then return ""
        else
          return
            (Printf.sprintf "%s%s" (string_of_s_frac ~sep ~frac_s ~ns) unit_str)
    in
    choice
      [
        single ~empty_on_zero:false ~handle_padding:false ~name:"days"
          ~number:view.days;
        single ~empty_on_zero:true ~handle_padding:false ~name:"days"
          ~number:view.days;
        single ~empty_on_zero:false ~handle_padding:true ~name:"hours"
          ~number:view.hours;
        single ~empty_on_zero:true ~handle_padding:true ~name:"hours"
          ~number:view.hours;
        single ~empty_on_zero:false ~handle_padding:true ~name:"mins"
          ~number:view.minutes;
        single ~empty_on_zero:true ~handle_padding:true ~name:"mins"
          ~number:view.minutes;
        single ~empty_on_zero:false ~handle_padding:true ~name:"secs"
          ~number:view.seconds;
        single ~empty_on_zero:true ~handle_padding:true ~name:"secs"
          ~number:view.seconds;
        single ~empty_on_zero:false ~handle_padding:true ~name:"ns"
          ~number:view.ns;
        single ~empty_on_zero:true ~handle_padding:true ~name:"ns"
          ~number:view.ns;
        sec_frac ~empty_on_zero:false ~ns:view.ns;
        sec_frac ~empty_on_zero:true ~ns:view.ns;
      ]
end

let default_date_time_format_string =
  "{year} {mon:Xxx} {day:0X} {hour:0X}:{min:0X}:{sec:0X}{sec-frac:.} \
   {tzoff-sign}{tzoff-hour:0X}:{tzoff-min:0X}:{tzoff-sec:0X}"

let default_interval_format_string =
  "[{syear} {smon:Xxx} {sday:0X} {shour:0X}:{smin:0X}:{ssec:0X}{ssec-frac:.} \
   {stzoff-sign}{stzoff-hour:0X}:{stzoff-min:0X}:{stzoff-sec:0X}, {eyear} \
   {emon:Xxx} {eday:0X} {ehour:0X}:{emin:0X}:{esec:0X}{esec-frac:.} \
   {etzoff-sign}{etzoff-hour:0X}:{etzoff-min:0X}:{etzoff-sec:0X})"

let default_span_for_human_format_string =
  "{days-nz: days }{hours-nz:X hours }{mins-nz:X mins }{secs:X}{sec-frac:.X} \
   secs"

exception Invalid_format_string of string

let invalid_format_string s = raise (Invalid_format_string s)

let pp_date_time ?(format : string = default_date_time_format_string) ()
    (formatter : Format.formatter) (x : Date_time.t) : unit =
  let open MParser in
  let open Parser_components in
  let single formatter (date_time : Date_time.t) : (unit, unit) t =
    choice
      [
        attempt (string "{{" |>> fun _ -> Fmt.pf formatter "{");
        (attempt (char '{')
        >> (Format_string_parsers.date_time_inner date_time << char '}')
        |>> fun s -> Fmt.pf formatter "%s" s);
        (many1_satisfy (function '{' -> false | _ -> true)
        |>> fun s -> Fmt.pf formatter "%s" s);
      ]
  in
  let p formatter (date_time : Date_time.t) : (unit, unit) t =
    many (single formatter date_time) >> return ()
  in
  match
    result_of_mparser_result @@ parse_string (p formatter x << eof) format ()
  with
  | Error msg -> invalid_format_string msg
  | Ok () -> ()

let string_of_date_time ?format (x : Date_time.t) : string option =
  try Some (Fmt.str "%a" (pp_date_time ?format ()) x)
  with Date_time_cannot_deduce_offset_from_utc _ -> None

let pp_timestamp ?(display_using_tz = Time_zone.utc) ?format () formatter time =
  match Date_time.of_timestamp ~tz_of_date_time:display_using_tz time with
  | None -> invalid_arg "Invalid unix second"
  | Some dt -> Fmt.pf formatter "%a" (pp_date_time ?format ()) dt

let string_of_timestamp ?display_using_tz ?format (time : Span.t) : string =
  Fmt.str "%a" (pp_timestamp ?display_using_tz ?format ()) time

let pp_interval ?(display_using_tz = Time_zone.utc)
    ?(format = default_interval_format_string) () formatter
    ((s, e) : Date_time.interval) : unit =
  let open MParser in
  let open Parser_components in
  let single (start_date_time : Date_time.t) (end_date_time : Date_time.t) :
      (unit, unit) t =
    choice
      [
        attempt (string "{{" |>> fun _ -> Fmt.pf formatter "{");
        (attempt (char '{')
        >> (attempt (char 's' >> return start_date_time)
           <|> (char 'e' >> return end_date_time))
        >>= fun date_time ->
        Format_string_parsers.date_time_inner date_time
        << char '}'
        |>> fun s -> Fmt.pf formatter "%s" s);
        (many1_satisfy (function '{' -> false | _ -> true)
        |>> fun s -> Fmt.pf formatter "%s" s);
      ]
  in
  let p (start_date_time : Date_time.t) (end_date_time : Date_time.t) :
      (unit, unit) t =
    many (single start_date_time end_date_time) >> return ()
  in
  match Date_time.of_timestamp ~tz_of_date_time:display_using_tz s with
  | None -> invalid_arg "Invalid start unix time"
  | Some s -> (
      match Date_time.of_timestamp ~tz_of_date_time:display_using_tz e with
      | None -> invalid_arg "Invalid end unix time"
      | Some e -> (
          match
            result_of_mparser_result
            @@ parse_string
                 (p s e
                 >>= fun s ->
                 get_pos
                 >>= fun pos ->
                 attempt eof
                 >> return s
                 <|> fail
                       (Printf.sprintf "Expected EOI, pos: %s"
                          (string_of_pos pos)))
                 format ()
          with
          | Error msg -> invalid_format_string msg
          | Ok () -> ()))

let string_of_interval ?display_using_tz ?format (interval : Date_time.interval)
    : string =
  Fmt.str "%a" (pp_interval ?display_using_tz ?format ()) interval

let pp_intervals ?display_using_tz ?format ?(sep = Fmt.cut) () formatter
    intervals =
  Fmt.seq ~sep (pp_interval ?display_using_tz ?format ()) formatter intervals

let pp_span formatter (x : Span.t) : unit =
  let s, ns = Span.to_s_ns x in
  Fmt.pf formatter "%Ld s + %d ns" s ns

let string_of_span (x : Span.t) : string = Fmt.str "%a" pp_span x

let pp_span_for_human ?(format : string = default_span_for_human_format_string)
    () formatter (x : Span.t) : unit =
  let open MParser in
  let open Parser_components in
  let single formatter (view : Span.For_human'.view) : (unit, unit) t =
    choice
      [
        attempt (string "{{" |>> fun _ -> Fmt.pf formatter "{");
        (attempt (char '{')
        >> (Format_string_parsers.span_for_human_inner view << char '}')
        |>> fun s -> Fmt.pf formatter "%s" s);
        (many1_satisfy (function '{' -> false | _ -> true)
        |>> fun s -> Fmt.pf formatter "%s" s);
      ]
  in
  let p formatter (view : Span.For_human'.view) : (unit, unit) t =
    many (single formatter view) >> return ()
  in
  match
    result_of_mparser_result
    @@ parse_string (p formatter (Span.For_human'.view x) << eof) format ()
  with
  | Error msg -> invalid_format_string msg
  | Ok () -> ()

let string_of_span_for_human ?format (x : Span.t) : string =
  Fmt.str "%a" (pp_span_for_human ?format ()) x

let wrap_to_sexp_into_pp_sexp (f : 'a -> CCSexp.t) :
    Format.formatter -> 'a -> unit =
 fun formatter x -> CCSexp.pp formatter (f x)
OCaml

Innovation. Community. Security.