std.time

time coercion, durations, zones, formats, and representations

std.time is a higher-level standard library family in foundation-base. This page explains when to use it, how it fits internally, and where to find the API surface.

1    Motivation

Use this layer when application or tooling code needs the behavior described by the page title without reaching directly into implementation namespaces. The top-level namespace is the starting point; subnamespaces expose more focused building blocks.

2    How to use it

Require the top-level namespace for common workflows, then move to subnamespaces when you need a lower-level primitive. Existing tests under test/std/time and test/std/time_test.clj are the best executable examples for edge cases.

coerce instants between representations

(time/coerce-instant 0 {:type java.util.Date})
=> #inst "1970-01-01T00:00:00.000-00:00"

format and parse timestamps

(time/format (java.util.Date. 0) "yyyy-MM-dd")
=> "1970-01-01"

(time/parse "1970-01-01" "yyyy-MM-dd")
=> #(instance? java.util.Date %)

duration calculations

(time/to-ms 1 :s)
=> 1000

3    Internal usage

This library family is used across source, tests, generated examples, and docs tooling. During detailed documentation passes, collect concrete usage with code.manage/find-usages and code.manage/locate-code, then keep only high-signal examples in the page narrative.

4    API



adjust ^

[t rep] [t rep opts]
Added 3.0

adjust fields of a particular time

v 3.0
(defn adjust
  ([t rep]
   (adjust t rep {}))
  ([t rep opts]
   (let [m (-> (to-map t opts)
               (merge rep)
               (dissoc :long))]
     (from-long (from-map m {:type Long})
                (assoc m :type (class t))))))
link
(t/adjust (Date. 0) {:year 2000 :second 10} {:timezone "GMT"}) => #inst "2000-01-01T00:00:10.000-00:00" (t/adjust {:year 1970, :month 1 :day 1, :day-of-week 4, :hour 0 :minute 0 :second 0 :millisecond 0, :timezone "GMT"} {:year 1999}) => {:type clojure.lang.PersistentHashMap, :timezone "GMT", :long 915148800000, :year 1999, :month 1, :day 1, :hour 0, :minute 0 :second 0, :millisecond 0}

after ^

[t1 t2] [t1 t2 & more]
Added 3.0

compare dates, returns true if t1 is after t2, etc

v 3.0
(defn after
  ([t1 t2]
   (> (to-long t1) (to-long t2)))
  ([t1 t2 & more]
   (apply > (map to-long (cons t1 (cons t2 more))))))
link
(t/after 2 (Date. 1) (common/calendar (Date. 0) (TimeZone/getTimeZone "GMT"))) => true

before ^

[t1 t2] [t1 t2 & more]
Added 3.0

compare dates, returns true if t1 is before t2, etc

v 3.0
(defn before
  ([t1 t2]
   (< (to-long t1) (to-long t2)))
  ([t1 t2 & more]
   (apply < (map to-long (cons t1 (cons t2 more))))))
link
(t/before 0 (Date. 1) (common/calendar (Date. 2) (TimeZone/getTimeZone "GMT"))) => true

calendar ^

[date timezone]
Added 3.0

creates a calendar to be used by the base date classes

v 3.0
(defn calendar
  ([^Date date ^TimeZone timezone]
   (doto (Calendar/getInstance timezone)
     (.setTime date))))
link
(-> ^Calendar (calendar (Date. 0) (TimeZone/getTimeZone "GMT")) (.getTime)) => #inst "1970-01-01T00:00:00.000-00:00"

coerce ^

[t {:keys [type timezone], :as opts}]
Added 3.0

adjust fields of a particular time

v 3.0
(defn coerce
  ([t {:keys [type timezone] :as opts}]
   (let [type (or type common/*default-type*)
         timezone (or timezone
                      (get-timezone t))]
     (-> (to-long t)
         (from-long {:type type :timezone timezone})))))
link
(t/coerce 0 {:type Date}) => #inst "1970-01-01T00:00:00.000-00:00" (t/coerce {:type clojure.lang.PersistentHashMap, :timezone "PST", :long 915148800000, :year 1999, :month 1, :day 1, :hour 0, :minute 0 :second 0, :millisecond 0} {:type Date}) => #inst "1999-01-01T08:00:00.000-00:00"

day ^

[t] [t opts]
Added 3.0

accesses the day representated by the instant

v 3.0
(defn day
  ([t] (day t {}))
  ([t opts]
   ((wrap-proxy protocol.time/-day) t opts)))
link
(t/day 0 {:timezone "GMT"}) => 1 (t/day (Date. 0) {:timezone "EST"}) => 31

day-of-week ^

[t] [t opts]
Added 3.0

accesses the day of week representated by the instant

v 3.0
(defn day-of-week
  ([t] (day-of-week t {}))
  ([t opts]
   ((wrap-proxy protocol.time/-day-of-week) t opts)))
link
(t/day-of-week 0 {:timezone "GMT"}) => 4 (t/day-of-week (Date. 0) {:timezone "EST"}) => 3

default-timezone ^

[] [tz]
Added 3.0

accesses the default timezone as a string

v 3.0
(defn default-timezone
  ([]
   (or *default-timezone*
       (local-timezone)))
  ([tz]
   (alter-var-root #'*default-timezone*
                   (constantly (protocol.string/-to-string tz)))))
link
(default-timezone) ;; getter => "Asia/Ho_Chi_Minh" (default-timezone "GMT") ;; setter => "GMT"

default-type ^

[] [cls]
Added 3.0

accesses the default type for datetime

v 3.0
(defn default-type
  ([] *default-type*)
  ([cls]
   (alter-var-root #'*default-type*
                   (constantly cls))))
link
(default-type) ;; getter => clojure.lang.PersistentArrayMap (default-type Long) ;; setter => java.lang.Long

duration? ^

[obj]
Added 3.0

checks if an object implements the duration protocol

v 3.0
(defn duration?
  ([obj]
   (satisfies? protocol.time/IDuration obj)))
link
(t/duration? 0) => true (t/duration? {:weeks 1}) => true

earliest ^

[t1 t2 & more]
Added 3.0

returns the earliest date out of a range of inputs

v 3.0
(defn earliest
  ([t1 t2 & more]
   (first (sort-by protocol.time/-to-long (apply vector t1 t2 more)))))
link
(t/earliest (Date. 0) (Date. 1000) (Date. 20000)) => #inst "1970-01-01T00:00:00.000-00:00"

epoch ^

[] [opts]
Added 3.0

returns the beginning of unix epoch

v 3.0
(defn epoch
  ([] (epoch {}))
  ([opts] (from-long 0 (merge {:type common/*default-type*}
                              opts))))
link
(t/epoch {:type Date}) => #inst "1970-01-01T00:00:00.000-00:00" (t/epoch {:type clojure.lang.PersistentArrayMap :timezone "GMT"}) {:type clojure.lang.PersistentArrayMap, :timezone "GMT", :long 0, :year 1970, :month 1, :day 1, :hour 0, :minute 0, :second 0, :millisecond 0}

equal ^

[t1 t2] [t1 t2 & more]
Added 3.0

compares dates, retruns true if all inputs are the same

v 3.0
(defn equal
  ([t1 t2]
   (= (to-long t2) (to-long t1)))
  ([t1 t2 & more]
   (apply = (map to-long (cons t1 (cons t2 more))))))
link
(t/equal 1 (Date. 1) (common/calendar (Date. 1) (TimeZone/getTimeZone "GMT"))) => true

format ^

[t pattern] [t pattern {:keys [cached], :as opts}]
Added 3.0

converts a date into a string

v 3.0
(defn format
  ([t pattern] (format t pattern {}))
  ([t pattern {:keys [cached] :as opts}]
   (let [tmeta (protocol.time/-time-meta (class t))
         ftype (-> tmeta :formatter :type)
         fmt   (cache +format-cache+
                      (fn [] (protocol.time/-formatter pattern (assoc opts :type ftype)))
                      [ftype pattern]
                      cached)]
     (protocol.time/-format fmt t opts))))
link
(f/format (Date. 0) "HH MM dd Z" {:timezone "GMT" :cached true}) => "00 01 01 +0000" (f/format (common/calendar (Date. 0) (TimeZone/getTimeZone "GMT")) "HH MM dd Z" {}) => "00 01 01 +0000" (f/format (Timestamp. 0) "HH MM dd Z" {:timezone "PST"}) => "16 12 31 -0800" (f/format (Date. 0) "HH MM dd Z") => string?

from-long ^

[t] [t opts]
Added 3.0

creates an instant from a long

v 3.0
(defn from-long
  ([t]
   (from-long t nil))
  ([t opts]
   (protocol.time/-from-long t (merge {:type common/*default-type*} opts))))
link
(-> (t/from-long 0 {:timezone "Asia/Kolkata" :type Calendar}) (t/to-map)) => {:type java.util.GregorianCalendar, :timezone "Asia/Kolkata", :long 0 :year 1970, :month 1, :day 1, :hour 5, :minute 30 :second 0, :millisecond 0}

from-map ^

[t] [t opts] [t {:keys [type timezone], :as opts} fill]
Added 3.0

creates an map from an instant

v 3.0
(defn from-map
  ([t] (from-map t {}))
  ([t opts] (from-map t opts common/+zero-values+))
  ([t {:keys [type timezone] :as opts} fill]
   (cond (#{PersistentArrayMap PersistentHashMap} type)
         (protocol.time/-with-timezone t timezone)

         :else
         (map/from-map t opts fill))))
link
(t/from-map {:type java.util.GregorianCalendar, :timezone "Asia/Kolkata", :long 0 :year 1970, :month 1, :day 1, :hour 5, :minute 30 :second 0, :millisecond 0} {:timezone "Asia/Kolkata" :type Date}) => #inst "1970-01-01T00:00:00.000-00:00" (t/from-map {:type Long, :timezone "Asia/Kolkata", :long 0 :year 1970, :month 1, :day 1, :hour 5, :minute 30 :second 0, :millisecond 0}) => 0 (t/from-map {:type Long :timezone "Asia/Kolkata", :year 1970, :month 1, :day 1, :hour 5, :minute 30 :second 0, :millisecond 0} {:type clojure.lang.PersistentHashMap :timezone "GMT"}) => {:type clojure.lang.PersistentHashMap :timezone "GMT", :long 0 :year 1970, :month 1, :day 1, :hour 0, :minute 0 :second 0, :millisecond 0}

get-timezone ^

[t]
Added 3.0

returns the contained timezone if exists

v 3.0
(defn get-timezone
  ([t]
   (protocol.time/-get-timezone t)))
link
(t/get-timezone 0) => nil (t/get-timezone (common/calendar (Date. 0) (TimeZone/getTimeZone "EST"))) => "EST"

has-timezone? ^

[t]
Added 3.0

checks if the instance contains a timezone

v 3.0
(defn has-timezone?
  ([t]
   (protocol.time/-has-timezone? t)))
link
(t/has-timezone? 0) => false (t/has-timezone? (common/calendar (Date. 0) (TimeZone/getDefault))) => true

hour ^

[t] [t opts]
Added 3.0

accesses the hour representated by the instant

v 3.0
(defn hour
  ([t] (hour t {}))
  ([t opts]
   ((wrap-proxy protocol.time/-hour) t opts)))
link
(t/hour 0 {:timezone "GMT"}) => 0 (t/hour (Date. 0) {:timezone "Asia/Kolkata"}) => 5

instant? ^

[obj]
Added 3.0

checks if an object implements the instant protocol

v 3.0
(defn instant?
  ([obj]
   (satisfies? protocol.time/IInstant obj)))
link
(t/instant? 0) => true (t/instant? (Date.)) => true

latest ^

[t1 t2 & more]
Added 3.0

returns the latest date out of a range of inputs

v 3.0
(defn latest
  ([t1 t2 & more]
   (last (sort-by protocol.time/-to-long (apply vector t1 t2 more)))))
link
(t/latest (Date. 0) (Date. 1000) (Date. 20000)) => #inst "1970-01-01T00:00:20.000-00:00"

local-timezone ^

[]
Added 3.0

returns the current timezone as a string

v 3.0
(defn local-timezone
  ([]
   (.getID (TimeZone/getDefault))))
link
(local-timezone) => "Asia/Ho_Chi_Minh"

millisecond ^

[t] [t opts]
Added 3.0

accesses the millisecond representated by the instant

v 3.0
(defn millisecond
  ([t] (millisecond t {}))
  ([t opts]
   ((wrap-proxy protocol.time/-millisecond) t opts)))
link
(t/millisecond 1010 {:timezone "GMT"}) => 10

minus ^

[t duration] [t duration opts]
Added 3.0

substracts a duration from the time

v 3.0
(defn minus
  ([t duration]
   (minus t duration {}))
  ([t duration opts]
   (from-long (- (to-long t)
                 (to-length duration
                            (-> (to-map t opts [:day :month :year])
                                (assoc :backward true))))
              (assoc opts :type (class t)))))
link
(t/minus (Date. 0) {:years 1}) => #inst "1969-01-01T00:00:00.000-00:00" (-> (t/from-map {:type java.time.ZonedDateTime :timezone "GMT", :year 1970, :month 1, :day 1, :hour 0, :minute 0, :second 0, :millisecond 0}) (t/minus {:years 10 :months 1 :weeks 4 :days 2}) (t/to-map {:timezone "GMT"})) => {:type java.time.ZonedDateTime, :timezone "GMT", :long -320803200000 :year 1959, :month 11, :day 2, :hour 0, :minute 0, :second 0, :millisecond 0}

minute ^

[t] [t opts]
Added 3.0

accesses the minute representated by the instant

v 3.0
(defn minute
  ([t] (minute t {}))
  ([t opts]
   ((wrap-proxy protocol.time/-minute) t opts)))
link
(t/minute 0 {:timezone "GMT"}) => 0 (t/minute (Date. 0) {:timezone "Asia/Kolkata"}) => 30

month ^

[t] [t opts]
Added 3.0

accesses the month representated by the instant

v 3.0
(defn month
  ([t] (month t {}))
  ([t opts]
   ((wrap-proxy protocol.time/-month) t opts)))
link
(t/month 0 {:timezone "GMT"}) => 1 (t/month (Date. 0) {:timezone "EST"}) => 12

now ^

[] [opts]
Added 3.0

returns the current datetime

v 3.0
(defn now
  ([] (now {}))
  ([opts] (protocol.time/-now (merge {:type common/*default-type*}
                                     opts))))
link
(t/now) ;; => #(instance? (t/default-type) %) (t/now {:type Date}) => #(instance? Date %) (t/now {:type Calendar}) => #(instance? Calendar %)

parse ^

[s pattern] [s pattern {:keys [cached], :as opts}]

testing format for java.time datastructures

NONE
(defn parse
  ([s pattern] (parse s pattern {}))
  ([s pattern {:keys [cached] :as opts}]
   (let [opts   (merge {:type common/*default-type*} opts)
         type   (:type opts)
         tmeta  (protocol.time/-time-meta type)
         ptype  (-> tmeta :parser :type)
         parser (cache +parse-cache+
                       (fn [] (protocol.time/-parser pattern (assoc opts :type ptype)))
                       [ptype pattern]
                       cached)]
     (protocol.time/-parse parser s opts))))
link
(-> (f/parse "00 00 01 01 01 1989 +0000" "ss mm HH dd MM yyyy Z" {:type Clock}) (map/to-map {} common/+default-keys+)) => {:type java.time.Clock$FixedClock, :timezone "Etc/GMT" :long 599619600000, :year 1989, :month 1, :day 1, :hour 1, :minute 0, :second 0, :millisecond 0} (-> (f/parse "00 00 01 01 01 1989 -1000" "ss mm HH dd MM yyyy Z" {:type Clock}) (map/to-map {} common/+default-keys+)) => {:type java.time.Clock$FixedClock, :timezone "Etc/GMT-10", :long 599583600000, :year 1989, :month 1, :day 1, :hour 1, :minute 0, :second 0, :millisecond 0} (-> (f/parse "00 00 01 01 01 1989 +1000" "ss mm HH dd MM yyyy Z" {:type Clock}) (map/to-map {} common/+default-keys+)) => {:type java.time.Clock$FixedClock, :timezone "Etc/GMT+10", :long 599655600000, :year 1989, :month 1, :day 1, :hour 1, :minute 0, :second 0, :millisecond 0}

plus ^

[t duration] [t duration opts]
Added 3.0

adds a duration to the time

v 3.0
(defn plus
  ([t duration]
   (plus t duration {}))
  ([t duration opts]
   (from-long (+ (to-long t)
                 (to-length duration
                            (to-map t opts [:day :month :year])))
              (assoc opts :type (class t)))))
link
(t/plus (Date. 0) {:weeks 2}) => #inst "1970-01-15T00:00:00.000-00:00" (t/plus (Date. 0) 1000) => #inst "1970-01-01T00:00:01.000-00:00" (t/plus (java.util.Date. 0) {:years 10 :months 1 :weeks 4 :days 2}) => #inst "1980-03-02T00:00:00.000-00:00"

representation? ^

[obj]
Added 3.0

checks if an object implements the representation protocol

v 3.0
(defn representation?
  ([obj]
   (satisfies? protocol.time/IRepresentation obj)))
link
(t/representation? 0) => false (t/representation? (common/calendar (Date. 0) (TimeZone/getTimeZone "GMT"))) => true

second ^

[t] [t opts]
Added 3.0

accesses the second representated by the instant

v 3.0
(defn second
  ([t] (second t {}))
  ([t opts]
   ((wrap-proxy protocol.time/-second) t opts)))
link
(t/second 1000 {:timezone "GMT"}) => 1

time-meta ^

[cls]
Added 3.0

retrieves the meta-data for the time object

v 3.0
(defn time-meta
  ([cls]
   (protocol.time/-time-meta cls)))
link
(t/time-meta TimeZone) => {:base :zone} (t/time-meta Date) => (contains {:base :instant, :map (contains {:from (contains {:proxy java.util.Calendar, :via fn?}), :to (contains {:proxy java.util.Calendar, :via fn?})})})

to-length ^

[t] [t rep]
Added 3.0

converts a object implementing IDuration to a long

v 3.0
(defn to-length
  ([t]
   (to-length t {:year 0 :month 1 :day 1}))
  ([t rep]
   (protocol.time/-to-length t rep)))
link
(t/to-length {:days 1}) => 86400000

to-long ^

[t]
Added 3.0

gets the long representation for the instant

v 3.0
(defn to-long
  ([t]
   (protocol.time/-to-long t)))
link
(t/to-long #inst "1970-01-01T00:00:10.000-00:00") => 10000

to-map ^

[t] [t opts] [t {:keys [timezone], :as opts} ks]
Added 3.0

creates an map from an instant

v 3.0
(defn to-map
  ([t] (to-map t {}))
  ([t opts] (to-map t opts common/+default-keys+))
  ([t {:keys [timezone] :as opts} ks]
   (cond (map? t)
         (if timezone
           (protocol.time/-with-timezone t timezone)
           t)

         :else
         (map/to-map t opts ks))))
link
(-> (t/from-long 0 {:timezone "Asia/Kolkata" :type Date}) (t/to-map {:timezone "GMT"} [:year :month :day])) => {:type java.util.Date, :timezone "GMT", :long 0, :year 1970, :month 1, :day 1}

to-vector ^

[t {:keys [timezone], :as opts} ks]
Added 3.0

converts an instant to an array representation

v 3.0
(defn to-vector
  ([t {:keys [timezone] :as opts} ks]
   (cond (map? t)
         (if (or (nil? timezone)
                 (= timezone (:timezone opts)))
           (mapv t ks)
           (-> (map/from-map t (assoc opts :type java.util.Calendar))
               (to-vector opts ks)))

         :else
         (let [tmeta (protocol.time/-time-meta (class t))
               [p pmeta] (if-let [{:keys [proxy via]} (-> tmeta :map :to)]
                           [(via t opts) (protocol.time/-time-meta proxy)]
                           [t tmeta])
               p         (if timezone
                           (protocol.time/-with-timezone p timezone)
                           p)
               ks   (cond (vector? ks) ks

                          (= :all ks)
                          (reverse common/+default-keys+)

                          (keyword? ks)
                          (->> common/+default-keys+
                               (drop-while #(not= % ks))
                               (reverse)))
               rep  (reduce (fn [out k]
                              (let [t-fn (get common/+default-fns+ k)]
                                (conj out (t-fn p opts))))
                            []
                            ks)]
           rep))))
link
(to-vector 0 {:timezone "GMT"} :all) => [1970 1 1 0 0 0 0] (to-vector (Date. 0) {:timezone "GMT"} :day) => [1970 1 1] (to-vector (common/calendar (Date. 0) (TimeZone/getTimeZone "EST")) {} [:month :day :year]) => [12 31 1969] (to-vector (common/calendar (Date. 0) (TimeZone/getTimeZone "EST")) {:timezone "GMT"} [:month :day :year]) => [1 1 1970]

truncate ^

[t col] [t col opts]
Added 3.0

truncates the time to a particular field

v 3.0
(defn truncate
  ([t col]
   (truncate t col {}))
  ([t col opts]
   (let [rep  (to-map t opts)
         trep (->> common/+default-keys+
                   (drop-while #(not= col %))
                   (concat [:type :timezone])
                   (select-keys rep))]
     (from-map (merge common/+zero-values+ (dissoc trep :long))
               (assoc opts :type (class t))))))
link
(t/truncate #inst "1989-12-28T12:34:00.000-00:00" :hour {:timezone "GMT"}) => #inst "1989-12-28T12:00:00.000-00:00" (t/truncate #inst "1989-12-28T12:34:00.000-00:00" :year {:timezone "GMT"}) => #inst "1989-01-01T00:00:00.000-00:00" (t/truncate (t/to-map #inst "1989-12-28T12:34:00.000-00:00" {:timezone "GMT"}) :hour) => {:type clojure.lang.PersistentHashMap, :timezone "GMT", :long 630849600000, :year 1989, :month 12, :day 28, :hour 12, :minute 0, :second 0, :millisecond 0}

with-timezone ^

[t tz]
Added 3.0

returns the same instance in a different timezone

v 3.0
(defn with-timezone
  ([t tz]
   (protocol.time/-with-timezone t tz)))
link
(t/with-timezone 0 "EST") => 0 (t/to-map (t/with-timezone (common/calendar (Date. 0) (TimeZone/getTimeZone "GMT")) "EST")) => {:type java.util.GregorianCalendar, :timezone "EST", :long 0, :year 1969, :month 12, :day 31, :hour 19, :minute 0, :second 0, :millisecond 0}

wrap-proxy ^

[f]
Added 3.0

converts one representation to another via a third object, used by java.sql.Timestamp and others

v 3.0
(defn wrap-proxy
  ([f]
   (fn [t opts]
     (cond (representation? t)
           (f t opts)

           :else
           (let [tmeta (protocol.time/-time-meta (class t))]
             (if-let [p-fn (-> tmeta :map :to :via)]
               (f (p-fn t opts) opts)
               (throw (Exception. (str "No proxy method for type " (class t))))))))))
link

year ^

[t] [t opts]
Added 3.0

accesses the year representated by the instant

v 3.0
(defn year
  ([t] (year t {}))
  ([t opts]
   ((wrap-proxy protocol.time/-year) t opts)))
link
(t/year 0 {:timezone "GMT"}) => 1970 (t/year (Date. 0) {:timezone "EST"}) => 1969


coerce-instant ^

[value {:keys [type], :as opts}]

coerce-instant for java.time datastructures

NONE
(defn coerce-instant
  ([value {:keys [type] :as opts}]
   (cond (instance? type value)
         value

         (or (= type Long) (nil? type))
         (protocol.time/-to-long value)

         (instance? Long value)
         (protocol.time/-from-long value opts)

         (instance? clojure.lang.APersistentMap value)
         (map/from-map value opts)

         :else
         (-> value
             (protocol.time/-to-long)
             (protocol.time/-from-long opts)))))
link
(coerce-instant 0 {:type Long :timezone "GMT"}) => 0 (-> (coerce-instant 0 {:type ZonedDateTime :timezone "GMT"}) (map/to-map {} common/+default-keys+)) => {:type ZonedDateTime :timezone "GMT", :long 0, :year 1970, :month 1, :day 1, :hour 0, :minute 0, :second 0 :millisecond 0} (-> (time/-from-long 0 {:type ZonedDateTime :timezone "GMT"}) (coerce-instant {:type Clock :timezone "Asia/Kolkata"}) (map/to-map {} common/+default-keys+)) => {:type java.time.Clock$FixedClock, :timezone "Asia/Kolkata", :long 0, :year 1970, :month 1, :day 1, :hour 5, :minute 30, :second 0 :millisecond 0} (-> (time/-from-long 0 {:type Clock :timezone "GMT"}) (coerce-instant {:type Calendar :timezone "Asia/Kolkata"}) (map/to-map {} common/+default-keys+)) => {:type java.util.GregorianCalendar :timezone "Asia/Kolkata", :long 0, :year 1970, :month 1, :day 1, :hour 5, :minute 30, :second 0 :millisecond 0}

coerce-zone ^

[value {:keys [type], :as opts}]
Added 3.0

coercion of one zone object to another

v 3.0
(defn coerce-zone
  ([value {:keys [type] :as opts}]
   (cond (nil? value)
         (protocol.string/-from-string (common/default-timezone)
                                       type
                                       opts)

         (instance? type value)
         value

         (string? value)
         (protocol.string/-from-string value type opts)

         :else
         (->  value
              (protocol.string/-to-string)
              (protocol.string/-from-string type opts)))))
link
(-> (coerce-zone "Asia/Kolkata" {:type TimeZone}) (string/-to-string)) => "Asia/Kolkata" (-> (coerce-zone nil {:type TimeZone}) (string/-to-string)) => (-> (TimeZone/getDefault) (string/-to-string))


adjust-days ^

[{:keys [years months], :or {years 0, months 0}} {:keys [year month day], :as rep}]
Added 3.0

calculates the number of days to be forwarded based on year and month

v 3.0
(defn adjust-days
  ([{:keys [years months]
     :or   {years 0 months 0}}
    {:keys [year month day] :as rep}]
   (if-not (and year month day)
     (throw (Exception. (format "Year (%s), Month (%s) and Day (%s) are required:" year month day)))) (let [year-days  (adjust-year-days years rep)
                                                                                                            month-days (adjust-month-days years months rep)]
                                                                                                        (+ year-days month-days))))
link
(adjust-days {:years 0 :months 2} {:year 2012 :month 1 :day 31}) => 60 (adjust-days {:years 1 :months 2} {:year 2012 :month 1 :day 31}) => 425 (adjust-days {:months 2} {:year 2012 :month 1 :day 31 :backward true}) => 62 (adjust-days {:years 1} {:year 2013 :month 1 :day 31 :backward true}) => 366

adjust-month-days ^

[years months {:keys [year month day backward]}]
Added 3.0

calculates the number of days to be adjusted based on month

v 3.0
(defn adjust-month-days
  ([years months {:keys [year month day backward]}]
   (let [midx (rem (-> (+ year years)
                       (* 12)
                       (+ (dec month)))
                   48)
         month-opts (-> adjust-options
                        ((if backward :backward :forward))
                        :month)]
     (->> (cycle (:array month-opts))
          (drop midx)
          (take months)
          (apply +)))))
link
(adjust-month-days 0 2 {:year 2012 :month 3 :day 1}) => 61 (adjust-month-days 0 2 {:year 2012 :month 3 :day 1 :backward true}) => 60 (adjust-month-days 1 2 {:year 2011 :month 1 :day 3}) => 60 (adjust-month-days 1 2 {:year 2012 :month 1 :day 3}) => 59

adjust-options ^

NONE
(def adjust-options)
link

adjust-year-days ^

[years {:keys [year month day backward]}]
Added 3.0

calculates the number of days to be adjusted based on year

v 3.0
(defn adjust-year-days
  ([years {:keys [year month day backward]}]
   (let [yidx (rem year 4)
         year-opts (-> adjust-options
                       ((if backward :backward :forward))
                       :year)
         year-days (->> (cycle (:array year-opts))
                        (drop yidx)
                        (take years)
                        (apply +))]
     (or (if (zero? year-days) year-days)
         (if-let [[_ f]
                  (first (filter (fn [[pred _]]
                                   (pred yidx month day))
                                 (:pred year-opts)))]
           (f year-days)
           year-days)))))
link
(adjust-year-days 1 {:year 2012 :month 2 :day 28}) => 366 (adjust-year-days 1 {:year 2012 :month 2 :day 29}) => 365 (adjust-year-days 1 {:year 2012 :month 3 :day 1}) => 365 (adjust-year-days 1 {:year 2011 :month 3 :day 1}) => 366

backward-month-array ^

NONE
(def backward-month-array [31 31 29 31 30 31 30 31 31 30 31 30
                           31 31 28 31 30 31 30 31 31 30 31 30
                           31 31 28 31 30 31 30 31 31 30 31 30
                           31 31 28 31 30 31 30 31 31 30 31 30])
link

backward-year-array ^

NONE
(def backward-year-array  [365 366 365 365])
link

forward-month-array ^

NONE
(def forward-month-array [31 29 31 30 31 30 31 31 30 31 30 31
                          31 28 31 30 31 30 31 31 30 31 30 31
                          31 28 31 30 31 30 31 31 30 31 30 31
                          31 28 31 30 31 30 31 31 30 31 30 31])
link

forward-year-array ^

NONE
(def forward-year-array  [366 365 365 365])
link

map-to-length ^

[d rep]
Added 3.0

converts a duration to a length

v 3.0
(defn map-to-length
  ([d rep]
   (+ (if (some d [:months :years])
        (* 86400000 (adjust-days d rep))
        0)
      (to-fixed-length d))))
link
(map-to-length {:months 2} {:year 2012 :month 1 :day 31 :backward true}) => 5356800000

ms-length ^

NONE
(def ms-length [[:weeks        604800000]
                [:days         86400000]
                [:hours        3600000]
                [:minutes      60000]
                [:seconds      1000]
                [:milliseconds 1]])
link

to-fixed-length ^

[m]
Added 3.0

converts a duration map to a duration in milliseconds

v 3.0
(defn to-fixed-length
  ([m]
   (reduce (fn [len [k mul]]
             (+ len (* mul (or (get m k) 0))))
           0
           ms-length)))
link
(to-fixed-length {:days 1 :hours 3}) => 97200000 (to-fixed-length {:weeks 2 :days 7 :hours 53}) => 2005200000

variable-keys ^

NONE
(def variable-keys [:months :years])
link


+format-cache+ ^

NONE
(defonce +format-cache+ (atom {}))
link

+parse-cache+ ^

NONE
(defonce +parse-cache+  (atom {}))
link

cache ^

[cache constructor ks flag]
Added 3.0

helper function to access formatters by keyword

v 3.0
(defn cache
  ([cache constructor ks flag]
   (cond flag
         (or (get-in @cache ks)
             (let [obj (constructor)]
               (swap! cache assoc-in ks obj)
               obj))

         :else
         (constructor))))
link
(f/cache f/+format-cache+ nil [java.text.SimpleDateFormat "HH MM dd Z"] true)

format ^

[t pattern] [t pattern {:keys [cached], :as opts}]
Added 3.0

converts a date into a string

v 3.0
(defn format
  ([t pattern] (format t pattern {}))
  ([t pattern {:keys [cached] :as opts}]
   (let [tmeta (protocol.time/-time-meta (class t))
         ftype (-> tmeta :formatter :type)
         fmt   (cache +format-cache+
                      (fn [] (protocol.time/-formatter pattern (assoc opts :type ftype)))
                      [ftype pattern]
                      cached)]
     (protocol.time/-format fmt t opts))))
link
(f/format (Date. 0) "HH MM dd Z" {:timezone "GMT" :cached true}) => "00 01 01 +0000" (f/format (common/calendar (Date. 0) (TimeZone/getTimeZone "GMT")) "HH MM dd Z" {}) => "00 01 01 +0000" (f/format (Timestamp. 0) "HH MM dd Z" {:timezone "PST"}) => "16 12 31 -0800" (f/format (Date. 0) "HH MM dd Z") => string?

parse ^

[s pattern] [s pattern {:keys [cached], :as opts}]

testing format for java.time datastructures

NONE
(defn parse
  ([s pattern] (parse s pattern {}))
  ([s pattern {:keys [cached] :as opts}]
   (let [opts   (merge {:type common/*default-type*} opts)
         type   (:type opts)
         tmeta  (protocol.time/-time-meta type)
         ptype  (-> tmeta :parser :type)
         parser (cache +parse-cache+
                       (fn [] (protocol.time/-parser pattern (assoc opts :type ptype)))
                       [ptype pattern]
                       cached)]
     (protocol.time/-parse parser s opts))))
link
(-> (f/parse "00 00 01 01 01 1989 +0000" "ss mm HH dd MM yyyy Z" {:type Clock}) (map/to-map {} common/+default-keys+)) => {:type java.time.Clock$FixedClock, :timezone "Etc/GMT" :long 599619600000, :year 1989, :month 1, :day 1, :hour 1, :minute 0, :second 0, :millisecond 0} (-> (f/parse "00 00 01 01 01 1989 -1000" "ss mm HH dd MM yyyy Z" {:type Clock}) (map/to-map {} common/+default-keys+)) => {:type java.time.Clock$FixedClock, :timezone "Etc/GMT-10", :long 599583600000, :year 1989, :month 1, :day 1, :hour 1, :minute 0, :second 0, :millisecond 0} (-> (f/parse "00 00 01 01 01 1989 +1000" "ss mm HH dd MM yyyy Z" {:type Clock}) (map/to-map {} common/+default-keys+)) => {:type java.time.Clock$FixedClock, :timezone "Etc/GMT+10", :long 599655600000, :year 1989, :month 1, :day 1, :hour 1, :minute 0, :second 0, :millisecond 0}


by-offset ^

NONE
(def by-offset
  (->> (reduce (fn [out ^String id]
                 (update-in out [(.getRawOffset (TimeZone/getTimeZone id))]
                            (fnil #(conj % id) #{})))
               {}
               (TimeZone/getAvailableIDs))
       (reduce-kv (fn [out k ids]
                    (let [id (or (first (filter (fn [^String id] (.startsWith id "Etc")) ids))
                                 (first (sort-by count ids)))]
                      (assoc out k id)))
                  {})))
link

by-string-offset ^

NONE
(def by-string-offset
  (let [half (generate-offsets)]
    (->> (concat (map (fn [[s val]] [(str "+" s) (- val)]) half)
                 (map (fn [[s val]] [(str "-" s) val]) half))
         (into {"Z" 0}))))
link

generate-offsets ^

[]
Added 3.0

make offsets in milliseconds

v 3.0
(defn generate-offsets
  ([]
   (for [i (range 0 12)
         j (range 0 60 15)]
     [(format "%s:%s"
              (pad-zeros (str i))
              (pad-zeros (str j)))
      (+ (* 3600000 i)
         (* 60000 j))])))
link
(zone/generate-offsets) => (["00:00" 0] ["00:15" 900000] .... ["11:30" 41400000] ["11:45" 42300000])

pad-zeros ^

[s]
Added 3.0

if number less than 10, make double digit

v 3.0
(defn pad-zeros
  ([s]
   (if (= 1 (count s))
     (str 0 s)
     s)))
link
(zone/pad-zeros "5") => "05"

5    std.time: A Comprehensive Summary

The std.time module in foundation-base provides a comprehensive and extensible framework for handling time-related data in Clojure. It focuses on abstracting various Java time representations (e.g., java.util.Date, java.util.Calendar, java.time.Instant, java.time.ZonedDateTime, Long milliseconds) into a unified system. This is achieved through a set of protocols for instants, durations, and representations, along with multimethods for coercion, formatting, and parsing. The module aims to provide flexible and type-agnostic time manipulation capabilities.

The module is organized into several sub-namespaces:

5.1    std.time.coerce

This namespace handles the conversion of time-related objects between different types, including timezones and instants.

  • coerce-zone [value opts]: Coerces a timezone object from one type to another (e.g., String to java.util.TimeZone). It handles nil values by defaulting to the system's timezone.n* coerce-instant [value opts]: Coerces an instant object between various Java time types (e.g., Long to ZonedDateTime, Clock to Calendar). It supports conversion to/from Long milliseconds and map representations.

5.2    std.time.common

This namespace provides common definitions and utility functions used across the std.time module, including default settings and helper functions for java.util.Calendar.

  • *default-type*: A dynamic var holding the default time representation type (initially clojure.lang.PersistentArrayMap).n *default-timezone*: A dynamic var holding the default timezone string (initially nil, defaulting to local timezone).n +default-keys+: A list of common time components (millisecond, second, minute, hour, day, month, year) used for map and vector representations.n +zero-values+: A map of zero/default values for time components.n +default-fns+: A map linking time component keywords to their respective protocol functions (e.g., :millisecond to protocol.time/-millisecond).n calendar [date timezone]: Creates a java.util.Calendar instance from a java.util.Date and java.util.TimeZone.n default-type [& [cls]]: Getter/setter for *default-type*.n local-timezone []: Returns the ID of the system's default timezone.n default-timezone [& [tz]]: Getter/setter for *default-timezone*.

5.3    std.time.data

This namespace primarily serves to load and initialize various time-related extensions by requiring other sub-namespaces. It acts as a central point for ensuring all necessary time data structures and formatters are available.

  • It requires namespaces for java.text.SimpleDateFormat, java.sql.Timestamp, java.util.Calendar, java.util.Date, Long, Map, java.util.TimeZone, java.time.format.DateTimeFormatter, java.time.Clock, java.time.Instant, java.time.ZonedDateTime, and java.time.ZoneId.

5.4    std.time.duration

This namespace provides functionalities for working with time durations, including calculations for adjusting days based on years and months, and converting duration maps to fixed millisecond lengths.

  • forward-year-array, forward-month-array, backward-year-array, backward-month-array: Arrays used for calculating days in years and months, considering leap years and month lengths.n variable-keys: Keys representing variable-length durations (e.g., :months, :years).n ms-length: A list of time units and their equivalent milliseconds, used for fixed-length duration conversions.n adjust-options: A map containing options for adjusting days based on year and month, including prediction functions for leap years.n adjust-year-days [years rep]: Calculates the number of days to adjust based on a number of years and a time representation.n adjust-month-days [years months rep]: Calculates the number of days to adjust based on a number of months and a time representation.n adjust-days [duration-map rep]: Calculates the total number of days to adjust based on a duration map (containing :years and :months) and a time representation.n to-fixed-length [m]: Converts a duration map (containing fixed units like :days, :hours) to its equivalent length in milliseconds.n map-to-length [d rep]: Converts a duration map d to its total length in milliseconds, considering variable units like :months and :years based on a time representation rep.n protocol.time/-from-length extension: Extends the default implementation to convert a long duration into a map of time units.n protocol.time/IDuration extension: Extends clojure.lang.APersistentMap to implement IDuration, allowing duration maps to be converted to lengths.

5.5    std.time.format

This namespace provides functions for formatting time objects into strings and parsing strings into time objects, supporting various Java formatters.

  • +format-cache+, +parse-cache+: Atoms used for caching formatter and parser instances for performance.n cache [cache constructor ks flag]: A helper function to manage caching of formatter/parser instances.n format [t pattern & [opts]]: Formats a time object t into a string according to a pattern and optional opts (including caching). It uses the appropriate formatter based on the type of t.n* parse [s pattern & [opts]]: Parses a string s into a time object according to a pattern and optional opts (including caching and specifying the target type).

5.6    std.time.long

This namespace provides extensions for java.lang.Long to integrate it into the std.time protocol system, treating Long values as milliseconds since the epoch.

  • long-meta: A map defining the metadata for Long as a time instant, including its base type, and map conversion proxies.n protocol.time/-time-meta extension: Extends Long to return long-meta.n protocol.time/IInstant extension: Extends Long to implement IInstant, defining -to-long, -has-timezone?, -get-timezone, and -with-timezone.n protocol.time/IDuration extension: Extends Long to implement IDuration, defining -to-length.n protocol.time/-from-long extension: Extends Long to return the Long value itself.n protocol.time/-now extension: Extends Long to return the current time in milliseconds.n protocol.time/-from-length extension: Extends Long to return the Long value itself.

5.7    std.time.map

This namespace provides functionalities for converting time objects to and from map representations, allowing for easy manipulation of individual time components. It also extends clojure.lang.PersistentArrayMap and clojure.lang.PersistentHashMap to be time instants.

  • to-map [t opts & [ks]]: Converts a time object t into a map representation, extracting specified keys (ks) and including timezone and long value.n from-map [m opts fill]: Converts a map representation m back into a time object of a specified type, filling in missing values with fill.n with-timezone [m tz]: Adds or changes the timezone of a map-based time representation.n arraymap-meta: Metadata for PersistentArrayMap as a time instant.n protocol.time/-time-meta extension: Extends PersistentArrayMap to return arraymap-meta.n protocol.time/IInstant extension: Extends PersistentArrayMap to implement IInstant.n protocol.time/IRepresentation extension: Extends PersistentArrayMap to implement IRepresentation, allowing access to individual time components.n protocol.time/-from-long extension: Extends PersistentArrayMap to create a map representation from a long.n protocol.time/-now extension: Extends PersistentArrayMap to create a map representation of the current time.n hashmap-meta: Metadata for PersistentHashMap as a time instant.n protocol.time/-time-meta extension: Extends PersistentHashMap to return hashmap-meta.n protocol.time/-from-long extension: Extends PersistentHashMap to create a map representation from a long.n protocol.time/-now extension: Extends PersistentHashMap to create a map representation of the current time.n protocol.time/IInstant extension: Extends PersistentHashMap to implement IInstant.n protocol.time/IRepresentation extension: Extends PersistentHashMap to implement IRepresentation.

5.8    std.time.vector

This namespace provides functionalities for converting time objects to and from vector representations, allowing for ordered access to time components.

  • to-vector [t opts ks]: Converts a time object t into a vector representation, extracting specified keys (ks) in order. It supports :all and keyword-based selection for ks.

5.9    std.time.zone

This namespace provides utilities for working with timezones, including mapping offsets to timezone IDs and generating offset strings.

  • by-offset: A map that groups timezone IDs by their raw offset in milliseconds.n pad-zeros [s]: Pads a single-digit string with a leading zero.n generate-offsets []: Generates a sequence of common time offsets (e.g., "00:00", "00:15") and their millisecond equivalents.n* by-string-offset: A map that maps string representations of offsets (e.g., "+00:00", "-05:00", "Z") to their millisecond values.

Overall Importance:

The std.time module is a critical component for any application within the foundation-base project that deals with dates, times, or durations. Its key contributions include:

  • Unified Time Handling: Provides a consistent API for working with diverse Java time types, simplifying time manipulation logic.n Flexibility and Extensibility: The protocol-based design allows for easy integration of new time representations and custom behaviors.n Timezone Awareness: Comprehensive support for timezones ensures accurate calculations and displays across different geographical regions.n Human-Readable Formatting: Tools for formatting and parsing time strings enhance user interaction and data exchange.n Duration Management: Capabilities for calculating and converting time durations are essential for scheduling, logging, and performance analysis.

By offering a robust and flexible time management system, std.time significantly contributes to the foundation-base project's ability to handle complex temporal data accurately and efficiently.