(.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 (and (i.>= ..min_year year) (i.<= ..max_year year)) (#try.Success (:abstraction raw)) (exception.except ..year_is_out_of_range [(date.year 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 (<>.lift (year.year (.int year))) month (<>.codec n.decimal (.exactly 2 .decimal)) month (<>.lift (month.by_number month)) day_of_month (<>.codec n.decimal (.exactly 2 .decimal)) date (<>.lift (date.date year month day_of_month))] (in (:abstraction date)))))