123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325open!ImportmoduleSpan=Span_nstypeunderlying=Int63.ttypet=Span.t(* since wall-clock midnight *)[@@derivingbin_io,compare,hash,typerep]include(Span:Robustly_comparable.Swithtypet:=t)letto_partst=Span.to_partstletstart_of_day:t=Span.zeroletstart_of_next_day:t=Span.dayletapproximate_end_of_day=Span.(-)start_of_next_daySpan.nanosecondletto_span_since_start_of_dayt=tlet[@cold]input_out_of_boundsspan=raise_s[%message"Time_ns.Ofday.of_span_since_start_of_day_exn: input out of bounds"~_:(span:Span.t)];;let[@inlinealways]is_invalidspan=(* Why we use [Span.( > )] rather than [( >= )] below:
We allow to represent the end-of-day sentinel value ([24.000000000h]), which is not
itself a valid clock face time. However, since valid clock face times readily
round up to it, it's better to allow it to be represented. *)Span.(<)spanstart_of_day||Span.(>)spanstart_of_next_day;;letspan_since_start_of_day_is_validspan=not(is_invalidspan)letof_span_since_start_of_day_uncheckedspan=spanletof_span_since_start_of_day_exnspan=ifis_invalidspantheninput_out_of_boundsspanelsespan;;letof_span_since_start_of_day_optspan=ifis_invalidspanthenNoneelseSomespanletadd_exntspan=of_span_since_start_of_day_exn(Span.(+)tspan)letsub_exntspan=of_span_since_start_of_day_exn(Span.(-)tspan)letaddtspan=of_span_since_start_of_day_opt(Span.(+)tspan)letsubtspan=of_span_since_start_of_day_opt(Span.(-)tspan)letnextt=of_span_since_start_of_day_opt(Span.nextt)letprevt=of_span_since_start_of_day_opt(Span.prevt)letdifftu=Span.(-)tuletcreate?hr?min?sec?ms?us?ns()=(* Similar to [Time.Ofday.create], if we detect a leap second we strip off all
sub-second elements so that HH:MM:60.XXXXXXXXX is all mapped to HH:MM:60. *)letms,us,ns=matchsecwith|Some60->Some0,Some0,Some0|_->ms,us,nsinof_span_since_start_of_day_exn(Span.create?hr?min?sec?ms?us?ns());;moduleStable=structmoduleOption=structendmoduleZoned=structendmoduleV1=structinclude(Span.Stable.V2:Comparator.Swithtypet=tandtypecomparator_witness=Span.Stable.V2.comparator_witness)typenonrect=t[@@derivingcompare,bin_io]letto_string_with_unit=let(/)=Int63.(/)inlet(mod)=Int63.reminlet(!)=Int63.of_intinleti=Int63.to_int_exninfunt~unit->ifSpan.(<)tstart_of_day||Span.(<)start_of_next_daytthen"Incorrect day"else(letsixty=!60inletthousand=!1000inletns=Span.to_int63_nstinletus=ns/thousandinletns=nsmodthousand|>iinletms=us/thousandinletus=usmodthousand|>iinlets=ms/thousandinletms=msmodthousand|>iinletm=s/sixtyinlets=smodsixty|>iinleth=m/sixty|>iinletm=mmodsixty|>iinletunit=matchunitwith|(`Nanosecond|`Microsecond|`Millisecond|`Second)asunit->unit|`Minute_or_less->ifns<>0then`Nanosecondelseifus<>0then`Microsecondelseifms<>0then`Millisecondelseifs<>0then`Secondelse`Minuteinletlen=matchunitwith|`Minute->5|`Second->8|`Millisecond->12|`Microsecond->15|`Nanosecond->18inletstr=Bytes.createleninDigit_string_helpers.write_2_digit_intstr~pos:0h;Bytes.setstr2':';Digit_string_helpers.write_2_digit_intstr~pos:3m;(matchunitwith|`Minute->()|(`Second|`Millisecond|`Microsecond|`Nanosecond)asunit->Bytes.setstr5':';Digit_string_helpers.write_2_digit_intstr~pos:6s;(matchunitwith|`Second->()|(`Millisecond|`Microsecond|`Nanosecond)asunit->Bytes.setstr8'.';Digit_string_helpers.write_3_digit_intstr~pos:9ms;(matchunitwith|`Millisecond->()|(`Microsecond|`Nanosecond)asunit->Digit_string_helpers.write_3_digit_intstr~pos:12us;(matchunitwith|`Microsecond->()|`Nanosecond->Digit_string_helpers.write_3_digit_intstr~pos:15ns))));Bytes.unsafe_to_string~no_mutation_while_string_reachable:str);;letparse_nanosecondsstring~pos~until=letopenInt.Oinletdigits=ref0inletnum_digits=ref0inletpos=refposin(* read up to 10 digits; store the first 9, use the 10th to round *)while!pos<until&&!num_digits<10doletc=string.[!pos]inifChar.is_digitcthen(incrnum_digits;if!num_digits<10thendigits:=(!digits*10)+Char.get_digit_exncelseifChar.get_digit_exnc>=5thenincrdigitselse());incrposdone;(* if there are missing digits, add zeroes *)if!num_digits<9thendigits:=!digits*Int.pow10(9-!num_digits);!digits;;letcreate_from_parsedstring~hr~min~sec~subsec_pos~subsec_len=letnanoseconds=ifInt.equalsubsec_len0then0elseparse_nanosecondsstring~pos:(subsec_pos+1)~until:(subsec_pos+subsec_len)inSpan.of_int63_ns(Int63.of_intnanoseconds)|>Span.(+)(Span.scale_intSpan.secondsec)|>Span.(+)(Span.scale_intSpan.minutemin)|>Span.(+)(Span.scale_intSpan.hourhr)|>of_span_since_start_of_day_exn;;letof_stringstring=Ofday_helpers.parsestring~f:create_from_parsedlett_of_sexpsexp:t=matchsexpwith|Sexp.List_->of_sexp_error"expected an atom"sexp|Sexp.Atoms->(tryof_stringswith|exn->of_sexp_error_exnexnsexp);;lett_sexp_grammar=String.t_sexp_grammarletto_string(t:t)=to_string_with_unitt~unit:`Nanosecondletsexp_of_t(t:t)=Sexp.Atom(to_stringt)letto_int63t=Span_ns.Stable.V2.to_int63tletof_int63_exnt=of_span_since_start_of_day_exn(Span_ns.Stable.V2.of_int63_exnt)endendinclude(Stable.V1:Comparator.Swithtypet:=tandtypecomparator_witness=Stable.V1.comparator_witness)includeIdentifiable.Make_using_comparator(structtypet=Stable.V1.t[@@derivingbin_io,compare,sexp]include(Stable.V1:Comparator.Swithtypet:=tandtypecomparator_witness=Stable.V1.comparator_witness)include(Stable.V1:Stringable.Swithtypet:=t)letmodule_name="Core.Time_ns.Ofday"lethash=Span.hashlethash_fold_t=Span.hash_fold_tend)lett_sexp_grammar=Sexplib.Sexp_grammar.coerceStable.V1.t_sexp_grammarletto_microsecond_stringt=Stable.V1.to_string_with_unitt~unit:`Microsecondletto_millisecond_stringt=Stable.V1.to_string_with_unitt~unit:`Millisecondletto_sec_stringt=Stable.V1.to_string_with_unitt~unit:`Secondletto_string_trimmedt=Stable.V1.to_string_with_unitt~unit:`Minute_or_lessletof_string_iso8601_extended?pos?lenstr=tryOfday_helpers.parse_iso8601_extended?pos?lenstr~f:Stable.V1.create_from_parsedwith|exn->raise_s[%message"Time_ns.Ofday.of_string_iso8601_extended: cannot parse string"~_:(String.subostr?pos?len:string)~_:(exn:exn)];;letevery=letrecevery_valid_ofday_spanspan~start~stop~acc=(* Assumes [span], [start], and [stop] are valid ofdays. Assumes [start < stop].
Assumes [span > 0]. *)letacc=start::accinletstart=Span.(+)startspaninifSpan.(>)startstop(* cannot overflow *)thenList.revaccelseevery_valid_ofday_spanspan~start~stop~accin(* internal [every] named to show up in stack traces *)leteveryspan~start~stop=ifSpan.(>)startstopthenOr_error.error_s[%message"[Time_ns.Ofday.every] called with [start] > [stop]"(start:t)(stop:t)]elseifSpan.(<=)spanSpan.zerothenOr_error.error_s[%message"[Time_ns.Ofday.every] called with negative span"~_:(span:Span.t)]elseifis_invalidspanthenOk[start]elseOk(every_valid_ofday_spanspan~start~stop~acc:[])inevery;;letsmall_diff=lethour=Span.to_int63_nsSpan.hourinfunofday1ofday2->letopenInt63.Oinletofday1=Span.to_int63_ns(to_span_since_start_of_dayofday1)inletofday2=Span.to_int63_ns(to_span_since_start_of_dayofday2)inletdiff=ofday1-ofday2in(* d1 is in (-hour; hour) *)letd1=Int63.remdiffhourin(* d2 is in (0;hour) *)letd2=Int63.rem(d1+hour)hourinletd=ifd2>hour/Int63.of_int2thend2-hourelsed2inSpan.of_int63_nsd;;let%expect_test"small_diff"=lettestxy=letdiff=small_diffxyinprintf!"small_diff %s %s = %s\n"(to_stringx)(to_stringy)(Span.to_stringdiff)inletexamples=List.map~f:(fun(x,y)->of_stringx,of_stringy)["12:00","12:05";"12:58","13:02";"00:52","23:19";"00:00","24:00"]inList.iterexamples~f:(fun(x,y)->testxy;testyx);[%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 |}];;letgen_incl=Span.gen_inclletgen_uniform_incl=Span.gen_uniform_inclletquickcheck_generator=gen_inclstart_of_daystart_of_next_dayletquickcheck_observer=Span.quickcheck_observerletquickcheck_shrinker=Quickcheck.Shrinker.empty()include(Span:Comparisons.Swithtypet:=t)(* deprecated bindings *)letof_span_since_start_of_day=of_span_since_start_of_day_exnletto_millisec_string=to_millisecond_stringletarg_type=`Use_Time_ns_unixletnow=`Use_Time_ns_unixletof_ofday_float_round_nearest=`Use_Time_ns_unixletof_ofday_float_round_nearest_microsecond=`Use_Time_ns_unixletto_ofday_float_round_nearest=`Use_Time_ns_unixletto_ofday_float_round_nearest_microsecond=`Use_Time_ns_unixmoduleOption=structendmoduleZoned=structend