(.module: [library [lux "*" [abstract [monad {"+" [do]}] [equivalence {"+" [Equivalence]}]] [control ["." try {"+" [Try]}] ["." exception {"+" [exception:]}] ["<>" parser ["<.>" text {"+" [Parser]}]]] [data [text ["%" format]]] [math [number ["n" nat] ["i" int]]] [time ["." date ("#\." equivalence)] ["." year] ["." month]] [type abstract]]]) (def: .public (pad value) (-> Nat Text) (if (n.< 10 value) (%.format "0" (%.nat value)) (%.nat value))) (def: min_year +1,000) (def: max_year +9,999) (exception: .public (year_is_out_of_range {year year.Year}) (exception.report ["Minimum" (%.int ..min_year)] ["Maximum" (%.int ..max_year)] ["Year" (%.int (year.value year))])) (abstract: .public Date {} date.Date (def: .public epoch Date (:abstraction date.epoch)) (def: .public (date raw) (-> date.Date (Try Date)) (let [year (|> raw date.year year.value)] (if (or (i.< ..min_year year) (i.> ..max_year year)) (exception.except ..year_is_out_of_range [(date.year raw)]) (#try.Success (:abstraction raw))))) (def: .public value (-> Date date.Date) (|>> :representation)) (implementation: .public equivalence (Equivalence Date) (def: (= reference subject) (date\= (:representation reference) (:representation subject)))) (def: .public (format value) (%.Format Date) (%.format (|> value :representation date.year year.value .nat %.nat) (|> value :representation date.month month.number ..pad) (|> value :representation date.day_of_month ..pad))) (def: .public parser (Parser Date) (do <>.monad [year (<>.codec n.decimal (.exactly 4 .decimal)) year (<>.lifted (year.year (.int year))) month (<>.codec n.decimal (.exactly 2 .decimal)) month (<>.lifted (month.by_number month)) day_of_month (<>.codec n.decimal (.exactly 2 .decimal)) date (<>.lifted (date.date year month day_of_month))] (in (:abstraction date)))))