aboutsummaryrefslogtreecommitdiff
path: root/stdlib/source/lux/time/instant.lux
diff options
context:
space:
mode:
Diffstat (limited to 'stdlib/source/lux/time/instant.lux')
-rw-r--r--stdlib/source/lux/time/instant.lux349
1 files changed, 119 insertions, 230 deletions
diff --git a/stdlib/source/lux/time/instant.lux b/stdlib/source/lux/time/instant.lux
index ba451ef18..fd842aff6 100644
--- a/stdlib/source/lux/time/instant.lux
+++ b/stdlib/source/lux/time/instant.lux
@@ -2,15 +2,15 @@
[lux #*
[abstract
[equivalence (#+ Equivalence)]
- ["." order (#+ Order)]
+ [order (#+ Order)]
[enum (#+ Enum)]
- codec
- [monad (#+ do Monad)]]
+ [codec (#+ Codec)]
+ [monad (#+ Monad do)]]
[control
[io (#+ IO io)]
- ["." try (#+ Try)]
- ["p" parser
- ["l" text (#+ Parser)]]]
+ ["." exception (#+ exception:)]
+ ["<>" parser
+ ["<t>" text (#+ Parser)]]]
[data
["." maybe]
[number
@@ -18,15 +18,16 @@
["i" int ("#@." decimal)]]
["." text ("#@." monoid)]
[collection
- ["." list ("#@." fold)]
- ["." row (#+ Row row) ("#@." functor fold)]]]
+ ["." row]
+ ["." list ("#@." fold)]]]
[type
abstract]]
[//
["." duration (#+ Duration)]
- ["." date (#+ Date)]
+ ["." year (#+ Year)]
["." month (#+ Month)]
- ["." day (#+ Day)]])
+ ["." day (#+ Day)]
+ ["." date (#+ Date)]])
(abstract: #export Instant
Int
@@ -85,52 +86,7 @@
(def: #export epoch
{#.doc "The instant corresponding to 1970-01-01T00:00:00Z"}
Instant
- (from-millis +0))
-
-## Codec::encode
-(def: (divisible? factor input)
- (-> Int Int Bit)
- (|> input (i.% factor) (i.= +0)))
-
-(def: (leap-year? year)
- (-> Int Bit)
- (and (divisible? +4 year)
- (or (not (divisible? +100 year))
- (divisible? +400 year))))
-
-(def: epoch-year Int +1970)
-
-(def: (find-year now)
- (-> Instant [Int Duration])
- (loop [reference epoch-year
- time-left (relative now)]
- (let [year (if (leap-year? reference)
- duration.leap-year
- duration.normal-year)]
- (if (i.= +0 (duration.query year time-left))
- [reference time-left]
- (if (order.>= duration.order duration.empty time-left)
- (recur (inc reference) (duration.merge (duration.inverse year) time-left))
- (recur (dec reference) (duration.merge year time-left)))
- ))))
-
-(def: (find-month months time)
- (-> (Row Nat) Duration [Nat Duration])
- (if (order.>= duration.order duration.empty time)
- (row@fold (function (_ month-days [current-month time-left])
- (let [month-duration (duration.scale-up month-days duration.day)]
- (if (i.= +0 (duration.query month-duration time-left))
- [current-month time-left]
- [(inc current-month) (duration.merge (duration.inverse month-duration) time-left)])))
- [0 time]
- months)
- (row@fold (function (_ month-days [current-month time-left])
- (let [month-duration (duration.scale-up month-days duration.day)]
- (if (i.= +0 (duration.query month-duration time-left))
- [current-month time-left]
- [(dec current-month) (duration.merge month-duration time-left)])))
- [11 time]
- (row.reverse months))))
+ (..from-millis +0))
(def: (pad value)
(-> Nat Text)
@@ -152,194 +108,127 @@
## (n.< 1,000 millis)
($_ text@compose "." (n@encode millis))))
-(def: seconds-per-day Int (duration.query duration.second duration.day))
-(def: days-up-to-epoch Int +719468)
-
-(def: (extract-date instant)
- (-> Instant [[Int Int Int] Duration])
- (let [offset (relative instant)
- seconds (duration.query duration.second offset)
- z (|> seconds (i./ seconds-per-day) (i.+ days-up-to-epoch))
- era (i./ +146097
- (if (i.>= +0 z)
- z
- (i.- +146096 z)))
- days-of-era (|> z (i.- (i.* +146097 era)))
- years-of-era (|> days-of-era
- (i.- (i./ +1460 days-of-era))
- (i.+ (i./ +36524 days-of-era))
- (i.- (i./ +146096 days-of-era))
- (i./ +365))
- year (|> years-of-era (i.+ (i.* +400 era)))
- days-of-year (|> days-of-era
- (i.- (|> (i.* +365 years-of-era)
- (i.+ (i./ +4 years-of-era))
- (i.- (i./ +100 years-of-era)))))
- day-time (duration.frame duration.day offset)
- days-of-year (if (order.>= duration.order duration.empty day-time)
- days-of-year
- (dec days-of-year))
- mp (|> days-of-year (i.* +5) (i.+ +2) (i./ +153))
- day (|> days-of-year
- (i.- (|> mp (i.* +153) (i.+ +2) (i./ +5)))
- (i.+ +1))
- month (|> mp
- (i.+ (if (i.< +10 mp)
- +3
- -9)))
- year (if (i.<= +2 month)
- (inc year)
- year)]
- [[year month day]
- day-time]))
+(def: millis-per-day
+ (duration.query duration.milli-second duration.day))
+
+(def: (date-time instant)
+ (-> Instant [Date Duration])
+ (let [offset (..to-millis instant)
+ bce? (i.< +0 offset)
+ [days day-time] (if bce?
+ (let [[days millis] (i./% ..millis-per-day offset)]
+ (case millis
+ +0 [days millis]
+ _ [(dec days) (i.+ ..millis-per-day millis)]))
+ (i./% ..millis-per-day offset))]
+ [(date.from-days days)
+ (duration.from-millis day-time)]))
+
+(template [<value> <definition>]
+ [(def: <definition> Text <value>)]
+
+ ["T" date-suffix]
+
+ [":" time-separator]
+ ["Z" time-suffix]
+ )
-## Based on this: https://stackoverflow.com/a/42936293/6823464
-(def: #export (to-text instant)
+(def: #export (encode instant)
(-> Instant Text)
- (let [[[year month day] day-time] (extract-date instant)
- day-time (if (order.>= duration.order duration.empty day-time)
- day-time
- (duration.merge duration.day day-time))
- [hours day-time] [(duration.query duration.hour day-time) (duration.frame duration.hour day-time)]
- [minutes day-time] [(duration.query duration.minute day-time) (duration.frame duration.minute day-time)]
- [seconds millis] [(duration.query duration.second day-time) (duration.frame duration.second day-time)]]
- ($_ text@compose (i@encode year) "-" (pad (.nat month)) "-" (pad (.nat day)) "T"
- (pad (.nat hours)) ":" (pad (.nat minutes)) ":" (pad (.nat seconds))
+ (let [[date time] (..date-time instant)
+ time (if (:: duration.order < duration.empty time)
+ (duration.merge duration.day time)
+ time)
+ [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)]]
+ ($_ text@compose
+ (:: date.codec encode date)
+ ..date-suffix (..pad (.nat hours))
+ ..time-separator (..pad (.nat minutes))
+ ..time-separator (..pad (.nat seconds))
(|> millis
- (adjust-negative duration.second)
+ (..adjust-negative duration.second)
duration.to-millis
.nat
- encode-millis)
- "Z")))
-
-## Codec::decode
-(def: lex-year
- (Parser Int)
- (do p.monad
- [sign (p.or (l.this "-") (l.this "+"))
- raw-year (p.codec i.decimal (l.many l.decimal))
- #let [signum (case sign
- (#.Left _) -1
- (#.Right _) +1)]]
- (wrap (i.* signum raw-year))))
-
-(def: lex-section
- (Parser Int)
- (p.codec i.decimal (l.exactly 2 l.decimal)))
-
-(def: lex-millis
- (Parser Int)
- (p.either (|> (l.at-most 3 l.decimal)
- (p.codec i.decimal)
- (p.after (l.this ".")))
- (:: p.monad wrap +0)))
-
-(def: (leap-years year)
- (-> Int Int)
- (|> (i./ +4 year)
- (i.- (i./ +100 year))
- (i.+ (i./ +400 year))))
-
-## Based on: https://stackoverflow.com/a/3309340/6823464
-## (def: lex-instant
-## (Parser Instant)
-## (do p.monad
-## [utc-year lex-year
-## _ (l.this "-")
-## utc-month lex-section
-## _ (p.assert "Invalid month."
-## (and (i.>= +1 utc-month)
-## (i.<= +12 utc-month)))
-## #let [months (if (leap-year? utc-year)
-## date.leap-year-months
-## date.common-months)
-## month-days (|> months
-## (row.nth (.nat (dec utc-month)))
-## maybe.assume)]
-## _ (l.this "-")
-## utc-day lex-section
-## _ (p.assert "Invalid day."
-## (and (i.>= +1 utc-day)
-## (i.<= (.int month-days) utc-day)))
-## _ (l.this "T")
-## utc-hour lex-section
-## _ (p.assert "Invalid hour."
-## (and (i.>= +0 utc-hour)
-## (i.<= +23 utc-hour)))
-## _ (l.this ":")
-## utc-minute lex-section
-## _ (p.assert "Invalid minute."
-## (and (i.>= +0 utc-minute)
-## (i.<= +59 utc-minute)))
-## _ (l.this ":")
-## utc-second lex-section
-## _ (p.assert "Invalid second."
-## (and (i.>= +0 utc-second)
-## (i.<= +59 utc-second)))
-## utc-millis lex-millis
-## _ (l.this "Z")
-## #let [years-since-epoch (i.- epoch-year utc-year)
-## previous-leap-days (i.- (leap-years epoch-year)
-## (leap-years (dec utc-year)))
-## year-days-so-far (|> (i.* +365 years-since-epoch)
-## (i.+ previous-leap-days))
-## month-days-so-far (|> months
-## row.to-list
-## (list.take (.nat (dec utc-month)))
-## (list@fold n.+ 0))
-## total-days (|> year-days-so-far
-## (i.+ (.int month-days-so-far))
-## (i.+ (dec utc-day)))]]
-## (wrap (|> epoch
-## (shift (duration.scale-up total-days duration.day))
-## (shift (duration.scale-up utc-hour duration.hour))
-## (shift (duration.scale-up utc-minute duration.minute))
-## (shift (duration.scale-up utc-second duration.second))
-## (shift (duration.scale-up utc-millis duration.milli))))))
-
-## (def: (decode input)
-## (-> Text (Try Instant))
-## (l.run input lex-instant))
+ ..encode-millis)
+ ..time-suffix)))
+
+(def: parse-section
+ (Parser Nat)
+ (<>.codec n.decimal (<t>.exactly 2 <t>.decimal)))
+
+(def: parse-millis
+ (Parser Nat)
+ (<>.either (|> (<t>.at-most 3 <t>.decimal)
+ (<>.codec n.decimal)
+ (<>.after (<t>.this ".")))
+ (:: <>.monad wrap 0)))
+
+(template [<minimum> <maximum> <parser> <exception>]
+ [(exception: #export (<exception> {value Nat})
+ (exception.report
+ ["Value" (n@encode value)]
+ ["Minimum" (n@encode <minimum>)]
+ ["Maximum" (n@encode <maximum>)]))
+
+ (def: <parser>
+ (Parser Nat)
+ (do <>.monad
+ [value ..parse-section]
+ (if (and (n.>= <minimum> value)
+ (n.<= <maximum> value))
+ (wrap value)
+ (<>.lift (exception.throw <exception> [value])))))]
+
+ [0 23 parse-hour invalid-hour]
+ [0 59 parse-minute invalid-minute]
+ [0 59 parse-second invalid-second]
+ )
-## (structure: #export _
-## {#.doc (doc "Based on ISO 8601."
-## "For example: 2017-01-15T21:14:51.827Z")}
-## (Codec Text Instant)
-## (def: encode encode)
-## (def: decode decode))
+(def: parser
+ (Parser Instant)
+ (do {@ <>.monad}
+ [days (:: @ map date.days date.parser)
+ _ (<t>.this ..date-suffix)
+ utc-hour (<>.before (<t>.this ..time-separator)
+ ..parse-hour)
+ utc-minute (<>.before (<t>.this ..time-separator)
+ ..parse-minute)
+ utc-second ..parse-second
+ utc-millis (<>.before (<t>.this ..time-suffix)
+ ..parse-millis)]
+ (wrap (|> (if (i.< +0 days)
+ (|> duration.day
+ (duration.scale-up (.nat (i.* -1 days)))
+ duration.inverse)
+ (duration.scale-up (.nat days) duration.day))
+ (duration.merge (duration.scale-up utc-hour duration.hour))
+ (duration.merge (duration.scale-up utc-minute duration.minute))
+ (duration.merge (duration.scale-up utc-second duration.second))
+ (duration.merge (duration.scale-up utc-millis duration.milli-second))
+ ..absolute))))
+
+(structure: #export codec
+ {#.doc (doc "Based on ISO 8601."
+ "For example: 2017-01-15T21:14:51.827Z")}
+ (Codec Text Instant)
+
+ (def: encode ..encode)
+ (def: decode (<t>.run ..parser)))
(def: #export now
(IO Instant)
- (io (from-millis ("lux io current-time"))))
+ (io (..from-millis ("lux io current-time"))))
(def: #export (date instant)
(-> Instant Date)
- (let [[[year month day] _] (extract-date instant)]
- {#date.year year
- #date.month (case (dec month)
- +0 #month.January
- +1 #month.February
- +2 #month.March
- +3 #month.April
- +4 #month.May
- +5 #month.June
- +6 #month.July
- +7 #month.August
- +8 #month.September
- +9 #month.October
- +10 #month.November
- +11 #month.December
- _ (undefined))
- #date.day (.nat (dec day))}))
-
-(def: #export (month instant)
- (-> Instant Month)
- (let [[year month day] (date instant)]
- month))
+ (let [[date _] (..date-time instant)]
+ date))
-(def: #export (day instant)
+(def: #export (day-of-week instant)
(-> Instant Day)
- (let [offset (relative instant)
+ (let [offset (..relative instant)
days (duration.query duration.day offset)
day-time (duration.frame duration.day offset)
days (if (and (duration.negative? offset)