Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file date0.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666open!ImportopenStd_internalopenDigit_string_helpersletis_leap_year~year=(yearmod4=0&¬(yearmod100=0))||yearmod400=0(* Create a local private date type to ensure that all dates are created via
Date.create_exn.
*)moduleStable=structmoduleV1=structmoduleWithout_comparable=structmoduleT:sigtypet[@@immediate][@@derivingbin_io,equal,hash,typerep]valcreate_exn:y:int->m:Month.Stable.V1.t->d:int->tvalyear:t->intvalmonth:t->Month.Stable.V1.tvalday:t->intvaldays_in_month:year:int->month:Month.t->intvalto_int:t->intvalof_int_exn:int->tvalinvalid_value__for_internal_use_only:tend=struct(* We used to store dates like this:
type t = { y: int; m: Month.Stable.V1.t; d: int; }
In the below we make sure that the bin_io representation is
identical (and the stable unit tests check this)
In memory we use the following much more compact representation:
2 bytes year
1 byte month
1 byte day
all packed into a single immediate int (so from 4 words down to 1).
*)typet=int[@@derivingequal,hash,typerep,bin_shape~basetype:"899ee3e0-490a-11e6-a10a-a3734f733566"]letcreate0~year~month~day=(* create_exn's validation make sure that each value fits *)(yearlsl16)lor(Month.to_intmonthlsl8)lorday;;letyeart=tlsr16letmontht=Month.of_int_exn((tlsr8)land0xff)letdayt=tland0xffletdays_in_month~year~month=match(month:Month.t)with|Jan|Mar|May|Jul|Aug|Oct|Dec->31|Apr|Jun|Sep|Nov->30|Feb->ifis_leap_year~yearthen29else28;;letcreate_exn~y:year~m:month~d:day=(* year, month, and day need to be passed as parameters to avoid allocating
a closure (see unit test below) *)letinvalid~year~month~daymsg=invalid_argf!"Date.create_exn ~y:%d ~m:%{Month} ~d:%d error: %s"yearmonthdaymsg()inifyear<0||year>9999theninvalid~year~month~day"year outside of [0..9999]";ifday<=0theninvalid~year~month~day"day <= 0";letdays_in_month=days_in_month~year~monthinifday>days_in_monththeninvalid~year~month~day(sprintf"%d day month violation"days_in_month);create0~year~month~day;;(* We don't use Make_binable here, because that would go via an immediate
tuple or record. That is exactly the 32 bytes we worked so hard above to
get rid of. We also don't want to just bin_io the integer directly
because that would mean a new bin_io format. *)letbin_read_tbuf~pos_ref=letyear=Int.bin_read_tbuf~pos_refinletmonth=Month.Stable.V1.bin_read_tbuf~pos_refinletday=Int.bin_read_tbuf~pos_refincreate0~year~month~day;;let__bin_read_t___buf~pos_ref=(* __bin_read_t is only needed for variants *)Bin_prot.Common.raise_variant_wrong_type"Date.t"!pos_ref;;letbin_reader_t={Bin_prot.Type_class.read=bin_read_t;vtag_read=__bin_read_t__};;letbin_size_tt=Int.bin_size_t(yeart)+Month.bin_size_t(montht)+Int.bin_size_t(dayt);;letbin_write_tbuf~post=letpos=Int.bin_write_tbuf~pos(yeart)inletpos=Month.bin_write_tbuf~pos(montht)inInt.bin_write_tbuf~pos(dayt);;letbin_writer_t={Bin_prot.Type_class.size=bin_size_t;write=bin_write_t}letbin_t={Bin_prot.Type_class.reader=bin_reader_t;writer=bin_writer_t;shape=bin_shape_t};;letto_intt=tletof_int_exnn=create_exn~y:(yearn)~m:(monthn)~d:(dayn)letinvalid_value__for_internal_use_only=0let%test"invalid value"=Exn.does_raise(fun():t->of_int_exninvalid_value__for_internal_use_only);;endincludeT(** YYYY-MM-DD *)letto_string_iso8601_extendedt=letbuf=Bytes.create10inwrite_4_digit_intbuf~pos:0(yeart);Bytes.setbuf4'-';write_2_digit_intbuf~pos:5(Month.to_int(montht));Bytes.setbuf7'-';write_2_digit_intbuf~pos:8(dayt);Bytes.unsafe_to_string~no_mutation_while_string_reachable:buf;;letto_string=to_string_iso8601_extended(** YYYYMMDD *)letto_string_iso8601_basict=letbuf=Bytes.create8inwrite_4_digit_intbuf~pos:0(yeart);write_2_digit_intbuf~pos:4(Month.to_int(montht));write_2_digit_intbuf~pos:6(dayt);Bytes.unsafe_to_string~no_mutation_while_string_reachable:buf;;(** MM/DD/YYYY *)letto_string_americant=letbuf=Bytes.create10inwrite_2_digit_intbuf~pos:0(Month.to_int(montht));Bytes.setbuf2'/';write_2_digit_intbuf~pos:3(dayt);Bytes.setbuf5'/';write_4_digit_intbuf~pos:6(yeart);Bytes.unsafe_to_string~no_mutation_while_string_reachable:buf;;letparse_year4strpos=read_4_digit_intstr~posletparse_monthstrpos=Month.of_int_exn(read_2_digit_intstr~pos)letparse_daystrpos=read_2_digit_intstr~pos(** YYYYMMDD *)letof_string_iso8601_basicstr~pos=ifpos+8>String.lengthstrtheninvalid_arg"Date.of_string_iso8601_basic: pos + 8 > string length";create_exn~y:(parse_year4strpos)~m:(parse_monthstr(pos+4))~d:(parse_daystr(pos+6));;(* WARNING: if you are going to change this function in a material way, be sure you
understand the implications of working in Stable *)letof_strings=letinvalid()=failwith("invalid date: "^s)inletensureb=ifnotbtheninvalid()inletmonth_num~year~month~day=create_exn~y:(parse_year4syear)~m:(parse_monthsmonth)~d:(parse_daysday)inletmonth_abrv~year~month~day=create_exn~y:(parse_year4syear)~m:(Month.of_string(String.subs~pos:month~len:3))~d:(parse_daysday)inifString.containss'/'then(lety,m,d=matchString.splits~on:'/'with|[a;b;c]->ifString.lengtha=4thena,b,c(* y/m/d *)elsec,a,b(* m/d/y *)|_->invalid()inletyear=Int.of_stringyinletyear=ifyear>=100thenyearelseifyear<75then2000+yearelse1900+yearinletmonth=Month.of_int_exn(Int.of_stringm)inletday=Int.of_stringdincreate_exn~y:year~m:month~d:day)elseifString.containss'-'then((* yyyy-mm-dd *)ensure(String.lengths=10&&Char.(=)s.[4]'-'&&Char.(=)s.[7]'-');month_num~year:0~month:5~day:8)elseifString.containss' 'thenifString.lengths=11&&Char.(=)s.[2]' '&&Char.(=)s.[6]' 'then(* DD MMM YYYY *)month_abrv~day:0~month:3~year:7else((* YYYY MMM DD *)ensure(String.lengths=11&&Char.(=)s.[4]' '&&Char.(=)s.[8]' ');month_abrv~day:9~month:5~year:0)elseifString.lengths=9then(* DDMMMYYYY *)month_abrv~day:0~month:2~year:5elseifString.lengths=8then(* assume YYYYMMDD *)month_num~year:0~month:4~day:6elseinvalid();;letof_strings=tryof_stringswith|exn->invalid_argf"Date.of_string (%s): %s"s(Exn.to_stringexn)();;moduleSexpable=structmoduleOld_date=structtypet={y:int;m:int;d:int}[@@derivingsexp]letto_datet=T.create_exn~y:t.y~m:(Month.of_int_exnt.m)~d:t.dendlett_of_sexp=function|Sexp.Atoms->of_strings|Sexp.List_assexp->Old_date.to_date(Old_date.t_of_sexpsexp);;lett_of_sexps=tryt_of_sexpswith|Of_sexp_error_asexn->raiseexn|Invalid_argumenta->of_sexp_erroras;;letsexp_of_tt=Sexp.Atom(to_stringt)lett_sexp_grammar=Sexplib.Sexp_grammar.coerceSexplib.Sexp.t_sexp_grammarendincludeSexpableletcomparet1t2=letn=Int.compare(yeart1)(yeart2)inifn<>0thennelse(letn=Month.compare(montht1)(montht2)inifn<>0thennelseInt.compare(dayt1)(dayt2));;include(valComparator.Stable.V1.make~compare~sexp_of_t)endincludeWithout_comparableincludeComparable.Stable.V1.Make(Without_comparable)includeHashable.Stable.V1.Make(Without_comparable)endmoduleOption=structmoduleV1=structtypet=int[@@derivingbin_io,bin_shape~basetype:"826a3e79-3321-451a-9707-ed6c03b84e2f",compare,hash,typerep]letnone=V1.(to_intinvalid_value__for_internal_use_only)letis_nonet=t=noneletis_somet=not(is_nonet)letsome_is_representable_=trueletsomet=V1.to_inttletunchecked_value=V1.of_int_exnletto_optiont=ifis_sometthenSome(unchecked_valuet)elseNoneletof_optionopt=matchoptwith|None->none|Somev->somev;;letvalue_exnt=ifis_sometthenunchecked_valuetelseraise_s[%message[%here]"Date.Option.value_exn none"];;letvaluet~default=ifis_sometthenunchecked_valuetelsedefaultletsexp_of_tt=to_optiont|>Option.sexp_of_tV1.sexp_of_tlett_of_sexpsexp=(Option.t_of_sexpV1.t_of_sexp)sexp|>of_optionlett_sexp_grammar=Sexplib.Sexp_grammar.coerce[%sexp_grammar:V1.tOption.t]endendendmoduleWithout_comparable=Stable.V1.Without_comparableincludeWithout_comparablemoduleC=Comparable.Make_binable_using_comparator(Without_comparable)includeCmoduleO=structinclude(C:Comparable.Infixwithtypet:=t)endinclude(Hashable.Make_binable(structincludeTincludeSexpableincludeBinableletcompare(a:t)(b:t)=compareabend):Hashable.S_binablewithtypet:=t)includePretty_printer.Register(structtypenonrect=tletmodule_name="Core.Date"letto_string=to_stringend)letunix_epoch=create_exn~y:1970~m:Jan~d:1(* The Days module is used for calculations that involve adding or removing a known number
of days from a date. Internally the date is translated to a day number, the days are
added, and the new date is returned. Those interested in the math can read:
http://alcor.concordia.ca/~gpkatch/gdate-method.html
note: unit tests are in lib_test/time_test.ml
*)moduleDays:sigtypedate=ttypet[@@immediate]valof_date:date->tvalto_date:t->datevaldiff:t->t->intvaladd_days:t->int->tvalunix_epoch:tendwithtypedate:=t=structopenInttypet=intletof_yeary=(365*y)+(y/4)-(y/100)+(y/400)letof_datedate=letm=(Month.to_int(monthdate)+9)%12inlety=yeardate-(m/10)inof_yeary+(((m*306)+5)/10)+(daydate-1);;letc_10_000=Int63.of_int10_000letc_14_780=Int63.of_int14_780letc_3_652_425=Int63.of_int3_652_425letto_datedays=lety=letopenInt63into_int_exn(((c_10_000*of_intdays)+c_14_780)/c_3_652_425)inletddd=days-of_yearyinlety,ddd=ifddd<0then(lety=y-1iny,days-of_yeary)elsey,dddinletmi=((100*ddd)+52)/3_060inlety=y+((mi+2)/12)inletm=((mi+2)%12)+1inletd=ddd-(((mi*306)+5)/10)+1increate_exn~y~m:(Month.of_int_exnm)~d;;letunix_epoch=of_dateunix_epochletadd_daystdays=t+daysletdifft1t2=t1-t2endletadd_daystdays=Days.to_date(Days.add_days(Days.of_datet)days)letdifft1t2=Days.diff(Days.of_datet1)(Days.of_datet2)letadd_monthstn=lettotal_months=Month.to_int(montht)+ninlety=yeart+(total_months/%12)inletm=total_months%12in(* correct for december *)lety,m=ifInt.(=)m0theny-1,m+12elsey,minletm=Month.of_int_exnmin(* handle invalid dates for months with fewer number of days *)letrectry_created=trycreate_exn~y~m~dwith|_exn->assert(Int.(>=)d1);try_create(d-1)intry_create(dayt);;letadd_yearstn=add_monthst(n*12)(* http://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week#Purely_mathematical_methods
note: unit tests in lib_test/time_test.ml
*)letday_of_week=lettable=[|0;3;2;5;0;3;5;1;4;6;2;4|]infunt->letm=Month.to_int(montht)inlety=ifInt.(<)m3thenyeart-1elseyeartinDay_of_week.of_int_exn((y+(y/4)-(y/100)+(y/400)+table.(m-1)+dayt)%7);;(* http://en.wikipedia.org/wiki/Ordinal_date *)letnon_leap_year_table=[|0;31;59;90;120;151;181;212;243;273;304;334|]letleap_year_table=[|0;31;60;91;121;152;182;213;244;274;305;335|]letordinal_datet=lettable=ifis_leap_year~year:(yeart)thenleap_year_tableelsenon_leap_year_tableinletoffset=table.(Month.to_int(montht)-1)indayt+offset;;letlast_week_of_yeary=letfirst_of_year=create_exn~y~m:Jan~d:1inletistday=Day_of_week.equal(day_of_weekt)dayinifisfirst_of_yearThu||(is_leap_year~year:y&&isfirst_of_yearWed)then53else52;;(* See http://en.wikipedia.org/wiki/ISO_week_date or ISO 8601 for the details of this
algorithm.
Uses a [~f] argument to avoid allocating a tuple when called by [week_number].
*)letcall_with_week_and_yeart~f=letordinal=ordinal_datetinletweekday=Day_of_week.iso_8601_weekday_number(day_of_weekt)in(* [ordinal - weekday + 4] is the ordinal of this week's Thursday, then (n + 6) / 7 is
division by 7 rounding up *)letweek=(ordinal-weekday+10)/7inletyear=yeartinifInt.(<)week1thenf~week:(last_week_of_year(year-1))~year:(year-1)elseifInt.(>)week(last_week_of_yearyear)thenf~week:1~year:(year+1)elsef~week~year;;letweek_number_and_yeart=call_with_week_and_yeart~f:(fun~week~year->week,year)letweek_numbert=call_with_week_and_yeart~f:(fun~week~year:_->week)letis_weekendt=Day_of_week.is_sun_or_sat(day_of_weekt)letis_weekdayt=not(is_weekendt)letis_business_dayt~is_holiday=is_weekdayt&¬(is_holidayt)letrecdiff_weekend_dayst1t2=ift1<t2then-diff_weekend_dayst2t1else((* Basic date diff *)letdiff=difft1t2in(* Compute the number of Saturday -> Sunday crossings *)letd1=day_of_weekt1inletd2=day_of_weekt2inletnum_satsun_crossings=ifInt.(<)(Day_of_week.to_intd1)(Day_of_week.to_intd2)then1+(diff/7)elsediff/7in(num_satsun_crossings*2)+(ifDay_of_week.(=)d2Day_of_week.Sunthen1else0)+ifDay_of_week.(=)d1Day_of_week.Sunthen-1else0);;letdiff_weekdayst1t2=difft1t2-diff_weekend_dayst1t2letadd_days_skippingt~skipn=letstep=ifInt.(>=)n0then1else-1inletreclooptk=lett_next=add_dayststepinifskiptthenloopt_nextkelseifInt.(=)k0thentelseloopt_next(k-1)inloopt(absn);;letrecfirst_day_satisfyingt~step~condition=ifconditiontthentelsefirst_day_satisfying(add_dayststep)~step~condition;;letnext_day_satisfyingt~step~condition=letnext_day=add_dayststepinfirst_day_satisfyingnext_day~step~condition;;letfollowing_weekdayt=next_day_satisfyingt~step:1~condition:is_weekdayletprevious_weekdayt=next_day_satisfyingt~step:(-1)~condition:is_weekdayletround_forward_to_weekdayt=first_day_satisfyingt~step:1~condition:is_weekdayletround_backward_to_weekdayt=first_day_satisfyingt~step:(-1)~condition:is_weekdayletround_forward_to_business_dayt~is_holiday=first_day_satisfyingt~step:1~condition:(is_business_day~is_holiday);;letround_backward_to_business_dayt~is_holiday=first_day_satisfyingt~step:(-1)~condition:(is_business_day~is_holiday);;letadd_weekdaystn=add_days_skippingt~skip:is_weekendnletadd_weekdays_rounding_in_direction_of_step=add_weekdaysletadd_weekdays_rounding_forwardtn=add_days_skipping(round_forward_to_weekdayt)~skip:is_weekendn;;letadd_weekdays_rounding_backwardtn=add_days_skipping(round_backward_to_weekdayt)~skip:is_weekendn;;letadd_business_dayst~is_holidayn=add_days_skippingtn~skip:(fund->is_weekendd||is_holidayd);;letadd_business_days_rounding_in_direction_of_step=add_business_daysletadd_business_days_rounding_forwardt~is_holidayn=add_days_skipping(round_forward_to_business_day~is_holidayt)n~skip:(fund->not(is_business_day~is_holidayd));;letadd_business_days_rounding_backwardt~is_holidayn=add_days_skipping(round_backward_to_business_day~is_holidayt)n~skip:(fund->not(is_business_day~is_holidayd));;letdates_between~min:t1~max:t2=letreclooptl=ift<t1thenlelseloop(add_dayst(-1))(t::l)inloopt2[];;letweekdays_between~min~max=letall_dates=dates_between~min~maxinOption.value_map(List.hdall_dates)~default:[]~f:(funfirst_date->(* to avoid a system call on every date, we just get the weekday for the first
date and use it to get all the other weekdays *)letfirst_weekday=day_of_weekfirst_dateinletdate_and_weekdays=List.mapiall_dates~f:(funidate->date,Day_of_week.shiftfirst_weekdayi)inList.filter_mapdate_and_weekdays~f:(fun(date,weekday)->ifDay_of_week.is_sun_or_satweekdaythenNoneelseSomedate));;letbusiness_dates_between~min~max~is_holiday=weekdays_between~min~max|>List.filter~f:(fund->not(is_holidayd));;letfirst_strictly_aftert~on:dow=letdow=Day_of_week.to_intdowinlettplus1=add_dayst1inletcur=Day_of_week.to_int(day_of_weektplus1)inletdiff=(dow+7-cur)mod7inadd_daystplus1diff;;moduleFor_quickcheck=structopenQuickcheckletgen_uniform_incld1d2=ifd1>d2thenraise_s[%message"Date.gen_uniform_incl: bounds are crossed"~lower_bound:(d1:t)~upper_bound:(d2:t)];Generator.map(Int.gen_uniform_incl0(diffd2d1))~f:(fundays->add_daysd1days);;letgen_incld1d2=Generator.weighted_union[1.,Generator.returnd1;1.,Generator.returnd2;18.,gen_uniform_incld1d2];;letquickcheck_generator=gen_incl(of_string"1900-01-01")(of_string"2100-01-01")letquickcheck_observer=Observer.create(funt~size:_~hash->hash_fold_thasht)letquickcheck_shrinker=Shrinker.empty()endletquickcheck_generator=For_quickcheck.quickcheck_generatorletgen_incl=For_quickcheck.gen_inclletgen_uniform_incl=For_quickcheck.gen_uniform_inclletquickcheck_observer=For_quickcheck.quickcheck_observerletquickcheck_shrinker=For_quickcheck.quickcheck_shrinkermodulePrivate=structletleap_year_table=leap_year_tableletnon_leap_year_table=non_leap_year_tableletordinal_date=ordinal_dateendmoduleOption=structmoduleStable=Stable.OptionincludeStable.V1moduleOptional_syntax=structmoduleOptional_syntax=structletis_none=is_noneletunsafe_value=unchecked_valueendendletquickcheck_generator=Quickcheck.Generator.map(Option.quickcheck_generatorquickcheck_generator)~f:of_option;;letquickcheck_shrinker=Quickcheck.Shrinker.map(Option.quickcheck_shrinkerquickcheck_shrinker)~f:of_option~f_inverse:to_option;;letquickcheck_observer=Quickcheck.Observer.of_hash(modulestructtypenonrect=t[@@derivinghash]end);;includeComparable.Make_plain(structtypenonrect=t[@@derivingcompare,sexp_of]end)end