Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file email_date.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200open!CoremoduleTime=Time_float_unixletutc_offset_stringtime~zone=letutc_offset=Time.utc_offsettime~zoneinletis_utc=Time.Span.(=)utc_offsetTime.Span.zeroinifis_utcthen"Z"elseString.concat[(ifTime.Span.(<)utc_offsetTime.Span.zerothen"-"else"+");Time.Ofday.to_string_trimmed(Time.Ofday.of_span_since_start_of_day_exn(Time.Span.absutc_offset))];;letrfc822_date?(zone=forceTime.Zone.local)now=letoffset_string=utc_offset_string~zonenow|>String.filter~f:(func->Base.Char.(<>)c':')inletnow_string=Time.formatnow"%a, %d %b %Y %H:%M:%S"~zoneinsprintf"%s %s"now_stringoffset_string;;openAngstromopenLet_syntax(* Folding whitespace and comments. See RFC2822#3.2.3 *)letcomment=letcomment_text=skip(function(* slight simplification of the RFC *)|'('|')'|'\\'->false|_->true)inletquoted_pair=let%map(_:char)=char'\\'*>any_charin()infix(funcomment->char'('*>skip_many(comment_text<|>quoted_pair<|>comment)<*char')');;letsingle_whitespace_or_comment=comment<|>skipChar.is_whitespaceletfolding_whitespace=skip_many1single_whitespace_or_comment<?>"FWS"letoptional_folding_whitespace=skip_manysingle_whitespace_or_comment<?>"?FWS"letrecskip_min_max~min~maxthing=assert(min<=max);ifmin>0thenthing*>skip_min_max~min:(min-1)~max:(max-1)thingelseifmax>0thenoption()(skip_min_max~min:1~maxthing)elsereturn();;letparse_two_digit_int=consumed(skip_min_max(skipChar.is_digit)~min:2~max:2)>>|Int.of_string;;letparse_two_to_four_digit_int=consumed(skip_min_max(skipChar.is_digit)~min:2~max:4)>>|Int.of_string;;letparse_one_or_two_digit_int=consumed(skip_min_max(skipChar.is_digit)~min:1~max:2)>>|Int.of_string;;letparse_day_of_week=choice(List.mapDay_of_week.all~f:(Fn.composestring_ciDay_of_week.to_string))<?>"day of week";;letparse_month=choice(List.mapMonth.all~f:(funmonth->constmonth<$>string_ci(Month.to_stringmonth)))<?>"month";;letparse_time_zone=letutc_offset=(let%mapnsign=Angstrom.choice[constSign.Pos<$>char'+';constSign.Neg<$>char'-']<?>"sign"andhours=parse_two_digit_int<?>"hours"andminutes=parse_two_digit_int<?>"minutes"inletutc_offset=Time.Span.create~sign~hr:hours~min:minutes()inifnot(Time.Span.betweenutc_offset~low:(Time.Span.negTime.Span.day)~high:Time.Span.day)thenraise_s[%message"The supplied UTC offset is semantically invalid."];utc_offset)<?>"utc offset"inletmilitary_time_zone=constTime.Span.zero<$>(List.init26~f:(funi->Char.of_int_exn(Char.to_int'A'+i)|>Char.to_string)|>List.filter~f:(String.(<>)"J")|>List.map~f:string_ci|>choice)<?>"military zone"inletobsolete_zone=["UT",Time.Span.zero;"GMT",Time.Span.zero;"EST",Time.Span.create~sign:Sign.Neg~hr:5();"EDT",Time.Span.create~sign:Sign.Neg~hr:4();"CST",Time.Span.create~sign:Sign.Neg~hr:6();"CDT",Time.Span.create~sign:Sign.Neg~hr:5();"MST",Time.Span.create~sign:Sign.Neg~hr:7();"MDT",Time.Span.create~sign:Sign.Neg~hr:6();"PST",Time.Span.create~sign:Sign.Neg~hr:8();"PDT",Time.Span.create~sign:Sign.Neg~hr:7()]|>List.map~f:(fun(abbrev,offset)->constoffset<$>string_ciabbrev)|>choice<?>"obsolote zone"inchoice[obsolete_zone;military_time_zone;utc_offset]<?>"time zone";;letuntruncate_yearyear=(* As per https://tools.ietf.org/html/rfc5322#section-4.3 *)ifyear<=49then2000+yearelseifyear<=999then1900+yearelseyear;;letparse_date=(let%mapn()=option()(parse_day_of_week*>option' '(char',')*>folding_whitespace)andday=parse_one_or_two_digit_int<?>"day"<*folding_whitespaceandmonth=parse_monthandyear=folding_whitespace*>(parse_two_to_four_digit_int>>|untruncate_year<?>"year")inDate.create_exn~y:year~m:month~d:day)<?>"date";;letparse_time_of_day=(let%mapnhour=parse_two_digit_int<?>"hour"andminutes=char':'*>parse_two_digit_int<?>"minute"andseconds=option0(char':'*>parse_two_digit_int<?>"second")inTime.Ofday.create~hr:hour~min:minutes~sec:seconds())<?>"time of day";;letrfc2822_date_parser=let%mapndate=parse_date<*folding_whitespaceandtime_of_day=parse_time_of_day<*folding_whitespaceandutc_offset=parse_time_zoneinlettime_no_zone=Time.of_date_ofday~zone:Time.Zone.utcdatetime_of_dayinTime.subtime_no_zoneutc_offset,utc_offset;;(* See https://tools.ietf.org/html/rfc5322#section-3.3 for the full spec. Also note
https://tools.ietf.org/html/rfc5322#appendix-A.5 on whitespace.
https://github.com/moment/moment/blob/022dc038af5ebafafa375f4566fb23366f4e4aa8/src/lib/create/from-string.js#L189
(alongside the RFC), was used as a reference for this implementation. *)letof_string_exn_with_utc_offsetdate=matchparse_string~consume:All(optional_folding_whitespace*>rfc2822_date_parser<*optional_folding_whitespace<*end_of_input)datewith|Oktime_and_utc_offset->time_and_utc_offset|Errormessage->failwith("Failed to parse RFC822 "^message);;letof_string_exndate=lettime,_utc_offset=of_string_exn_with_utc_offsetdateintime;;letof_string_exn_with_time_zonedate=lettime,utc_offset=of_string_exn_with_utc_offsetdatein(* In cases where the utc_offset has minutes, we might be dropping information. Let's
just raise to avoid any possible confusion. We don't really expect to see time
zones with a minute offset in the wild.
FYI: India is the main non-hour time zone (IST, UTC+5:30).
Its probably fine to punt for now, as long as this is only used with internal email flow. *)letutc_offset_parts=Time.Span.to_partsutc_offsetinifutc_offset_parts.min<>0thenraise_s[%message"NOT IMPLEMENTED: Time zones with minute offsets are not yet supported (probably \
IST/UTC+5:30)"(utc_offset:Time.Span.t)];(time,Time.Zone.of_utc_offset~hours:(Sign.to_intutc_offset_parts.sign*utc_offset_parts.hr));;