Printing calendar starting from the first day of the week based on locale using java.time/Java-8

All the examples I’ve seen of calendar view libraries and calendar based date pickers, use the old Calendar API. However, I have not found any that use the Java 8 date API to build out a calendar view. This is something I’m trying to achieve and have essentially done it but the issue I’m running into is setting the first day of the week using the locale.

I’ve managed to get and display all the dates for each day of the week for a given month. However, the issue I’m having is that the resulting calendar does not start from the first day of the week based on my locale. The DayOfWeek enum which Java.Time uses, starts counting the days from Monday to Sunday (1 – 7). However I want the calendar to display the days by the locale, in my case, Sunday to Saturday.

According to the documentation, the WeekFields class provides access to the days of the week based on locale, with the “correct” values, but I’m not sure how to properly utilize it.

This is what I have done so far:

private enum class DaysOfWeek {
    SUNDAY,
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY
}

fun TimetableCalendar() {
    val dates = getDaysOfMonth(LocalDate.now().year, LocalDate.now().month)
    
    // WORK AROUND: Hardcoding the first day of the week by manually inserting them into a new map
    val tempDates = hashMapOf<DaysOfWeek, MutableList<LocalDate>>()
    tempDates[DaysOfWeek.SUNDAY] = dates[DayOfWeek.SUNDAY] !!
    tempDates[DaysOfWeek.MONDAY] = dates[DayOfWeek.MONDAY] !!
    tempDates[DaysOfWeek.TUESDAY] = dates[DayOfWeek.TUESDAY] !!
    tempDates[DaysOfWeek.WEDNESDAY] = dates[DayOfWeek.WEDNESDAY] !!
    tempDates[DaysOfWeek.THURSDAY] = dates[DayOfWeek.THURSDAY] !!
    tempDates[DaysOfWeek.FRIDAY] = dates[DayOfWeek.FRIDAY] !!
    tempDates[DaysOfWeek.SATURDAY] = dates[DayOfWeek.SATURDAY] !!

    // Sort the days by ordinal, 0 - 6
    val sortedDates = tempDates.toSortedMap(compareBy {
        d -> d.ordinal
    })

    LazyVerticalGrid(
        cells = GridCells.Fixed(7),
        contentPadding = PaddingValues(16. dp)
    ) {
        // Display short day of week name
        items(sortedDates.keys.toList()) { dayOfWeek ->
                Text(text = dayOfWeek.name.substring(0, 3), textAlign = TextAlign.Center)
        }
        itemsIndexed(sortedDates.values.toList()) { _, date ->
            Column(
                modifier = Modifier.padding(4. dp)
            ) {
                date.forEach { day ->
                        DateView(date = day.dayOfMonth.toString())
                }
            }
        }
    }
}

/**
 * Returns the dates for each day of the week in the given [year] and [month]
 * Including dates of the previous and next month if the [month] does not
 * begin on the first or last day of the week
 */
fun getDaysOfMonth(year: Int, month: Month): HashMap<DayOfWeek, MutableList<LocalDate>> {
    val weekFields = WeekFields.of(Locale.getDefault())

    val daysOfWeek = mutableSetOf<DayOfWeek>()
    daysOfWeek.add(DayOfWeek.SUNDAY)
    daysOfWeek.add(DayOfWeek.MONDAY)
    daysOfWeek.add(DayOfWeek.TUESDAY)
    daysOfWeek.add(DayOfWeek.WEDNESDAY)
    daysOfWeek.add(DayOfWeek.THURSDAY)
    daysOfWeek.add(DayOfWeek.FRIDAY)
    daysOfWeek.add(DayOfWeek.SATURDAY)

    val ym = YearMonth.of(year, month)
    val firstOfMonth = ym.atDay(1) // first day of the month
    val lastDayOfMonth = ym.atDay(ym.lengthOfMonth())

    val dayDates = hashMapOf<DayOfWeek, MutableList<LocalDate>>()

    // Get all the dates for each day of the week
    daysOfWeek.forEach { day ->
            val dates = mutableListOf<LocalDate>()
        var ld = firstOfMonth.with(TemporalAdjusters.dayOfWeekInMonth(1, day))
        do {
            dates.add(ld)
            ld = ld.plusWeeks(1)
        } while (YearMonth.from(ld).equals(ym))
        dayDates[day] = dates
    }

    // If current month does not start on a Sunday, get the last few days of the previous month
    if (firstOfMonth.dayOfWeek != weekFields.firstDayOfWeek) {
        val previousMonth = YearMonth.of(year, month.minus(1))
        var lastDateOfPrevMonth = LocalDate.of(year, previousMonth.month, previousMonth.atEndOfMonth().dayOfMonth)

        do {
            dayDates[lastDateOfPrevMonth.dayOfWeek]?.add(0, lastDateOfPrevMonth)
            lastDateOfPrevMonth = lastDateOfPrevMonth.minusDays(1)
        } while (lastDateOfPrevMonth.dayOfWeek != DayOfWeek.SATURDAY)
    }

    // If current month does not end on a saturday, get the first few days of the next month
    if (lastDayOfMonth.dayOfWeek != weekFields.firstDayOfWeek.minus(1)) {
        val nextMonth = YearMonth.of(year, month.plus(1))
        var firstDateOfNextMonth = LocalDate.of(year, nextMonth.month, 1)

        do {
            dayDates[firstDateOfNextMonth.dayOfWeek]?.add(firstDateOfNextMonth)
            firstDateOfNextMonth = firstDateOfNextMonth.plusDays(1)
        } while (firstDateOfNextMonth.dayOfWeek != DayOfWeek.SUNDAY)
    }

    return dayDates
}

The above code works as intended and displays a calendar with the week starting on Sunday. Would it make sense to manually insert the days of the week into a new hashmap using a new enum which starts at Sunday, as I’ve done? Or would it make more sense to let the date api handle it (if possible) and how?

Or am I just taking the wrong approach and the Calendar API would be a much better option?

Answer

WeekFields.dayOfWeek()

You are entirely correct: to sort the days of the week correctly for a locale, that is, with the day of week first that according to that locale is the first day of the week, you need to use an appropriate WeekFields object. The following Java method gives just a short demonstration:

public static void sortAndPrintDaysOfWeek(Locale loc) {
    List<DayOfWeek> daysOfTheWeek = Arrays.asList(DayOfWeek.values());
    WeekFields wf = WeekFields.of(loc);

    daysOfTheWeek.sort(Comparator.comparingInt(dow -> dow.get(wf.dayOfWeek())));

    System.out.println(daysOfTheWeek);
}

I trust you to apply a similar comparator to your Kotlin sorted map. Trying the above method out:

    sortAndPrintDaysOfWeek(Locale.FRANCE);

Output:

[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]

    sortAndPrintDaysOfWeek(Locale.US);

[SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY]

    sortAndPrintDaysOfWeek(Locale.forLanguageTag("ar-EG"));

[SATURDAY, SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY]