123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681(*---------------------------------------------------------------------------
Copyright (c) 2015 The ptime programmers. All rights reserved.
Distributed under the ISC license, see terms at the end of the file.
ptime v0.8.5
---------------------------------------------------------------------------*)openResult(* Julian day and proleptic Gregorian calendar date conversion.
Formulae are from the calendar FAQ:
http://www.tondering.dk/claus/cal/julperiod.php#formula
These formulae work for positive Julian days. They represent
Gegorian calendar BCE year `y` by `-(y-1)`, e.g. 2 BCE is -1, this
follows the convention of ISO 8601.
All timestamps in Ptime's [min;max] range are represented by
positive Julian days and the formulae do not overflow on 32-bit
platforms in this restricted range. *)letjd_to_datejd=leta=jd+32044inletb=(4*a+3)/146097inletc=a-((146097*b)/4)inletd=(4*c+3)/1461inlete=c-((1461*d)/4)inletm=(5*e+2)/153inletday=e-((153*m+2)/5)+1inletmonth=m+3-(12*(m/10))inletyear=100*b+d-4800+(m/10)in(year,month,day)letjd_of_date(year,month,day)=leta=(14-month)/12inlety=year+4800-ainletm=month+12*a-3inday+((153*m)+2)/5+365*y+(y/4)-(y/100)+(y/400)-32045letjd_posix_epoch=2_440_588(* the Julian day of the POSIX epoch *)letjd_ptime_min=1_721_060(* the Julian day of Ptime.min *)letjd_ptime_max=5_373_484(* the Julian day of Ptime.max *)(* Picosecond precision POSIX timestamps and time span representation.
POSIX timestamps and spans are represented by int * int64 pairs
with the int64 in the range [0L;86_399_999_999_999_999L]. A pair
[(d, ps)] denotes the POSIX picosecond duration [d] * 86_400e12 +
[ps].
For a timestamp this can be seen as a POSIX day count from the
epoch paired with a picosecond precision POSIX time point in that
day starting from 00:00:00.
By definition with a negative [d] the [ps] duration brings us
towards zero, *not* towards infinity:
(d * 86_400e12) (d * 86_400e12 + ps) 0
... -----+-----------------+-------------------+--------- ...
[---------------->|
ps
[d] is largely sufficent to represent all the days in Ptime's
[min;max] range on both 32-bit and 64-bit platforms. *)typet=int*int64letps_count_in_ps=1Lletps_count_in_ns=1_000Lletps_count_in_100ns=100_000Lletps_count_in_us=1_000_000Lletps_count_in_100us=100_000_000Lletps_count_in_ms=1_000_000_000Lletps_count_in_100ms=100_000_000_000Lletps_count_in_s=1_000_000_000_000Lletps_count_in_min=60_000_000_000_000Lletps_count_in_hour=3600_000_000_000_000Lletps_count_in_day=86_400_000_000_000_000Lletps_day_max=86_399_999_999_999_999Lletday_min=jd_ptime_min-jd_posix_epochletday_max=jd_ptime_max-jd_posix_epochletepoch=(0,0L)(* 1970-01-01 00:00:00 UTC *)letmin=(day_min,0L)(* 0000-01-01 00:00:00 UTC *)letmax=(day_max,ps_day_max)(* 9999-12-31 23:59:59 UTC *)(* POSIX time spans *)typespan=tmoduleSpan=struct(* Arithmetic *)letneg=function|(d,0L)->(-d,0L)|(d,ps)->(-(d+1),Int64.subps_count_in_dayps)letadd(d0,ps0)(d1,ps1)=letd=d0+d1inletps=Int64.addps0ps1inletps_clamp=Int64.rempsps_count_in_dayinletd=d+Int64.comparepsps_clampind,ps_clampletsubs0s1=adds0(negs1)letabs(d,_ass)=ifd<0thennegselses(* POSIX time spans *)typet=spanletzero=(0,0L)letv(d,psass)=ifps<0L||ps>ps_day_maxtheninvalid_arg(Format.sprintf"illegal ptime time span: (%d,%Ld)"dps)elsesletof_d_ps(d,psass)=ifps<0L||ps>ps_day_maxthenNoneelseSomesletunsafe_of_d_pss=sletunsafe_of_d_ps_options=sletto_d_pss=sletof_int_ssecs=letd=Pervasives.abssecsinlets=(d/86_400,Int64.(mul(of_int(dmod86_400))ps_count_in_s))inifsecs<0thennegselsesletday_int_min=min_int/86_400letday_int_max=max_int/86_400letto_int_s(d,ps)=ifd<day_int_min||d>day_int_maxthenNoneelseletdays_s=d*86_400inletday_s=Int64.(to_int(divpsps_count_in_s))(* always positive *)inletsecs=days_s+day_sinifsecs<days_s(* positive overflow *)thenNoneelseSomesecsletmin_int_float=floatmin_intletmax_int_float=floatmax_intletof_float_ssecs=ifsecs<>secs(* nan *)thenNoneelseletdays=floor(secs/.86_400.)inifdays<min_int_float||days>max_int_floatthenNoneelseletrem_s=mod_floatsecs86_400.inletrem_s=ifrem_s<0.then86_400.+.rem_selserem_sinifrem_s>=86_400.thenSome(int_of_floatdays+1,0L)elseletfrac_s,rem_s=modfrem_sinletrem_ps=Int64.(mul(of_floatrem_s)ps_count_in_s)inletfrac_ps=Int64.(of_float(frac_s*.1e12))inSome(int_of_floatdays,(Int64.addrem_psfrac_ps))letto_float_s(d,ps)=letdays_s=(floatd)*.86_400.inletday_s=Int64.(to_float(divpsps_count_in_s))inletday_rem_ps=Int64.(to_float(rempsps_count_in_s))indays_s+.day_s+.(day_rem_ps*.1e-12)(* Predicates *)letequal(d0,ps0)(d1,ps1)=(Pervasives.compare:int->int->int)d0d1=0&&Int64.compareps0ps1=0letcompare(d0,ps0)(d1,ps1)=letc=Pervasives.compared0d1inifc<>0thencelsePervasives.compareps0ps1(* Rounding *)letround_divab=(* a >= 0 and b > 0 *)ifa=0Lthen0LelseInt64.(div(adda(divb2L))b)letfrac_div=[|1_000_000_000_000L;100_000_000_000L;10_000_000_000L;1_000_000_000L;100_000_000L;10_000_000L;1_000_000L;100_000L;10_000L;1_000L;100L;10L;1L;|]letround~frac_s:frac(sign,_ast)=letfrac=iffrac<0then0else(iffrac>12then12elsefrac)inlet(d,ps)=ifsign<0thennegtelsetinletrps=Int64.mul(round_divpsfrac_div.(frac))frac_div.(frac)inlett=ifrps>ps_day_maxthen(d+1,0L)else(d,rps)inifsign<0thennegtelsetlettruncate~frac_s:frac(sign,_ast)=letfrac=iffrac<0then0else(iffrac>12then12elsefrac)inlet(d,ps)=ifsign<0thennegtelsetinlettps=Int64.(subps(rempsfrac_div.(frac)))inifsign<0thenneg(d,tps)else(d,tps)(* Pretty printing *)letdumpppf(d,ps)=Format.fprintfppf"@[<1>(%d,@,%Ld)@]"dps(* Warning laborious code follows. Is there a better way ? *)letdivide_ps~carrypshilo=lethi_d=Int64.(to_int(divpshi))inletrem_ps=Int64.rempshiinletlo_d=Int64.to_int(round_divrem_pslo)iniflo_d=carrythenhi_d+1,0elsehi_d,lo_dletpp_y_dppf~negdps=(* assert d >= 0 *)lety,rem_d=letmax_d=max_int/4inifd>max_dthen(* d * 4 overflows *)d/365,dmod365elselety=(d*4)/1461(* / 365.25 *)iny,d-(y*1461)/4inletdays=rem_d+Int64.to_int(round_divpsps_count_in_day)inlety,days=ifdays=366theny+1,1elsey,daysinlety=ifnegthen-yelseyinFormat.fprintfppf"%dy"y;ifdays<>0thenFormat.fprintfppf"%dd"days;()letpp_d_hppf~negdps=leth,_=divide_ps~carry:1psps_count_in_hourps_count_in_hourinletd,h=ifh=24thend+1,0elsed,hinifd=366thenFormat.fprintfppf"%dy1d"(ifnegthen-1else1)elseifd=365&&h>=6thenFormat.fprintfppf"%dy"(ifnegthen-1else1)elseletd=ifnegthen-delsedinFormat.fprintfppf"%dd"d;ifh<>0thenFormat.fprintfppf"%dh"h;()letpp_h_mppf~negps=leth,m=divide_ps~carry:60psps_count_in_hourps_count_in_mininifh=24thenFormat.fprintfppf"%dd"(ifnegthen-1else1)elseleth=ifnegthen-helsehinFormat.fprintfppf"%dh"h;ifm<>0thenFormat.fprintfppf"%dmin"m;()letpp_m_sppf~negps=letm,s=divide_ps~carry:60psps_count_in_minps_count_in_sinifm=60thenFormat.fprintfppf"%dh"(ifnegthen-1else1)elseletm=ifnegthen-melseminFormat.fprintfppf"%dmin"m;ifs<>0thenFormat.fprintfppf"%ds"s;()letpp_sppf~negps=lets,ms=divide_ps~carry:1000psps_count_in_sps_count_in_msinifs=60thenFormat.fprintfppf"%dmin"(ifnegthen-1else1)elselets=ifnegthen-selsesinifms<>0thenFormat.fprintfppf"%d.%ds"smselseFormat.fprintfppf"%ds"sletpp_unithigher_strhihi_strfrac_limitloppf~negps=letpp_unit_integralppf~negh=ifh=1000thenFormat.fprintfppf"%d%s"(ifnegthen-1else1)higher_strelseFormat.fprintfppf"%d%s"(ifnegthen-helseh)hi_strinifps<frac_limitthenbeginleth,l=divide_ps~carry:1000pshiloinifh>=100||l=0thenpp_unit_integralppf~neghelseleth=ifnegthen-helsehinFormat.fprintfppf"%d.%d%s"hlhi_strendelsebeginletms,_=divide_ps~carry:1pshihiinpp_unit_integralppf~negmsendletpp_ms=pp_unit"s"ps_count_in_ms"ms"ps_count_in_100msps_count_in_usletpp_us=pp_unit"ms"ps_count_in_us"us"ps_count_in_100usps_count_in_nsletpp_ns=pp_unit"us"ps_count_in_ns"ns"ps_count_in_100nsps_count_in_psletpp_psppf~negps=letps=Int64.to_intpsinFormat.fprintfppf"%dps"(ifnegthen-pselseps)letppppf(sign,_ass)=letneg=sign<0inmatch(abss)with|(0,ps)->ifps>=ps_count_in_hourthenpp_h_mppf~negpselseifps>=ps_count_in_minthenpp_m_sppf~negpselseifps>=ps_count_in_sthenpp_sppf~negpselseifps>=ps_count_in_msthenpp_msppf~negpselseifps>=ps_count_in_usthenpp_usppf~negpselseifps>=ps_count_in_nsthenpp_nsppf~negpselsepp_psppf~negps|(d,ps)->ifd>365thenpp_y_dppfnegdpselsepp_d_hppfnegdpsend(* POSIX timestamps *)letv(d,psass)=if(ps<0L||ps>ps_day_max||d<day_min||d>day_max)theninvalid_arg(Format.sprintf"illegal ptime timestamp: (%d,%Ld)"dps)elsesletunsafe_of_d_pss=sletof_span(d,_asspan)=ifd<day_min||d>day_maxthenNoneelseSomespanletto_spant=tletof_float_ssecs=matchSpan.of_float_ssecswith|None->None|Somed->of_spandletto_float_s=Span.to_float_slettruncate=Span.truncateletfrac_s(_,ps)=(0,Int64.(rempsps_count_in_s))(* Predicates *)letequal=Span.equalletcompare=Span.compareletis_earliert~than=comparetthan=-1letis_latert~than=comparetthan=1(* POSIX arithmetic *)letadd_spantd=of_span(Span.addtd)letsub_spantd=of_span(Span.subtd)letdifft1t0=Span.subt1t0(* Time zone offsets between local and UTC timelines *)typetz_offset_s=int(* Date-time conversion
POSIX time counts seconds since 1970-01-01 00:00:00 UTC without
counting leap seconds -- when a leap second occurs a POSIX second
can be two SI seconds or zero SI second. Hence 86400 POSIX seconds
always represent an UTC day and the translations below are accurate
without having to refer to a leap seconds table. *)typedate=(int*int*int)typetime=(int*int*int)*tz_offset_sletmax_month_day=(* max day number in a given year's month. *)letis_leap_yeary=(ymod4=0)&&(ymod100<>0||ymod400=0)inletmlen=[|31;28(* or not *);31;30;31;30;31;31;30;31;30;31|]infunym->if(m=2&&is_leap_yeary)then29elsemlen.(m-1)letis_date_valid(y,m,d)=0<=y&&y<=9999&&1<=m&&m<=12&&1<=d&&d<=max_month_dayymletis_time_valid((hh,mm,ss),_)=0<=hh&&hh<=23&&0<=mm&&mm<=59&&0<=ss&&ss<=60letof_date_time(date,((hh,mm,ss),tz_offset_sast))=(* We first verify that the given date and time are Ptime-valid.
Once this has been established we find find the number of Julian
days since the epoch for the given proleptic Georgian calendar
date. This gives us the POSIX day component of the timestamp. The
remaining time fields are used to derive the picosecond precision
time in that day compensated by the time zone offset. The final
result is checked to be in Ptime's [min;max] range.
By definition POSIX timestamps cannot represent leap seconds.
With the code below any date-time with a seconds value of 60
(leap second addition) is mapped to the POSIX timestamp that
happens 1 second later which is what POSIX mktime would to. Any
formally non-existing UTC date-time with a seconds value of 59
(leap second subtraction) is mapped on the POSIX timestamp that
represents this non existing instant. *)ifnot(is_date_validdate&&is_time_validt)thenNoneelseletd=jd_of_datedate-jd_posix_epochinlethh_ps=Int64.(mul(of_inthh)ps_count_in_hour)inletmm_ps=Int64.(mul(of_intmm)ps_count_in_min)inletss_ps=Int64.(mul(of_intss)ps_count_in_s)inletps=Int64.(addhh_ps(addmm_psss_ps))insub_span(d,ps)(Span.of_int_stz_offset_s)letto_date_time?(tz_offset_s=0)t=(* To render the timestamp in the given time zone offset we first
express the timestamp in local time and then compute the date
fields on that stamp as if it were UTC. If the local timestamp is
not in [min;max] then its date fields cannot be valid according
to the constraints guaranteed by Ptime and we fallback to UTC,
i.e. a time zone offset of 0.
We then apply the following algorithm whose description makes
sense on a POSIX timestamp (i.e. UTC) but works equally well to
render the date-time fields of a local timestamp.
We first take take the POSIX day count [d] (equivalent by
definition to an UTC day count) from the epoch, convert it to a
Julian day and use this to get the proleptic Gregorian calendar
date. The POSIX picoseconds [ps] in the day are are converted to
a daytime according to to its various units.
By definition no POSIX timestamp can represent a date-time with a
seconds value of 60 (leap second addition) and thus the function
will never return a date-time with such a value. On the other
hand it will return an inexisting UTC date-time with a seconds
value of 59 whenever a leap second is subtracted since there is a
POSIX timestamp that represents this instant. *)let(d,ps),tz_offset_s=matchadd_spant(Span.of_int_stz_offset_s)with|None->t,0(* fallback to UTC *)|Somelocal->local,tz_offset_sinletjd=d+jd_posix_epochinletdate=jd_to_datejdinlethh=Int64.(to_int(divpsps_count_in_hour))inlethh_rem=Int64.rempsps_count_in_hourinletmm=Int64.(to_int(divhh_remps_count_in_min))inletmm_rem=Int64.remhh_remps_count_in_mininletss=Int64.(to_int(divmm_remps_count_in_s))indate,((hh,mm,ss),tz_offset_s)letof_datedate=of_date_time(date,((00,00,00),0))letto_datet=fst(to_date_time~tz_offset_s:0t)letweekday=letwday=(* Epoch was a thursday *)[|`Thu;`Fri;`Sat;`Sun;`Mon;`Tue;`Wed|]infun?(tz_offset_s=0)t->let(d,_)=Span.addt(Span.of_int_stz_offset_s)in(* N.B. in contrast to [to_date_time] we don't care if we fall outside
[min;max]. Even if it happens the result of the computation is still
correct *)leti=dmod7inwday.(ifi<0then7+ielsei)(* RFC 3339 timestamp conversions *)(* RFC 3339 timestamp parser *)typeerror_range=int*inttyperfc3339_error=[`Invalid_stamp|`Eoi|`Exp_charsofcharlist|`Trailing_input]letpp_rfc3339_errorppf=function|`Invalid_stamp->Format.fprintfppf"@[invalid@ time@ stamp@]"|`Eoi->Format.fprintfppf"@[unexpected@ end@ of@ input@]"|`Trailing_input->Format.fprintfppf"@[trailing@ input@]"|`Exp_charscs->letrecpp_charsppf=function|c::cs->Format.fprintfppf"@ %C"c;pp_charsppfcs|[]->()inFormat.fprintfppf"@[expected@ a@ character@ in:%a@]"pp_charscsletrfc3339_error_to_msg=function|Ok_asv->v|Error(`RFC3339((s,e),err))->Error(`Msg(Format.asprintf"%d-%d: %a"sepp_rfc3339_errorerr))exceptionRFC3339of(int*int)*rfc3339_error(* Internal *)leterrorre=raise(RFC3339(r,e))leterror_pospe=error(p,p)eleterror_exp_digitp=error_posp(`Exp_chars['0';'1';'2';'3';'4';'5';'6';'7';'8';'9'])letis_digit=function'0'..'9'->true|_->falseletparse_digits~countposmaxs=letstop=pos+count-1inifstop>maxthenerror_posmax`Eoielseletrecloopkacc=ifk>stopthenaccelseifis_digits.[k]thenloop(k+1)(acc*10+Char.codes.[k]-0x30)elseerror_exp_digitkinlooppos0letparse_charcposmaxs=ifpos>maxthenerror_posmax`Eoielseifs.[pos]=cthen()elseerror_pospos(`Exp_chars[c])letparse_dt_sep~strictposmaxs=letis_dt_sep=function|'T'->true|'t'|' 'whennotstrict->true|_->falseinifpos>maxthenerror_posmax`Eoielseifis_dt_seps.[pos]then()elseerror_pospos(`Exp_chars(['T']@ifstrictthen[]else['t';' ']))letdecide_frac_or_tz~strictposmaxs=ifpos>maxthenerror_posmax`Eoielsematchs.[pos]with|'.'->`Frac|'+'|'-'|'Z'->`Tz|'z'whennotstrict->`Tz|c->letchars=['.';'+';'-';'Z']@ifstrictthen[]else['z']inerror_pospos(`Exp_charschars)letparse_frac_psposmaxs=ifpos>maxthenerror_posmax`Eoielseifnot(is_digits.[pos])thenerror_exp_digitposelseletrecloopkaccpow=ifk>maxthenerror_posmax`Eoielseifnot(is_digits.[k])then(Someacc),kelseletcount=k-pos+1inifcount>12then(* truncate *)loop(k+1)accpowelseletpow=Int64.divpow10Linletacc=Int64.(addacc(mul(of_int(Char.codes.[k]-0x30))pow))inloop(k+1)accpowinlooppos0Lps_count_in_sletparse_tz_s~strictposmaxs=letparse_tz_magsignpos=lethh_pos=posinletmm_pos=hh_pos+3inlethh=parse_digits~count:2hh_posmaxsinparse_char':'(mm_pos-1)maxs;letmm=parse_digits~count:2mm_posmaxsinifhh>23thenerror(hh_pos,hh_pos+1)`Invalid_stampelseifmm>59thenerror(mm_pos,mm_pos+1)`Invalid_stampelseletsecs=hh*3600+mm*60inlettz_s=matchsecs=0&&sign=-1with|true->None(* -00:00 convention *)|false->Some(sign*secs)intz_s,mm_pos+1inifpos>maxthenerror_posmax`Eoielsematchs.[pos]with|'Z'->Some0,pos|'z'whennotstrict->Some0,pos|'+'->parse_tz_mag(1)(pos+1)|'-'->parse_tz_mag(-1)(pos+1)|c->letchars=['+';'-';'Z']@ifstrictthen[]else['z']inerror_pospos(`Exp_charschars)letof_rfc3339?(strict=false)?(sub=false)?(start=0)s=trylets_len=String.lengthsinletmax=s_len-1inifs_len=0||start<0||start>maxthenerror_posstart`Eoielselety_pos=startinletm_pos=y_pos+5inletd_pos=m_pos+3inlethh_pos=d_pos+3inletmm_pos=hh_pos+3inletss_pos=mm_pos+3inletdecide_pos=ss_pos+2inlety=parse_digits~count:4y_posmaxsinparse_char'-'(m_pos-1)maxs;letm=parse_digits~count:2m_posmaxsinparse_char'-'(d_pos-1)maxs;letd=parse_digits~count:2d_posmaxsinparse_dt_sep~strict(hh_pos-1)maxs;lethh=parse_digits~count:2hh_posmaxsinparse_char':'(mm_pos-1)maxs;letmm=parse_digits~count:2mm_posmaxsinparse_char':'(ss_pos-1)maxs;letss=parse_digits~count:2ss_posmaxsinletfrac,tz_pos=matchdecide_frac_or_tz~strictdecide_posmaxswith|`Frac->parse_frac_ps(decide_pos+1)maxs|`Tz->None,decide_posinlettz_s_opt,last_pos=parse_tz_s~stricttz_posmaxsinlettz_s=matchtz_s_optwithNone->0|Somes->sinmatchof_date_time((y,m,d),((hh,mm,ss),tz_s))with|None->error(start,last_pos)`Invalid_stamp|Somet->lett,tz_s=matchfracwith|None|Some0L->t,tz_s|Somefrac->matchadd_spant(0,frac)with|None->error(start,last_pos)`Invalid_stamp|Somet->t,tz_sinifnotsub&&last_pos<>maxthenerror_pos(last_pos+1)`Trailing_inputelseOk(t,tz_s_opt,last_pos-start+1)withRFC3339(r,e)->Error(`RFC3339(r,e))(* RFC 3339 timestamp formatter *)letrfc3339_adjust_tz_offsettz_offset_s=(* The RFC 3339 time zone offset field is limited in expression to
the bounds below with minute precision. If the requested time
zone offset exceeds these bounds or is not an *integral* number
of minutes we simply use UTC. An alternative would be to
compensate the offset *and* the timestamp but it's more
complicated to explain and maybe more surprising to the user. *)letmin=-86340(* -23h59 in secs *)inletmax=+86340(* +23h59 in secs *)inifmin<=tz_offset_s&&tz_offset_s<=max&&tz_offset_smod60=0thentz_offset_s,falseelse0(* UTC *),truelets_frac_of_psfracps=Int64.(div(rempsps_count_in_s)Span.frac_div.(frac))letto_rfc3339?(space=false)?frac_s:(frac=0)?tz_offset_s(_,psast)=letbuf=Buffer.create255inlettz_offset_s,tz_unknown=matchtz_offset_swith|Sometz->rfc3339_adjust_tz_offsettz|None->0,trueinlet(y,m,d),((hh,ss,mm),tz_offset_s)=to_date_time~tz_offset_stinletdt_sep=ifspacethen' 'else'T'inPrintf.bprintfbuf"%04d-%02d-%02d%c%02d:%02d:%02d"ymddt_sephhssmm;letfrac=iffrac<0then0else(iffrac>12then12elsefrac)iniffrac<>0thenPrintf.bprintfbuf".%0*Ld"frac(s_frac_of_psfracps);iftz_offset_s=0&¬tz_unknownthenPrintf.bprintfbuf"Z"elsebeginlettz_sign=iftz_offset_s<0||tz_unknownthen'-'else'+'inlettz_min=abs(tz_offset_s/60)inlettz_hh=tz_min/60inlettz_mm=tz_minmod60inPrintf.bprintfbuf"%c%02d:%02d"tz_signtz_hhtz_mm;end;Buffer.contentsbufletpp_rfc3339?space?frac_s?tz_offset_s()ppft=Format.fprintfppf"%s"(to_rfc3339?space?frac_s?tz_offset_st)(* Pretty printing *)letpp_human?frac_s:(frac=0)?tz_offset_s()ppf(_,psast)=lettz_offset_s,tz_unknown=matchtz_offset_swith|Sometz->rfc3339_adjust_tz_offsettz|None->0,trueinlet(y,m,d),((hh,ss,mm),tz_offset_s)=to_date_time~tz_offset_stinFormat.fprintfppf"%04d-%02d-%02d %02d:%02d:%02d"ymdhhssmm;letfrac=iffrac<0then0else(iffrac>12then12elsefrac)iniffrac<>0thenFormat.fprintfppf".%0*Ld"frac(s_frac_of_psfracps);lettz_sign=iftz_offset_s<0||tz_unknownthen'-'else'+'inlettz_min=abs(tz_offset_s/60)inlettz_hh=tz_min/60inlettz_mm=tz_minmod60inFormat.fprintfppf" %c%02d:%02d"tz_signtz_hhtz_mm;()letpp=pp_human~tz_offset_s:0()letdump=Span.dump(*---------------------------------------------------------------------------
Copyright (c) 2015 The ptime programmers
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------------------------------------------*)