Age Calculator in Haskell is a practical example of working with calendar dates in a functional programming language. A reliable calculator should count completed calendar years rather than divide an elapsed duration by an assumed 365-day year. Haskell’s time package provides Day, Gregorian calendar operations, parsing, formatting and duration functions that make this possible without treating a date of birth as a simple timestamp. Read the Haskell time documentation.

Quick Answer: Haskell Age Calculator
For completed age, represent the DOB and reference date as Data.Time.Day values. Extract their Gregorian years, estimate the year difference, then check whether the birthday has already occurred in the reference year. The final result is the number of completed calendar years.
import Data.Time.Calendar (Day, addGregorianYearsClip, toGregorian)
import Data.Time.Clock (getCurrentTime, utctDay)
calculateAge :: Day -> Day -> Either String Integer
calculateAge dob asOf
| dob > asOf = Left "date of birth cannot be after the reference date"
| otherwise = Right completedYears
where
(dobYear, _, _) = toGregorian dob
(asYear, _, _) = toGregorian asOf
estimate = asYear - dobYear
anniversary = addGregorianYearsClip estimate dob
completedYears = if anniversary > asOf then estimate - 1 else estimate
main :: IO ()
main = do
now <- getCurrentTime
let today = utctDay now
dob = read "2000-06-15" :: Day
print (calculateAge dob today)The important detail is the birthday comparison. If the anniversary for the reference year is still in the future, subtract one from the raw year difference. This avoids the common off-by-one error around birthdays.
Why Use Haskell’s Day Type for a Date of Birth?
A date of birth is normally a calendar date, not an exact moment. Haskell’s Day type from Data.Time.Calendar represents a day in the Gregorian calendar and is therefore a natural fit for a DOB. You do not need to attach a timezone when the input is simply a date such as 15 June 2000.
| Haskell tool | Age-calculator purpose |
|---|---|
Day | Stores a date of birth or date-only reference date |
toGregorian | Extracts year, month and day components |
addGregorianYearsClip | Builds a birthday anniversary in another year |
diffDays | Calculates elapsed whole days |
fromGregorianValid | Safely constructs a date and rejects invalid calendar combinations |
getCurrentTime / utctDay | Obtains today’s UTC calendar date |
Keeping the date-only calculation separate from timezone-aware timestamps makes the business rule easier to understand. If the application defines “today” according to a user’s local timezone, obtain the local date deliberately rather than silently using UTC.
Set Up a Haskell Project
Modern Haskell projects commonly use a project toolchain such as GHCup together with Cabal or Stack. The date functionality used here comes from the time package. Before starting a production project, check the package version supported by your compiler and project resolver.
cabal init --interactiveAdd time to the package dependencies in your Cabal file if it is not already available through your project’s dependency set.
build-depends:
base,
time1. Calculate Age on a Specific Reference Date
Using an explicit reference date is preferable for eligibility checks, historical reports and tests. It also makes the function pure: the result depends only on its two date arguments.
import Data.Time.Calendar (Day, addGregorianYearsClip, toGregorian)
calculateAge :: Day -> Day -> Either String Integer
calculateAge dob asOf
| dob > asOf = Left "DOB cannot be after the reference date"
| otherwise = Right age
where
(dobYear, _, _) = toGregorian dob
(asYear, _, _) = toGregorian asOf
years = asYear - dobYear
birthday = addGregorianYearsClip years dob
age = if birthday > asOf then years - 1 else yearsThis design also illustrates a useful Haskell principle: separate pure calculation from input/output. The function has no dependency on the system clock, so the same inputs always produce the same result.
2. Parse a Date of Birth from Text
User interfaces usually provide the DOB as text. The time package provides parsing functions for calendar values. For a simple ISO-style input, read can work when the expected type is known, but an application that needs controlled error messages should use a parser such as parseTimeM.
import Data.Time (Day, defaultTimeLocale, parseTimeM)
parseDob :: String -> Either String Day
parseDob input =
case parseTimeM True defaultTimeLocale "%Y-%m-%d" input of
Just day -> Right day
Nothing -> Left "invalid date; use YYYY-MM-DD"
main :: IO ()
main = print (parseDob "2000-06-15")Parsing should be treated as validation, not as part of the age formula. An input such as 2000-02-31 should be rejected rather than converted into a plausible age.

3. Calculate Current Age in Haskell
When the application really means “age today,” obtain the current date at the application boundary and pass it to the pure calculator. getCurrentTime obtains the current UTC time and utctDay extracts its calendar day.
import Data.Time.Calendar (Day)
import Data.Time.Clock (getCurrentTime, utctDay)
main :: IO ()
main = do
now <- getCurrentTime
let today = utctDay now
dob = read "1998-09-22" :: Day
case calculateAge dob today of
Left err -> putStrLn err
Right age -> putStrLn ("Completed age: " ++ show age)4. Calculate Total Elapsed Days
Sometimes an application needs the number of elapsed days rather than completed years. Haskell’s diffDays is appropriate for this separate requirement.
import Data.Time.Calendar (Day, diffDays)
elapsedDays :: Day -> Day -> Integer
elapsedDays dob asOf = diffDays asOf dobDo not replace the calendar-age calculation with diffDays / 365. Leap years mean that an elapsed-day count does not map cleanly to completed calendar years.
5. Calculate Complete Weeks
For an elapsed-week display, divide the total elapsed days by seven using integer division.
completeWeeks :: Day -> Day -> Integer
completeWeeks dob asOf = diffDays asOf dob `div` 7This represents complete elapsed weeks. It is different from asking for the ISO calendar week number of a particular date.
6. Calculate Age in Years, Months and Days
A more detailed age display can be built by first calculating completed years, then moving the DOB forward by those years, then counting complete calendar months and the remaining days. Because months have different lengths, this is a calendar calculation rather than a fixed-duration calculation.
import Data.Time.Calendar
( Day
, addGregorianMonthsClip
, addGregorianYearsClip
, diffDays
)
ageYearsMonthsDays :: Day -> Day -> Either String (Integer, Integer, Integer)
ageYearsMonthsDays dob asOf = do
years <- calculateAge dob asOf
let afterYears = addGregorianYearsClip years dob
months = countMonths afterYears asOf 0
afterMonths = addGregorianMonthsClip months afterYears
days = diffDays asOf afterMonths
pure (years, months, days)
countMonths :: Day -> Day -> Integer -> Integer
countMonths cursor asOf n =
let next = addGregorianMonthsClip 1 cursor
in if next > asOf
then n
else countMonths next asOf (n + 1)The use of “clip” matters. Calendar-month addition must deal with dates such as the 31st when the target month has fewer days. Test month-end birthdays and document the convention your application needs.
7. Handle Leap Years and February 29
Leap years are especially important for age calculators because 29 February exists only in leap years. A robust program should not invent a universal business rule for a 29 February birthday when the reference year is not a leap year. Depending on the context, an organization may define the relevant anniversary differently.
import Data.Time.Calendar (Day, fromGregorianValid)
validLeapDay :: Maybe Day
validLeapDay = fromGregorianValid 2000 2 29
invalidLeapDay :: Maybe Day
invalidLeapDay = fromGregorianValid 2021 2 29The checked constructor returns Nothing for an impossible Gregorian date. Keep the leap-day policy separate from this basic validity check.
8. Validate Future DOBs
A date can be syntactically valid and still be invalid for an age calculation. For example, a DOB after the reference date cannot describe a completed age as of that reference date.
validateDob :: Day -> Day -> Either String Day
validateDob dob asOf
| dob > asOf = Left "date of birth cannot be in the future"
| otherwise = Right dobReturning Either keeps invalid input visible to the caller. Avoid silently returning zero, because that can turn bad input into a result that looks legitimate.
9. Format Dates for Display
Store and exchange dates in a predictable format, then format them separately for users. Haskell’s formatTime supports formatting with a locale and format string.
import Data.Time (defaultTimeLocale, formatTime)
displayDate :: Day -> String
displayDate day =
formatTime defaultTimeLocale "%d %B %Y" day10. When Time Zones Matter
A date-only DOB normally does not require a timezone. The question changes when the application defines “today” using a user’s local region. Haskell’s time package distinguishes calendar dates, local date-times and UTC date-times. Choose the representation that matches the requirement.
| Requirement | Useful representation |
|---|---|
| Date of birth only | Day |
| UTC date/time | UTCTime |
| Local civil date/time | LocalTime |
| Calendar duration | Calendar-duration facilities in the time package |
11. Build a Reusable Pure Age Function
The most reusable architecture keeps the calculation function pure and leaves parsing, current-time access and user-interface concerns outside it.
module AgeCalculator (calculateAge) where
import Data.Time.Calendar (Day, addGregorianYearsClip, toGregorian)
calculateAge :: Day -> Day -> Either String Integer
calculateAge dob asOf
| dob > asOf = Left "DOB cannot be after reference date"
| otherwise = Right age
where
(dobYear, _, _) = toGregorian dob
(asYear, _, _) = toGregorian asOf
years = asYear - dobYear
birthday = addGregorianYearsClip years dob
age = if birthday > asOf then years - 1 else yearsWorked Age Examples
| Date of birth | Reference date | Completed age |
|---|---|---|
| 15-Jun-2000 | 21-Sep-2026 | 26 |
| 15-Jun-2000 | 01-Jun-2026 | 25 |
| 22-Sep-1998 | 22-Sep-2026 | 28 |
| 22-Sep-1998 | 21-Sep-2026 | 27 |
| 29-Feb-2000 | 28-Feb-2026 | Depends on the application’s leap-day rule |
The boundary cases demonstrate why the birthday comparison matters. Someone born on 15 June 2000 has completed 26 years by 21 September 2026, but has completed only 25 years on 1 June 2026.
How to Test a Haskell Age Calculator
Use fixed dates in unit tests rather than asking the system clock for today’s date. This makes the expected result stable.
import Data.Time.Calendar (fromGregorian)
birthdayHasArrived :: Bool
birthdayHasArrived =
calculateAge (fromGregorian 2000 6 15)
(fromGregorian 2026 9 21)
== Right 26
birthdayHasNotArrived :: Bool
birthdayHasNotArrived =
calculateAge (fromGregorian 2000 6 15)
(fromGregorian 2026 6 1)
== Right 25
futureDobRejected :: Bool
futureDobRejected =
case calculateAge (fromGregorian 2030 1 1)
(fromGregorian 2026 9 21) of
Left _ -> True
Right _ -> FalseCommon Haskell Age-Calculation Mistakes
- Subtracting only Gregorian years. The raw year difference can be one too high before the birthday.
- Dividing elapsed days by 365. Leap years make this unsuitable for completed calendar age.
- Using a timestamp when only a date is required. A
Dayvalue can avoid unnecessary timezone complexity. - Hiding the current date inside the calculation. Pass the reference date into a pure function for deterministic behavior.
- Ignoring parsing failures. Invalid DOB text should produce an explicit error.
- Assuming one universal February 29 rule. Document the business rule required by the application.
- Testing only ordinary birthdays. Boundary and leap-day cases are where off-by-one errors usually appear.
Haskell vs Other Age-Calculator Implementations
| Implementation | Main date approach | Useful for |
|---|---|---|
| Haskell | Data.Time Day and Gregorian operations | Functional programs and services |
| Rust | Chrono NaiveDate | Rust applications and services |
| Go | time.Time | Go services and command-line tools |
| Java | LocalDate and Period | Java applications |
| C++ | Chrono calendar facilities | Native applications |
| Swift | Foundation Calendar | Apple-platform applications |
| Python | datetime and calendar logic | Scripts and web applications |
For related tutorials, see our guides to Age Calculator in Rust, Age Calculator in Go, Age Calculator in Java, Age Calculator in C++, Age Calculator in Swift, Age Calculator in Python and Age Calculator in LibreOffice Calc.
FAQs
How do I calculate age in Haskell?
Represent the DOB and reference date as Day values, calculate the Gregorian year difference, construct the birthday anniversary for the reference year, and subtract one when that anniversary is still ahead of the reference date.
Does Haskell have a built-in age calculator?
Haskell’s date/time facilities do not provide a dedicated age-calculator function. The time package provides the calendar primitives needed to build one.
What Haskell type should I use for a DOB?
For a date-only DOB, Day from Data.Time.Calendar is a natural choice. Use a date-time type when the exact time or timezone is part of the requirement.
How do I calculate total days between DOB and today?
Use diffDays between the two Day values. This gives elapsed whole days and should not be confused with completed calendar years.
How should February 29 be handled?
First validate that the DOB itself is a real Gregorian date. For a 29 February birthday in a non-leap reference year, define the application’s required anniversary rule and test it explicitly.
Can I calculate age on a past cutoff date?
Yes. Pass the historical cutoff as the second argument to the pure age function. This is one reason not to hide the current date inside the calculation.
Useful Haskell Date and Time References
- Haskell.org — official Haskell community and language site.
- Data.Time.Calendar documentation — Gregorian dates and calendar operations.
- Data.Time.LocalTime documentation — local date/time and calendar-duration facilities.
- Data.Time.Format documentation — date parsing and formatting.
Final Takeaway
A reliable age calculator in Haskell should treat age as a calendar concept. Use Day for date-only DOB values, calculate the Gregorian year difference, and compare the reference date with the birthday anniversary before returning completed years. Use diffDays only when the requirement is elapsed days.
For production applications, keep the reference date explicit, validate dates, define the February 29 rule required by the application, and separate pure calculation from system-clock and user-interface code. That structure makes the calculator easier to test and reuse.
Technical note: this article explains programming techniques. It does not establish a universal legal definition of age. When an age result is used for official eligibility, follow the exact rule and cutoff date specified by the relevant authority.