Back to Stream: map & Transform
Q6Question 6 of 34

Convert a String date "2020-01-15" to LocalDate and calculate the employee's tenure in years

Key Concepts
  • LocalDate.parse("2020-01-15") parses an ISO-8601 date string without a custom formatter
  • ChronoUnit.YEARS.between(joinDate, now) returns completed years, accounting for whether the anniversary has passed
  • Period.between(join, now).getYears() gives the same completed years as the year component
  • LocalDate is zone-free; inject a Clock for testable, deterministic 'now'
  • Catch DateTimeParseException for malformed date strings in production data
  • Use java.time instead of legacy Date/Calendar to avoid timezone and mutability bugs

Definition

LocalDate.parse("2020-01-15") creates a date, and ChronoUnit.YEARS.between(join, now) gives completed tenure years.
  • LocalDate.parse(text) parses an ISO-8601 date string (yyyy-MM-dd) into a LocalDate.
  • ChronoUnit.YEARS.between(start, end) returns completed years elapsed.
  • Period.between(start, end).getYears() gives the years component of a year-month-day breakdown.
  • LocalDate is zone-free and immutable.
  • Clock injection makes LocalDate.now(clock) deterministic in tests.

Priya at the National Bank of India reads a joining date written as text, turns it into a real calendar date, and counts how many full years the employee has worked since then.

Understanding the Concepts

How do you parse a date string and compute tenure?

LocalDate.parse uses the default ISO format for yyyy-MM-dd strings. ChronoUnit.YEARS.between gives completed years.

JAVA
package com.bank.hr;

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

public class TenureCalculator {
    public static void main(String[] args) {
        String joinDateStr = "2020-01-15";
        LocalDate joinDate = LocalDate.parse(joinDateStr);
        LocalDate now = LocalDate.now();

        long tenureYears = ChronoUnit.YEARS.between(joinDate, now);

        System.out.println("Join date:    " + joinDate);
        System.out.println("Today:        " + now);
        System.out.println("Tenure years: " + tenureYears);
    }
}
TEXT
Join date:    2020-01-15
Today:        2025-01-15
Tenure years: 5

What is the difference between ChronoUnit and Period?

| Method | Returns | Example |
|---|---|---|
| ChronoUnit.YEARS.between(join, now) | long (completed years) | 5 |
| Period.between(join, now) | Period (years, months, days) | 5y 0m 0d |
| Period.between(join, now).getYears() | int (years component) | 5 |

JAVA
import java.time.Period;

Period p = Period.between(
    LocalDate.of(2020, 1, 15),
    LocalDate.of(2025, 7, 20));
System.out.println(p.getYears() + "y " + p.getMonths() + "m " + p.getDays() + "d");
// 5y 6m 5d

How do you handle custom date formats?

JAVA
import java.time.format.DateTimeFormatter;

String customDate = "15/01/2020";
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate parsed = LocalDate.parse(customDate, fmt);
System.out.println(parsed); // 2020-01-15

How do you make tenure testable with Clock?

JAVA
import java.time.Clock;
import java.time.ZoneId;

public class TenureService {
    private final Clock clock;

    public TenureService(Clock clock) { this.clock = clock; }

    public long computeTenureYears(LocalDate joinDate) {
        return ChronoUnit.YEARS.between(joinDate, LocalDate.now(clock));
    }
}

// Test with fixed clock
Clock fixed = Clock.fixed(
    LocalDate.of(2025, 1, 15).atStartOfDay(ZoneId.systemDefault()).toInstant(),
    ZoneId.systemDefault());
var service = new TenureService(fixed);
assert service.computeTenureYears(LocalDate.of(2020, 1, 15)) == 5;

What are common mistakes?

Mistake 1: not handling invalid date strings.

JAVA
// WRONG: throws DateTimeParseException on invalid input
LocalDate.parse("not-a-date");

// CORRECT: catch and handle
try {
    LocalDate date = LocalDate.parse(dateStr);
} catch (java.time.format.DateTimeParseException e) {
    System.err.println("Invalid date: " + dateStr);
}

Mistake 2: using old Date/Calendar classes.

JAVA
// WRONG: Date is mutable, timezone-dependent, poorly designed
java.util.Date d = new java.util.Date();

// CORRECT: LocalDate is immutable, zone-free, well-designed
LocalDate d = LocalDate.now();

What should a senior engineer know?

  • LocalDate is zone-free, so 'now' depends on the system clock's zone. Inject a Clock for correctness.
  • ChronoUnit.YEARS.between correctly handles the anniversary: if today is before the anniversary this year, it reports one fewer year.
  • Always use java.time over the legacy Date and Calendar classes.
  • In streams, map each employee to tenure and aggregate with averagingLong or summarizingLong for dashboard stats.

What are common interview follow-up questions?

DIAGRAM
- "How do you compute tenure in months instead of years?" Use ChronoUnit.MONTHS.between.
- "How do you handle the case where the join date is in the future?" The result is negative; check and handle.
- "How do you compute tenure for all employees in a stream?" .mapToLong(e -> YEARS.between(e.getJoinDate(), now)).average().

Visual Overview

DIAGRAM
  String "2020-01-15"
       │
       │  LocalDate.parse()
       ▼
  LocalDate: 2020-01-15
       │
       │  ChronoUnit.YEARS.between(join, now)
       │  now = 2025-01-15
       ▼
  long: 5  (five completed years)

  Note: if now were 2025-01-14 (one day before anniversary)
  → result would be 4 (anniversary not yet passed this year)

Key Takeaway

To convert a date string to a date and compute tenure, use LocalDate.parse for ISO-format strings and ChronoUnit.YEARS.between for completed years. The java.time API correctly handles the anniversary: if today is before the anniversary, it reports one fewer year.

Inject a Clock so 'now' is deterministic in tests, and catch DateTimeParseException for malformed input. At the National Bank of India, this drives loyalty benefits based on completed years of service.

Always use java.time instead of legacy Date and Calendar. The modern API is immutable, thread-safe, and handles leap years correctly.

Remember one sentence: LocalDate.parse converts a date string, ChronoUnit.YEARS.between gives completed tenure years, and inject a Clock for testable date logic.

---

Share
Use ← → arrow keys to navigate