Utility functions for programatically building a The basic idea here is you pass a number of TODO - this is a prime library candidate. | (ns metabase.util.date-2.parse.builder (:require [metabase.util.date-2.common :as u.date.common]) (:import (java.time.format DateTimeFormatter DateTimeFormatterBuilder SignStyle) (java.time.temporal TemporalField))) |
(set! *warn-on-reflection* true) | |
(defprotocol ^:private Section (^:private apply-section [this builder])) | |
(extend-protocol Section
String
(apply-section [s builder]
(.appendLiteral ^DateTimeFormatterBuilder builder s))
clojure.lang.Fn
(apply-section [f builder]
(f builder))
clojure.lang.Sequential
(apply-section [sections builder]
(doseq [section sections]
(apply-section section builder)))
DateTimeFormatter
(apply-section [formatter builder]
(.append ^DateTimeFormatterBuilder builder formatter))) | |
Make wrapped | (defn optional
[& sections]
(reify Section
(apply-section [_ builder]
(.optionalStart ^DateTimeFormatterBuilder builder)
(apply-section sections builder)
(.optionalEnd ^DateTimeFormatterBuilder builder)))) |
(defn- set-option [^DateTimeFormatterBuilder builder option]
(case option
:strict (.parseStrict builder)
:lenient (.parseLenient builder)
:case-sensitive (.parseCaseSensitive builder)
:case-insensitive (.parseCaseInsensitive builder))) | |
(def ^:private ^:dynamic *options*
{:strictness :strict
:case-sensitivity :case-sensitive}) | |
(defn- do-with-option [builder k new-value thunk]
(let [old-value (get *options* k)]
(if (= old-value new-value)
(thunk)
(binding [*options* (assoc *options* k new-value)]
(set-option builder new-value)
(thunk)
(set-option builder old-value))))) | |
(defn- with-option-section [k v sections]
(reify Section
(apply-section [_ builder]
(do-with-option builder k v (fn [] (apply-section sections builder)))))) | |
Use strict parsing for wrapped | (defn strict [& sections] (with-option-section :strictness :strict sections)) |
Use lenient parsing for wrapped | (defn lenient [& sections] (with-option-section :strictness :lenient sections)) |
Make wrapped | (defn case-sensitive [& sections] (with-option-section :case-sensitivity :case-sensitive sections)) |
Make wrapped | (defn case-insensitive [& sections] (with-option-section :case-sensitivity :case-insensitive sections)) |
(def ^:private ^SignStyle sign-style (u.date.common/static-instances SignStyle)) | |
(defn- temporal-field ^TemporalField [x]
(let [field (if (keyword? x)
(u.date.common/temporal-field x)
x)]
(assert (instance? TemporalField field)
(format "Invalid TemporalField: %s" (pr-str field)))
field)) | |
Define a section for a specific field such as | (defn value
([temporal-field-name]
(fn [^DateTimeFormatterBuilder builder]
(.appendValue builder (temporal-field temporal-field-name))))
([temporal-field-name width]
(fn [^DateTimeFormatterBuilder builder]
(.appendValue builder (temporal-field temporal-field-name) width)))
([temporal-field-name min-val max-val sign-style-name]
(fn [^DateTimeFormatterBuilder builder]
(.appendValue builder (temporal-field temporal-field-name) min-val max-val (sign-style sign-style-name))))) |
Define a section that sets a default value for a field like | (defn default-value
[temporal-field-name default-value]
(fn [^DateTimeFormatterBuilder builder]
(.parseDefaulting builder (temporal-field temporal-field-name) default-value))) |
Define a section for a fractional value, e.g. milliseconds or nanoseconds. | (defn fraction
[temporal-field-name _min-val-width _max-val-width & {:keys [decimal-point?]}]
(fn [^DateTimeFormatterBuilder builder]
(.appendFraction builder (temporal-field temporal-field-name) 0 9 (boolean decimal-point?)))) |
Define a section for a timezone offset. e.g. | (defn zone-offset
[]
(lenient
(fn [^DateTimeFormatterBuilder builder]
(.appendOffsetId builder)))) |
An a section for a timezone ID wrapped in square brackets, e.g. | (defn zone-id
[]
(strict
(case-sensitive
(optional "[")
(fn [^DateTimeFormatterBuilder builder]
(.appendZoneRegionId builder))
(optional "]")))) |
Return a new (formatter (case-insensitive (value :hour-of-day 2) (optional ":" (value :minute-of-hour 2) (optional ":" (value :second-of-minute))))) -> #object[java.time.format.DateTimeFormatter "ParseCaseSensitive(false)Value(HourOfDay,2)[':'Value(MinuteOfHour,2)[':'Value(SecondOfMinute)]]"] | (defn formatter
^DateTimeFormatter [& sections]
(let [builder (DateTimeFormatterBuilder.)]
(apply-section sections builder)
(.toFormatter builder))) |