Clojure is a JVM language, so an age calculator in Clojure can use the modern Java date and time API directly. The most useful classes are java.time.LocalDate for a date of birth and target date, and java.time.Period for a calendar difference expressed in years, months, and days. This gives Clojure a concise way to calculate completed age without treating a year as a fixed number of days.

Table of Contents
Quick answer: age calculator in Clojure
A simple Clojure age calculator can import java.time.LocalDate and java.time.Period. Create the DOB with (LocalDate/of 1990 5 17), obtain today with (LocalDate/now), and calculate the calendar period with (Period/between dob today). The completed age in years is (.getYears period).
(import (java.time LocalDate Period))\n\n(def dob (LocalDate/of 1990 5 17))\n(def today (LocalDate/now))\n(def age (Period/between dob today))\n\n(println "Age:" (.getYears age))LocalDate represents a date without a time zone, which matches the usual meaning of a birthday. Java documents it as an immutable date object in the ISO calendar system, and Period represents a date-based amount in years, months, and days. Clojure can call these Java APIs directly because it is hosted on the JVM.
Why Clojure works well for age calculations
Clojure has direct Java interoperability, so date calculations do not require a Clojure-specific date model when the JVM date-time API already provides the needed calendar operations. The Clojure documentation demonstrates importing java.time.LocalDate and calling methods such as .plusDays. The same interop mechanism works with Period, DateTimeFormatter, ZoneId, and related classes.
For an age calculator, this approach is useful because the date-of-birth calculation is primarily a calendar problem. LocalDate intentionally has no time-of-day or time-zone component, while Period is designed for date-based quantities such as 25 years, 4 months, and 12 days.
Clojure date classes for an age calculator
| Java type | Clojure use | Age-calculator role |
|---|---|---|
LocalDate | Imported Java class | Store DOB and target calendar dates |
Period | Period/between | Get years, months, and days between dates |
DateTimeFormatter | Formatting/parsing | Read and display ISO-style DOB values |
Year | Leap-year checks | Test whether a year is a leap year |
ZoneId | Used with zoned date-time APIs | Convert timestamps to the intended local date |
The first two types are enough for a basic calculator. The other classes become useful when the application accepts text input, needs explicit leap-year logic, or receives timestamps that must be converted into a calendar date for a particular time zone.
Create a date of birth in Clojure
Use LocalDate/of when the year, month, and day are already separate values. Months use the normal Java range of 1 through 12.
(import (java.time LocalDate))\n\n(def dob (LocalDate/of 1990 5 17))\n\n(println (.getYear dob))\n(println (.getMonthValue dob))\n(println (.getDayOfMonth dob))An invalid calendar date, such as February 30, causes the Java date API to reject the value instead of silently storing an unrelated date. That behavior is useful when a DOB comes from a form and should be validated before the age calculation continues.
Get today’s date in Clojure
(def today (LocalDate/now))\n\n(println today)LocalDate/now uses the system clock and the default time zone to obtain the current local date. For most ordinary birthday calculators that is convenient. For tests, historical calculations, or a service serving users in multiple regions, passing an explicit target date is more deterministic.
Calculate completed age in years
The simplest Clojure implementation delegates the calendar difference to Period/between and reads its years component.
(defn completed-age [dob target]\n (.getYears (Period/between dob target)))\n\n(completed-age\n (LocalDate/of 1990 5 17)\n (LocalDate/of 2026 9 22))The result is the number of completed calendar years represented by the period. This avoids the common error of calculating age by dividing elapsed days by 365 or 365.25. The Java API defines Period/between as a years-months-days calculation between the start date, inclusive, and end date, exclusive.
Calculate age on a fixed date
A fixed target date is especially useful when an age calculation must be reproducible. It is also the easiest form to test because the answer does not depend on the machine clock.
(def dob (LocalDate/of 2000 9 22))\n(def target (LocalDate/of 2026 9 21))\n\n(println (completed-age dob target))On September 21, 2026 the completed age for a September 22, 2000 DOB is 25. On September 22, 2026 it becomes 26. The birthday boundary is handled by the calendar-period calculation rather than an approximate day count.
Calculate years, months, and days
If the application needs more than completed years, keep the complete Period. Its years, months, and days fields can be read independently.
(defn age-period [dob target]\n (Period/between dob target))\n\n(let [p (age-period\n (LocalDate/of 1990 5 17)\n (LocalDate/of 2026 9 22))]\n {:years (.getYears p)\n :months (.getMonths p)\n :days (.getDays p)})This is one of the strongest reasons to use Period rather than a duration in seconds. Months and years are calendar units with variable lengths. Java’s API documents Period as a date-based amount with years, months, and days, while Duration is time-based.
Calculate elapsed days and weeks
Calendar age and elapsed-day age are different outputs. If you need the number of days between two dates, use ChronoUnit/DAYS with LocalDate.
(import (java.time LocalDate) (java.time.temporal ChronoUnit))\n\n(def dob (LocalDate/of 1990 5 17))\n(def target (LocalDate/of 2026 9 22))\n\n(def days (ChronoUnit/DAYS between dob target))\n(def weeks (quot days 7))\n\n(println "Days:" days)\n(println "Complete weeks:" weeks)Use this for elapsed-day or elapsed-week displays, not as a replacement for completed calendar age. A person’s age in completed years and the number of elapsed days since birth answer different questions.
Validate a date of birth
LocalDate/of validates the calendar date when the object is created. You can wrap construction in a try/catch when input arrives as separate numeric fields.
(import (java.time LocalDate DateTimeException))\n\n(defn valid-date? [year month day]\n (try\n (LocalDate/of year month day)\n true\n (catch DateTimeException _\n false)))\n\n(valid-date? 2000 2 29)\n(valid-date? 2023 2 29)Also validate the application rule that the DOB cannot be after the target date. For user-facing calculators, rejecting future DOB values before displaying an age avoids confusing negative results.
Leap years and February 29
Leap years are handled by the ISO calendar rules used by LocalDate. Java also provides Year/isLeap and IsoChronology/isLeapYear for explicit checks. A February 29 DOB is therefore a valid date only in a leap year.
(import (java.time Year))\n\n(.isLeap (Year/of 2024))\n;; true\n\n(.isLeap (Year/of 2025))\n;; falseThe arithmetic is only part of the February 29 question. If your application needs to identify a birthday in a non-leap year, define whether February 28 or March 1 should be treated as the birthday. That is a business rule, so it should be documented instead of being hidden inside the age function.
Parse and format DOB strings
For web forms and APIs, ISO-style YYYY-MM-DD input maps naturally to LocalDate/parse. The default ISO local-date formatter can parse values such as 1990-05-17.
(import (java.time LocalDate))\n\n(def dob (LocalDate/parse "1990-05-17"))\n\n(println dob)\n(println (.format dob java.time.format.DateTimeFormatter/ISO_LOCAL_DATE))For a different display format, use DateTimeFormatter/ofPattern. Keep machine-readable input in a stable format and format dates separately for presentation.

Build a reusable Clojure age calculator
A reusable function should accept both the DOB and target date. That keeps the core calculation pure and makes it straightforward to test.
(ns age-calculator.core\n (:import [java.time LocalDate Period]))\n\n(defn age-period [dob target]\n (Period/between dob target))\n\n(defn completed-age [dob target]\n (.getYears (age-period dob target)))\n\n(defn age-today [dob]\n (completed-age dob (LocalDate/now)))This structure separates the deterministic calendar calculation from the system clock. A command-line program can call age-today, while tests can call completed-age with fixed dates.
Time zones and LocalDate
LocalDate deliberately has no time zone. That is normally desirable for a birthday because the DOB is a calendar date, not an instant. Problems can arise when an application starts with a timestamp, such as an ISO instant from a database or API.
When a timestamp must become a birthday or current local date, first convert it using the intended ZoneId, then extract the LocalDate. The zone should be chosen according to the application’s semantics rather than whatever time zone happens to be configured on the server.
Period versus Duration
| Requirement | Use | Reason |
|---|---|---|
| Completed age | Period | Calendar years and birthday boundaries |
| Years, months, days | Period | Native date-based units |
| Elapsed calendar days | ChronoUnit.DAYS | Direct date difference |
| Exact seconds/nanoseconds | Duration or another time-based type | Elapsed time rather than calendar age |
Java’s documentation distinguishes Period from Duration: a period is date-based and supports years, months, and days, while a duration is time-based. That distinction is important when daylight-saving changes or variable month lengths are involved.
Test the Clojure age calculator
| Test | Expected behavior |
|---|---|
| Birthday today | Completed age includes the new birthday |
| Day before birthday | Completed age remains one year lower |
| February 29 DOB | Leap-year validity is respected |
| Invalid DOB | Date construction or validation rejects it |
| Future DOB | Application rejects or explicitly handles it |
| Fixed target date | Same inputs always produce the same result |
| Month-end dates | Calendar-period result handles variable month lengths |
(deftest age-tests\n (is (= 25\n (completed-age\n (LocalDate/of 2000 9 22)\n (LocalDate/of 2026 9 21))))\n (is (= 26\n (completed-age\n (LocalDate/of 2000 9 22)\n (LocalDate/of 2026 9 22)))))Tests should also cover year boundaries, leap years, dates at the end of February, and parsing failures. Passing an explicit target date makes the core function independent of the system clock.
Common mistakes in Clojure age calculations
- Dividing days by 365: this is not equivalent to completed calendar age.
- Using
Durationfor a birthday: a duration measures elapsed time, not years and months on a calendar. - Ignoring the target date: calculations based only on the current clock are harder to test.
- Mixing timestamps with dates: convert an instant to the intended local date before applying birthday rules.
- Leaving February 29 unspecified: define the non-leap-year birthday policy when the application needs one.
- Parsing user input without validation: let
LocalDatereject invalid calendar dates rather than accepting malformed values.
Compare Clojure with other language implementations
| Language | Typical date API | Age-calculation pattern |
|---|---|---|
| Clojure | java.time.LocalDate and Period | Java interop plus concise Clojure functions |
| Java | LocalDate and Period | Direct calendar-period calculation |
| Python | datetime.date | Date arithmetic with explicit birthday logic |
| Scala | java.time.LocalDate and Period | JVM date API through Scala syntax |
| Rust | Date/time crates | Typed date representations and calendar operations |
| Go | time.Time | Built-in time and date operations |
Our related guides cover age calculators in Java, Python, Scala, Rust, Go, Haskell, Elixir, and Lua.
Complete Clojure example
(ns age-calculator.core\n (:import [java.time LocalDate Period]\n [java.time.format DateTimeFormatter]))\n\n(defn parse-dob [text]\n (LocalDate/parse text DateTimeFormatter/ISO_LOCAL_DATE))\n\n(defn age-period [dob target]\n (when (.isAfter dob target)\n (throw (ex-info "DOB cannot be after target date"\n {:dob dob :target target})))\n (Period/between dob target))\n\n(defn completed-age [dob target]\n (.getYears (age-period dob target)))\n\n(defn age-today [dob]\n (completed-age dob (LocalDate/now)))\n\n(def dob (parse-dob "1990-05-17"))\n(def target (LocalDate/of 2026 9 22))\n(def period (age-period dob target))\n\n(println "Age:" (.getYears period))\n(println "Years:" (.getYears period))\n(println "Months:" (.getMonths period))\n(println "Days:" (.getDays period))This example keeps the core age calculation deterministic, validates that the DOB is not after the target, and uses the ISO local-date formatter for machine-readable input. Replacing the fixed target with (LocalDate/now) gives a current-age function for a typical calculator.
FAQs
Can Clojure calculate age without a separate date library?
Yes. Because Clojure runs on the JVM and can call Java classes directly, java.time.LocalDate and Period provide the core functionality for a date-only age calculator.
What is the simplest Clojure age calculation?
Create two LocalDate values, call Period/between, and read .getYears for completed age. Keep the target date explicit when reproducibility matters.
How does Clojure handle leap years?
The underlying Java ISO calendar implementation handles valid leap-year dates. You can also use Year/isLeap when an application needs an explicit leap-year check.
What is the difference between Period and Duration?
Period is date-based and represents years, months, and days. Duration is time-based and represents elapsed time. A DOB age calculation normally needs the calendar semantics of Period.
Does LocalDate include a time zone?
No. LocalDate represents a calendar date without a time or time zone. If your input is an instant or timestamp, convert it into the intended time zone before extracting the date.
Official references and image credits
- Clojure: Hosted on the JVM — official documentation explaining Java interoperability.
- Clojure API Documentation — official API reference.
- Clojure Deps and CLI Guide — official guide with date/time examples using Java and Clojure libraries.
- Java LocalDate API — official Java documentation for date-only values.
- Java Period API — official documentation for years-months-days periods.
- Java DateTimeFormatter API — official date parsing and formatting documentation.
- Clojure logo: Wikimedia Commons, public-domain file Clojure-icon.png.
- Rich Hickey photograph: Tapestry Dude via Flickr, Wikimedia Commons, CC BY-SA 2.0.
Clojure’s official documentation confirms that the language is hosted on the JVM and can consume Java libraries directly. The official Clojure guide also demonstrates Java date/time interoperability, including LocalDate.
Final takeaway
A Clojure age calculator can stay small while still handling real calendar rules correctly. Use LocalDate for the DOB and target, Period/between for completed years or years-months-days, and ChronoUnit/DAYS when an elapsed-day figure is actually required. Keep the target date injectable for testing, validate future or malformed DOB values, and define your February 29 policy when your application needs one.