-
-
Notifications
You must be signed in to change notification settings - Fork 711
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
Open
rabestro
wants to merge
13
commits into
exercism:main
Choose a base branch
from
rabestro:issue/2935-TemporalAdjusters
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
11890c8
Add Temporal Adjusters concept with documentation and config
5c6101e
Refactor and streamline TemporalAdjusters documentation
0c94854
Simplify JSON formatting in Temporal Adjusters config.
0bff3ea
Enhance formatting in temporal-adjusters documentation
fd1ca6e
Update section headings in temporal adjusters guide.
c1ece9d
Remove unused links from temporal-adjusters documentation
2dc27b9
Move meetup exercise from practice to concept track
e533bf9
Remove "Meetup" exercise from config.json
874839d
Add documentation for the Meetup concept exercise
95a5639
Update meetup exercise metadata and fix file formatting
69ce481
Remove deprecated test configuration for meetup exercise
9150859
Add design document for the Meetup concept exercise
e76ec25
Add configuration for concept 'meetup' exercise
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,140 @@ | ||
# 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 | ||
``` | ||
|
||
The `nextOrSame()` adjuster is particularly useful for finding dates in ranges like the "teenth" days (13th-19th) of a month, as seen in the Meetup exercise. Applying `nextOrSame(targetDayOfWeek)` to the 13th day of the month will find the correct "teenth" date. | ||
|
||
## 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/javase/8/docs/api/java/time/temporal/TemporalAdjuster.html | ||
[temporaladjusters-docs]: https://docs.oracle.com/javase/8/docs/api/java/time/temporal/TemporalAdjusters.html | ||
[functionalinterface-docs]: https://docs.oracle.com/javase/8/docs/api/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,62 @@ | ||
# 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 | ||
``` | ||
|
||
## Finding Relative Occurrences ("Teenth" dates) | ||
|
||
Use `nextOrSame(DayOfWeek dayOfWeek)`. This is useful for finding dates like the "teenth" days (13th-19th). Apply it to the 13th day of the month to find the target day of the week within that "teenth" range. | ||
|
||
```java | ||
LocalDate thirteenth = LocalDate.of(2024, Month.AUGUST, 13); // Tuesday the 13th | ||
|
||
// Find the first Saturday on or after the 13th ("Teenth" Saturday) | ||
LocalDate teenthSaturday = thirteenth.with(TemporalAdjusters.nextOrSame(DayOfWeek.SATURDAY)); | ||
// => 2024-08-17 | ||
|
||
// Find the first Tuesday on or after the 13th ("Teenth" Tuesday) | ||
LocalDate teenthTuesday = thirteenth.with(TemporalAdjusters.nextOrSame(DayOfWeek.TUESDAY)); | ||
// => 2024-08-13 (returns the same date as it matches) | ||
``` | ||
|
||
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/21/docs/api/java.base/java/time/temporal/TemporalAdjuster.html", | ||
"description": "TemporalAdjuster interface documentation" | ||
}, | ||
{ | ||
"url": "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/time/temporal/TemporalAdjusters.html", | ||
"description": "TemporalAdjusters utility class documentation" | ||
}, | ||
{ | ||
"url": "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/time/temporal/Temporal.html", | ||
"description": "Temporal interface documentation" | ||
}, | ||
{ | ||
"url": "https://docs.oracle.com/en/java/javase/21/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,43 @@ | ||
# Hints | ||
|
||
## 1. Representing the Date | ||
|
||
- The core class for representing dates without time in Java is [`java.time.LocalDate`][localdate-docs]. | ||
- You'll need to work with the `year` and `month` provided in the constructor. | ||
- The target day of the week is conveniently provided directly as a [`java.time.DayOfWeek`][dayofweek-docs] enum instance in the `day()` method. | ||
|
||
## 2. Starting Point for Adjustments | ||
|
||
- `LocalDate` objects are immutable. To find the target meetup date, you typically start with *any* date within the target `month` and `year` and then apply an "adjuster" to find the correct day. | ||
- For example, you could create a `LocalDate` for the 1st of the month: `LocalDate.of(year, month, 1)`. | ||
- For "Teenth" calculations, starting from the 13th of the month (`LocalDate.of(year, month, 13)`) can be helpful. | ||
|
||
## 3. Adjusting Dates with `TemporalAdjusters` | ||
|
||
- The key to solving this idiomatically is the [`java.time.temporal.TemporalAdjusters`][temporaladjusters-docs] utility class (note the plural 's'). It provides static methods that return [`TemporalAdjuster`][temporaladjuster-docs] instances for common date calculations. | ||
- You apply an adjuster to a `LocalDate` using its `.with()` method: `someLocalDate.with(TemporalAdjusters.someAdjuster(...))` | ||
- This returns a *new* `LocalDate` instance with the adjustment applied. | ||
|
||
## 4. Handling "Ordinal" Schedules (FIRST, SECOND, THIRD, FOURTH) | ||
|
||
- Look for a method in `TemporalAdjusters` that allows you to find a date based on its ordinal position (1st, 2nd, 3rd, 4th) and its `DayOfWeek` within the month. | ||
- You might need to convert the `MeetupSchedule` enum values (`FIRST`, `SECOND`, etc.) into the corresponding integer ordinals (1, 2, 3, 4) to use this adjuster. | ||
|
||
## 5. Handling the "LAST" Schedule | ||
|
||
- `TemporalAdjusters` provides a specific adjuster to find the *last* occurrence of a given `DayOfWeek` within the month. | ||
|
||
## 6. Handling the "TEENTH" Schedule | ||
|
||
- "Teenth" days run from the 13th to the 19th of the month. | ||
- Consider starting from the 13th day of the month (`LocalDate.of(year, month, 13)`). | ||
- Look for an adjuster in `TemporalAdjusters` that finds the *next* occurrence of the target `DayOfWeek`, potentially *including* the date you're adjusting if it already matches the target day of the week. | ||
|
||
## 7. Selecting the Right Adjuster | ||
|
||
- Use the input `schedule` (which is a `MeetupSchedule` enum value) to determine which specific `TemporalAdjuster` method to use. A `switch` statement or `if-else if` chain on the `schedule` value is a common approach. | ||
|
||
[localdate-docs]: https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html | ||
[dayofweek-docs]: https://docs.oracle.com/javase/8/docs/api/java/time/DayOfWeek.html | ||
[temporaladjuster-docs]: https://docs.oracle.com/javase/8/docs/api/java/time/temporal/TemporalAdjuster.html | ||
[temporaladjusters-docs]: https://docs.oracle.com/javase/8/docs/api/java/time/temporal/TemporalAdjusters.html |
File renamed without changes.
File renamed without changes.
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,3 @@ | ||
# Introduction | ||
|
||
%{concept:strings} |
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,45 @@ | ||
# Design | ||
|
||
## Learning objectives | ||
|
||
- Know about the `java.time.temporal.TemporalAdjuster` interface and its purpose. | ||
- Know about the `java.time.temporal.TemporalAdjusters` utility class and how to use its static factory methods. | ||
- Know how to apply a `TemporalAdjuster` to a `LocalDate` using the `.with()` method. | ||
- Know how to select and use common predefined adjusters like `TemporalAdjusters.dayOfWeekInMonth()`, `TemporalAdjusters.lastInMonth()`, and `TemporalAdjusters.nextOrSame()`. | ||
- Understand that `java.time` objects are immutable and adjustment methods return new instances. | ||
- Know how to use a `java.time.DayOfWeek` enum value. | ||
- Know how to use a custom `enum` (like `MeetupSchedule`) in conditional logic (e.g., `switch` statement). | ||
|
||
## Out of scope | ||
|
||
- Creating complex custom `TemporalAdjuster` implementations. | ||
- Time zones, offsets, `ZonedDateTime`. | ||
- `LocalDateTime`, `LocalTime`. | ||
- Durations (`java.time.Duration`) and Periods (`java.time.Period`). | ||
- Date/time parsing and formatting. | ||
|
||
## Concepts | ||
|
||
- `temporal-adjusters`: This exercise introduces and focuses on the `TemporalAdjusters` concept. | ||
- `datetime`: Reinforces basic usage of `LocalDate` and `DayOfWeek`. | ||
- `enums`: Reinforces usage of enums. | ||
|
||
## Prerequisites | ||
|
||
- `datetime`: Must know how to create and use basic `java.time.LocalDate` objects and understand `DayOfWeek`. | ||
- `enums`: Must know how to work with `enum` types, including using them in `switch` statements or accessing properties like `ordinal()`. | ||
- `classes`: Basic understanding of Java classes and methods. | ||
- `methods`: Basic understanding of Java methods. | ||
|
||
## Analyzer | ||
|
||
This exercise could benefit from the following rules in the [analyzer]: | ||
|
||
- `essential` (replaces `actionable` if it MUST be fixed): If the solution involves significant manual date calculation logic (e.g., loops checking day of week, complex `plusDays`/`minusDays` sequences) instead of using `TemporalAdjusters`, instruct the student that using the `TemporalAdjusters` class is the idiomatic approach for this exercise. Provide a link to the `TemporalAdjusters` concept documentation. | ||
- `actionable`: If the student uses `TemporalAdjusters.dayOfWeekInMonth()`, check if they correctly map the `MeetupSchedule` enum ordinal (0-based) to the required 1-based ordinal argument. If not, provide a hint. | ||
- `actionable`: If the logic for `MeetupSchedule.TEENTH` seems incorrect or overly complex, suggest using `TemporalAdjusters.nextOrSame()` applied to the 13th day of the month. | ||
- `informative`: If the student uses `TemporalAdjusters` correctly, mention the specific adjusters used (`dayOfWeekInMonth`, `lastInMonth`, `nextOrSame`) as good choices for their respective cases. | ||
|
||
If the solution uses `TemporalAdjusters` correctly for all `MeetupSchedule` cases, it should be considered exemplar. Leave a `celebratory` comment to celebrate the success! | ||
|
||
[analyzer]: https://github.com/exercism/java-analyzer |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a left over from writing for the "meetup" exercise? Since this is an introduction to the temporal adjusters concept, I think we can copy the Relative Adjustments (Next/Previous) content from the about.md here.