Skip to content
Snippets Groups Projects
Unverified Commit 5dd1cca5 authored by Braden Shepherdson's avatar Braden Shepherdson Committed by GitHub
Browse files

[util] Add `u.time/local-date`, `local-time` and `local-date-time` (#49445)

These are helpful for creating literal dates and times in a
cross-platform way. Useful for generating queries, in particular for
expression and filter values.
parent 11324019
No related branches found
No related tags found
No related merge requests found
......@@ -30,7 +30,10 @@
unit-diff
truncate
add
format-for-base-type])
format-for-base-type
local-date
local-date-time
local-time])
(defn- prep-options [options]
(merge internal/default-options (u/normalize-map options)))
......
......@@ -222,6 +222,52 @@
[value]
(t/local-time value))
;;; ----------------------------------------------- constructors -----------------------------------------------------
(defn local-time
"Constructs a platform time value (eg. Moment, LocalTime) for the given hour and minute, plus optional seconds and
milliseconds.
If called with no arguments, returns the current time."
([]
(t/local-time))
([hours minutes]
(local-time hours minutes 0 0))
([hours minutes seconds]
(local-time hours minutes seconds 0))
([hours minutes seconds millis]
(t/local-time hours minutes seconds (* 1000000 millis))))
(defn local-date
"Constructs a platform date value (eg. Moment, LocalDate) for the given year, month and day.
Day is 1-31. January = 1, or you can specify keywords like `:jan`, `:jun`.
If called with no arguments, returns the current date."
([]
(t/local-date))
([year month day]
(t/local-date year
(or (common/month-keywords month) month)
day)))
(defn local-date-time
"Constructs a platform datetime (eg. Moment, LocalDateTime).
Accepts either:
- no arguments (returns the current datetime)
- a local date and local time (see [[local-date]] and [[local-time]]); or
- year, month, day, hour, and minute, plus optional seconds and millis."
([]
(t/local-date-time))
([a-date a-time]
(t/local-date-time a-date a-time))
([year month day hours minutes]
(local-date-time (local-date year month day) (local-time hours minutes)))
([year month day hours minutes seconds]
(local-date-time (local-date year month day) (local-time hours minutes seconds)))
([year month day hours minutes seconds millis]
(local-date-time (local-date year month day) (local-time hours minutes seconds millis))))
;;; ------------------------------------------------ arithmetic ------------------------------------------------------
(defn unit-diff
......
......@@ -158,6 +158,59 @@
[value]
(moment value parse-time-formats))
;;; ----------------------------------------------- constructors -----------------------------------------------------
(defn local-time
"Constructs a platform time value (eg. Moment, LocalTime) for the given hour and minute, plus optional seconds and
milliseconds.
If called without arguments, returns the current time."
([]
;; Actually a full datetime, but Moment doesn't have freestanding time values.
(moment))
([hours minutes]
(moment #js {:hours hours, :minutes minutes}))
([hours minutes seconds]
(moment #js {:hours hours, :minutes minutes, :seconds seconds}))
([hours minutes seconds millis]
(moment #js {:hours hours, :minutes minutes, :seconds seconds, :milliseconds millis})))
(declare truncate)
(defn local-date
"Constructs a platform date value (eg. Moment, LocalDate) for the given year, month and day.
Day is 1-31. January = 1, or you can specify keywords like `:jan`, `:jun`."
([] (truncate (moment) :day))
([year month day]
(moment #js {:year year
:day day
;; Moment uses 0-based months, unlike Metabase.
:month (dec (or (common/month-keywords month) month))})))
(defn local-date-time
"Constructs a platform datetime (eg. Moment, LocalDateTime).
Accepts either:
- no arguments (current datetime)
- a local date and local time (see [[local-date]] and [[local-time]]); or
- year, month, day, hour, and minute, plus optional seconds and millis."
([] (moment))
([a-date a-time]
(when-not (and (valid? a-date) (valid? a-time))
(throw (ex-info "Expected valid Moments for date and time" {:date a-date
:time a-time})))
(let [^moment/Moment d (.clone a-date)
^moment/Moment t a-time]
(doseq [unit ["hour" "minute" "second" "millisecond"]]
(.set d unit (.get t unit)))
d))
([year month day hours minutes]
(local-date-time (local-date year month day) (local-time hours minutes)))
([year month day hours minutes seconds]
(local-date-time (local-date year month day) (local-time hours minutes seconds)))
([year month day hours minutes seconds millis]
(local-date-time (local-date year month day) (local-time hours minutes seconds millis))))
;;; ------------------------------------------------ arithmetic ------------------------------------------------------
(declare unit-diff)
......
......@@ -111,3 +111,20 @@
[time-str]
(or (second (re-matches (re-pattern (str "(.*?)" (optional offset-part) \$)) time-str))
time-str))
(def ^:const month-keywords
"Mapping of human-friendly keywords to literal month numbers.
1 = January."
{:jan 1
:feb 2
:mar 3
:apr 4
:may 5
:jun 6
:jul 7
:aug 8
:sep 9
:oct 10
:nov 11
:dec 12})
......@@ -14,6 +14,21 @@
#?(:cljs (moment/utc time-str)
:clj (t/offset-date-time time-str)))
(defn- from-local [time-str]
#?(:cljs (moment time-str)
:clj (t/local-date-time time-str)))
(defn- from-local-date [time-str]
#?(:cljs (moment time-str)
:clj (t/local-date time-str)))
(defn- from-local-time [time-str]
#?(:cljs (moment time-str
#js [(.. moment -HTML5_FMT -TIME_MS)
(.. moment -HTML5_FMT -TIME_SECONDS)
(.. moment -HTML5_FMT -TIME)])
:clj (t/local-time time-str)))
(defn- same?
"True if these two datetimes are equivalent.
JVM objects are [[=]] but Moment.js values are not, so use the Moment.isSame method in CLJS."
......@@ -392,3 +407,27 @@
:second "00:00:02"
:minute "00:02"
:hour "02:00"))
(deftest ^:parallel local-date-test
(are [exp-str act] (same? (from-local-date exp-str) act)
"2024-11-01" (shared.ut/local-date 2024 11 1)
"2000-02-29" (shared.ut/local-date 2000 2 29)
"2037-12-31" (shared.ut/local-date 2037 12 31)))
(deftest ^:parallel local-time-test
(are [exp-str act] (same? (from-local-time exp-str) act)
"12:34:00" (shared.ut/local-time 12 34)
"12:34:56" (shared.ut/local-time 12 34 56)
"12:34:56.789" (shared.ut/local-time 12 34 56 789)))
(deftest ^:parallel local-date-time-test
(let [nov4 (shared.ut/local-date 2024 11 4)]
(are [exp-str act] (same? (from-local exp-str) act)
;; Local date + local time
"2024-11-04T12:34:00" (shared.ut/local-date-time nov4 (shared.ut/local-time 12 34))
"2024-11-04T12:34:56" (shared.ut/local-date-time nov4 (shared.ut/local-time 12 34 56))
"2024-11-04T12:34:56.789" (shared.ut/local-date-time nov4 (shared.ut/local-time 12 34 56 789))
;; Literal values
"2024-11-04T12:34:00" (shared.ut/local-date-time 2024 11 4 12 34)
"2024-11-04T12:34:56" (shared.ut/local-date-time 2024 11 4 12 34 56)
"2024-11-04T12:34:56.789" (shared.ut/local-date-time 2024 11 4 12 34 56 789))))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment