Add a concept for Duration and Period

Problem

The datetime concept covers LocalDate, LocalDateTime and formatting, but it does not cover amounts of time. Duration, Period and ChronoUnit.between() do not appear anywhere in the concept docs. The recently added beauty-salon-regulars exercise (#2944) also lists Duration and Period as out of scope.

As a result, students who meet date arithmetic in practice exercises (gigasecond, swift-scheduling, ledger) either do the arithmetic by hand or run into the classic pitfalls of the API:

LocalDate start = LocalDate.of(2024, 1, 15);
LocalDate end = LocalDate.of(2024, 3, 20);

Period.between(start, end);            // P2M5D
Period.between(start, end).getDays();  // 5, not 65
ChronoUnit.DAYS.between(start, end);   // 65

Duration.between(start, end);          // throws UnsupportedTemporalTypeException

LocalDate.of(2025, 1, 31).plus(Period.ofMonths(1)); // 2025-02-28

The Period.getDays() mistake in particular comes up again and again when people compute ā€œhow many days between two datesā€.

Proposal

Add one concept, durations-and-periods (the name is open to discussion), together with a concept exercise.

I’d suggest one concept rather than two: what students most need to learn is the contrast between the two types.

Period Duration
Measures years, months, days (calendar based) seconds and nanoseconds (exact time)
Works with LocalDate LocalTime, LocalDateTime, Instant
Typical use ā€œmembership expires in 1 monthā€ ā€œthe session lasted 90 minutesā€

Learning objectives

  • Know the difference between Period (date-based) and Duration (time-based).
  • Create amounts with Period.ofX() / Duration.ofX() and add or subtract them with plus() / minus().
  • Compute the amount between two temporals with Period.between(), Duration.between() and ChronoUnit.X.between().
  • Understand that Period keeps its units separately (P2M5D) and that getDays() is not a total; use ChronoUnit.DAYS.between() or toTotalMonths() when a total is needed.
  • Understand month-end clamping (Jan 31 + 1 month = Feb 28/29).
  • Use Duration accessors such as toMinutes(), toHours() and toMinutesPart().

Out of scope

  • Time zones and ZonedDateTime. The DST difference between Period.ofDays(1) and Duration.ofDays(1) can be shown as a short aside in about.md, but the exercise itself would stay on Local* types, since zones are not taught anywhere in the track yet.
  • Parsing and formatting ISO-8601 amounts (P1M, PT90M), except for a short mention.
  • Custom TemporalUnit / TemporalAmount implementations.

Prerequisites

  • datetime
  • numbers

Concept exercise

The story should need both types, so students cannot get by with only one of them. One option is a gym or rental service:

  • The membership end date is the start date plus a Period of N months. This tests month-end clamping.
  • The number of remaining days is computed with ChronoUnit.DAYS.between(). This tests the getDays() trap.
  • The length of a visit is a Duration between check-in and check-out LocalDateTimes, billed per started 15 minutes.
  • An optional extra task checks whether a visit exceeded a time limit with Duration.compareTo().

I’m happy to work on the story if the general direction is accepted.

Practice exercises that could reference the concept

  • gigasecond: plus(Duration.ofSeconds(1_000_000_000))
  • swift-scheduling
  • ledger

Add the concept to their practices / prerequisites where it fits.

Deliverables

  • concepts/durations-and-periods/{about.md, introduction.md, links.json, .meta/config.json}
  • A concept exercise with .docs, .meta/design.md, a reference solution and tests
  • config.json updates (concept, exercise, and links from the practice exercises above)
  • Optional: analyzer rules, e.g. flag Period.between(...).getDays() used as a total day count

I’d be glad to open a PR for this, following the same structure as the temporal-adjusters concept.

I think this sounds good. Happy for you to raise a PR for this.

1 Like