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))) |