From 075eea1d20eec8068b0527aa8f543ac39751da18 Mon Sep 17 00:00:00 2001 From: Jegors Cemisovs Date: Sun, 4 May 2025 12:14:37 +0300 Subject: [PATCH 1/9] Add Temporal Adjusters concept with documentation and config Introduces the `Temporal Adjusters` concept, including an overview, examples, and links for Java's `TemporalAdjuster` and `TemporalAdjusters` utilities. Updates `config.json` to include the concept and a new exercise, "Meetup," with relevant prerequisites. --- concepts/temporal-adjusters/.meta/config.json | 5 + concepts/temporal-adjusters/about.md | 140 ++++++++++++++++++ concepts/temporal-adjusters/introduction.md | 140 ++++++++++++++++++ concepts/temporal-adjusters/links.json | 18 +++ config.json | 5 + 5 files changed, 308 insertions(+) create mode 100644 concepts/temporal-adjusters/.meta/config.json create mode 100644 concepts/temporal-adjusters/about.md create mode 100644 concepts/temporal-adjusters/introduction.md create mode 100644 concepts/temporal-adjusters/links.json diff --git a/concepts/temporal-adjusters/.meta/config.json b/concepts/temporal-adjusters/.meta/config.json new file mode 100644 index 000000000..0c2bc3cab --- /dev/null +++ b/concepts/temporal-adjusters/.meta/config.json @@ -0,0 +1,5 @@ +{ + "blurb": "Perform common and complex date adjustments using Java's `TemporalAdjuster` interface and `TemporalAdjusters` utility class.", + "authors": ["rabestro"], + "contributors": [] +} diff --git a/concepts/temporal-adjusters/about.md b/concepts/temporal-adjusters/about.md new file mode 100644 index 000000000..be786485a --- /dev/null +++ b/concepts/temporal-adjusters/about.md @@ -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 +[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 +[functionalinterface-docs]: https://docs.oracle.com/javase/8/docs/api/java/lang/FunctionalInterface.html diff --git a/concepts/temporal-adjusters/introduction.md b/concepts/temporal-adjusters/introduction.md new file mode 100644 index 000000000..afefbd651 --- /dev/null +++ b/concepts/temporal-adjusters/introduction.md @@ -0,0 +1,140 @@ +# Introduction + +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 +[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 +[functionalinterface-docs]: https://docs.oracle.com/javase/8/docs/api/java/lang/FunctionalInterface.html diff --git a/concepts/temporal-adjusters/links.json b/concepts/temporal-adjusters/links.json new file mode 100644 index 000000000..d2a0b0185 --- /dev/null +++ b/concepts/temporal-adjusters/links.json @@ -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" + } +] diff --git a/config.json b/config.json index cfa24a7a8..5e334b87a 100644 --- a/config.json +++ b/config.json @@ -2090,6 +2090,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": [ From ad675dbe69e3eeddfbf87baf0b607f0f61fb01e4 Mon Sep 17 00:00:00 2001 From: Jegors Cemisovs Date: Sun, 4 May 2025 12:21:59 +0300 Subject: [PATCH 2/9] Refactor and streamline TemporalAdjusters documentation Simplify the introduction and examples for `TemporalAdjusters` to improve clarity and focus on practical use cases. Removed redundant examples and reorganized sections to prioritize key adjusters like ordinal days and "teenth" date calculations. Enhanced consistency and readability across code snippets and explanations. --- concepts/temporal-adjusters/introduction.md | 116 ++++---------------- 1 file changed, 19 insertions(+), 97 deletions(-) diff --git a/concepts/temporal-adjusters/introduction.md b/concepts/temporal-adjusters/introduction.md index afefbd651..bd775babd 100644 --- a/concepts/temporal-adjusters/introduction.md +++ b/concepts/temporal-adjusters/introduction.md @@ -1,15 +1,11 @@ # Introduction -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"). +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` 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. +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 all `java.time` temporal classes are immutable. +Using `with(TemporalAdjuster)` returns a _new_ instance and does not update the existing instance, as `java.time` objects are immutable. ``` ```java @@ -20,121 +16,47 @@ 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 +// Use a predefined adjuster: find the last day of the month +LocalDate lastDay = date.with(TemporalAdjusters.lastDayOfMonth()); +// => 2024-08-31 ``` -## Common Predefined Adjusters - -The `TemporalAdjusters` class provides many useful static methods. Here are some common examples: +Here are some key adjusters useful for scheduling calculations: ### 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. +Use `dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek)` to find the nth occurrence of a day of the week. ```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. +Use `firstInMonth(DayOfWeek dayOfWeek)` or `lastInMonth(DayOfWeek dayOfWeek)`. ```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. +### Finding Relative Occurrences ("Teenth" dates) -* `next()`/`previous()`: Excludes the current date if it matches. -* `nextOrSame()`/`previousOrSame()`: Includes the current date if it matches. +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 thursday = LocalDate.of(2024, Month.AUGUST, 15); // A Thursday +LocalDate thirteenth = LocalDate.of(2024, Month.AUGUST, 13); // Tuesday the 13th -// Find the next Sunday (exclusive) -LocalDate nextSunday = thursday.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)); -// => 2024-08-18 +// Find the first Saturday on or after the 13th ("Teenth" Saturday) +LocalDate teenthSaturday = thirteenth.with(TemporalAdjusters.nextOrSame(DayOfWeek.SATURDAY)); +// => 2024-08-17 -// 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) +// 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) ``` -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 -[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 -[functionalinterface-docs]: https://docs.oracle.com/javase/8/docs/api/java/lang/FunctionalInterface.html +Using these adjusters from the `TemporalAdjusters` class can greatly simplify date manipulation tasks. From e424bf255863fdb5bacdd599ae39809094b14f73 Mon Sep 17 00:00:00 2001 From: Jegors Cemisovs Date: Sun, 4 May 2025 12:38:16 +0300 Subject: [PATCH 3/9] Simplify JSON formatting in Temporal Adjusters config. Adjusted the `blurb` text for clarity and reformatted the `authors` field for improved readability. These changes maintain consistency with JSON coding standards and enhance the file's maintainability. --- concepts/temporal-adjusters/.meta/config.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/concepts/temporal-adjusters/.meta/config.json b/concepts/temporal-adjusters/.meta/config.json index 0c2bc3cab..48d931526 100644 --- a/concepts/temporal-adjusters/.meta/config.json +++ b/concepts/temporal-adjusters/.meta/config.json @@ -1,5 +1,7 @@ { - "blurb": "Perform common and complex date adjustments using Java's `TemporalAdjuster` interface and `TemporalAdjusters` utility class.", - "authors": ["rabestro"], + "blurb": "Perform date adjustments using Java's `TemporalAdjuster` interface and `TemporalAdjusters` utility class.", + "authors": [ + "rabestro" + ], "contributors": [] } From 4727ea5a685027643146fce4334d5340e24940e2 Mon Sep 17 00:00:00 2001 From: Jegors Cemisovs Date: Sun, 4 May 2025 12:45:59 +0300 Subject: [PATCH 4/9] Enhance formatting in temporal-adjusters documentation Replaced asterisks with dashes for consistency in list formatting and added blank lines for better readability. These changes improve the overall structure and clarity of the documentation. --- concepts/temporal-adjusters/about.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/concepts/temporal-adjusters/about.md b/concepts/temporal-adjusters/about.md index be786485a..4b95a5140 100644 --- a/concepts/temporal-adjusters/about.md +++ b/concepts/temporal-adjusters/about.md @@ -85,8 +85,8 @@ LocalDate lastDayOfYear = date.with(TemporalAdjusters.lastDayOfYear()); 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. +- `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 @@ -107,6 +107,7 @@ LocalDate nextOrSameThursday = thursday.with(TemporalAdjusters.nextOrSame(DayOfW 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 @@ -131,6 +132,7 @@ 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 From aaf98b8f75cc8deb4e3cf588c95941ab00dc7c5b Mon Sep 17 00:00:00 2001 From: Jegors Cemisovs Date: Sun, 4 May 2025 12:46:51 +0300 Subject: [PATCH 5/9] Update section headings in temporal adjusters guide. Standardize heading levels for consistency and readability. Changed `###` to `##` in multiple sections to align with the document's structure. No content modifications were made. --- concepts/temporal-adjusters/introduction.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/concepts/temporal-adjusters/introduction.md b/concepts/temporal-adjusters/introduction.md index bd775babd..d2c6e4ce5 100644 --- a/concepts/temporal-adjusters/introduction.md +++ b/concepts/temporal-adjusters/introduction.md @@ -23,7 +23,7 @@ LocalDate lastDay = date.with(TemporalAdjusters.lastDayOfMonth()); Here are some key adjusters useful for scheduling calculations: -### Finding Ordinal Day of Week +## Finding Ordinal Day of Week Use `dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek)` to find the nth occurrence of a day of the week. @@ -33,7 +33,7 @@ LocalDate secondTuesday = date.with(TemporalAdjusters.dayOfWeekInMonth(2, DayOfW // => 2024-08-13 ``` -### Finding First/Last Day of Week in Month +## Finding First/Last Day of Week in Month Use `firstInMonth(DayOfWeek dayOfWeek)` or `lastInMonth(DayOfWeek dayOfWeek)`. @@ -43,7 +43,7 @@ LocalDate lastMonday = date.with(TemporalAdjusters.lastInMonth(DayOfWeek.MONDAY) // => 2024-08-26 ``` -### Finding Relative Occurrences ("Teenth" dates) +## 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. From 32c74e7aeac628fbc36bb3e7f34329d425db223e Mon Sep 17 00:00:00 2001 From: Jegors Cemisovs Date: Sun, 4 May 2025 12:49:15 +0300 Subject: [PATCH 6/9] Remove unused links from temporal-adjusters documentation The links to LocalDate and DayOfWeek documentation were unused and have been removed to improve clarity and maintainability. This helps keep the documentation clean and focused. --- concepts/temporal-adjusters/about.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/concepts/temporal-adjusters/about.md b/concepts/temporal-adjusters/about.md index 4b95a5140..7eb7bc99a 100644 --- a/concepts/temporal-adjusters/about.md +++ b/concepts/temporal-adjusters/about.md @@ -137,6 +137,4 @@ While custom adjusters are powerful, the predefined adjusters in the `TemporalAd [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 -[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 [functionalinterface-docs]: https://docs.oracle.com/javase/8/docs/api/java/lang/FunctionalInterface.html From fa33b3fcfb468becdffcd6b818e19deaf9ec878b Mon Sep 17 00:00:00 2001 From: Jegors Cemisovs Date: Fri, 30 Oct 2026 11:35:00 +0200 Subject: [PATCH 7/9] Add custom adjusters section to temporal-adjusters introduction The concept exercise asks students to write a TemporalAdjuster of their own, so the introduction shown before the exercise needs to cover it. Co-Authored-By: Claude Fable 5.1 --- concepts/temporal-adjusters/introduction.md | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/concepts/temporal-adjusters/introduction.md b/concepts/temporal-adjusters/introduction.md index d2c6e4ce5..813a0691a 100644 --- a/concepts/temporal-adjusters/introduction.md +++ b/concepts/temporal-adjusters/introduction.md @@ -59,4 +59,26 @@ LocalDate teenthTuesday = thirteenth.with(TemporalAdjusters.nextOrSame(DayOfWeek // => 2024-08-13 (returns the same date as it matches) ``` +## 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. From 135a3b270178f89335b4256ffda47ae52046f8c5 Mon Sep 17 00:00:00 2001 From: Jegors Cemisovs Date: Fri, 30 Oct 2026 11:35:00 +0200 Subject: [PATCH 8/9] Add beauty-salon-regulars concept exercise A sequel to booking-up-for-beauty that teaches the temporal-adjusters concept: standing appointments for regular clients (dayOfWeekInMonth, lastInMonth, nextOrSame), the month-end bookkeeping day (lastDayOfMonth, previous) and a reusable custom adjuster for the weekend rule. Also adds temporal-adjusters as a prerequisite of the meetup practice exercise, as requested in review. Co-Authored-By: Claude Fable 5.1 --- config.json | 16 +- .../beauty-salon-regulars/.docs/hints.md | 42 +++ .../.docs/instructions.md | 75 ++++++ .../.docs/introduction.md | 86 ++++++ .../.docs/introduction.md.tpl | 3 + .../beauty-salon-regulars/.meta/config.json | 20 ++ .../beauty-salon-regulars/.meta/design.md | 42 +++ .../src/reference/java/SalonCalendar.java | 40 +++ .../beauty-salon-regulars/build.gradle | 25 ++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 48462 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + .../concept/beauty-salon-regulars/gradlew | 248 ++++++++++++++++++ .../concept/beauty-salon-regulars/gradlew.bat | 82 ++++++ .../src/main/java/SalonCalendar.java | 31 +++ .../src/test/java/SalonCalendarTest.java | 199 ++++++++++++++ exercises/settings.gradle | 1 + 16 files changed, 915 insertions(+), 1 deletion(-) create mode 100644 exercises/concept/beauty-salon-regulars/.docs/hints.md create mode 100644 exercises/concept/beauty-salon-regulars/.docs/instructions.md create mode 100644 exercises/concept/beauty-salon-regulars/.docs/introduction.md create mode 100644 exercises/concept/beauty-salon-regulars/.docs/introduction.md.tpl create mode 100644 exercises/concept/beauty-salon-regulars/.meta/config.json create mode 100644 exercises/concept/beauty-salon-regulars/.meta/design.md create mode 100644 exercises/concept/beauty-salon-regulars/.meta/src/reference/java/SalonCalendar.java create mode 100644 exercises/concept/beauty-salon-regulars/build.gradle create mode 100644 exercises/concept/beauty-salon-regulars/gradle/wrapper/gradle-wrapper.jar create mode 100644 exercises/concept/beauty-salon-regulars/gradle/wrapper/gradle-wrapper.properties create mode 100755 exercises/concept/beauty-salon-regulars/gradlew create mode 100644 exercises/concept/beauty-salon-regulars/gradlew.bat create mode 100644 exercises/concept/beauty-salon-regulars/src/main/java/SalonCalendar.java create mode 100644 exercises/concept/beauty-salon-regulars/src/test/java/SalonCalendarTest.java diff --git a/config.json b/config.json index 5e334b87a..09dfb543a 100644 --- a/config.json +++ b/config.json @@ -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": [ @@ -1607,7 +1620,8 @@ "practices": [], "prerequisites": [ "datetime", - "enums" + "enums", + "temporal-adjusters" ], "difficulty": 7 }, diff --git a/exercises/concept/beauty-salon-regulars/.docs/hints.md b/exercises/concept/beauty-salon-regulars/.docs/hints.md new file mode 100644 index 000000000..cae25805d --- /dev/null +++ b/exercises/concept/beauty-salon-regulars/.docs/hints.md @@ -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/javase/8/docs/api/java/time/temporal/TemporalAdjusters.html +[temporaladjuster-docs]: https://docs.oracle.com/javase/8/docs/api/java/time/temporal/TemporalAdjuster.html +[localdate-with]: https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html#with-java.time.temporal.TemporalAdjuster- +[localdate-getdayofweek]: https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html#getDayOfWeek-- +[dayofweek-from]: https://docs.oracle.com/javase/8/docs/api/java/time/DayOfWeek.html#from-java.time.temporal.TemporalAccessor- +[dayofweekinmonth-docs]: https://docs.oracle.com/javase/8/docs/api/java/time/temporal/TemporalAdjusters.html#dayOfWeekInMonth-int-java.time.DayOfWeek- +[lastinmonth-docs]: https://docs.oracle.com/javase/8/docs/api/java/time/temporal/TemporalAdjusters.html#lastInMonth-java.time.DayOfWeek- +[nextorsame-docs]: https://docs.oracle.com/javase/8/docs/api/java/time/temporal/TemporalAdjusters.html#nextOrSame-java.time.DayOfWeek- +[lastdayofmonth-docs]: https://docs.oracle.com/javase/8/docs/api/java/time/temporal/TemporalAdjusters.html#lastDayOfMonth-- +[previous-docs]: https://docs.oracle.com/javase/8/docs/api/java/time/temporal/TemporalAdjusters.html#previous-java.time.DayOfWeek- diff --git a/exercises/concept/beauty-salon-regulars/.docs/instructions.md b/exercises/concept/beauty-salon-regulars/.docs/instructions.md new file mode 100644 index 000000000..8652b0160 --- /dev/null +++ b/exercises/concept/beauty-salon-regulars/.docs/instructions.md @@ -0,0 +1,75 @@ +# Instructions + +The beauty salon in New York that you helped in the Booking Up For Beauty exercise has been doing well, and many of its clients have become regulars. +Instead of booking every visit separately, a regular has a standing arrangement: "every second Tuesday", "the last Friday of the month", and so on. +The owner has asked you for a helper that works out the dates behind these arrangements, as well as the dates of the salon's own monthly routine. + +## 1. Find a regular's standing appointment + +Implement the `SalonCalendar.standingAppointment()` method that takes a year, a month, an occurrence (`1` for the first, `2` for the second, and so on) and a day of the week. +It returns the date of that occurrence of the day of the week within the month: + +```java +SalonCalendar calendar = new SalonCalendar(); +calendar.standingAppointment(2024, Month.AUGUST, 2, DayOfWeek.TUESDAY); +// => LocalDate.of(2024, 8, 13) +``` + +## 2. Find a regular's last-of-the-month appointment + +Some regulars prefer the last occurrence of their day in the month. +Implement the `SalonCalendar.lastAppointment()` method that takes a year, a month and a day of the week, and returns the date of the last occurrence of that day of the week within the month: + +```java +SalonCalendar calendar = new SalonCalendar(); +calendar.lastAppointment(2024, Month.AUGUST, DayOfWeek.FRIDAY); +// => LocalDate.of(2024, 8, 30) +``` + +## 3. Find the next available day for a walk-in + +A client at the front desk asks for "the next Saturday". +If the day they ask on is already a Saturday, they mean that very day. +Implement the `SalonCalendar.nextAvailable()` method that takes a date and a day of the week, and returns the first date on or after the given date that falls on that day of the week: + +```java +SalonCalendar calendar = new SalonCalendar(); +calendar.nextAvailable(LocalDate.of(2024, 8, 15), DayOfWeek.SATURDAY); +// => LocalDate.of(2024, 8, 17) +calendar.nextAvailable(LocalDate.of(2024, 8, 17), DayOfWeek.SATURDAY); +// => LocalDate.of(2024, 8, 17) +``` + +## 4. Find the bookkeeping day + +The salon closes its books on the last day of each month. +The salon is closed on weekends, though, so when the last day of the month falls on a Saturday or a Sunday, the books are closed on the Friday before. +Implement the `SalonCalendar.bookkeepingDay()` method that takes a year and a month, and returns the bookkeeping day of that month: + +```java +SalonCalendar calendar = new SalonCalendar(); +calendar.bookkeepingDay(2024, Month.SEPTEMBER); +// => LocalDate.of(2024, 9, 30) +calendar.bookkeepingDay(2024, Month.AUGUST); +// => LocalDate.of(2024, 8, 30) +``` + +## 5. Reuse the weekend rule for payday + +Staff are paid on the 15th of every month, with the same weekend rule: when the 15th falls on a Saturday or a Sunday, payday is the Friday before. +Rather than writing the rule a second time, turn it into a reusable adjuster. + +Implement the `SalonCalendar.precedingWorkday()` method that returns a `TemporalAdjuster` which leaves a weekday unchanged and moves a Saturday or a Sunday back to the preceding Friday. +Then implement the `SalonCalendar.payday()` method, which takes a year and a month, using that adjuster: + +```java +SalonCalendar calendar = new SalonCalendar(); +LocalDate.of(2024, 6, 15).with(calendar.precedingWorkday()); +// => LocalDate.of(2024, 6, 14) +calendar.payday(2024, Month.JUNE); +// => LocalDate.of(2024, 6, 14) +calendar.payday(2024, Month.AUGUST); +// => LocalDate.of(2024, 8, 15) +``` + +Feel free to go back and use the adjuster in `bookkeepingDay()` as well. diff --git a/exercises/concept/beauty-salon-regulars/.docs/introduction.md b/exercises/concept/beauty-salon-regulars/.docs/introduction.md new file mode 100644 index 000000000..3cb2cb555 --- /dev/null +++ b/exercises/concept/beauty-salon-regulars/.docs/introduction.md @@ -0,0 +1,86 @@ +# Introduction + +## Temporal Adjusters + +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) +``` + +### 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. diff --git a/exercises/concept/beauty-salon-regulars/.docs/introduction.md.tpl b/exercises/concept/beauty-salon-regulars/.docs/introduction.md.tpl new file mode 100644 index 000000000..40626f4f4 --- /dev/null +++ b/exercises/concept/beauty-salon-regulars/.docs/introduction.md.tpl @@ -0,0 +1,3 @@ +# Introduction + +%{concept:temporal-adjusters} diff --git a/exercises/concept/beauty-salon-regulars/.meta/config.json b/exercises/concept/beauty-salon-regulars/.meta/config.json new file mode 100644 index 000000000..0fcdb96f4 --- /dev/null +++ b/exercises/concept/beauty-salon-regulars/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "rabestro" + ], + "files": { + "solution": [ + "src/main/java/SalonCalendar.java" + ], + "test": [ + "src/test/java/SalonCalendarTest.java" + ], + "exemplar": [ + ".meta/src/reference/java/SalonCalendar.java" + ], + "invalidator": [ + "build.gradle" + ] + }, + "blurb": "Learn about temporal adjusters by scheduling standing appointments for a beauty salon's regular clients." +} diff --git a/exercises/concept/beauty-salon-regulars/.meta/design.md b/exercises/concept/beauty-salon-regulars/.meta/design.md new file mode 100644 index 000000000..65b6b81db --- /dev/null +++ b/exercises/concept/beauty-salon-regulars/.meta/design.md @@ -0,0 +1,42 @@ +# Design + +## Learning objectives + +- Know about the `java.time.temporal.TemporalAdjuster` interface and its purpose. +- Know about the `java.time.temporal.TemporalAdjusters` utility class and its static factory methods. +- Know how to apply an adjuster to a `LocalDate` using the `with()` method. +- Know how to use the common predefined adjusters: `dayOfWeekInMonth()`, `lastInMonth()`, `nextOrSame()`, `lastDayOfMonth()` and `previous()`. +- Know how to write a simple custom adjuster as a lambda and how to chain adjusters. +- Understand that `java.time` objects are immutable and that adjustment methods return new instances. + +## Out of scope + +- Time zones, offsets and `ZonedDateTime`. +- `LocalDateTime` and `LocalTime`. +- `Duration` and `Period`. +- Parsing and formatting dates. +- Anything depending on the current date (`LocalDate.now()`). + +## Concepts + +- `temporal-adjusters` + +## Prerequisites + +This exercise's prerequisites Concepts are: + +- `datetime`: creating and using `LocalDate` and `Month`. +- `enums`: working with enum constants such as `DayOfWeek.MONDAY`. + +## Analyzer + +This exercise could benefit from the following rules in the [analyzer]: + +- `essential`: If a solution computes dates by looping over days or by arithmetic with `plusDays()`/`minusDays()` instead of using `TemporalAdjusters`, instruct the student that adjusters are the idiomatic approach for this exercise and link to the `temporal-adjusters` concept. +- `actionable`: If `nextAvailable()` uses `next()` instead of `nextOrSame()`, point out that a date that already matches must be kept. +- `actionable`: If `payday()` or `bookkeepingDay()` spells out the weekend rule instead of reusing the `precedingWorkday()` adjuster, suggest reusing it. + +If the solution does not receive any of the above feedback, it must be exemplar. +Leave a `celebratory` comment to celebrate the success! + +[analyzer]: https://github.com/exercism/java-analyzer diff --git a/exercises/concept/beauty-salon-regulars/.meta/src/reference/java/SalonCalendar.java b/exercises/concept/beauty-salon-regulars/.meta/src/reference/java/SalonCalendar.java new file mode 100644 index 000000000..6462bf9ea --- /dev/null +++ b/exercises/concept/beauty-salon-regulars/.meta/src/reference/java/SalonCalendar.java @@ -0,0 +1,40 @@ +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.Month; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalAdjusters; + +class SalonCalendar { + + public LocalDate standingAppointment(int year, Month month, int occurrence, DayOfWeek dayOfWeek) { + return LocalDate.of(year, month, 1).with(TemporalAdjusters.dayOfWeekInMonth(occurrence, dayOfWeek)); + } + + public LocalDate lastAppointment(int year, Month month, DayOfWeek dayOfWeek) { + return LocalDate.of(year, month, 1).with(TemporalAdjusters.lastInMonth(dayOfWeek)); + } + + public LocalDate nextAvailable(LocalDate from, DayOfWeek dayOfWeek) { + return from.with(TemporalAdjusters.nextOrSame(dayOfWeek)); + } + + public LocalDate bookkeepingDay(int year, Month month) { + return LocalDate.of(year, month, 1) + .with(TemporalAdjusters.lastDayOfMonth()) + .with(precedingWorkday()); + } + + public LocalDate payday(int year, Month month) { + return LocalDate.of(year, month, 15).with(precedingWorkday()); + } + + public TemporalAdjuster precedingWorkday() { + return temporal -> { + DayOfWeek dayOfWeek = DayOfWeek.from(temporal); + if (dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY) { + return temporal.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY)); + } + return temporal; + }; + } +} diff --git a/exercises/concept/beauty-salon-regulars/build.gradle b/exercises/concept/beauty-salon-regulars/build.gradle new file mode 100644 index 000000000..dd3862eb9 --- /dev/null +++ b/exercises/concept/beauty-salon-regulars/build.gradle @@ -0,0 +1,25 @@ +plugins { + id "java" +} + +repositories { + mavenCentral() +} + +dependencies { + testImplementation platform("org.junit:junit-bom:5.10.0") + testImplementation "org.junit.jupiter:junit-jupiter" + testImplementation "org.assertj:assertj-core:3.25.1" + + testRuntimeOnly "org.junit.platform:junit-platform-launcher" +} + +test { + useJUnitPlatform() + + testLogging { + exceptionFormat = "full" + showStandardStreams = true + events = ["passed", "failed", "skipped"] + } +} diff --git a/exercises/concept/beauty-salon-regulars/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/beauty-salon-regulars/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..b1b8ef56b44f16b14dc800fa8103a6d89abb526f GIT binary patch literal 48462 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 R2np~f1o-$>Q}y+){|A@R9n$~+ literal 0 HcmV?d00001 diff --git a/exercises/concept/beauty-salon-regulars/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/beauty-salon-regulars/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..6394b4647 --- /dev/null +++ b/exercises/concept/beauty-salon-regulars/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/exercises/concept/beauty-salon-regulars/gradlew b/exercises/concept/beauty-salon-regulars/gradlew new file mode 100755 index 000000000..b9bb139f7 --- /dev/null +++ b/exercises/concept/beauty-salon-regulars/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/exercises/concept/beauty-salon-regulars/gradlew.bat b/exercises/concept/beauty-salon-regulars/gradlew.bat new file mode 100644 index 000000000..24c62d56f --- /dev/null +++ b/exercises/concept/beauty-salon-regulars/gradlew.bat @@ -0,0 +1,82 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/exercises/concept/beauty-salon-regulars/src/main/java/SalonCalendar.java b/exercises/concept/beauty-salon-regulars/src/main/java/SalonCalendar.java new file mode 100644 index 000000000..929735c88 --- /dev/null +++ b/exercises/concept/beauty-salon-regulars/src/main/java/SalonCalendar.java @@ -0,0 +1,31 @@ +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.Month; +import java.time.temporal.TemporalAdjuster; + +class SalonCalendar { + + public LocalDate standingAppointment(int year, Month month, int occurrence, DayOfWeek dayOfWeek) { + throw new UnsupportedOperationException("Please implement the SalonCalendar.standingAppointment() method"); + } + + public LocalDate lastAppointment(int year, Month month, DayOfWeek dayOfWeek) { + throw new UnsupportedOperationException("Please implement the SalonCalendar.lastAppointment() method"); + } + + public LocalDate nextAvailable(LocalDate from, DayOfWeek dayOfWeek) { + throw new UnsupportedOperationException("Please implement the SalonCalendar.nextAvailable() method"); + } + + public LocalDate bookkeepingDay(int year, Month month) { + throw new UnsupportedOperationException("Please implement the SalonCalendar.bookkeepingDay() method"); + } + + public LocalDate payday(int year, Month month) { + throw new UnsupportedOperationException("Please implement the SalonCalendar.payday() method"); + } + + public TemporalAdjuster precedingWorkday() { + throw new UnsupportedOperationException("Please implement the SalonCalendar.precedingWorkday() method"); + } +} diff --git a/exercises/concept/beauty-salon-regulars/src/test/java/SalonCalendarTest.java b/exercises/concept/beauty-salon-regulars/src/test/java/SalonCalendarTest.java new file mode 100644 index 000000000..8b9657b32 --- /dev/null +++ b/exercises/concept/beauty-salon-regulars/src/test/java/SalonCalendarTest.java @@ -0,0 +1,199 @@ +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.Month; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SalonCalendarTest { + + private final SalonCalendar calendar = new SalonCalendar(); + + @Test + @Tag("task:1") + @DisplayName("Second Tuesday of August 2024") + public void testStandingAppointmentSecondTuesday() { + var expected = LocalDate.of(2024, 8, 13); + + assertThat(calendar.standingAppointment(2024, Month.AUGUST, 2, DayOfWeek.TUESDAY)).isEqualTo(expected); + } + + @Test + @Tag("task:1") + @DisplayName("First Monday of September 2024") + public void testStandingAppointmentFirstMonday() { + var expected = LocalDate.of(2024, 9, 2); + + assertThat(calendar.standingAppointment(2024, Month.SEPTEMBER, 1, DayOfWeek.MONDAY)).isEqualTo(expected); + } + + @Test + @Tag("task:1") + @DisplayName("Fourth Friday of November 2024") + public void testStandingAppointmentFourthFriday() { + var expected = LocalDate.of(2024, 11, 22); + + assertThat(calendar.standingAppointment(2024, Month.NOVEMBER, 4, DayOfWeek.FRIDAY)).isEqualTo(expected); + } + + @Test + @Tag("task:2") + @DisplayName("Last Friday of August 2024") + public void testLastAppointmentFriday() { + var expected = LocalDate.of(2024, 8, 30); + + assertThat(calendar.lastAppointment(2024, Month.AUGUST, DayOfWeek.FRIDAY)).isEqualTo(expected); + } + + @Test + @Tag("task:2") + @DisplayName("Last Monday of May 2024") + public void testLastAppointmentMonday() { + var expected = LocalDate.of(2024, 5, 27); + + assertThat(calendar.lastAppointment(2024, Month.MAY, DayOfWeek.MONDAY)).isEqualTo(expected); + } + + @Test + @Tag("task:2") + @DisplayName("Last Sunday of February in a leap year") + public void testLastAppointmentSundayInLeapFebruary() { + var expected = LocalDate.of(2024, 2, 25); + + assertThat(calendar.lastAppointment(2024, Month.FEBRUARY, DayOfWeek.SUNDAY)).isEqualTo(expected); + } + + @Test + @Tag("task:3") + @DisplayName("Next Saturday later in the same week") + public void testNextAvailableLaterInWeek() { + var thursday = LocalDate.of(2024, 8, 15); + var expected = LocalDate.of(2024, 8, 17); + + assertThat(calendar.nextAvailable(thursday, DayOfWeek.SATURDAY)).isEqualTo(expected); + } + + @Test + @Tag("task:3") + @DisplayName("Next Saturday when asked on a Saturday is the same day") + public void testNextAvailableSameDay() { + var saturday = LocalDate.of(2024, 8, 17); + + assertThat(calendar.nextAvailable(saturday, DayOfWeek.SATURDAY)).isEqualTo(saturday); + } + + @Test + @Tag("task:3") + @DisplayName("Next Saturday when asked on a Sunday is in the following week") + public void testNextAvailableNextWeek() { + var sunday = LocalDate.of(2024, 8, 18); + var expected = LocalDate.of(2024, 8, 24); + + assertThat(calendar.nextAvailable(sunday, DayOfWeek.SATURDAY)).isEqualTo(expected); + } + + @Test + @Tag("task:3") + @DisplayName("Next Monday crosses into the following month") + public void testNextAvailableCrossesMonth() { + var friday = LocalDate.of(2024, 8, 30); + var expected = LocalDate.of(2024, 9, 2); + + assertThat(calendar.nextAvailable(friday, DayOfWeek.MONDAY)).isEqualTo(expected); + } + + @Test + @Tag("task:4") + @DisplayName("Bookkeeping day is the last day of the month when it is a weekday") + public void testBookkeepingDayOnWeekday() { + var expected = LocalDate.of(2024, 9, 30); + + assertThat(calendar.bookkeepingDay(2024, Month.SEPTEMBER)).isEqualTo(expected); + } + + @Test + @Tag("task:4") + @DisplayName("Bookkeeping day moves to Friday when the month ends on a Saturday") + public void testBookkeepingDayOnSaturday() { + var expected = LocalDate.of(2024, 8, 30); + + assertThat(calendar.bookkeepingDay(2024, Month.AUGUST)).isEqualTo(expected); + } + + @Test + @Tag("task:4") + @DisplayName("Bookkeeping day moves to Friday when the month ends on a Sunday") + public void testBookkeepingDayOnSunday() { + var expected = LocalDate.of(2024, 6, 28); + + assertThat(calendar.bookkeepingDay(2024, Month.JUNE)).isEqualTo(expected); + } + + @Test + @Tag("task:4") + @DisplayName("Bookkeeping day in February of a leap year") + public void testBookkeepingDayInLeapFebruary() { + var expected = LocalDate.of(2024, 2, 29); + + assertThat(calendar.bookkeepingDay(2024, Month.FEBRUARY)).isEqualTo(expected); + } + + @Test + @Tag("task:5") + @DisplayName("Preceding workday adjuster keeps a weekday") + public void testPrecedingWorkdayKeepsWeekday() { + var thursday = LocalDate.of(2024, 8, 15); + + assertThat(thursday.with(calendar.precedingWorkday())).isEqualTo(thursday); + } + + @Test + @Tag("task:5") + @DisplayName("Preceding workday adjuster moves a Saturday to Friday") + public void testPrecedingWorkdayMovesSaturday() { + var saturday = LocalDate.of(2024, 6, 15); + var expected = LocalDate.of(2024, 6, 14); + + assertThat(saturday.with(calendar.precedingWorkday())).isEqualTo(expected); + } + + @Test + @Tag("task:5") + @DisplayName("Preceding workday adjuster moves a Sunday to Friday") + public void testPrecedingWorkdayMovesSunday() { + var sunday = LocalDate.of(2024, 9, 15); + var expected = LocalDate.of(2024, 9, 13); + + assertThat(sunday.with(calendar.precedingWorkday())).isEqualTo(expected); + } + + @Test + @Tag("task:5") + @DisplayName("Payday is the 15th when it is a weekday") + public void testPaydayOnWeekday() { + var expected = LocalDate.of(2024, 8, 15); + + assertThat(calendar.payday(2024, Month.AUGUST)).isEqualTo(expected); + } + + @Test + @Tag("task:5") + @DisplayName("Payday moves to Friday when the 15th is a Saturday") + public void testPaydayOnSaturday() { + var expected = LocalDate.of(2024, 6, 14); + + assertThat(calendar.payday(2024, Month.JUNE)).isEqualTo(expected); + } + + @Test + @Tag("task:5") + @DisplayName("Payday moves to Friday when the 15th is a Sunday") + public void testPaydayOnSunday() { + var expected = LocalDate.of(2024, 9, 13); + + assertThat(calendar.payday(2024, Month.SEPTEMBER)).isEqualTo(expected); + } +} diff --git a/exercises/settings.gradle b/exercises/settings.gradle index 28f6eca2d..019a3ee94 100644 --- a/exercises/settings.gradle +++ b/exercises/settings.gradle @@ -2,6 +2,7 @@ rootProject.name = 'exercism-java' // concept exercises include 'concept:annalyns-infiltration' +include 'concept:beauty-salon-regulars' include 'concept:bird-watcher' // include 'concept:blackjack' // deprecated include 'concept:booking-up-for-beauty' From 658421b84cdea3f52538e69f9d86c4f3af30783b Mon Sep 17 00:00:00 2001 From: Jegors Cemisovs Date: Sun, 1 Nov 2026 23:32:07 +0200 Subject: [PATCH 9/9] Address review feedback on the temporal-adjusters concept - Put each sentence on its own line in about.md and introduction.md - Drop the paragraph and section that hint at the Meetup practice exercise; introduce next()/previous()/nextOrSame() on their own instead - Point Javadoc links in about.md, links.json and the exercise hints to Java 25 - Regenerate the exercise introduction from the updated concept Co-Authored-By: Claude Fable 5.1 --- concepts/temporal-adjusters/about.md | 21 +++++++------ concepts/temporal-adjusters/introduction.md | 31 ++++++++++++------- concepts/temporal-adjusters/links.json | 8 ++--- .../beauty-salon-regulars/.docs/hints.md | 20 ++++++------ .../.docs/introduction.md | 31 ++++++++++++------- 5 files changed, 66 insertions(+), 45 deletions(-) diff --git a/concepts/temporal-adjusters/about.md b/concepts/temporal-adjusters/about.md index 7eb7bc99a..66291ab02 100644 --- a/concepts/temporal-adjusters/about.md +++ b/concepts/temporal-adjusters/about.md @@ -2,11 +2,14 @@ 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. +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. +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. @@ -30,7 +33,8 @@ LocalDate nextSunday = date.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)); ## Common Predefined Adjusters -The `TemporalAdjusters` class provides many useful static methods. Here are some common examples: +The `TemporalAdjusters` class provides many useful static methods. +Here are some common examples: ### Finding Ordinal Day of Week @@ -108,11 +112,10 @@ LocalDate previousMonday = thursday.with(TemporalAdjusters.previous(DayOfWeek.MO // => 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)`. +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. @@ -135,6 +138,6 @@ LocalDate nextWorkdayFromSunday = sunday.with(nextWorkday); 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 +[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 diff --git a/concepts/temporal-adjusters/introduction.md b/concepts/temporal-adjusters/introduction.md index 813a0691a..d1bee817f 100644 --- a/concepts/temporal-adjusters/introduction.md +++ b/concepts/temporal-adjusters/introduction.md @@ -1,8 +1,10 @@ # 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`**. +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`). +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. @@ -43,20 +45,27 @@ LocalDate lastMonday = date.with(TemporalAdjusters.lastInMonth(DayOfWeek.MONDAY) // => 2024-08-26 ``` -## Finding Relative Occurrences ("Teenth" dates) +## Relative Adjustments (Next/Previous) -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. +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 thirteenth = LocalDate.of(2024, Month.AUGUST, 13); // Tuesday the 13th +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 first Saturday on or after the 13th ("Teenth" Saturday) -LocalDate teenthSaturday = thirteenth.with(TemporalAdjusters.nextOrSame(DayOfWeek.SATURDAY)); -// => 2024-08-17 +// Find the next Thursday (inclusive) - returns the same date +LocalDate nextOrSameThursday = thursday.with(TemporalAdjusters.nextOrSame(DayOfWeek.THURSDAY)); +// => 2024-08-15 -// 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) +// Find the previous Monday (exclusive) +LocalDate previousMonday = thursday.with(TemporalAdjusters.previous(DayOfWeek.MONDAY)); +// => 2024-08-12 ``` ## Custom Adjusters diff --git a/concepts/temporal-adjusters/links.json b/concepts/temporal-adjusters/links.json index d2a0b0185..ca655ff49 100644 --- a/concepts/temporal-adjusters/links.json +++ b/concepts/temporal-adjusters/links.json @@ -1,18 +1,18 @@ [ { - "url": "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/time/temporal/TemporalAdjuster.html", + "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/21/docs/api/java.base/java/time/temporal/TemporalAdjusters.html", + "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/21/docs/api/java.base/java/time/temporal/Temporal.html", + "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/21/docs/api/java.base/java/time/DayOfWeek.html", + "url": "https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/time/DayOfWeek.html", "description": "DayOfWeek enum documentation" } ] diff --git a/exercises/concept/beauty-salon-regulars/.docs/hints.md b/exercises/concept/beauty-salon-regulars/.docs/hints.md index cae25805d..8f5cb6ae4 100644 --- a/exercises/concept/beauty-salon-regulars/.docs/hints.md +++ b/exercises/concept/beauty-salon-regulars/.docs/hints.md @@ -30,13 +30,13 @@ - 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/javase/8/docs/api/java/time/temporal/TemporalAdjusters.html -[temporaladjuster-docs]: https://docs.oracle.com/javase/8/docs/api/java/time/temporal/TemporalAdjuster.html -[localdate-with]: https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html#with-java.time.temporal.TemporalAdjuster- -[localdate-getdayofweek]: https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html#getDayOfWeek-- -[dayofweek-from]: https://docs.oracle.com/javase/8/docs/api/java/time/DayOfWeek.html#from-java.time.temporal.TemporalAccessor- -[dayofweekinmonth-docs]: https://docs.oracle.com/javase/8/docs/api/java/time/temporal/TemporalAdjusters.html#dayOfWeekInMonth-int-java.time.DayOfWeek- -[lastinmonth-docs]: https://docs.oracle.com/javase/8/docs/api/java/time/temporal/TemporalAdjusters.html#lastInMonth-java.time.DayOfWeek- -[nextorsame-docs]: https://docs.oracle.com/javase/8/docs/api/java/time/temporal/TemporalAdjusters.html#nextOrSame-java.time.DayOfWeek- -[lastdayofmonth-docs]: https://docs.oracle.com/javase/8/docs/api/java/time/temporal/TemporalAdjusters.html#lastDayOfMonth-- -[previous-docs]: https://docs.oracle.com/javase/8/docs/api/java/time/temporal/TemporalAdjusters.html#previous-java.time.DayOfWeek- +[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) diff --git a/exercises/concept/beauty-salon-regulars/.docs/introduction.md b/exercises/concept/beauty-salon-regulars/.docs/introduction.md index 3cb2cb555..62c695095 100644 --- a/exercises/concept/beauty-salon-regulars/.docs/introduction.md +++ b/exercises/concept/beauty-salon-regulars/.docs/introduction.md @@ -2,9 +2,11 @@ ## Temporal Adjusters -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`**. +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`). +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. @@ -45,20 +47,27 @@ LocalDate lastMonday = date.with(TemporalAdjusters.lastInMonth(DayOfWeek.MONDAY) // => 2024-08-26 ``` -### Finding Relative Occurrences ("Teenth" dates) +### Relative Adjustments (Next/Previous) -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. +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 thirteenth = LocalDate.of(2024, Month.AUGUST, 13); // Tuesday the 13th +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 first Saturday on or after the 13th ("Teenth" Saturday) -LocalDate teenthSaturday = thirteenth.with(TemporalAdjusters.nextOrSame(DayOfWeek.SATURDAY)); -// => 2024-08-17 +// Find the next Thursday (inclusive) - returns the same date +LocalDate nextOrSameThursday = thursday.with(TemporalAdjusters.nextOrSame(DayOfWeek.THURSDAY)); +// => 2024-08-15 -// 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) +// Find the previous Monday (exclusive) +LocalDate previousMonday = thursday.with(TemporalAdjusters.previous(DayOfWeek.MONDAY)); +// => 2024-08-12 ``` ### Custom Adjusters