HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

Find all instances of a given weekday in February for a given year

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
weekdayyearinstancesallfebruaryforfindgiven

Problem

A friend of mine was practicing his programming skills with a textbook meant to prepare students for computer science exams. He asked for help with a specific task.

The task is to capture user input (day of week and a year in the range 1500 to 2005 inclusive), and output all instances of the weekday in February that year.

The differences between the Julian and Gregorian calendars are to be accounted for.

Now, most likely the idea behind this task was to have the student create an algorithm to manually calculate the dates. However, as Java SE seems to be allowed in exams in my country, I came up with the idea of utilizing the GregorianCalendar class (which, despite its name, combines the Julian and Gregorian calendars).

```
package calendar;

import java.text.DateFormat;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Locale;
import java.util.Scanner;

public class CalendarTask {
public static void main(String[] args) {
GregorianCalendar cal = new GregorianCalendar();

HashMap daysOfWeek = new HashMap<>();
daysOfWeek.put("monday", cal.MONDAY);
daysOfWeek.put("tuesday", cal.TUESDAY);
daysOfWeek.put("wednesday", cal.WEDNESDAY);
daysOfWeek.put("thursday", cal.THURSDAY);
daysOfWeek.put("friday", cal.FRIDAY);
daysOfWeek.put("saturday", cal.SATURDAY);
daysOfWeek.put("sunday", cal.SUNDAY);

System.out.print("Enter day of week: ");
Scanner sc = new Scanner(System.in);
int dayOfWeek, year;
try {
dayOfWeek = daysOfWeek.get(sc.next().toLowerCase());
System.out.print("Enter year (1500-2005 inclusive): ");
year = Integer.parseInt(sc.next());
if (year 2005) throw new Exception();

System.out.println("Output:");
DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.GERMANY);
cal.set(cal.YEAR, year);
trySetDay(cal, dayOfWeek, 1);

Solution

Java 8 Time APIs

Instead of the 'legacy' Calendar and DateFormat classes, you can rely on Java 8's new java.time.* APIs for more fluent chronology-related calculations.

For starters, your manually constructed Map can be replaced with a simple look-up on the DayOfWeek enum:

// scanner will be a wrapper over System.in
private static DayOfWeek getDayOfWeek(Scanner scanner) {
    String values = Arrays.toString(DayOfWeek.values());
    System.out.printf("Enter a day of week:%n%s%n", values);
    while (true) {
        try {
            return DayOfWeek.valueOf(scanner.nextLine().trim().toUpperCase());
        } catch (IllegalArgumentException e) {
            System.err.printf("Please try again with one of these:%n%s%n",
                    values);
        }
    }
}


The looping-validation ensures that only a valid DayOfWeek value is returned.

Next, you can use a couple of TemporalAdjusters to get the LocalDates you require:

  • firstInMonth(DayOfWeek): the first day-of-week of the month.



  • lastDayOfMonth(): the last day of the month.



  • next(DayOfWeek): the next day-of-week, i.e. 7 days from the LocalDate instance.



Then, with a helpful serving of DateTimeFormatter, the main processing logic can just be:

try (Scanner scanner = new Scanner(System.in)) {
    DayOfWeek dayOfWeek = getDayOfWeek(scanner);
    // getYear(Scanner) returns an int between the year range, MONTH = 2
    LocalDate first = LocalDate.of(getYear(scanner), MONTH, 1)
                                .with(TemporalAdjusters.firstInMonth(dayOfWeek));
    LocalDate last = first.with(TemporalAdjusters.lastDayOfMonth());
    DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
                                                    .withLocale(Locale.GERMANY);
    for (LocalDate date = first; !date.isAfter(last);
            date = date.with(TemporalAdjusters.next(dayOfWeek))) {
        System.out.println(formatter.format(date));
    }
}


Other observations

-
It's not recommended to throw a generic Exception as it's... too vague. You can consider using IllegalArgumentException for invalid year inputs outside \$[1500, 2005]\$, as the exception type is then more defined.

-
More importantly, you shouldn't be throwing your own Exceptions and then catching it purely as a form of flow control.

-
HashMap daysOfWeek = new HashMap<>() can be better written as Map daysOfWeek = new HashMap<>(), to program to an interface rather than the implementation.

-
As illustrated above, you should also consider using try-with-resources on the Scanner instance for safe and efficient handling of the underlying I/O resource.

Code Snippets

// scanner will be a wrapper over System.in
private static DayOfWeek getDayOfWeek(Scanner scanner) {
    String values = Arrays.toString(DayOfWeek.values());
    System.out.printf("Enter a day of week:%n%s%n", values);
    while (true) {
        try {
            return DayOfWeek.valueOf(scanner.nextLine().trim().toUpperCase());
        } catch (IllegalArgumentException e) {
            System.err.printf("Please try again with one of these:%n%s%n",
                    values);
        }
    }
}
try (Scanner scanner = new Scanner(System.in)) {
    DayOfWeek dayOfWeek = getDayOfWeek(scanner);
    // getYear(Scanner) returns an int between the year range, MONTH = 2
    LocalDate first = LocalDate.of(getYear(scanner), MONTH, 1)
                                .with(TemporalAdjusters.firstInMonth(dayOfWeek));
    LocalDate last = first.with(TemporalAdjusters.lastDayOfMonth());
    DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
                                                    .withLocale(Locale.GERMANY);
    for (LocalDate date = first; !date.isAfter(last);
            date = date.with(TemporalAdjusters.next(dayOfWeek))) {
        System.out.println(formatter.format(date));
    }
}

Context

StackExchange Code Review Q#122730, answer score: 3

Revisions (0)

No revisions yet.