123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314open!ImportopenStd_internalopenDigit_string_helpersopen!Int.Replace_polymorphic_comparemoduleSpan=Span_float(* Create an abstract type for Ofday to prevent us from confusing it with
other floats.
*)moduleStable=structmoduleV1=structmoduleT:sigtypeunderlying=floattypet=privateunderlying[@@derivingbin_io,hash,typerep]includeComparable.S_commonwithtypet:=tincludeRobustly_comparablewithtypet:=tincludeFloatablewithtypet:=tvaladd:t->Span.t->toptionvalsub:t->Span.t->toptionvalnext:t->toptionvalprev:t->toptionvaldiff:t->t->Span.tvalof_span_since_start_of_day_exn:Span.t->tvalof_span_since_start_of_day_unchecked:Span.t->tvalspan_since_start_of_day_is_valid:Span.t->boolvalto_span_since_start_of_day:t->Span.tvalstart_of_day:tvalstart_of_next_day:tend=struct(* Number of seconds since midnight. *)typeunderlying=Float.tinclude(structincludeFloatletsign=sign_exnend:sigtypet=underlying[@@derivingbin_io,hash,typerep]includeComparable.S_commonwithtypet:=tincludeComparable.With_zerowithtypet:=tincludeRobustly_comparablewithtypet:=tincludeFloatablewithtypet:=tend)(* 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 *)includeFloat.Robust_compare.Make(structletrobust_comparison_tolerance=1E-6end)letto_span_since_start_of_dayt=Span.of_sect(* 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 *)letis_valid(t:t)=lett=to_span_since_start_of_daytinSpan.(<=)Span.zerot&&Span.(<=)tSpan.day;;letof_span_since_start_of_day_uncheckedspan=Span.to_secspanletspan_since_start_of_day_is_validspan=is_valid(of_span_since_start_of_day_uncheckedspan);;letof_span_since_start_of_day_exnspan=letmoduleC=Float.Classinlets=Span.to_secspaninmatchFloat.classifyswith|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->ifnot(is_valids)theninvalid_argf!"Ofday out of range: %{Span}"span()elses;;letstart_of_day=0.letstart_of_next_day=of_span_since_start_of_day_exnSpan.dayletadd(t:t)(span:Span.t)=lett=t+.Span.to_secspaninifis_validtthenSometelseNone;;letsub(t:t)(span:Span.t)=lett=t-.Span.to_secspaninifis_validtthenSometelseNone;;letnextt=letcandidate=Float.one_ulp`Uptinifis_validcandidatethenSomecandidateelseNone;;letprevt=letcandidate=Float.one_ulp`Downtinifis_validcandidatethenSomecandidateelseNone;;letdifft1t2=Span.(-)(to_span_since_start_of_dayt1)(to_span_since_start_of_dayt2);;endletapproximate_end_of_day=Option.value_exn(T.subT.start_of_next_daySpan.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". *)letcreate?hr?min?sec?ms?us?ns()=letms,us,ns=matchsecwith|Some60->Some0,Some0,Some0|_->ms,us,nsinT.of_span_since_start_of_day_exn(Span.create?hr?min?sec?ms?us?ns());;letto_partst=Span.to_parts(T.to_span_since_start_of_dayt)letto_string_gen~drop_ms~drop_us~trimt=let(/)=Int63.(/)inlet(!)=Int63.of_intinlet(mod)=Int63.reminleti=Int63.to_int_exninassert(ifdrop_msthendrop_uselsetrue);letfloat_sec=Span.to_sec(T.to_span_since_start_of_dayt)inletus=Float.int63_round_nearest_exn(float_sec*.1e6)inletms,us=us/!1000,usmod!1000|>iinletsec,ms=ms/!1000,msmod!1000|>iinletmin,sec=sec/!60,secmod!60|>iinlethr,min=min/!60,minmod!60|>iinlethr=ihrinletdont_print_us=drop_us||(trim&&us=0)inletdont_print_ms=drop_ms||(trim&&ms=0&&dont_print_us)inletdont_print_s=trim&&sec=0&&dont_print_msinletlen=ifdont_print_sthen5elseifdont_print_msthen8elseifdont_print_usthen12else15inletbuf=Bytes.createleninwrite_2_digit_intbuf~pos:0hr;Bytes.setbuf2':';write_2_digit_intbuf~pos:3min;ifdont_print_sthen()else(Bytes.setbuf5':';write_2_digit_intbuf~pos:6sec;ifdont_print_msthen()else(Bytes.setbuf8'.';write_3_digit_intbuf~pos:9ms;ifdont_print_usthen()elsewrite_3_digit_intbuf~pos:12us));Bytes.unsafe_to_string~no_mutation_while_string_reachable:buf;;letto_string_trimmedt=to_string_gen~drop_ms:false~drop_us:false~trim:truetletto_sec_stringt=to_string_gen~drop_ms:true~drop_us:true~trim:falsetletto_millisecond_stringt=to_string_gen~drop_ms:false~drop_us:true~trim:falsetletsmall_diff=lethour=3600.infunofday1ofday2->letofday1=Span.to_sec(T.to_span_since_start_of_dayofday1)inletofday2=Span.to_sec(T.to_span_since_start_of_dayofday2)inletdiff=ofday1-.ofday2in(* d1 is in (-hour; hour) *)letd1=Float.mod_floatdiffhourin(* d2 is in (0;hour) *)letd2=Float.mod_float(d1+.hour)hourinletd=ifFloat.(>)d2(hour/.2.)thend2-.hourelsed2inSpan.of_secd;;includeTletto_stringt=to_string_gen~drop_ms:false~drop_us:false~trim:falsetincludePretty_printer.Register(structtypenonrect=tletto_string=to_stringletmodule_name="Core.Time.Ofday"end)letcreate_from_parsedstring~hr~min~sec~subsec_pos~subsec_len=letsubsec=ifInt.equalsubsec_len0then0.elseFloat.of_string(String.substring~pos:subsec_pos~len:subsec_len)inFloat.of_int((hr*3600)+(min*60)+sec)+.subsec|>Span.of_sec|>T.of_span_since_start_of_day_exn;;letof_strings=Ofday_helpers.parses~f:create_from_parsedlett_of_sexpsexp=matchsexpwith|Sexp.Atoms->(tryof_stringswith|Invalid_arguments->of_sexp_error("Ofday.t_of_sexp: "^s)sexp)|_->of_sexp_error"Ofday.t_of_sexp"sexp;;lett_sexp_grammar=Sexplib.Sexp_grammar.coerceString.t_sexp_grammarletsexp_of_tspan=Sexp.Atom(to_stringspan)letof_string_iso8601_extended?pos?lenstr=tryOfday_helpers.parse_iso8601_extended?pos?lenstr~f:create_from_parsedwith|exn->invalid_argf"Ofday.of_string_iso8601_extended(%s): %s"(String.subostr?pos?len)(Exn.to_stringexn)();;endendincludeStable.V1letgen_incllohi=Span.gen_incl(to_span_since_start_of_daylo)(to_span_since_start_of_dayhi)|>Quickcheck.Generator.map~f:of_span_since_start_of_day_exn;;letgen_uniform_incllohi=Span.gen_uniform_incl(to_span_since_start_of_daylo)(to_span_since_start_of_dayhi)|>Quickcheck.Generator.map~f:of_span_since_start_of_day_exn;;letquickcheck_generator=gen_inclstart_of_daystart_of_next_dayletquickcheck_observer=Quickcheck.Observer.unmapSpan.quickcheck_observer~f:to_span_since_start_of_day;;letquickcheck_shrinker=Quickcheck.Shrinker.empty()includeHashable.Make_binable(structtypenonrect=t[@@derivingbin_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. *)lett_of_sexpsexp=matchFloat.t_of_sexpsexpwith|float->of_floatfloat|exception_->t_of_sexpsexp;;end)moduleC=structtypet=T.t[@@derivingbin_io]typecomparator_witness=T.comparator_witnessletcomparator=T.comparatorletcompare=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. *)letsexp_of_t=sexp_of_tlett_of_sexpsexp=matchOption.try_with(fun()->T.of_float(Float.t_of_sexpsexp))with|Somet->t|None->t_of_sexpsexp;;endmoduleMap=Map.Make_binable_using_comparator(C)moduleSet=Set.Make_binable_using_comparator(C)includeComparable.Validate(C)letof_span_since_start_of_day=of_span_since_start_of_day_exnletto_millisec_string=to_millisecond_string