123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987moduleDay=structtypet=|Mon|Tue|Wed|Thu|Fri|Sat|Sunletmon=Monlettue=Tueletwed=Wedletthu=Thuletfri=Friletsat=Satletsun=Sunletppppf=function|Mon->Fmt.pfppf"Mon"|Tue->Fmt.pfppf"Tue"|Wed->Fmt.pfppf"Wed"|Thu->Fmt.pfppf"Thu"|Fri->Fmt.pfppf"Fri"|Sat->Fmt.pfppf"Sat"|Sun->Fmt.pfppf"Sun"letto_string=Fmt.to_to_stringppletof_string=function|"Mon"->OkMon|"Tue"->OkTue|"Wed"->OkWed|"Thu"->OkThu|"Fri"->OkFri|"Sat"->OkSat|"Sun"->OkSun|x->Rresult.R.error_msgf"invalid day %s"xletof_string_exnx=matchof_stringxwith|Okv->v|Error(`Msgerr)->invalid_argerrletvx=of_string_exnxletequalab=matcha,bwith|Mon,Mon->true|Tue,Tue->true|Wed,Wed->true|Thu,Thu->true|Fri,Fri->true|Sat,Sat->true|Sun,Sun->true|_,_->falseendmoduleMonth=structtypet=|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Decletjan=Janletfeb=Febletmar=Marletapr=Aprletmay=Mayletjun=Junletjul=Julletaug=Augletsep=Sepletoct=Octletnov=Novletdec=Decletequalab=matcha,bwith|Jan,Jan->true|Feb,Feb->true|Mar,Mar->true|Apr,Apr->true|May,May->true|Jun,Jun->true|Jul,Jul->true|Aug,Aug->true|Sep,Sep->true|Oct,Oct->true|Nov,Nov->true|Dec,Dec->true|_,_->falseletppppf=function|Jan->Fmt.pfppf"Jan"|Feb->Fmt.pfppf"Feb"|Mar->Fmt.pfppf"Mar"|Apr->Fmt.pfppf"Apr"|May->Fmt.pfppf"May"|Jun->Fmt.pfppf"Jun"|Jul->Fmt.pfppf"Jul"|Aug->Fmt.pfppf"Aug"|Sep->Fmt.pfppf"Sep"|Oct->Fmt.pfppf"Oct"|Nov->Fmt.pfppf"Nov"|Dec->Fmt.pfppf"Dec"letto_int=function|Jan->1|Feb->2|Mar->3|Apr->4|May->5|Jun->6|Jul->7|Aug->8|Sep->9|Oct->10|Nov->11|Dec->12letof_int=function|1->OkJan|2->OkFeb|3->OkMar|4->OkApr|5->OkMay|6->OkJun|7->OkJul|8->OkAug|9->OkSep|10->OkOct|11->OkNov|12->OkDec|n->Rresult.R.error_msgf"Invalid number of month: %d"nletof_int_exnx=matchof_intxwith|Okv->v|Error(`Msgerr)->invalid_argerrletto_string=Fmt.to_to_stringppletof_string=function|"Jan"->OkJan|"Feb"->OkFeb|"Mar"->OkMar|"Apr"->OkApr|"May"->OkMay|"Jun"->OkJun|"Jul"->OkJul|"Aug"->OkAug|"Sep"->OkSep|"Oct"->OkOct|"Nov"->OkNov|"Dec"->OkDec|x->Rresult.R.error_msgf"Invalid month %s"xletof_string_exnx=matchof_stringxwith|Okv->v|Error(`Msgerr)->invalid_argerrletvx=of_string_exnxendmoduleZone=structtypet=|UT|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT|Military_zoneofchar|TZofint*intletequalab=matcha,bwith|UT,UT->true|GMT,GMT->true|EST,EST->true|EDT,EDT->true|CST,CST->true|CDT,CDT->true|MST,MST->true|MDT,MDT->true|PST,PST->true|PDT,PDT->true|Military_zonea,Military_zoneb->Char.equalab|TZ(aa,ab),TZ(ba,bb)->aa=ba&&ab=bb|_,_->falseletut=UTletgmt=GMTletest=ESTletedt=EDTletcst=CSTletcdt=CDTletmst=MSTletmdt=MDTletpst=PSTletpdt=PDTletis_military_zone=function|'\065'..'\073'|'\075'..'\090'|'\097'..'\105'|'\107'..'\122'->true|_->falseletmilitary_zone=function|('A'..'I'|'K'..'Z')aschr->Ok(Military_zonechr)|('a'..'i'|'k'..'z')aschr->letchr=Char.chr(Char.codechr-32)inOk(Military_zonechr)|chr->Rresult.R.error_msgf"Invalid military zone '%c'"chrlettzhhmm=if(abshh)>=0&&(abshh)<24&&mm>=0&&mm<60thenOk(TZ(hh,mm))elseRresult.R.error_msgf"Invalid time-zone (hours: %d, minutes: %d)"hhmmletppppf=function|UT->Fmt.pfppf"UT"|GMT->Fmt.pfppf"GMT"|EST->Fmt.pfppf"EST"|EDT->Fmt.pfppf"EDT"|CST->Fmt.pfppf"CST"|CDT->Fmt.pfppf"CDT"|MST->Fmt.pfppf"MST"|MDT->Fmt.pfppf"MDT"|PST->Fmt.pfppf"PST"|PDT->Fmt.pfppf"PDT"|TZ(hh,mm)->Fmt.pfppf"(TZ %02d%02d)"hhmm|Military_zonec->Fmt.pfppf"(Military_zone %c)"cletto_string=function|TZ(hh,mm)->ifhh>=0thenFmt.strf"+%02d%02d"hhmmelseFmt.strf"-%02d%02d"(abshh)mm|Military_zonec->String.make1c|x->Fmt.to_to_stringppxletto_int=function|UT|GMT->00,00|EST->-05,00|EDT->-04,00|CST->-06,00|CDT->-05,00|MST->-07,00|MDT->-06,00|PST->-08,00|PDT->-07,00|TZ(hh,mm)->hh,mm|Military_zonec->matchcwith|'A'->01,00|'B'->02,00|'C'->03,00|'D'->04,00|'E'->05,00|'F'->06,00|'G'->07,00|'H'->08,00|'I'->09,00|'K'->10,00|'L'->11,00|'M'->12,00|'N'->-01,00|'O'->-02,00|'P'->-03,00|'Q'->-04,00|'R'->-05,00|'S'->-06,00|'T'->-07,00|'U'->-08,00|'V'->-09,00|'W'->-10,00|'X'->-11,00|'Y'->-12,00|'Z'->00,00|c->Fmt.invalid_arg"Invalid military zone %c"cletparser_tz=letopenAngstrominletis_digit=function'0'..'9'->true|_->falseinoption'+'(satisfy(function'+'|'-'->true|_->false))>>=funsign->satisfyis_digit>>=funz0->satisfyis_digit>>=funz1->satisfyis_digit>>=funz2->satisfyis_digit>>=funz3->letone=letres=Bytes.create2inBytes.setres0z0;Bytes.setres1z1;Bytes.unsafe_to_stringresinlettwo=letres=Bytes.create2inBytes.setres0z2;Bytes.setres1z3;Bytes.unsafe_to_stringresinletone=ifsign='-'then-int_of_stringoneelseint_of_stringoneinlettwo=int_of_stringtwoinif(absone)>=0&&(absone)<24&&two>=0&&two<60thenreturn(one,two)elsefail"Invalid time-zone"letof_string=function|"UT"->OkUT|"GMT"->OkGMT|"EST"->OkEST|"EDT"->OkEDT|"CST"->OkCST|"CDT"->OkCDT|"MST"->OkMST|"MDT"->OkMDT|"PST"->OkPST|"PDT"->OkPDT|x->matchAngstrom.parse_string~consume:Angstrom.Consume.Allparser_tzxwith|Ok(hh,mm)->Ok(TZ(hh,mm))|Error_->ifString.lengthx=1&&is_military_zonex.[0]thenOk(Military_zonex.[0])elseRresult.R.error_msgf"Invalid time-zone: %S"xletof_string_exnx=matchof_stringxwith|Okv->v|Error(`Msgerr)->invalid_argerrletvx=of_string_exnxendtypet={day:Day.toption;date:int*Month.t*int;time:int*int*intoption;zone:Zone.t}letpp_ptime_day=letf=function|`Mon->Day.Mon|`Thu->Day.Thu|`Tue->Day.Tue|`Wed->Day.Wed|`Fri->Day.Fri|`Sat->Day.Sat|`Sun->Day.SuninFmt.usingfDay.ppletmake?day(y,m,d)(hh,mm,ss)zone=letz=lethh,mm=Zone.to_intzoneinhh*3600+mm*60inletsame_dayaptime_b=matcha,ptime_bwith|Day.Mon,`Mon->true|Day.Thu,`Thu->true|Day.Tue,`Tue->true|Day.Wed,`Wed->true|Day.Fri,`Fri->true|Day.Sat,`Sat->true|Day.Sun,`Sun->true|_,_->falseinletm'=Month.to_intminmatchPtime.of_date_time((y,m',d),((hh,mm,Option.value~default:0ss),z))with|None->Rresult.R.error_msgf"Invalid date"|Somet->letday'=Ptime.weekday~tz_offset_s:ztinmatchdaywith|None->Ok{day=None;date=(d,m,y);time=(hh,mm,ss);zone}|Someday->ifsame_daydayday'thenOk{day=Someday;date=(d,m,y);time=(hh,mm,ss);zone}elseRresult.R.error_msgf"Expected day mismatch (%a <> %a)"Day.ppdaypp_ptime_dayday'letppppf=function|{day=Someday;date=(d,m,y);time=(hh,mm,ss);zone;}->Fmt.pfppf"{@[<hov>day = %a;@ \
date = (@[<hov>%d,@ %a,@ %d@]);@ \
time = (@[<hov>%d,@ %d,@ %d@]);@ \
zone = %a@]}"Day.ppdaydMonth.ppmyhhmm(Option.value~default:0ss)Zone.ppzone|{day=None;date=(d,m,y);time=(hh,mm,ss);zone;}->Fmt.pfppf"{@[<hov>date = (@[<hov>%d,@ %a,@ %d@]);@ \
time = (@[<hov>%d,@ %d,@ %d@]);@ \
zone = %a@]}"dMonth.ppmyhhmm(Option.value~default:0ss)Zone.ppzoneletto_ptimedate=letz=lethh,mm=Zone.to_intdate.zoneinhh*3600+mm*60inletm=let(_,m,_)=date.dateinMonth.to_intminlet(d,_,y)=date.dateinlet(hh,mm,ss)=date.timeinletss=Option.value~default:0ssinmatchPtime.of_date_time((y,m,d),((hh,mm,ss),z))with|Someptime->Okptime|None->Rresult.R.error_msgf"Invalid date: %a"ppdateletof_ptime~zoneptime=lettz_offset_s=let(hh,mm)=Zone.to_intzoneinhh*3600+mm*60inlet(y,m,d),((hh,mm,ss),_)=Ptime.to_date_time~tz_offset_sptimeinletdate=(y,(Month.of_int_exnm),d)inletday=matchPtime.weekday~tz_offset_sptimewith|`Mon->Day.Mon|`Tue->Day.Tue|`Wed->Day.Wed|`Thu->Day.Thu|`Fri->Day.Fri|`Sat->Day.Sat|`Sun->Day.Suninmatchmake~daydate(hh,mm,Somess)zonewith|Okdate_time->date_time|Error_->assertfalse(* XXX(dinosaure): error can reach if [Ptime.of_date_time] fails (but values
come from [Ptime.to_date_time]), so this should not fail. Then, an other path
is when we define a [?day] and it does not correspond to what [Ptime] expecs.
Of course, this call, we did not notice [?day]. *)letcompareab=matchto_ptimea,to_ptimebwith|Oka,Okb->Ptime.compareab|Error(`Msgerr),_->failwitherr|_,Error(`Msgerr)->failwitherrletequal_optionequalab=matcha,bwith|Somea,Someb->equalab|None,None->true|_,_->falseletequal_intab=a=bletequal_date(a_day,a_month,a_year)(b_day,b_month,b_year)=a_day=b_day&&Month.equala_monthb_month&&a_year=b_yearletequal_time(a_hour,a_min,a_sec)(b_hour,b_min,b_sec)=a_hour=b_hour&&a_min=b_min&&equal_optionequal_inta_secb_secletequalab=equal_optionDay.equala.dayb.day&&equal_datea.dateb.date&&equal_timea.timeb.time&&Zone.equala.zoneb.zonemoduleDecoder=structopenAngstromletis_digit=function'0'..'9'->true|_->falseletis_wsp=function' '|'\t'->true|_->falselettwo_digit=lift2(funab->letres=Bytes.create2inBytes.unsafe_setres0a;Bytes.unsafe_setres1b;Bytes.unsafe_to_stringres)(satisfyis_digit)(satisfyis_digit)letfour_digit=lift4(funabcd->letres=Bytes.create4inBytes.unsafe_setres0a;Bytes.unsafe_setres1b;Bytes.unsafe_setres2c;Bytes.unsafe_setres3d;Bytes.unsafe_to_stringres)(satisfyis_digit)(satisfyis_digit)(satisfyis_digit)(satisfyis_digit)letat_least_n_digitn=take_while1is_digit>>=funres->ifString.lengthres>=nthenreturnreselsefail"at_least_n_digit"letone_or_two_digit=satisfyis_digit>>=funone->peek_char>>=function|Sometwowhenis_digittwo->letres=Bytes.create2inBytes.unsafe_setres0one;Bytes.unsafe_setres1two;advance1*>return(Bytes.unsafe_to_stringres)|_->return(String.make1one)(* From RFC 2822
obs-hour = [CFWS] 2DIGIT [CFWS]
From RFC 5322
obs-hour = [CFWS] 2DIGIT [CFWS]
*)letobs_hour=skip_whileis_wsp*>two_digit<*skip_whileis_wsp>>|int_of_string(* From RFC 2822
obs-minute = [CFWS] 2DIGIT [CFWS]
From RFC 5322
obs-minute = [CFWS] 2DIGIT [CFWS]
*)letobs_minute=skip_whileis_wsp*>two_digit<*skip_whileis_wsp>>|int_of_string(* From RFC 2822
obs-second = [CFWS] 2DIGIT [CFWS]
From RFC 5322
obs-second = [CFWS] 2DIGIT [CFWS]
*)letobs_second=skip_whileis_wsp*>two_digit<*skip_whileis_wsp>>|int_of_string(* From RFC 2822
hour = 2DIGIT / obs-hour
From RFC 5322
hour = 2DIGIT / obs-hour
*)lethour=obs_hour<|>(two_digit>>|int_of_string)(* From RFC 2822
minute = 2DIGIT / obs-minute
From RFC 5322
minute = 2DIGIT / obs-minute
*)letminute=obs_minute<|>(two_digit>>|int_of_string)(* From RFC 2822
second = 2DIGIT / obs-second
From RFC 5322
second = 2DIGIT / obs-second
*)letsecond=obs_second<|>(two_digit>>|int_of_string)(* From RFC 2822
obs-year = [CFWS] 2*DIGIT [CFWS]
From RFC 5322
obs-year = [CFWS] 2*DIGIT [CFWS]
*)letobs_year=skip_whileis_wsp*>at_least_n_digit2<*skip_whileis_wsp>>|int_of_string(* From RFC 2822
year = 4*DIGIT / obs-year
Where a two or three digit year occurs in a date, the year is to be
interpreted as follows: If a two digit year is encountered whose
value is between 00 and 49, the year is interpreted by adding 2000,
ending up with a value between 2000 and 2049. If a two digit year is
encountered with a value between 50 and 99, or any three digit year
is encountered, the year is interpreted by adding 1900.
Differences from Earlier Specifications
3. Four or more digits allowed for year.
15. Two digit years not allowed.*
16. Three digit years interpreted, but not allowed for generation.*
From RFC 5322
year = (FWS 4*DIGIT FWS) / obs-year
Where a two or three digit year occurs in a date, the year is to be
interpreted as follows: If a two digit year is encountered whose
value is between 00 and 49, the year is interpreted by adding 2000,
ending up with a value between 2000 and 2049. If a two digit year is
encountered with a value between 50 and 99, or any three digit year
is encountered, the year is interpreted by adding 1900.
Differences from Earlier Specifications
3. Four or more digits allowed for year.
15. Two digit years not allowed.*
16. Three digit years interpreted, but not allowed for generation.*
*)letyear=skip_whileis_wsp*>at_least_n_digit4<*skip_whileis_wsp>>|int_of_string<|>obs_year(* From RFC 2822
obs-day = [CFWS] 1*2DIGIT [CFWS]
From RFC 5322
obs-day = [CFWS] 1*2DIGIT [CFWS]
*)letobs_day=skip_whileis_wsp*>one_or_two_digit<*skip_whileis_wsp>>|int_of_string(* From RFC 2822
day = ([FWS] 1*2DIGIT) / obs-day
From RFC 5322
day = ([FWS] 1*2DIGIT FWS) / obs-day
*)letday=obs_day<|>(skip_whileis_wsp*>one_or_two_digit<*skip_whileis_wsp>>|int_of_string)(* From RFC 822
month = "Jan" / "Feb" / "Mar" / "Apr"
/ "May" / "Jun" / "Jul" / "Aug"
/ "Sep" / "Oct" / "Nov" / "Dec"
From RFC 2822
month = (FWS month-name FWS) / obs-month
month-name = "Jan" / "Feb" / "Mar" / "Apr" /
"May" / "Jun" / "Jul" / "Aug" /
"Sep" / "Oct" / "Nov" / "Dec"
From RFC 5322
month = "Jan" / "Feb" / "Mar" / "Apr" /
"May" / "Jun" / "Jul" / "Aug" /
"Sep" / "Oct" / "Nov" / "Dec"
*)letmonth=string"Jan"*>returnMonth.Jan<|>string"Feb"*>returnMonth.Feb<|>string"Mar"*>returnMonth.Mar<|>string"Apr"*>returnMonth.Apr<|>string"May"*>returnMonth.May<|>string"Jun"*>returnMonth.Jun<|>string"Jul"*>returnMonth.Jul<|>string"Aug"*>returnMonth.Aug<|>string"Sep"*>returnMonth.Sep<|>string"Oct"*>returnMonth.Oct<|>string"Nov"*>returnMonth.Nov<|>string"Dec"*>returnMonth.Dec(* From RFC 822
day = "Mon" / "Tue" / "Wed" / "Thu"
/ "Fri" / "Sat" / "Sun"
From RFC 2822
day-name = "Mon" / "Tue" / "Wed" / "Thu" /
"Fri" / "Sat" / "Sun"
From RFC 5322
day-name = "Mon" / "Tue" / "Wed" / "Thu" /
"Fri" / "Sat" / "Sun"
*)letday_name=string"Mon"*>returnDay.Mon<|>string"Tue"*>returnDay.Tue<|>string"Wed"*>returnDay.Wed<|>string"Thu"*>returnDay.Thu<|>string"Fri"*>returnDay.Fri<|>string"Sat"*>returnDay.Sat<|>string"Sun"*>returnDay.Sun(* From RFC 2822
obs-day-of-week = [CFWS] day-name [CFWS]
From RFC 5322
obs-day-of-week = [CFWS] day-name [CFWS]
*)letobs_day_of_week=skip_whileis_wsp*>day_name<*skip_whileis_wsp(* From RFC 2822
day-of-week = ([FWS] day-name) / obs-day-of-week
From RFC 5322
day-of-week = ([FWS] day-name) / obs-day-of-week
*)letday_of_week=obs_day_of_week<|>skip_whileis_wsp*>day_name(* From RFC 822
date = 1*2DIGIT month 2DIGIT ; day month year
; e.g. 20 Jun 82
From RFC 2822
date = day month year
From RFC 5322
date = day month year
*)letdate=lift3(fundaymonthyear->(day,month,year))(day<?>"day")(month<?>"month")(year<?>"year")(* From RFC 822
hour = 2DIGIT ":" 2DIGIT [":" 2DIGIT]
From RFC 2822
time-of-day = hour ":" minute [ ":" second ]
From RFC 5322
time-of-day = hour ":" minute [ ":" second ]
*)lettime_of_day=hour<?>"hour">>=funhour->char':'*>minute<?>"minute">>=funminute->optionNone(skip_whileis_wsp*>char':'*>second<?>"second">>|funsecond->Somesecond)>>|funsecond->(hour,minute,second)(* From RFC 822
zone = "UT" / "GMT" ; Universal Time
; North American : UT
/ "EST" / "EDT" ; Eastern: - 5/ - 4
/ "CST" / "CDT" ; Central: - 6/ - 5
/ "MST" / "MDT" ; Mountain: - 7/ - 6
/ "PST" / "PDT" ; Pacific: - 8/ - 7
/ 1ALPHA ; Military: Z = UT;
; A:-1; (J not used)
; M:-12; N:+1; Y:+12
/ ( ("+" / "-") 4DIGIT ) ; Local differential
; hours+min. (HHMM)
Time zone may be indicated in several ways. "UT" is Univer-
sal Time (formerly called "Greenwich Mean Time"); "GMT" is per-
mitted as a reference to Universal Time. The military standard
uses a single character for each zone. "Z" is Universal Time.
"A" indicates one hour earlier, and "M" indicates 12 hours ear-
lier; "N" is one hour later, and "Y" is 12 hours later. The
letter "J" is not used. The other remaining two forms are taken
from ANSI standard X3.51-1975. One allows explicit indication of
the amount of offset from UT; the other uses common 3-character
strings for indicating time zones in North America.
From RFC 2822
obs-zone = "UT" / "GMT" / ; Universal Time
"EST" / "EDT" / ; Eastern: - 5/ - 4
"CST" / "CDT" / ; Central: - 6/ - 5
"MST" / "MDT" / ; Mountain: - 7/ - 6
"PST" / "PDT" / ; Pacific: - 8/ - 7
;
%d65-73 / ; Military zones - "A"
%d75-90 / ; through "I" and "K"
%d97-105 / ; through "Z", both
%d107-122 ; upper and lower case
In the obsolete time zone, "UT" and "GMT" are indications of
"Universal Time" and "Greenwich Mean Time" respectively and are both
semantically identical to "+0000".
The remaining three character zones are the US time zones. The first
letter, "E", "C", "M", or "P" stands for "Eastern", "Central",
"Mountain" and "Pacific". The second letter is either "S" for
"Standard" time, or "D" for "Daylight" (or summer) time. Their
interpretations are as follows:
EDT is semantically equivalent to -0400
EST is semantically equivalent to -0500
CDT is semantically equivalent to -0500
CST is semantically equivalent to -0600
MDT is semantically equivalent to -0600
MST is semantically equivalent to -0700
PDT is semantically equivalent to -0700
PST is semantically equivalent to -0800
The 1 character military time zones were defined in a non-standard
way in [RFC822] and are therefore unpredictable in their meaning.
The original definitions of the military zones "A" through "I" are
equivalent to "+0100" through "+0900" respectively; "K", "L", and "M"
are equivalent to "+1000", "+1100", and "+1200" respectively; "N"
through "Y" are equivalent to "-0100" through "-1200" respectively;
and "Z" is equivalent to "+0000". However, because of the error in
[RFC822], they SHOULD all be considered equivalent to "-0000" unless
there is out-of-band information confirming their meaning.
Other multi-character (usually between 3 and 5) alphabetic time zones
have been used in Internet messages. Any such time zone whose
meaning is not known SHOULD be considered equivalent to "-0000"
unless there is out-of-band information confirming their meaning.
From RFC 5322
obs-zone = "UT" / "GMT" / ; Universal Time
; North American UT
; offsets
"EST" / "EDT" / ; Eastern: - 5/ - 4
"CST" / "CDT" / ; Central: - 6/ - 5
"MST" / "MDT" / ; Mountain: - 7/ - 6
"PST" / "PDT" / ; Pacific: - 8/ - 7
;
%d65-73 / ; Military zones - "A"
%d75-90 / ; through "I" and "K"
%d97-105 / ; through "Z", both
%d107-122 ; upper and lower case
Where a two or three digit year occurs in a date, the year is to be
interpreted as follows: If a two digit year is encountered whose
value is between 00 and 49, the year is interpreted by adding 2000,
ending up with a value between 2000 and 2049. If a two digit year is
encountered with a value between 50 and 99, or any three digit year
is encountered, the year is interpreted by adding 1900.
In the obsolete time zone, "UT" and "GMT" are indications of
"Universal Time" and "Greenwich Mean Time", respectively, and are
both semantically identical to "+0000".
The remaining three character zones are the US time zones. The first
letter, "E", "C", "M", or "P" stands for "Eastern", "Central",
"Mountain", and "Pacific". The second letter is either "S" for
"Standard" time, or "D" for "Daylight Savings" (or summer) time.
Their interpretations are as follows:
EDT is semantically equivalent to -0400
EST is semantically equivalent to -0500
CDT is semantically equivalent to -0500
CST is semantically equivalent to -0600
MDT is semantically equivalent to -0600
MST is semantically equivalent to -0700
PDT is semantically equivalent to -0700
PST is semantically equivalent to -0800
The 1 character military time zones were defined in a non-standard
way in [RFC0822] and are therefore unpredictable in their meaning.
The original definitions of the military zones "A" through "I" are
equivalent to "+0100" through "+0900", respectively; "K", "L", and
"M" are equivalent to "+1000", "+1100", and "+1200", respectively;
"N" through "Y" are equivalent to "-0100" through "-1200".
respectively; and "Z" is equivalent to "+0000". However, because of
the error in [RFC0822], they SHOULD all be considered equivalent to
"-0000" unless there is out-of-band information confirming their
meaning.
Other multi-character (usually between 3 and 5) alphabetic time zones
have been used in Internet messages. Any such time zone whose
meaning is not known SHOULD be considered equivalent to "-0000"
unless there is out-of-band information confirming their meaning.
*)letobs_zone=string"UT"*>returnZone.UT<|>string"GMT"*>returnZone.GMT<|>string"EST"*>returnZone.EST<|>string"EDT"*>returnZone.EDT<|>string"CST"*>returnZone.CST<|>string"CDT"*>returnZone.CDT<|>string"MST"*>returnZone.MST<|>string"MDT"*>returnZone.MDT<|>string"PST"*>returnZone.PST<|>string"PDT"*>returnZone.PDT<|>(satisfyZone.is_military_zone>>=funz->return(Zone.Military_zonez))(* From RFC 2822
zone = (( "+" / "-" ) 4DIGIT) / obs-zone
The zone specifies the offset from Coordinated Universal Time (UTC,
formerly referred to as "Greenwich Mean Time") that the date and
time-of-day represent. The "+" or "-" indicates whether the
time-of-day is ahead of (i.e., east of) or behind (i.e., west of)
Universal Time. The first two digits indicate the number of hours
difference from Universal Time, and the last two digits indicate the
number of minutes difference from Universal Time. (Hence, +hhmm
means +(hh * 60 + mm) minutes, and -hhmm means -(hh * 60 + mm)
minutes). The form "+0000" SHOULD be used to indicate a time zone at
Universal Time. Though "-0000" also indicates Universal Time, it is
used to indicate that the time was generated on a system that may be
in a local time zone other than Universal Time and therefore
indicates that the date-time contains no information about the local
time zone.
From RFC 5322
zone = (FWS ( "+" / "-" ) 4DIGIT) / obs-zone
The zone specifies the offset from Coordinated Universal Time (UTC,
formerly referred to as "Greenwich Mean Time") that the date and
time-of-day represent. The "+" or "-" indicates whether the time-of-
day is ahead of (i.e., east of) or behind (i.e., west of) Universal
Time. The first two digits indicate the number of hours difference
from Universal Time, and the last two digits indicate the number of
additional minutes difference from Universal Time. (Hence, +hhmm
means +(hh * 60 + mm) minutes, and -hhmm means -(hh * 60 + mm)
minutes). The form "+0000" SHOULD be used to indicate a time zone at
Universal Time. Though "-0000" also indicates Universal Time, it is
used to indicate that the time was generated on a system that may be
in a local time zone other than Universal Time and that the date-time
contains no information about the local time zone.
*)letzone=(* XXX(dinosaure): we clearly have a bug in this place. Indeed, ABNF expects
an explicit space between [zone] and [time_of_day]. However, if we see
[second] or [minute], they are surrounded by [CFWS]. That mean, they
consume trailing spaces. If we explicitly expect [FWS] here, we will fail -
mostly because this expected space is a part of [minute] or [second]. To
avoid an error, [FWS] is optional but a better way should to check if we
consumed at least one space before [zone]. *)(skip_whileis_wsp*>satisfy(function'+'|'-'->true|_->false)<?>"sign">>=(funsign->four_digit<?>"four-digit">>|funzone->letone=ifsign='-'then-int_of_string(String.subzone02)elseint_of_string(String.subzone02)inlettwo=int_of_string(String.subzone22)inZone.TZ(one,two)))<|>(skip_whileis_wsp*>obs_zone)(* From RFC 822
time = hour zone ; ANSI and Military
From RFC 2822
time = time-of-day FWS zone
From RFC 5322
time = time-of-day zone
*)lettime=lift2(funtimezone->(time,zone))(time_of_day<?>"time-of-day")(zone<?>"zone")(* From RFC 822
date-time = [ day "," ] date time ; dd mm yy
; hh:mm:ss zzz
From RFC 2822
Date and time occur in several header fields. This section specifies
the syntax for a full date and time specification. Though folding
white space is permitted throughout the date-time specification, it
is RECOMMENDED that a single space be used in each place that FWS
appears (whether it is required or optional); some older
implementations may not interpret other occurrences of folding white
space correctly.
date-time = [ day-of-week "," ] date FWS time [CFWS]
From RFC 5322
Date and time values occur in several header fields. This section
specifies the syntax for a full date and time specification. Though
folding white space is permitted throughout the date-time
specification, it is RECOMMENDED that a single space be used in each
place that FWS appears (whether it is required or optional); some
older implementations will not interpret longer sequences of folding
white space correctly.
date-time = [ day-of-week "," ] date time [CFWS]
*)letdate_time=lift3(fundaydate(time,zone)->{day;date;time;zone})(optionNone(day_of_week>>=funday->char','*>return(Someday)))datetime<*skip_whileis_wspendmoduleEncoder=structopenPrettymletdayppf=letday=usingDay.to_stringstringinevalppf[!!day;char$',';fws]letmonth=usingMonth.to_stringstringlettimeppf(hours,minutes,seconds)=letstring_of_number=Fmt.strf"%02d"inletnumberppfx=evalppf[cut;!!(usingstring_of_numberstring);cut]xinmatchsecondswith|Someseconds->evalppf[tbox1;!!number;char$':';!!number;char$':';!!number;close]hoursminutesseconds|None->evalppf[tbox1;!!number;char$':';!!number;close]hoursminutesletzone=usingZone.to_stringstringletint=usingstring_of_intstringletdateppft=let(d,m,y)=t.dateinevalppf[tbox1;!!(optionday);!!int;fws;!!month;fws;!!int;fws;!!time;fws;!!zone;close]t.daydmyt.timet.zoneend