Date and Time
This cookbook covers working with Java's java.time
package in two styles, the
first with interop and the second using libraries.
This guide covers Clojure 1.11.4 and Java 8 or later.
This work is licensed under a Creative Commons Attribution 3.0 Unported License (including images & stylesheets). The source is available on Github.
Introduction
This cookbook does not assume a background in Java, if you have such a background the interop section might be of interest.
This cookbook will be useful if you got your Clojure environment setup, know some of the basics and are looking to work with time and dates in Clojure.
This guide aims to cover date and time in the JVM and JS domains. The guide currently covers only JVM.
The scope of this cookbook covers basic operations using the two styles.
This cookbook will cover working with java.time
as java.util.Date
is legacy1.
Overview
The representation of date and time is dependent upon the host, for example in the JVM
- Dates are a
java.util.LocalDate
object. - Time is a a
java.util.LocalTime
object. - Clojure can represent these objects as a
java.util.LocalDate
,java.util.LocalTime
andjava.util.LocalDateTime
respectively. - The
#inst
is a tagged literal is used to represent ajava.util.Date
object.
While in JavaScript: TBD
Libraries
- clojure.java-time
A Clojure wrapper for Java 8 Date-Time API.
- cljc.java-time
A Clojure(Script) library which mirrors the java.time api through kebab-case-named vars.
- tick
A Clojure(Script) & babashka library for dealing with time. Intended as a replacement for clj-time.
Preliminaries
The examples below assume the following deps.edn
{:paths ["src"]
:deps {org.clojure/clojure {:mvn/version "1.11.4"}
clojure.java-time/clojure.java-time {:mvn/version "1.3.0"}
com.widdindustries/cljc.java-time {:mvn/version "0.1.21"}}}
Recipes
clojure.java-time
Basics
For the people coming from a non-Java background, we are creating an instance
of the java.time.LocalDate
, java.time.LocalTime
and
java.time.LocalDateTime
classes respectively, that's what the #object
is for.
(require '[java-time.api :as jt])
;; What's the current day?
(jt/local-date)
;; => #object[java.time.LocalDate 0x28cb30b8 "2023-11-01"]
;; You may see a different result.
;; What's the current time?
(jt/local-time)
;; => #object[java.time.LocalTime 0x7f536ee5 "23:53:32.896427602"]
;; You may see a different result.
;; What's the date and time of today?
(jt/local-date-time)
;; => #object[java.time.LocalDateTime 0x3ac70fac "2023-11-01T23:54:14.020607313"]
;; You may see a different result.
;; Does date1 come before date2?
(let [date1 (jt/local-date "2023-01-01")
date2 (jt/local-date "2023-10-01")]
(jt/before? date1
date2))
;; => true
;; Add N days to a date
(jt/plus (jt/local-date "2023-11-01")
(jt/days 10))
;; => #object[java.time.LocalDate 0x165637c5 "2023-11-13"]
;; What's the date a year before?
(jt/minus (jt/local-date "2023-11-03") (jt/years 1))
;; => #object[java.time.LocalDate 0x6e927fd0 "2022-11-03"]
;; Difference in days between a date and a year after
(jt/time-between :days
(jt/local-date "2023-11-03")
(jt/plus (jt/local-date "2023-11-03")
(jt/years 1)))
;; => 366
;; What day of the week was it?
(jt/day-of-week (jt/minus (jt/local-date) (jt/years 1)))
;; => #object[java.time.DayOfWeek 0x10fb6e5f "THURSDAY"]
;; How to format the output in a specific way?
(jt/format :iso-date (jt/local-date "2023-11-01"))
;; => "2023-11-01"
(jt/format "yyyy/MM/dd" (jt/local-date "2023-11-01"))
;; => "2023/11/01"
;; To parse the date we just formatted
(jt/local-date "yyyy/MM/dd" "2023/11/01")
;; This opens up the door to creating your own parsers.
(defn ydm
"A parses similar to ydm() from the R package lubridate."
[s]
(jt/local-date "yyyyddMM" s))
;; => #'user/ydm
(ydm "20170108")
;; => #object[java.time.LocalDate 0x4851aa55 "2017-08-01"]
For more formatting patterns check out the
DateTimeFormatter
class, the table Predefined Formatters is where the :iso-date
came from
although note it is written as ISO_DATE
in the Formatted column.
More basic recipes can be read in the
README for
clojure.java-time
.
Date and Time via interop
You can use interop by either using the full package name and the class or by importing the class.
Using the package and class name to create an instance of LocalDate
:
(java.time.LocalDate/now) ; => #object[java.time.LocalDate 0x4d98485b "2023-11-01"]
Using the import and the class name to create an instance of LocalDate
:
(import (java.time LocalDate LocalTime LocalDateTime))
;; What's the current day?
(LocalDate/now)
;; => #object[java.time.LocalDate 0x28cb30b8 "2023-11-01"]
;; You may see a different result.
;; What's the current time?
(LocalTime/now)
;; => #object[java.time.LocalTime 0x4dc69d04 "12:53:09.791974265"]
;; You may see a different result.
;; What's the date and time of today?
(LocalDateTime/now)
;; => #object[java.time.LocalDateTime 0x6d7119a7 "2023-11-03T12:53:36.765946815"]
;; You may see a different result.
;; Does date1 come before date2?
(let [date1 (LocalDate/parse "2023-01-01")
date2 (LocalDate/parse "2023-10-01")]
(.isBefore date1 date2))
;; => true
;; Add N days to a date
(.plus (LocalDate/now) (java.time.Period/ofDays 10))
;; => #object[java.time.LocalDate 0x507f3b0d "2023-11-13"]
;; Or
(.plusDays (LocalDate/now) 10)
;; => #object[java.time.LocalDate 0x439c6250 "2023-11-13"]
;; What's the date a year before?
(.minus (LocalDate/parse "2023-11-03") (java.time.Period/ofYears 1))
;; => #object[java.time.LocalDate 0x37380cf2 "2022-11-03"]
;; Difference in days between a date and a year after
(.between (java.time.temporal.ChronoUnit/DAYS)
(LocalDate/parse "2023-11-03")
(.plusYears (LocalDate/parse "2023-11-03")
1))
;; => 366
;; What day of the week was it?
(.getDayOfWeek (LocalDate/parse "2023-11-03"))
;; => #object[java.time.DayOfWeek 0x7823658a "FRIDAY"]
;; How to format the output in a specific way?
(.format (LocalDate/parse "2023-11-01")
(java.time.format.DateTimeFormatter/ofPattern "yyyy/MM/dd"))
;; => "2023/11/01"
;; To parse the date we just formatted
(LocalDate/parse "2023/11/01"
(java.time.format.DateTimeFormatter/ofPattern "yyyy/MM/dd"))
;; => #object[java.time.LocalDate 0xa349bd0 "2023-11-01"]
;; This opens up the door to creating your own parsers.
(defn ydm
"A parses similar to ydm() from the R package lubridate."
[s]
(LocalDate/parse s
(java.time.format.DateTimeFormatter/ofPattern "yyyyddMM")))
;; => #'user/ydm
(ydm "20170108")
;; => #object[java.time.LocalDate 0x3135d642 "2017-08-01"]
cljc.java-time
According to the How it
works section of
cljc.java-time
each class in java.time
has a corresponding Clojure
namespace. This means that we need to require each class as a namespace.
(require '[cljc.java-time local-date local-time local-date-time period temporal])
(require '[cljc.java-time.temporal.chrono-unit])
(require '[cljc.java-time.format.date-time-formatter])
;; What's the current day?
(cljc.java-time.local-date/now)
;; => #object[java.time.LocalDate 0x7f314585 "2023-11-06"]
;; You may see a different result.
;; What's the current time?
(cljc.java-time.local-time/now)
;; => #object[java.time.LocalTime 0x101358e2 "21:39:08.521579413"]
;; You may see a different result.
;; What's the date and time of today?
(cljc.java-time.local-date-time/now)
;; => #object[java.time.LocalDateTime 0x56ba51c9 "2023-11-06T21:40:52.985876111"]
;; You may see a different result.
;; Does date1 come before date2?
(let [date1 (cljc.java-time.local-date/parse "2023-01-01")
date2 (cljc.java-time.local-date/parse "2023-10-01")]
(cljc.java-time.local-date/is-before date1 date2))
;; => true
;; Add N days to a date
(cljc.java-time.local-date/plus (cljc.java-time.local-date/parse "2023-11-03")
(cljc.java-time.period/of-days 10))
;; => #object[java.time.LocalDate 0x7c03648a "2023-11-13"]
;; Or
(cljc.java-time.local-date/plus-days (cljc.java-time.local-date/parse "2023-11-03")
10)
;; => #object[java.time.LocalDate 0x6ef62d95 "2023-11-13"]
;; What's the date a year before?
(cljc.java-time.local-date/minus (cljc.java-time.local-date/parse "2023-11-03")
(cljc.java-time.period/of-years 1))
;; => #object[java.time.LocalDate 0x24023790 "2022-11-03"]
;; Difference in days between a date and a year after
(cljc.java-time.temporal.chrono-unit/between cljc.java-time.temporal.chrono-unit/days
(cljc.java-time.local-date/parse "2023-11-03")
(cljc.java-time.local-date/plus-years
(cljc.java-time.local-date/parse "2023-11-03")
1))
;; => 366
;; What day of the week was it?
(cljc.java-time.local-date/get-day-of-week (cljc.java-time.local-date/parse "2023-11-03"))
;; => #object[java.time.DayOfWeek 0x38e22a90 "FRIDAY"]
;; How to format the output in a specific way?
(cljc.java-time.local-date/format (cljc.java-time.local-date/parse "2023-11-01")
(cljc.java-time.format.date-time-formatter/of-pattern "yyyy/MM/dd"))
;; => "2023/11/01"
;; To parse the date we just formatted
(cljc.java-time.local-date/parse "2023/11/01"
(cljc.java-time.format.date-time-formatter/of-pattern "yyyy/MM/dd"))
;; => #object[java.time.LocalDate 0x62756039 "2023-11-01"]
;; This opens up the door to creating your own parsers.
(defn ydm
"A parses similar to ydm() from the R package lubridate."
[s]
(cljc.java-time.local-date/parse s
(cljc.java-time.format.date-time-formatter/of-pattern "yyyyddMM")))
;; => #'user/ydm
(ydm "20170108")
;; #object[java.time.LocalDate 0x194dcb8 "2017-08-01"]