aboutsummaryrefslogtreecommitdiff
path: root/stdlib/source/library/lux/time.lux
diff options
context:
space:
mode:
Diffstat (limited to 'stdlib/source/library/lux/time.lux')
-rw-r--r--stdlib/source/library/lux/time.lux217
1 files changed, 217 insertions, 0 deletions
diff --git a/stdlib/source/library/lux/time.lux b/stdlib/source/library/lux/time.lux
new file mode 100644
index 000000000..5c043f696
--- /dev/null
+++ b/stdlib/source/library/lux/time.lux
@@ -0,0 +1,217 @@
+(.module:
+ [library
+ [lux #*
+ [abstract
+ [equivalence (#+ Equivalence)]
+ [order (#+ Order)]
+ [enum (#+ Enum)]
+ [codec (#+ Codec)]
+ [monad (#+ Monad do)]]
+ [control
+ [pipe (#+ case>)]
+ ["." try (#+ Try)]
+ ["." exception (#+ exception:)]
+ ["<>" parser
+ ["<.>" text (#+ Parser)]]]
+ [data
+ ["." text ("#\." monoid)]]
+ [math
+ [number
+ ["n" nat ("#\." decimal)]]]
+ [type
+ abstract]]]
+ [/
+ ["." duration (#+ Duration)]])
+
+(template [<name> <singular> <plural>]
+ [(def: #export <name>
+ Nat
+ (.nat (duration.query <singular> <plural>)))]
+
+ [milli_seconds duration.milli_second duration.second]
+ [seconds duration.second duration.minute]
+ [minutes duration.minute duration.hour]
+ [hours duration.hour duration.day]
+ )
+
+(def: limit
+ Nat
+ (.nat (duration.to_millis duration.day)))
+
+(exception: #export (time_exceeds_a_day {time Nat})
+ (exception.report
+ ["Time (in milli-seconds)" (n\encode time)]
+ ["Maximum (in milli-seconds)" (n\encode (dec limit))]))
+
+(def: separator ":")
+
+(def: parse_section
+ (Parser Nat)
+ (<>.codec n.decimal (<text>.exactly 2 <text>.decimal)))
+
+(def: parse_millis
+ (Parser Nat)
+ (<>.either (|> (<text>.at_most 3 <text>.decimal)
+ (<>.codec n.decimal)
+ (<>.after (<text>.this ".")))
+ (\ <>.monad wrap 0)))
+
+(template [<maximum> <parser> <exception> <sub_parser>]
+ [(exception: #export (<exception> {value Nat})
+ (exception.report
+ ["Value" (n\encode value)]
+ ["Minimum" (n\encode 0)]
+ ["Maximum" (n\encode (dec <maximum>))]))
+
+ (def: <parser>
+ (Parser Nat)
+ (do <>.monad
+ [value <sub_parser>]
+ (if (n.< <maximum> value)
+ (wrap value)
+ (<>.lift (exception.throw <exception> [value])))))]
+
+ [..hours parse_hour invalid_hour ..parse_section]
+ [..minutes parse_minute invalid_minute ..parse_section]
+ [..seconds parse_second invalid_second ..parse_section]
+ )
+
+(abstract: #export Time
+ Nat
+
+ {#.doc "Time is defined as milliseconds since the start of the day (00:00:00.000)."}
+
+ (def: #export midnight
+ {#.doc "The instant corresponding to the start of the day: 00:00:00.000"}
+ Time
+ (:abstraction 0))
+
+ (def: #export (from_millis milli_seconds)
+ (-> Nat (Try Time))
+ (if (n.< ..limit milli_seconds)
+ (#try.Success (:abstraction milli_seconds))
+ (exception.throw ..time_exceeds_a_day [milli_seconds])))
+
+ (def: #export to_millis
+ (-> Time Nat)
+ (|>> :representation))
+
+ (implementation: #export equivalence
+ (Equivalence Time)
+
+ (def: (= param subject)
+ (n.= (:representation param) (:representation subject))))
+
+ (implementation: #export order
+ (Order Time)
+
+ (def: &equivalence ..equivalence)
+
+ (def: (< param subject)
+ (n.< (:representation param) (:representation subject))))
+
+ (`` (implementation: #export enum
+ (Enum Time)
+
+ (def: &order ..order)
+
+ (def: succ
+ (|>> :representation inc (n.% ..limit) :abstraction))
+
+ (def: pred
+ (|>> :representation
+ (case> 0 ..limit
+ millis millis)
+ dec
+ :abstraction))))
+
+ (def: #export parser
+ (Parser Time)
+ (let [to_millis (: (-> Duration Nat)
+ (|>> duration.to_millis .nat))
+ hour (to_millis duration.hour)
+ minute (to_millis duration.minute)
+ second (to_millis duration.second)
+ millis (to_millis duration.milli_second)]
+ (do {! <>.monad}
+ [utc_hour ..parse_hour
+ _ (<text>.this ..separator)
+ utc_minute ..parse_minute
+ _ (<text>.this ..separator)
+ utc_second ..parse_second
+ utc_millis ..parse_millis]
+ (wrap (:abstraction
+ ($_ n.+
+ (n.* utc_hour hour)
+ (n.* utc_minute minute)
+ (n.* utc_second second)
+ (n.* utc_millis millis)))))))
+ )
+
+(def: (pad value)
+ (-> Nat Text)
+ (if (n.< 10 value)
+ (text\compose "0" (n\encode value))
+ (n\encode value)))
+
+(def: (adjust_negative space duration)
+ (-> Duration Duration Duration)
+ (if (duration.negative? duration)
+ (duration.merge space duration)
+ duration))
+
+(def: (encode_millis millis)
+ (-> Nat Text)
+ (cond (n.= 0 millis) ""
+ (n.< 10 millis) ($_ text\compose ".00" (n\encode millis))
+ (n.< 100 millis) ($_ text\compose ".0" (n\encode millis))
+ ## (n.< 1,000 millis)
+ ($_ text\compose "." (n\encode millis))))
+
+(type: #export Clock
+ {#hour Nat
+ #minute Nat
+ #second Nat
+ #milli_second Nat})
+
+(def: #export (clock time)
+ (-> Time Clock)
+ (let [time (|> time ..to_millis .int duration.from_millis)
+ [hours time] [(duration.query duration.hour time) (duration.frame duration.hour time)]
+ [minutes time] [(duration.query duration.minute time) (duration.frame duration.minute time)]
+ [seconds millis] [(duration.query duration.second time) (duration.frame duration.second time)]]
+ {#hour (.nat hours)
+ #minute (.nat minutes)
+ #second (.nat seconds)
+ #milli_second (|> millis
+ (..adjust_negative duration.second)
+ duration.to_millis
+ .nat)}))
+
+(def: #export (time clock)
+ (-> Clock (Try Time))
+ (|> ($_ duration.merge
+ (duration.up (get@ #hour clock) duration.hour)
+ (duration.up (get@ #minute clock) duration.minute)
+ (duration.up (get@ #second clock) duration.second)
+ (duration.from_millis (.int (get@ #milli_second clock))))
+ duration.to_millis
+ .nat
+ ..from_millis))
+
+(def: (encode time)
+ (-> Time Text)
+ (let [(^slots [#hour #minute #second #milli_second]) (..clock time)]
+ ($_ text\compose
+ (..pad hour)
+ ..separator (..pad minute)
+ ..separator (..pad second)
+ (..encode_millis milli_second))))
+
+(implementation: #export codec
+ {#.doc (doc "Based on ISO 8601."
+ "For example: 21:14:51.827")}
+ (Codec Text Time)
+
+ (def: encode ..encode)
+ (def: decode (<text>.run ..parser)))