Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions concepts/temporal-adjusters/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"blurb": "Perform date adjustments using Java's `TemporalAdjuster` interface and `TemporalAdjusters` utility class.",
"authors": [
"rabestro"
],
"contributors": []
}
143 changes: 143 additions & 0 deletions concepts/temporal-adjusters/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# About

While the core `java.time` classes like `LocalDate` and `LocalDateTime` provide basic methods for adding or subtracting time units, more complex date adjustments often require more sophisticated logic (e.g., finding the "second Tuesday of the month" or the "last day of the year").

The `java.time.temporal` package provides the [`TemporalAdjuster`][temporaladjuster-docs] functional interface for this purpose.
It represents a strategy for adjusting a temporal object (like a `LocalDate`).
The [`TemporalAdjusters`][temporaladjusters-docs] (note the plural 's') utility class contains static factory methods providing many common, predefined adjuster implementations.

## Using TemporalAdjusters

Temporal objects like `LocalDate` and `LocalDateTime` have a `with()` method that accepts a `TemporalAdjuster`.
This method returns a *new* temporal object with the adjustment applied.

```exercism/note
Using `with(TemporalAdjuster)` returns a _new_ instance and does not update the existing instance, as all `java.time` temporal classes are immutable.
```

```java
import java.time.LocalDate;
import java.time.Month;
import java.time.DayOfWeek;
import java.time.temporal.TemporalAdjusters;

LocalDate date = LocalDate.of(2024, Month.AUGUST, 15); // 2024-08-15 (a Thursday)

// Use a predefined adjuster from TemporalAdjusters
LocalDate firstDayOfNextMonth = date.with(TemporalAdjusters.firstDayOfNextMonth());
// => 2024-09-01

LocalDate nextSunday = date.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
// => 2024-08-18
```

## Common Predefined Adjusters

The `TemporalAdjusters` class provides many useful static methods.
Here are some common examples:

### Finding Ordinal Day of Week

The `dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek)` adjuster finds the nth occurrence of a specific day of the week within the month.

```java
LocalDate date = LocalDate.of(2024, Month.AUGUST, 15); // 2024-08-15

// Find the second Tuesday of August 2024
LocalDate secondTuesday = date.with(TemporalAdjusters.dayOfWeekInMonth(2, DayOfWeek.TUESDAY));
// => 2024-08-13

// Find the fourth Friday of August 2024
LocalDate fourthFriday = date.with(TemporalAdjusters.dayOfWeekInMonth(4, DayOfWeek.FRIDAY));
// => 2024-08-23
```

### Finding First/Last Day of Week in Month

The `firstInMonth(DayOfWeek dayOfWeek)` and `lastInMonth(DayOfWeek dayOfWeek)` adjusters find the first or last occurrence of a day of the week within the month.

```java
LocalDate date = LocalDate.of(2024, Month.AUGUST, 15); // 2024-08-15

// Find the first Sunday of August 2024
LocalDate firstSunday = date.with(TemporalAdjusters.firstInMonth(DayOfWeek.SUNDAY));
// => 2024-08-04

// Find the last Monday of August 2024
LocalDate lastMonday = date.with(TemporalAdjusters.lastInMonth(DayOfWeek.MONDAY));
// => 2024-08-26
```

### Finding First/Last Day of Month/Year

Simpler adjusters find the first or last day of the current month or year.

```java
LocalDate date = LocalDate.of(2024, Month.AUGUST, 15); // 2024-08-15

LocalDate firstDay = date.with(TemporalAdjusters.firstDayOfMonth());
// => 2024-08-01

LocalDate lastDay = date.with(TemporalAdjusters.lastDayOfMonth());
// => 2024-08-31

LocalDate lastDayOfYear = date.with(TemporalAdjusters.lastDayOfYear());
// => 2024-12-31
```

### Relative Adjustments (Next/Previous)

Adjusters like `next()`, `previous()`, `nextOrSame()`, and `previousOrSame()` find the next or previous occurrence of a given day of the week, relative to the current date.

- `next()`/`previous()`: Excludes the current date if it matches.
- `nextOrSame()`/`previousOrSame()`: Includes the current date if it matches.

```java
LocalDate thursday = LocalDate.of(2024, Month.AUGUST, 15); // A Thursday

// Find the next Sunday (exclusive)
LocalDate nextSunday = thursday.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
// => 2024-08-18

// Find the next Thursday (exclusive)
LocalDate nextThursday = thursday.with(TemporalAdjusters.next(DayOfWeek.THURSDAY));
// => 2024-08-22

// Find the next Thursday (inclusive) - returns the same date
LocalDate nextOrSameThursday = thursday.with(TemporalAdjusters.nextOrSame(DayOfWeek.THURSDAY));
// => 2024-08-15

// Find the previous Monday (exclusive)
LocalDate previousMonday = thursday.with(TemporalAdjusters.previous(DayOfWeek.MONDAY));
// => 2024-08-12
```

## Custom Adjusters

Since `TemporalAdjuster` is a [`@FunctionalInterface`][functionalinterface-docs], you can create your own custom adjusters using lambda expressions for logic not covered by the predefined methods.
The interface requires implementing a single method: `Temporal adjustInto(Temporal temporal)`.

```java
// Example: An adjuster that finds the next workday (Mon-Fri), skipping weekends.
TemporalAdjuster nextWorkday = temporal -> {
LocalDate result = (LocalDate) temporal; // Assuming input is LocalDate
do {
result = result.plusDays(1);
} while (result.getDayOfWeek() == DayOfWeek.SATURDAY || result.getDayOfWeek() == DayOfWeek.SUNDAY);
return result;
};

LocalDate friday = LocalDate.of(2024, Month.AUGUST, 16);
LocalDate nextWorkdayDate = friday.with(nextWorkday);
// => 2024-08-19 (Monday)

LocalDate sunday = LocalDate.of(2024, Month.AUGUST, 18);
LocalDate nextWorkdayFromSunday = sunday.with(nextWorkday);
// => 2024-08-19 (Monday)
```

While custom adjusters are powerful, the predefined adjusters in the `TemporalAdjusters` class cover a wide range of common use cases.

[temporaladjuster-docs]: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/time/temporal/TemporalAdjuster.html
[temporaladjusters-docs]: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/time/temporal/TemporalAdjusters.html
[functionalinterface-docs]: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/FunctionalInterface.html
93 changes: 93 additions & 0 deletions concepts/temporal-adjusters/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Introduction

When working with `java.time` objects like `LocalDate`, you often need to perform complex date adjustments, such as finding the "second Tuesday of the month".
Instead of calculating this manually, you can use **`TemporalAdjusters`**.

The `java.time.temporal.TemporalAdjusters` (note the plural 's') utility class provides static methods for many common date adjustments.
You apply these adjustments using the `.with()` method on a temporal object (like `LocalDate`).

```exercism/note
Using `with(TemporalAdjuster)` returns a _new_ instance and does not update the existing instance, as `java.time` objects are immutable.
```

```java
import java.time.LocalDate;
import java.time.Month;
import java.time.DayOfWeek;
import java.time.temporal.TemporalAdjusters;

LocalDate date = LocalDate.of(2024, Month.AUGUST, 15); // 2024-08-15 (a Thursday)

// Use a predefined adjuster: find the last day of the month
LocalDate lastDay = date.with(TemporalAdjusters.lastDayOfMonth());
// => 2024-08-31
```

Here are some key adjusters useful for scheduling calculations:

## Finding Ordinal Day of Week

Use `dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek)` to find the nth occurrence of a day of the week.

```java
// Find the second Tuesday of August 2024
LocalDate secondTuesday = date.with(TemporalAdjusters.dayOfWeekInMonth(2, DayOfWeek.TUESDAY));
// => 2024-08-13
```

## Finding First/Last Day of Week in Month

Use `firstInMonth(DayOfWeek dayOfWeek)` or `lastInMonth(DayOfWeek dayOfWeek)`.

```java
// Find the last Monday of August 2024
LocalDate lastMonday = date.with(TemporalAdjusters.lastInMonth(DayOfWeek.MONDAY));
// => 2024-08-26
```

## Relative Adjustments (Next/Previous)

Adjusters like `next()`, `previous()`, `nextOrSame()`, and `previousOrSame()` find the next or previous occurrence of a given day of the week, relative to the current date.

- `next()`/`previous()`: Excludes the current date if it matches.
- `nextOrSame()`/`previousOrSame()`: Includes the current date if it matches.

```java
LocalDate thursday = LocalDate.of(2024, Month.AUGUST, 15); // A Thursday

// Find the next Thursday (exclusive)
LocalDate nextThursday = thursday.with(TemporalAdjusters.next(DayOfWeek.THURSDAY));
// => 2024-08-22

// Find the next Thursday (inclusive) - returns the same date
LocalDate nextOrSameThursday = thursday.with(TemporalAdjusters.nextOrSame(DayOfWeek.THURSDAY));
// => 2024-08-15

// Find the previous Monday (exclusive)
LocalDate previousMonday = thursday.with(TemporalAdjusters.previous(DayOfWeek.MONDAY));
// => 2024-08-12
```

## Custom Adjusters

`TemporalAdjuster` is a functional interface, so you can write an adjuster of your own as a lambda that takes the temporal to adjust and returns the adjusted one.
It is applied with `.with()` just like the predefined adjusters, and it can use them internally.

```java
import java.time.temporal.TemporalAdjuster;

// An adjuster that moves a weekend date to the following Monday
TemporalAdjuster skipWeekend = temporal -> {
DayOfWeek day = DayOfWeek.from(temporal);
if (day == DayOfWeek.SATURDAY || day == DayOfWeek.SUNDAY) {
return temporal.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
}
return temporal;
};

LocalDate saturday = LocalDate.of(2024, Month.AUGUST, 17);
LocalDate monday = saturday.with(skipWeekend);
// => 2024-08-19
```

Using these adjusters from the `TemporalAdjusters` class can greatly simplify date manipulation tasks.
18 changes: 18 additions & 0 deletions concepts/temporal-adjusters/links.json
Comment thread
kahgoh marked this conversation as resolved.
Resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[
{
"url": "https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/time/temporal/TemporalAdjuster.html",
"description": "TemporalAdjuster interface documentation"
},
{
"url": "https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/time/temporal/TemporalAdjusters.html",
"description": "TemporalAdjusters utility class documentation"
},
{
"url": "https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/time/temporal/Temporal.html",
"description": "Temporal interface documentation"
},
{
"url": "https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/time/DayOfWeek.html",
"description": "DayOfWeek enum documentation"
}
]
21 changes: 20 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,19 @@
"generic-types"
],
"status": "active"
},
{
"slug": "beauty-salon-regulars",
"name": "Beauty Salon Regulars",
"uuid": "61084de5-1385-4589-acf7-b81d99b0d4b4",
"concepts": [
"temporal-adjusters"
],
"prerequisites": [
"datetime",
"enums"
],
"status": "active"
}
],
"practice": [
Expand Down Expand Up @@ -1607,7 +1620,8 @@
"practices": [],
"prerequisites": [
"datetime",
"enums"
"enums",
"temporal-adjusters"
],
"difficulty": 7
},
Expand Down Expand Up @@ -2090,6 +2104,11 @@
"uuid": "78f3c7b2-cb9c-4d21-8cb4-7106a188f713",
"slug": "ternary-operators",
"name": "Ternary Operators"
},
{
"uuid": "cc7a06d1-ddb3-4722-88a5-f762254d664e",
"slug": "temporal-adjusters",
"name": "Temporal Adjusters"
}
],
"key_features": [
Expand Down
42 changes: 42 additions & 0 deletions exercises/concept/beauty-salon-regulars/.docs/hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Hints

## General

- The [`TemporalAdjusters`][temporaladjusters-docs] class (note the plural) is a collection of static methods that return ready-made adjusters.
- An adjuster is applied to a `LocalDate` with its [`with()`][localdate-with] method, which returns a new `LocalDate` and leaves the original unchanged.
- To adjust "within a month", start from any date in that month, for example its first day: `LocalDate.of(year, month, 1)`.

## 1. Find a regular's standing appointment

- There is an [adjuster][dayofweekinmonth-docs] that finds the n-th occurrence of a day of the week in the month of the date it is applied to.

## 2. Find a regular's last-of-the-month appointment

- There is an [adjuster][lastinmonth-docs] that finds the last occurrence of a day of the week in the month of the date it is applied to.

## 3. Find the next available day for a walk-in

- There are two adjusters that move forward to a given day of the week: one that always moves to a later date, and [one][nextorsame-docs] that keeps the date if it already matches.

## 4. Find the bookkeeping day

- There is an [adjuster][lastdayofmonth-docs] that moves to the last day of the month.
- A `LocalDate` can tell you its [day of the week][localdate-getdayofweek].
- When the last day falls on a weekend, there is an [adjuster][previous-docs] that moves back to the previous occurrence of a day of the week.

## 5. Reuse the weekend rule for payday

- [`TemporalAdjuster`][temporaladjuster-docs] is a functional interface with a single method, `adjustInto()`, so it can be implemented with a lambda.
- Inside the lambda, [`DayOfWeek.from()`][dayofweek-from] gives the day of the week of the temporal being adjusted.
- Adjusters can be chained: the result of applying one adjuster is a temporal you can apply another adjuster to.

[temporaladjusters-docs]: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/time/temporal/TemporalAdjusters.html
[temporaladjuster-docs]: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/time/temporal/TemporalAdjuster.html
[localdate-with]: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/time/LocalDate.html#with(java.time.temporal.TemporalAdjuster)
[localdate-getdayofweek]: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/time/LocalDate.html#getDayOfWeek()
[dayofweek-from]: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/time/DayOfWeek.html#from(java.time.temporal.TemporalAccessor)
[dayofweekinmonth-docs]: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/time/temporal/TemporalAdjusters.html#dayOfWeekInMonth(int,java.time.DayOfWeek)
[lastinmonth-docs]: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/time/temporal/TemporalAdjusters.html#lastInMonth(java.time.DayOfWeek)
[nextorsame-docs]: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/time/temporal/TemporalAdjusters.html#nextOrSame(java.time.DayOfWeek)
[lastdayofmonth-docs]: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/time/temporal/TemporalAdjusters.html#lastDayOfMonth()
[previous-docs]: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/time/temporal/TemporalAdjusters.html#previous(java.time.DayOfWeek)
Loading
Loading