Repository navigation
docs: Introduce new concept for TemporalAdjusters #2944
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
075eea1
Add Temporal Adjusters concept with documentation and config
ad675db
Refactor and streamline TemporalAdjusters documentation
e424bf2
Simplify JSON formatting in Temporal Adjusters config.
4727ea5
Enhance formatting in temporal-adjusters documentation
aaf98b8
Update section headings in temporal adjusters guide.
32c74e7
Remove unused links from temporal-adjusters documentation
fa33b3f
Add custom adjusters section to temporal-adjusters introduction
135a3b2
Add beauty-salon-regulars concept exercise
658421b
Address review feedback on the temporal-adjusters concept
4a77b77
Merge branch 'main' into issue/2935-TemporalAdjusters
kahgoh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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": [] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" | ||
| } | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.