123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476(* See Time_float.ml for the primary instantiation of this functor that is visible outside
of Core_kernel as Time (see core_kernel.ml and std.ml). *)open!ImportopenStd_internalopen!Int.Replace_polymorphic_compareincludeTime_intfmoduleZone0=ZonemoduleMake(Time0:Time0_intf.S)=structmoduleTime0=Time0includeTime0letepoch=of_span_since_epochSpan.zeroletis_earliert1~than:t2=t1<.t2letis_latert1~than:t2=t1>.t2moduleZone:sigincludeTime_intf.ZonewithmoduleTime:=Time0end=structincludeZoneletof_span_in_secondsspan_in_seconds=(* NB. no actual rounding or exns can occur here *)Time_in_seconds.Span.to_int63_seconds_round_down_exnspan_in_seconds|>Time0.Span.of_int63_seconds;;letof_time_in_secondstime_in_seconds=Time_in_seconds.to_span_since_epochtime_in_seconds(* NB. no actual rounding or exns can occur here *)|>Time_in_seconds.Span.to_int63_seconds_round_down_exn|>Time0.Span.of_int63_seconds|>Time0.of_span_since_epoch;;letto_time_in_seconds_round_down_exntime=Time0.to_span_since_epochtime|>Time0.Span.to_int63_seconds_round_down_exn|>Time_in_seconds.Span.of_int63_seconds|>Time_in_seconds.of_span_since_epoch;;letto_date_and_ofday_in_seconds_round_down_exnrelative=Time0.Date_and_ofday.to_synthetic_span_since_epochrelative|>Time0.Span.to_int63_seconds_round_down_exn|>Time_in_seconds.Span.of_int63_seconds|>Time_in_seconds.Date_and_ofday.of_synthetic_span_since_epoch;;letindexttime=indext(to_time_in_seconds_round_down_exntime)letindex_of_date_and_ofdaytrelative=index_of_date_and_ofdayt(to_date_and_ofday_in_seconds_round_down_exnrelative);;letindex_offset_from_utc_exntindex=of_span_in_seconds(index_offset_from_utc_exntindex);;letindex_prev_clock_shift_time_exntindex=of_time_in_seconds(index_prev_clock_shift_time_exntindex);;letindex_next_clock_shift_time_exntindex=of_time_in_seconds(index_next_clock_shift_time_exntindex);;letindex_prev_clock_shift_amount_exntindex=of_span_in_seconds(index_prev_clock_shift_amount_exntindex);;letindex_next_clock_shift_amount_exntindex=of_span_in_seconds(index_next_clock_shift_amount_exntindex);;letabbreviationttime=(* no exn because [index] always returns a valid index *)index_abbreviation_exnt(indexttime);;letindex_prev_clock_shifttindex=matchindex_has_prev_clock_shifttindexwith|false->None|true->Some(index_prev_clock_shift_time_exntindex,index_prev_clock_shift_amount_exntindex);;letindex_next_clock_shifttindex=index_prev_clock_shiftt(Index.nextindex)letprev_clock_shiftt~at_or_before:time=index_prev_clock_shiftt(indexttime)letnext_clock_shiftt~strictly_after:time=index_next_clock_shiftt(indexttime)letdate_and_ofday_of_absolute_timettime=letindex=indexttimein(* no exn because [index] always returns a valid index *)letoffset_from_utc=index_offset_from_utc_exntindexinTime0.Date_and_ofday.of_absolutetime~offset_from_utc;;letabsolute_time_of_date_and_ofdaytrelative=letindex=index_of_date_and_ofdaytrelativein(* no exn because [index_of_date_and_ofday] always returns a valid index *)letoffset_from_utc=index_offset_from_utc_exntindexinTime0.Date_and_ofday.to_absoluterelative~offset_from_utc;;endletabs_difft1t2=Span.abs(difft1t2)letof_date_ofday~zonedateofday=letrelative=Date_and_ofday.of_date_ofdaydateofdayinZone.absolute_time_of_date_and_ofdayzonerelative;;letof_date_ofday_precisedateofday~zone=(* We assume that there will be only one zone shift within a given local day. *)letstart_of_day=of_date_ofday~zonedateOfday.start_of_dayinletproposed_time=addstart_of_day(Ofday.to_span_since_start_of_dayofday)inmatchZone.next_clock_shiftzone~strictly_after:start_of_daywith|None->`Onceproposed_time|Some(shift_start,shift_amount)->letshift_backwards=Span.(shift_amount<zero)in(* start and end of the "problematic region" *)lets,e=ifshift_backwardsthenaddshift_startshift_amount,shift_startelseshift_start,addshift_startshift_amountinifproposed_time<sthen`Onceproposed_timeelseifs<=proposed_time&&proposed_time<ethenifshift_backwardsthen`Twice(proposed_time,subproposed_timeshift_amount)else`Nevershift_startelse`Once(subproposed_timeshift_amount);;moduleDate_cache=structtypet={mutablezone:Zone.t;mutablecache_start_incl:Time0.t;mutablecache_until_excl:Time0.t;mutableeffective_day_start:Time0.t;mutabledate:Date0.t}endletdate_cache:Date_cache.t={zone=Zone.utc;cache_start_incl=epoch;cache_until_excl=epoch;effective_day_start=epoch;date=Date0.unix_epoch};;letreset_date_cache()=date_cache.zone<-Zone.utc;date_cache.cache_start_incl<-epoch;date_cache.cache_until_excl<-epoch;date_cache.effective_day_start<-epoch;date_cache.date<-Date0.unix_epoch;;letis_in_cachetime~zone=phys_equalzonedate_cache.zone&&Time0.(>=)timedate_cache.cache_start_incl&&Time0.(<)timedate_cache.cache_until_excl;;letset_date_cachetime~zone=matchis_in_cachetime~zonewith|true->()|false->letindex=Zone.indexzonetimein(* no exn because [Zone.index] always returns a valid index *)letoffset_from_utc=Zone.index_offset_from_utc_exnzoneindexinletrel=Date_and_ofday.of_absolutetime~offset_from_utcinletdate=Date_and_ofday.to_daterelinletspan=Date_and_ofday.to_ofdayrel|>Ofday.to_span_since_start_of_dayinleteffective_day_start=Time0.sub(Date_and_ofday.to_absoluterel~offset_from_utc)spaninleteffective_day_until=Time0.addeffective_day_startSpan.dayinletcache_start_incl=matchZone.index_has_prev_clock_shiftzoneindexwith|false->effective_day_start|true->effective_day_start|>Time0.max(Zone.index_prev_clock_shift_time_exnzoneindex)inletcache_until_excl=matchZone.index_has_next_clock_shiftzoneindexwith|false->effective_day_until|true->effective_day_until|>Time0.min(Zone.index_next_clock_shift_time_exnzoneindex)indate_cache.zone<-zone;date_cache.cache_start_incl<-cache_start_incl;date_cache.cache_until_excl<-cache_until_excl;date_cache.effective_day_start<-effective_day_start;date_cache.date<-date;;letto_datetime~zone=set_date_cachetime~zone;date_cache.date;;letto_ofdaytime~zone=set_date_cachetime~zone;Time0.difftimedate_cache.effective_day_start|>Ofday.of_span_since_start_of_day_exn;;letto_date_ofdaytime~zone=to_datetime~zone,to_ofdaytime~zone(* The correctness of this algorithm (interface, even) depends on the fact that
timezone shifts aren't too close together (as in, it can't simultaneously be the
case that a timezone shift of X hours occurred less than X hours ago, *and*
a timezone shift of Y hours will occur in less than Y hours' time) *)letto_date_ofday_precisetime~zone=letdate,ofday=to_date_ofdaytime~zoneinletclock_shift_after=Zone.next_clock_shiftzone~strictly_after:timeinletclock_shift_before_or_at=Zone.prev_clock_shiftzone~at_or_before:timeinletalso_skipped_earlieramount=(* Using [date] and raising on [None] here is OK on the assumption that clock
shifts can't cross date boundaries. This is true in all cases I've ever heard
of (and [of_date_ofday_precise] would need revisiting if it turned out to be
false) *)matchOfday.subofdayamountwith|Someofday->`Also_skipped(date,ofday)|None->raise_s[%message"Time.to_date_ofday_precise"~span_since_epoch:(to_span_since_epochtime:Span.t)(zone:Zone.t)]inletambiguity=(* Edge cases: the instant of transition belongs to the new zone regime. So if the
clock moved by an hour exactly one hour ago, there's no ambiguity, because the
hour-ago time belongs to the same regime as you, and conversely, if the clock
will move by an hour in an hours' time, there *is* ambiguity. Hence [>.] for
the first case and [<=.] for the second. *)matchclock_shift_before_or_at,clock_shift_afterwith|Some(start,amount),_whenaddstart(Span.absamount)>.time->(* clock shifted recently *)ifSpan.(amount>zero)then(* clock shifted forward recently: we skipped a time *)also_skipped_earlieramountelse((* clock shifted back recently: this date/ofday already happened *)assert(Span.(amount<zero));`Also_at(subtime(Span.absamount)))|_,Some(start,amount)whensubstart(Span.absamount)<=.time->(* clock is about to shift *)ifSpan.(amount>zero)then(* clock about to shift forward: no effect *)`Onlyelse((* clock about to shift back: this date/ofday will be repeated *)assert(Span.(amount<zero));`Also_at(addtime(Span.absamount)))|_->`Onlyindate,ofday,ambiguity;;letconvert~from_tz~to_tzdateofday=letstart_time=of_date_ofday~zone:from_tzdateofdayinto_date_ofday~zone:to_tzstart_time;;letutc_offsett~zone=letutc_epoch=Zone.date_and_ofday_of_absolute_timezonetinSpan.(-)(Date_and_ofday.to_synthetic_span_since_epochutc_epoch)(to_span_since_epocht);;letoffset_stringtime~zone=letutc_offset=utc_offsettime~zoneinletis_utc=Span.(=)utc_offsetSpan.zeroinifis_utcthen"Z"elseString.concat[(ifSpan.(<)utc_offsetSpan.zerothen"-"else"+");Ofday.to_string_trimmed(Ofday.of_span_since_start_of_day_exn(Span.absutc_offset))];;letto_string_abs_partstime~zone=letdate,ofday=to_date_ofdaytime~zoneinletoffset_string=offset_stringtime~zonein[Date0.to_stringdate;String.concat~sep:""[Ofday.to_stringofday;offset_string]];;letto_string_abs_trimmedtime~zone=letdate,ofday=to_date_ofdaytime~zoneinletoffset_string=offset_stringtime~zoneinString.concat~sep:" "[Date0.to_stringdate;Ofday.to_string_trimmedofday^offset_string];;letto_string_abstime~zone=String.concat~sep:" "(to_string_abs_parts~zonetime)letto_stringt=to_string_abst~zone:Zone.utcletto_string_iso8601_basictime~zone=String.concat~sep:"T"(to_string_abs_parts~zonetime);;letto_string_trimmedt~zone=letdate,sec=to_date_ofday~zonetinDate0.to_stringdate^" "^Ofday.to_string_trimmedsec;;letto_sec_stringt~zone=letdate,sec=to_date_ofday~zonetinDate0.to_stringdate^" "^Ofday.to_sec_stringsec;;letto_filename_stringt~zone=letdate,ofday=to_date_ofday~zonetinDate0.to_stringdate^"_"^String.tr~target:':'~replacement:'-'(Ofday.to_stringofday);;letof_filename_strings~zone=trymatchString.lsplit2s~on:'_'with|None->failwith"no space in filename string"|Some(date,ofday)->letdate=Date0.of_stringdateinletofday=String.tr~target:'-'~replacement:':'ofdayinletofday=Ofday.of_stringofdayinof_date_ofdaydateofday~zonewith|exn->invalid_argf"Time.of_filename_string (%s): %s"s(Exn.to_stringexn)();;letof_localized_string~zonestr=trymatchString.lsplit2str~on:' 'with|None->invalid_arg(sprintf"no space in date_ofday string: %s"str)|Some(date,time)->letdate=Date0.of_stringdateinletofday=Ofday.of_stringtimeinof_date_ofday~zonedateofdaywith|e->Exn.reraisee"Time.of_localized_string";;letoccurrencebefore_or_aftert~ofday~zone=letfirst_guess_date=to_datet~zoneinletfirst_guess=of_date_ofday~zonefirst_guess_dateofdayinletcmp,increment=matchbefore_or_afterwith|`Last_before_or_at->(<=),-1|`First_after_or_at->(>=),1inifcmpfirst_guesstthenfirst_guesselseof_date_ofday~zone(Date0.add_daysfirst_guess_dateincrement)ofday;;letensure_colon_in_offsetoffset=letoffset_length=String.lengthoffsetinifInt.(<=)offset_length2&&Char.is_digitoffset.[0]&&Char.is_digitoffset.[offset_length-1]thenoffset^":00"elseifChar.(=)offset.[1]':'||Char.(=)offset.[2]':'thenoffsetelseifInt.(<)offset_length3||Int.(>)offset_length4thenfailwithf"invalid offset %s"offset()elseString.concat[String.sliceoffset0(offset_length-2);":";String.sliceoffset(offset_length-2)offset_length];;exceptionTime_of_stringofstring*Exn.t[@@derivingsexp]letof_string_gen~default_zone~find_zones=tryletdate,ofday,tz=matchString.splits~on:' 'with|[day;month;year;ofday]->String.concat[day;" ";month;" ";year],ofday,None|[date;ofday;tz]->date,ofday,Sometz|[date;ofday]->date,ofday,None|[s]->(matchString.rsplit2~on:'T'swith|Some(date,ofday)->date,ofday,None|None->failwith"no spaces or T found")|_->failwith"too many spaces"inletofday_to_secod=Span.to_sec(Ofday.to_span_since_start_of_dayod)inletofday,utc_offset=matchtzwith|Some_->ofday,None|None->ifChar.(=)ofday.[String.lengthofday-1]'Z'thenString.subofday~pos:0~len:(String.lengthofday-1),Some0.else(matchString.lsplit2~on:'+'ofdaywith|Some(l,r)->l,Some(ofday_to_sec(Ofday.of_string(ensure_colon_in_offsetr)))|None->(matchString.lsplit2~on:'-'ofdaywith|Some(l,r)->l,Some(-1.*.ofday_to_sec(Ofday.of_string(ensure_colon_in_offsetr)))|None->ofday,None))inletdate=Date0.of_stringdateinletofday=Ofday.of_stringofdayinmatchtzwith|Sometz->of_date_ofday~zone:(find_zonetz)dateofday|None->(matchutc_offsetwith|None->letzone=default_zone()inof_date_ofday~zonedateofday|Someutc_offset->letutc_t=of_date_ofday~zone:Zone.utcdateofdayinsubutc_t(Span.of_secutc_offset))with|e->raise(Time_of_string(s,e));;letof_strings=letdefault_zone()=raise_s[%message"time has no time zone or UTC offset"s]inletfind_zonezone_name=failwithf"unable to lookup Zone %s. Try using Core.Time.of_string"zone_name()inof_string_gen~default_zone~find_zones;;letquickcheck_shrinker=Quickcheck.Shrinker.mapSpan.quickcheck_shrinker~f:of_span_since_epoch~f_inverse:to_span_since_epoch;;letquickcheck_observer=Quickcheck.Observer.unmapSpan.quickcheck_observer~f:to_span_since_epoch;;letquickcheck_generator=Quickcheck.Generator.mapSpan.quickcheck_generator~f:of_span_since_epoch;;letgen_incllohi=Span.gen_incl(to_span_since_epochlo)(to_span_since_epochhi)|>Quickcheck.Generator.map~f:of_span_since_epoch;;letgen_uniform_incllohi=Span.gen_uniform_incl(to_span_since_epochlo)(to_span_since_epochhi)|>Quickcheck.Generator.map~f:of_span_since_epoch;;end