Age Calculator in Kotlin: Calculate Age from Date of Birth
9 min read

Age Calculator in Kotlin: Calculate Age from Date of Birth

An age calculator in Kotlin can calculate completed age from a date of birth (DOB) using Kotlin’s interoperability with the Java date-time API. On the JVM, java.time.LocalDate is a natural fit for birthdays because it represents a date without a time or time zone, while java.time.Period can express the calendar difference in years, months and days.

Kotlin logo for an age calculator in Kotlin tutorial
Source: Wikimedia Commons, JetBrains; public-domain designation on Commons.

The key is to calculate age as a calendar value rather than simply subtracting years. This guide shows Kotlin examples for completed age, a fixed as-of date, years-months-days, total elapsed days, input validation, time-zone-aware current dates, February 29 birthdays, testing and a small command-line calculator.

Quick answer: calculate completed age in Kotlin

For a Kotlin/JVM application, use LocalDate for the DOB and reference date, then calculate a Period between them. Kotlin can call Java APIs directly because Kotlin is designed for Java interoperability.

import java.time.LocalDate
import java.time.Period

fun completedAge(dob: LocalDate, asOf: LocalDate): Int {
    require(!dob.isAfter(asOf)) { "DOB cannot be in the future" }
    return Period.between(dob, asOf).years
}

fun main() {
    val dob = LocalDate.of(2000, 9, 21)
    val today = LocalDate.of(2026, 9, 21)

    println(completedAge(dob, today)) // 26
}

Period.between(dob, asOf).years gives the completed calendar years in the period. The explicit asOf date also makes the function deterministic, which is useful in tests and eligibility calculations.

Why year subtraction alone is not enough

A formula such as asOf.year - dob.year ignores whether the birthday has happened in the reference year. Kotlin’s LocalDate and Period operate on calendar dates, so the month and day are included in the calculation.

DOBAs-of dateYear subtractionCompleted age
2000-01-152026-09-212626
2000-10-102026-09-212625
2000-09-212026-09-212626

Use an explicit as-of date

When age is used for an application deadline, eligibility cutoff, historical record or test case, pass the reference date explicitly. This avoids a calculation changing merely because the system clock moved to a new day.

import java.time.LocalDate

val dob = LocalDate.of(1998, 12, 10)
val cutoff = LocalDate.of(2026, 9, 21)

println(completedAge(dob, cutoff))

For an official rule, use the cutoff date stated by that application or authority rather than automatically using today’s date.

Get today’s date safely in Kotlin

LocalDate.now() uses the system clock and default time zone. For a service that has a defined business time zone, supplying a ZoneId makes the intended calendar date explicit.

import java.time.LocalDate
import java.time.ZoneId

val indiaToday = LocalDate.now(ZoneId.of("Asia/Kolkata"))

println(indiaToday)

This distinction matters near midnight: two servers in different time zones can be on different calendar dates at the same instant. For birthday-based age, choose the business time zone deliberately when the current date is generated.

Calculate age in years, months and days

If the output needs more detail than completed years, keep the full Period. Java’s Period represents date-based amounts in years, months and days, and Kotlin can use that API directly.

import java.time.LocalDate
import java.time.Period

fun ageBreakdown(dob: LocalDate, asOf: LocalDate): Period {
    require(!dob.isAfter(asOf)) { "DOB cannot be in the future" }
    return Period.between(dob, asOf)
}

fun main() {
    val period = ageBreakdown(
        LocalDate.of(1995, 9, 21),
        LocalDate.of(2026, 9, 21)
    )

    println(period.years.toString() + " years, " +
        period.months + " months, " + period.days + " days")
}

Calendar age is not the same as converting a total number of days into fixed 365-day years and 30-day months. Month lengths and leap years vary, so use Period when the requirement is a calendar years-months-days representation.

Calculate total elapsed days

Completed age and elapsed days answer different questions. For total days between two dates, use ChronoUnit.DAYS.between() or LocalDate.toEpochDay(). This is an elapsed-calendar-day calculation, not a birthday-based age.

import java.time.LocalDate
import java.time.temporal.ChronoUnit

val dob = LocalDate.of(2000, 1, 1)
val asOf = LocalDate.of(2026, 9, 21)

val totalDays = ChronoUnit.DAYS.between(dob, asOf)

println(totalDays)
RequirementKotlin/JVM approachResult
Completed agePeriod.between(dob, asOf).yearsWhole calendar years
Detailed agePeriod.between(dob, asOf)Years, months, days
Total elapsed daysChronoUnit.DAYS.between()Day difference

Validate a DOB before calculating age

Validate the input before calculating. A future DOB is normally invalid for an age calculator, and an impossible calendar date should not be silently corrected or guessed.

import java.time.DateTimeException
import java.time.LocalDate

fun parseDob(text: String): LocalDate {
    return try {
        LocalDate.parse(text) // expects ISO-8601 yyyy-MM-dd
    } catch (e: DateTimeException) {
        throw IllegalArgumentException("DOB must be a valid date such as 2000-09-21", e)
    }
}

fun validateDob(dob: LocalDate, asOf: LocalDate) {
    require(!dob.isAfter(asOf)) { "DOB cannot be in the future" }
}

val dob = parseDob("2000-09-21")
validateDob(dob, LocalDate.of(2026, 9, 21))

LocalDate.parse() is convenient when your input is ISO-style yyyy-MM-dd. If your user interface accepts another format, parse and validate that format explicitly before creating the LocalDate.

Handle February 29 birthdays

February 29 requires a documented policy because it does not occur in non-leap years. With a direct Period.between() calculation, the completed-year result changes when the calendar anniversary is reached according to the date comparison. If your product instead treats a leap-day birthday as February 28 or March 1 in non-leap years, encode that business rule explicitly.

import java.time.LocalDate
import java.time.Period

val dob = LocalDate.of(2000, 2, 29)

val beforeAnniversary = LocalDate.of(2026, 2, 28)
val afterAnniversary = LocalDate.of(2026, 3, 1)

println(Period.between(dob, beforeAnniversary).years)
println(Period.between(dob, afterAnniversary).years)

Do not describe one February 29 policy as universally correct. Employment, insurance, legal and product requirements can define different anniversary rules. The important engineering step is to choose, document and test the rule that your application requires.

Build a complete Kotlin age-calculator function

import java.time.LocalDate
import java.time.Period
import java.time.ZoneId

data class AgeResult(
    val years: Int,
    val months: Int,
    val days: Int
)

fun calculateAge(
    dob: LocalDate,
    asOf: LocalDate = LocalDate.now(ZoneId.of("Asia/Kolkata"))
): AgeResult {
    require(!dob.isAfter(asOf)) { "Date of birth cannot be in the future" }

    val period = Period.between(dob, asOf)

    return AgeResult(
        years = period.years,
        months = period.months,
        days = period.days
    )
}

fun main() {
    val result = calculateAge(LocalDate.of(1995, 9, 21))

    println(
        "Age: " + result.years + " years, " +
        result.months + " months, " + result.days + " days"
    )
}

The explicit AgeResult data class makes the output easy to pass into a UI, API response or template. For automated tests, pass a fixed asOf value instead of relying on the current clock.

Test a Kotlin age calculator with boundary cases

Date calculations deserve boundary tests because an off-by-one day can change a person’s completed age. Test the birthday itself, the day before and after it, month ends, leap years and invalid future DOBs.

Test caseExpected behavior
DOB equals as-of dateAge is 0
As-of date is one day before birthdayNew birthday year is not completed
As-of date equals birthdayCompleted age increments
DOB is after as-of dateReject input
February 29 DOBFollow the documented leap-day policy
Month-end datesVerify the years-months-days result
import java.time.LocalDate
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

class AgeCalculatorTest {
    @Test
    fun birthdayIsNewCompletedAge() {
        val dob = LocalDate.of(2000, 9, 21)
        val asOf = LocalDate.of(2026, 9, 21)

        assertEquals(26, completedAge(dob, asOf))
    }

    @Test
    fun futureDobIsRejected() {
        val dob = LocalDate.of(2027, 1, 1)
        val asOf = LocalDate.of(2026, 9, 21)

        assertFailsWith {
            completedAge(dob, asOf)
        }
    }
}

Common mistakes in a Kotlin age calculator

  • Subtracting years only: this can report one year too many before the birthday.
  • Using time duration for calendar age: elapsed hours are not the same thing as completed birthdays.
  • Ignoring the business time zone: a server near midnight can have a different current date than the user.
  • Accepting future DOBs: reject dates after the reference date unless the application has a special reason not to.
  • Leaving February 29 undefined: document how non-leap-year anniversaries are handled.
  • Testing only ordinary dates: include leap years, month ends and birthday boundaries.

LocalDate versus date-time values

A birthday is normally a date, not an instant in time. Java’s LocalDate deliberately has no time or time-zone component, which makes it suitable for date-only concepts such as birthdays. If the requirement genuinely involves an instant, time of day or time-zone conversion, use the appropriate Java/Kotlin date-time type instead.

RequirementTypical Kotlin/JVM type
Birthday or DOBLocalDate
Years-months-days agePeriod
Total elapsed daysChronoUnit.DAYS
Date and time without zoneLocalDateTime
Instant tied to a time zoneZone-aware date-time types

Kotlin compared with other age-calculator implementations

Kotlin’s JVM interoperability means the language can use the Java date-time API directly. For related implementation examples, see the age calculators for Java, C#, Python, PHP, JavaScript, React, and TypeScript.

Frequently asked questions

How do I calculate age from DOB in Kotlin?

Use LocalDate for the DOB and reference date, then calculate Period.between(dob, asOf).years for completed calendar years.

Can Kotlin calculate age on a specific date?

Yes. Pass the desired cutoff or as-of date to the calculation instead of using the system clock.

Can Kotlin return age in years, months and days?

Yes. Keep the complete Period returned by Period.between() and read its years, months and days components.

Should I use LocalDate or LocalDateTime for a birthday?

Use LocalDate when the requirement is a calendar date without a time of day. Use a date-time type only when the application actually needs time information.

How should February 29 be handled?

Define a non-leap-year anniversary policy and test it. A direct Period calculation is calendar-based, but a business application may need a different rule.

Final takeaway

A reliable age calculator in Kotlin should use calendar-aware types rather than treating age as a simple difference between years or a fixed number of hours. LocalDate represents the DOB and reference date, Period provides completed years or a years-months-days breakdown, and ChronoUnit.DAYS handles total elapsed days. Validate future dates, make the current time zone deliberate, document February 29 behavior and test the boundary dates that can produce off-by-one errors.

Sources