123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466(**************************************************************************)(* *)(* This file is part of Calendar. *)(* *)(* Copyright (C) 2003-2011 Julien Signoles *)(* *)(* you can redistribute it and/or modify it under the terms of the GNU *)(* Lesser General Public License version 2.1 as published by the *)(* Free Software Foundation, with a special linking exception (usual *)(* for Objective Caml libraries). *)(* *)(* It is distributed in the hope that it will be useful, *)(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)(* MERCHANTABILITY or FITNESS FOR A PARTICULAR *)(* *)(* See the GNU Lesser General Public Licence version 2.1 for more *)(* details (enclosed in the file LGPL). *)(* *)(* The special linking exception is detailled in the enclosed file *)(* LICENSE. *)(**************************************************************************)(*S Introduction.
This module implements operations on dates representing by their Julian day.
Most of the algorithms implemented in this module come from the FAQ
available at~:
\begin{center}http://www.tondering.dk/claus/calendar.html\end{center} *)(*S Datatypes. *)typefield=Period.date_field(* the integer represents the Julian day *)type-'adate=intconstraint'a=[<field]typet=fielddatetypeday=Sun|Mon|Tue|Wed|Thu|Fri|Sattypemonth=Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dectypeyear=int(*S Exceptions. *)exceptionOut_of_boundsexceptionUndefined(*S Locale coercions.
These coercions are used in the algorithms and do not respect ISO-8601.
The exported coercions are defined at the end of the module. *)(* pre: 0 <= n < 7 *)externalday_of_int:int->day="%identity"externalint_of_day:day->int="%identity"(* pre: 0 <= n < 12 *)externalmonth_of_int:int->month="%identity"externalint_of_month:month->int="%identity"(* Dates are comparable *)letcompare=Utils.Int.compareletequal=Utils.Int.equallet(>)xy=comparexy=1let(>=)xy=comparexy>-1let(<)xy=comparexy=-1let(<=)xy=comparexy<1let(<?>)c(ord,x,y)=ifc=0thenordxyelsecletcmp_date(y1,m1,d1)(y2,m2,d2)=comparey1y2<?>(compare,m1,m2)<?>(compare,d1,d2)lethash=Utils.Int.hash(* Constructors. *)letltd1d2=(cmp_dated1d2)<0(* [date_ok] returns [true] is the date belongs to the Julian period;
[false] otherwise. *)letdate_okymd=lt(-4713,12,31)(y,m,d)&<(y,m,d)(3268,1,23)(* Coerce month to the interval ]-oo; 12].
Note that the used algorithm of [make] does not require any coercion for
negative months *)letcoerce_monthym=ifm<0theny,m(* (* the below commented lines coerce [m] inside the interval [1;12]
instead of ]-oo;12]*)
let diff_y = (m + 1) / 12 - 1 in
y + diff_y, - 12 * diff_y + m*)elseletpred_m=predminy+pred_m/12,pred_mmod12+1letmakeymd=lety,m=coerce_monthyminifdate_okymdthenleta=(14-m)/12inlety'=y+4800-ainletm'=m+12*a-3iniflt(1582,10,14)(y,m,d)then(* Gregorian calendar *)d+(153*m'+2)/5+y'*365+y'/4-y'/100+y'/400-32045elseiflt(y,m,d)(1582,10,5)then(* Julian calendar *)d+(153*m'+2)/5+y'*365+y'/4-32083elseraiseUndefinedelseraiseOut_of_boundsletlmake~year?(month=1)?(day=1)()=makeyearmonthdayletmake_yeary=makey11letmake_year_monthym=makeym1letcurrent_daydaygmt_hour=lethour=Time_Zone.from_gmt()+gmt_hourin(* change the day according to the time zone *)ifhour<0thenbeginassert(hour>-13);day-1endelseifhour>=24thenbeginassert(hour<36);day+1endelsedayletjan_1_1970=2440588letfrom_unixfloatx=letd=int_of_float(x/.86400.)+jan_1_1970incurrent_dayd(Unix.gmtimex).Unix.tm_hourletfrom_day_of_yearyd=makey1dlettoday()=from_unixfloat(Unix.time())letfrom_jdn=nletto_jdd=dletfrom_mjdx=x+2400001letto_mjdd=d-2400001(*S Useful operations. *)letis_leap_yeary=ify>1582then(* Gregorian calendar *)ymod4=0&&(ymod100<>0||ymod400=0)else(* Julian calendar *)ify>(-45)&&y<=(-8)then(* every year divisible by 3 is a leap year between 45 BC and 9 BC *)ymod3=0elseify<=(-45)||y>=8thenymod4=0else(* no leap year between 8 BC and 7 AD *)false(*S Boolean operations on dates. *)letis_juliand=d<2299161letis_gregoriand=d>=2299161(*S Getters. *)(* [a] and [e] are auxiliary functions for [day_of_month], [month]
and [year]. *)letad=d+32044leted=letc=ifis_juliandthend+32082elseleta=adina-(((4*a+3)/146097)*146097)/4inc-(1461*((4*c+3)/1461))/4letday_of_monthd=lete=edinletm=(5*e+2)/153ine-(153*m+2)/5+1letint_monthd=letm=(5*ed+2)/153inm+3-12*(m/10)letmonthd=month_of_int(int_monthd-1)letyeard=letb,c=ifis_juliandthen0,d+32082elseleta=adinletb=(4*a+3)/146097inb,a-(b*146097)/4inletd=(4*c+3)/1461inlete=c-(1461*d)/4inb*100+d-4800+((5*e+2)/153)/10letint_day_of_weekd=(d+1)mod7letday_of_weekd=day_of_int(int_day_of_weekd)letday_of_yeard=d-make(yeard-1)1231(* [week] implements an algorithm coming from Stefan Potthast. *)letweekd=letd4=(d+31741-(dmod7))mod146097mod36524mod1461inletl=d4/1460in(((d4-l)mod365)+l)/7+1letdays_in_monthd=matchmonthdwith|Jan|Mar|May|Jul|Aug|Oct|Dec->31|Apr|Jun|Sep|Nov->30|Feb->ifis_leap_year(yeard)then29else28(* Boolean operation using some getters. *)letis_leap_dayd=is_leap_year(yeard)&&monthd=Feb&&day_of_monthd=24letis_valid_dateymd=trylett=makeymdinyeart=y&&int_montht=m&&day_of_montht=dwithOut_of_bounds|Undefined->false(*S Period. *)modulePeriod=struct(* Cannot use an [int] : periods on months and years have not a constant
number of days.
For example, if we add a "one year" period [p] to the date 2000-3-12,
[p] corresponds to 366 days (because 2000 is a leap year) and the
resulting date is 2001-3-12 (yep, one year later). But if we add [p] to
the date 1999-3-12, [p] corresponds to 365 days and the resulting date is
2000-3-12 (yep, one year later too). *)type+'aperiod={m(* month *):int;d(* day *):int}constraint'a=[<field]type+'ap='aperiodtypet=fieldperiodletempty={m=0;d=0}letmakeymd={m=12*y+m;d=d}letlmake?(year=0)?(month=0)?(day=0)()=makeyearmonthdayletdayn={emptywithd=n}letweekn={emptywithd=7*n}letmonthn={emptywithm=n}letyearn={emptywithm=12*n}letaddxy={m=x.m+y.m;d=x.d+y.d}letsubxy={m=x.m-y.m;d=x.d-y.d}letoppx={m=-x.m;d=-x.d}(* exactly equivalent to [Pervasives.compare] but more flexible typing *)letcomparexy=letn=comparex.my.minifn=0thencomparex.dy.delsenletequalxy=comparexy=0lethash=Hashtbl.hashexceptionNot_computableletnb_daysp=ifp.m<>0thenraiseNot_computableelsep.dletsafe_nb_daysp=p.dletymdp=p.m/12,p.mmod12,p.dend(*S Arithmetic operations on dates and periods. *)letadddp=lety,m,day=Period.ymdpinmake(yeard+y)(int_monthd+m)(day_of_monthd+day)letsubxy={Period.emptywithPeriod.d=x-y}letprecise_subyx=letrecauxm=ifx+31*m<ythenaux(m+1)elselety'=addx(Period.monthm)inletd=y-y'inifd<0thenletm=m-1in(* don't use [y'] below: [m] changes *)m,d+days_in_month(addx(Period.monthm))elseifd>=days_in_monthy'thenaux(m+1)elsem,dinletm,d=aux((y-x)/31)in{Period.m=m;d=d}letremdp=addd(Period.oppp)letnextd=function|`Year->addd(Period.year1)|`Month->addd(Period.month1)|`Week->addd(Period.day7)|`Day->addd(Period.day1)letprevd=function|`Year->addd(Period.year(-1))|`Month->addd(Period.month(-1))|`Week->addd(Period.day(-7))|`Day->addd(Period.day(-1))(*S Operations on years. *)letsame_calendary1y2=letd=y1-y2inletaux=ifis_leap_yeary1thentrueelseifis_leap_year(y1-1)thendmod6=0||dmod17=0elseifis_leap_year(y1-2)thendmod11=0||dmod17=0elseifis_leap_year(y1-3)thendmod11=0elsefalseindmod28=0||auxletdays_in_year=letdays=[|31;59;90;120;151;181;212;243;273;304;334;365|]infun?(month=Dec)y->letm=int_of_monthmonthinletres=days.(m)inifis_leap_yeary&&m>0thenres+1elseresletweeks_in_yeary=letfirst_day=day_of_week(makey11)inmatchfirst_daywith|Thu->53|Wed->ifis_leap_yearythen53else52|_->52letweek_first_lastwy=letd=makey14in(* January 4th must be in the first week (ISO 8601) *)letd=d-dmod7inletb=d+7*(w-1)inb,6+bletnth_weekday_of_monthymdn=letfirst=makey(int_of_monthm+1)1inletgap=letdiff=int_of_dayd-int_day_of_weekfirstinifdiff>=0thendiff-7elsediffinfirst+7*n+gapletcenturyy=ifymod100=0theny/100elsey/100+1letmilleniumy=ifymod1000=0theny/1000elsey/1000+1letsolar_numbery=(y+8)mod28+1letindictiony=(y+2)mod15+1letgolden_numbery=ymod19+1letepacty=letjulian_epact=(11*(golden_numbery-1))mod30inify<=1582thenjulian_epact(* Julian calendar *)else(* Gregorian calendar *)letc=y/100+1(* century *)in(* 1900 belongs to the 20th century for this algorithm *)abs((julian_epact-(3*c)/4+(8*c+5)/25+8)mod30)(* [easter] implements the algorithm of Oudin (1940) *)leteastery=letg=ymod19inleti,j=ify<=1582then(* Julian calendar *)leti=(19*g+15)mod30ini,(y+y/4+i)mod7else(* Gregorian calendar *)letc=y/100inleth=(c-c/4-(8*c+13)/25+19*g+15)mod30inleti=h-(h/28)*(1-(h/28)*(29/(h+1))*((21-g)/11))ini,(y+y/4+i+2-c+c/4)mod7inletl=i-jinletm=3+(l+40)/44inmakeym(l+28-31*(m/4))letcarnavaly=eastery-48letmardi_grasy=eastery-47letashy=eastery-46letpalmy=eastery-7leteaster_fridayy=eastery-2leteaster_saturdayy=eastery-1leteaster_mondayy=eastery+1letascensiony=eastery+39letwithsundayy=eastery+49letwithmondayy=eastery+50letcorpus_christiy=eastery+60(*S Exported Coercions. *)letfrom_unixtmx=letd=(* current day at GMT *)make(x.Unix.tm_year+1900)(x.Unix.tm_mon+1)x.Unix.tm_mdayincurrent_daydx.Unix.tm_hourletto_unixtmd={Unix.tm_sec=0;Unix.tm_min=0;Unix.tm_hour=0;Unix.tm_mday=day_of_monthd;Unix.tm_mon=int_monthd-1;Unix.tm_year=yeard-1900;Unix.tm_wday=int_day_of_weekd;Unix.tm_yday=day_of_yeard-1;Unix.tm_isdst=false}letto_unixfloatx=float_of_int(x-jan_1_1970)*.86400.(* do not replace [*.] by [*]: the result is bigger than [max_int] ! *)letto_businessd=letw=weekdinlety=lety=yeardinmatchint_monthdwith|1->letx=y-1inifw=weeks_in_yearxthenxelsey|12->ifw=1theny+1elsey|_->yiny,w,day_of_weekdletint_of_dayd=letn=int_of_daydinifn=0then7elsen(* Used by [from_business] *)letfrom_businessywd=ifw<1||w>weeks_in_yearytheninvalid_arg"from_business: bad week";letfirst=trymakey11withOut_of_bounds|Undefined->invalid_arg"from_business: bad date"inletfirst_day=int_day_of_weekfirstinletw=iffirst_day>4thenwelsew-1infirst+w*7+int_of_dayd-first_day(* These coercions redefine those defined at the beginning of the module.
They respect ISO-8601. *)letint_of_day=int_of_dayletday_of_intn=ifn>0&&n<7thenday_of_intnelseifn=7thenday_of_int0elseinvalid_arg"Not a day"letint_of_monthm=int_of_monthm+1letmonth_of_intn=ifn>0&&n<13thenmonth_of_int(n-1)elseinvalid_arg"Not a month"