Use kotlinx-datetime's YearMonth and java.time converters

This commit is contained in:
2026-10-16 13:35:45 +02:00
parent cab6a4aaf6
commit ce7df7f1d7
8 changed files with 55 additions and 54 deletions
@@ -3,8 +3,9 @@ package de.jeanlucmakiola.calendula.ui.agenda
import de.jeanlucmakiola.floret.locale.localizedDateFormatter import de.jeanlucmakiola.floret.locale.localizedDateFormatter
import kotlinx.datetime.DayOfWeek import kotlinx.datetime.DayOfWeek
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import java.time.YearMonth
import java.util.Locale import java.util.Locale
import kotlinx.datetime.toJavaLocalDate
import kotlinx.datetime.yearMonth
/** /**
* How far ahead the agenda (screen or widget) shows events, starting at today. * How far ahead the agenda (screen or widget) shows events, starting at today.
@@ -50,7 +51,7 @@ fun AgendaRange.dayCount(anchor: LocalDate, weekStart: DayOfWeek): Int = when (t
DAYS_PER_WEEK - sinceWeekStart DAYS_PER_WEEK - sinceWeekStart
} }
AgendaRange.ThisMonth -> { AgendaRange.ThisMonth -> {
val daysInMonth = YearMonth.of(anchor.year, anchor.month.ordinal + 1).lengthOfMonth() val daysInMonth = anchor.yearMonth.numberOfDays
daysInMonth - anchor.day + 1 daysInMonth - anchor.day + 1
} }
is AgendaRange.Custom -> days.coerceIn(AgendaRange.MIN_CUSTOM_DAYS, AgendaRange.MAX_CUSTOM_DAYS) is AgendaRange.Custom -> days.coerceIn(AgendaRange.MIN_CUSTOM_DAYS, AgendaRange.MAX_CUSTOM_DAYS)
@@ -105,8 +106,8 @@ fun agendaRangeWindowSummary(
locale: Locale, locale: Locale,
monthAsSpan: Boolean = false, monthAsSpan: Boolean = false,
): String { ): String {
val javaStart = java.time.LocalDate.of(start.year, start.month.ordinal + 1, start.day) val javaStart = start.toJavaLocalDate()
val javaEnd = java.time.LocalDate.of(end.year, end.month.ordinal + 1, end.day) val javaEnd = end.toJavaLocalDate()
val dayMonth = localizedDateFormatter(locale, "dMMM") val dayMonth = localizedDateFormatter(locale, "dMMM")
val dayMonthYear = localizedDateFormatter(locale, "dMMMy") val dayMonthYear = localizedDateFormatter(locale, "dMMMy")
return when { return when {
@@ -52,6 +52,7 @@ import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone
import kotlinx.datetime.plus import kotlinx.datetime.plus
import kotlinx.datetime.toJavaLocalDate
import kotlinx.datetime.toLocalDateTime import kotlinx.datetime.toLocalDateTime
import kotlin.time.Instant import kotlin.time.Instant
import java.util.Locale import java.util.Locale
@@ -210,7 +211,7 @@ private fun formatTime(
private fun formatAgendaDate(date: LocalDate): String { private fun formatAgendaDate(date: LocalDate): String {
val locale = Locale.getDefault() val locale = Locale.getDefault()
val java = java.time.LocalDate.of(date.year, date.month.ordinal + 1, date.day) val java = date.toJavaLocalDate()
// Weekday + date in the locale's own field order (e.g. "Wed, Jun 17, 2026" // Weekday + date in the locale's own field order (e.g. "Wed, Jun 17, 2026"
// vs "Mi., 17. Juni 2026") rather than a hardcoded day-month-year layout. // vs "Mi., 17. Juni 2026") rather than a hardcoded day-month-year layout.
return localizedDateFormatter(locale, "EEEdMMMy").format(java) return localizedDateFormatter(locale, "EEEdMMMy").format(java)
@@ -192,10 +192,13 @@ import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone
import kotlinx.datetime.plus import kotlinx.datetime.plus
import kotlinx.datetime.YearMonth import kotlinx.datetime.YearMonth
import kotlinx.datetime.toJavaDayOfWeek
import kotlinx.datetime.toJavaLocalDate import kotlinx.datetime.toJavaLocalDate
import kotlin.time.Instant import kotlin.time.Instant
import java.time.format.TextStyle as JavaTextStyle import java.time.format.TextStyle as JavaTextStyle
import java.util.Locale import java.util.Locale
import kotlinx.datetime.toJavaMonth
import kotlinx.datetime.yearMonth
private val LocalTodayPulse = staticCompositionLocalOf { 0 } private val LocalTodayPulse = staticCompositionLocalOf { 0 }
@@ -344,7 +347,7 @@ fun MonthScreen(
if (dense) { if (dense) {
val midweek = weekStartForIndex(listState.firstVisibleItemIndex + 1, weekStart) val midweek = weekStartForIndex(listState.firstVisibleItemIndex + 1, weekStart)
.plus(3, DateTimeUnit.DAY) .plus(3, DateTimeUnit.DAY)
YearMonth(midweek.year, midweek.month) midweek.yearMonth
} else { } else {
yearMonthForIndex(monthIndexForItem(listState.firstVisibleItemIndex)) yearMonthForIndex(monthIndexForItem(listState.firstVisibleItemIndex))
} }
@@ -393,7 +396,7 @@ fun MonthScreen(
} }
} }
val isOnCurrentMonth = titleMonth == YearMonth(today.year, today.month) val isOnCurrentMonth = titleMonth == today.yearMonth
var splitExpanded by rememberSaveable(viewStyle) { var splitExpanded by rememberSaveable(viewStyle) {
mutableStateOf(viewStyle == MonthViewStyle.Paged) mutableStateOf(viewStyle == MonthViewStyle.Paged)
} }
@@ -422,7 +425,7 @@ fun MonthScreen(
val jumpToToday: () -> Unit = { val jumpToToday: () -> Unit = {
todayPulse++ todayPulse++
if (scrolling) { if (scrolling) {
val targetMonth = YearMonth(today.year, today.month) val targetMonth = today.yearMonth
pendingScrollMonth = targetMonth pendingScrollMonth = targetMonth
val requestId = ++scrollRequestId val requestId = ++scrollRequestId
viewModel.goToToday() viewModel.goToToday()
@@ -440,13 +443,13 @@ fun MonthScreen(
} }
} else { } else {
viewModel.goToToday() viewModel.goToToday()
jumpToMonth(YearMonth(today.year, today.month)) jumpToMonth(today.yearMonth)
} }
} }
// Drawer jump-to-date. // Drawer jump-to-date.
val jumpToDate: (LocalDate) -> Unit = { target -> val jumpToDate: (LocalDate) -> Unit = { target ->
if (scrolling) { if (scrolling) {
val targetMonth = YearMonth(target.year, target.month) val targetMonth = target.yearMonth
pendingScrollMonth = targetMonth pendingScrollMonth = targetMonth
val requestId = ++scrollRequestId val requestId = ++scrollRequestId
viewModel.goToDate(target) viewModel.goToDate(target)
@@ -464,7 +467,7 @@ fun MonthScreen(
} }
} else { } else {
viewModel.goToDate(target) viewModel.goToDate(target)
jumpToMonth(YearMonth(target.year, target.month)) jumpToMonth(target.yearMonth)
} }
} }
@@ -548,7 +551,7 @@ fun MonthScreen(
// still settling would land and take the tap back. // still settling would land and take the tap back.
onSelectDay = { date -> onSelectDay = { date ->
viewModel.selectDate(date) viewModel.selectDate(date)
jumpToMonth(YearMonth(date.year, date.month)) jumpToMonth(date.yearMonth)
}, },
onOpenDay = onOpenDay, onOpenDay = onOpenDay,
onEventClick = onEventClick, onEventClick = onEventClick,
@@ -721,7 +724,7 @@ internal fun WeekdayHeader(weekStart: DayOfWeek, showWeekNumbers: Boolean) {
if (showWeekNumbers) Spacer(Modifier.width(rememberWeekNumberGutter())) if (showWeekNumbers) Spacer(Modifier.width(rememberWeekNumberGutter()))
days.forEach { dow -> days.forEach { dow ->
val isWeekend = dow == DayOfWeek.SATURDAY || dow == DayOfWeek.SUNDAY val isWeekend = dow == DayOfWeek.SATURDAY || dow == DayOfWeek.SUNDAY
val javaDow = java.time.DayOfWeek.of(dow.ordinal + 1) val javaDow = dow.toJavaDayOfWeek()
Text( Text(
text = javaDow.getDisplayName(JavaTextStyle.SHORT, locale), text = javaDow.getDisplayName(JavaTextStyle.SHORT, locale),
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
@@ -940,7 +943,7 @@ internal fun ContinuousMonthGrid(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val monthCount = remember { continuousMonthCount() } val monthCount = remember { continuousMonthCount() }
val todayMonth = remember(state.today) { YearMonth(state.today.year, state.today.month) } val todayMonth = remember(state.today) { state.today.yearMonth }
val timeChipWidth = rememberMonthTimeChipWidth() val timeChipWidth = rememberMonthTimeChipWidth()
LazyColumn( LazyColumn(
state = listState, state = listState,
@@ -2698,7 +2701,7 @@ private fun DayNumberCell(
private fun shortMonthName(date: LocalDate): String { private fun shortMonthName(date: LocalDate): String {
val locale = currentLocale() val locale = currentLocale()
return remember(date.month, locale) { return remember(date.month, locale) {
java.time.Month.of(date.month.ordinal + 1) date.month.toJavaMonth()
.getDisplayName(JavaTextStyle.SHORT, locale) .getDisplayName(JavaTextStyle.SHORT, locale)
} }
} }
@@ -2861,7 +2864,7 @@ private fun formatMonthTitle(
abbreviated: Boolean = false, abbreviated: Boolean = false,
): String = ): String =
formatCalendarTitle( formatCalendarTitle(
date = java.time.LocalDate.of(ym.year, ym.month.ordinal + 1, 1), date = ym.firstDay.toJavaLocalDate(),
locale = locale, locale = locale,
currentYear = currentYear, currentYear = currentYear,
skeleton = if (abbreviated) "LLL" else "LLLL", skeleton = if (abbreviated) "LLL" else "LLLL",
@@ -14,10 +14,10 @@ import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.DayOfWeek import kotlinx.datetime.DayOfWeek
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone
import kotlinx.datetime.YearMonth
import kotlinx.datetime.plus import kotlinx.datetime.plus
import kotlinx.datetime.toLocalDateTime import kotlinx.datetime.toLocalDateTime
import kotlin.time.Clock import kotlin.time.Clock
import kotlinx.datetime.yearMonth
/** /**
* A live, scaled-down Month view in a given [MonthViewStyle], for the settings * A live, scaled-down Month view in a given [MonthViewStyle], for the settings
@@ -51,7 +51,7 @@ internal fun MonthStylePreview(
state = sample.continuous, state = sample.continuous,
listState = rememberLazyListState( listState = rememberLazyListState(
initialFirstVisibleItemIndex = itemIndexForMonth( initialFirstVisibleItemIndex = itemIndexForMonth(
monthIndexOf(YearMonth(today.year, today.month)), monthIndexOf(today.yearMonth),
), ),
), ),
showWeekNumbers = false, showWeekNumbers = false,
@@ -109,7 +109,7 @@ private fun sampleMonthState(
weekStart: DayOfWeek, weekStart: DayOfWeek,
zone: TimeZone, zone: TimeZone,
): SampleMonth { ): SampleMonth {
val ym = YearMonth(today.year, today.month) val ym = today.yearMonth
val first = LocalDate(ym.year, ym.month, 1) val first = LocalDate(ym.year, ym.month, 1)
val events = sampleMonthEvents(first, today, zone) val events = sampleMonthEvents(first, today, zone)
@@ -47,6 +47,7 @@ import kotlinx.datetime.plus
import kotlinx.datetime.toLocalDateTime import kotlinx.datetime.toLocalDateTime
import kotlin.time.Instant import kotlin.time.Instant
import javax.inject.Inject import javax.inject.Inject
import kotlinx.datetime.yearMonth
/** How many months either side of the requested one [MonthViewModel.month] keeps cached. */ /** How many months either side of the requested one [MonthViewModel.month] keeps cached. */
private const val MONTH_CACHE_SPAN = 2 private const val MONTH_CACHE_SPAN = 2
@@ -100,7 +101,7 @@ class MonthViewModel @Inject constructor(
} }
.stateWhileSubscribed(viewModelScope, todayDate) .stateWhileSubscribed(viewModelScope, todayDate)
private val _month = MutableStateFlow(YearMonth(todayDate.year, todayDate.month)) private val _month = MutableStateFlow(todayDate.yearMonth)
val month: StateFlow<YearMonth> = _month val month: StateFlow<YearMonth> = _month
/** /**
@@ -181,11 +182,11 @@ class MonthViewModel @Inject constructor(
/** Last reported visible range, so a widening step can re-centre on it. */ /** Last reported visible range, so a widening step can re-centre on it. */
@Volatile @Volatile
private var visibleMonths = monthIndexOf(YearMonth(todayDate.year, todayDate.month)) private var visibleMonths = monthIndexOf(todayDate.yearMonth)
.let { it..it } .let { it..it }
private val _loadedMonths = MutableStateFlow( private val _loadedMonths = MutableStateFlow(
monthIndexOf(YearMonth(todayDate.year, todayDate.month)) monthIndexOf(todayDate.yearMonth)
.let { clampMonthWindow(it - INITIAL_PAD..it + INITIAL_PAD) }, .let { clampMonthWindow(it - INITIAL_PAD..it + INITIAL_PAD) },
) )
@@ -201,10 +202,8 @@ class MonthViewModel @Inject constructor(
// Widened to whole grid weeks at both ends: a month block still // Widened to whole grid weeks at both ends: a month block still
// has to know about an event that starts in the boundary week's // has to know about an event that starts in the boundary week's
// clipped-off days, or a bar running into the block would vanish. // clipped-off days, or a bar running into the block would vanish.
val first = firstOfMonth(yearMonthForIndex(window.first)).startOfWeek(ws) val first = yearMonthForIndex(window.first).firstDay.startOfWeek(ws)
val last = firstOfMonth(yearMonthForIndex(window.last)) val last = yearMonthForIndex(window.last).lastDay
.plus(1, DateTimeUnit.MONTH)
.minus(1, DateTimeUnit.DAY)
.startOfWeek(ws) .startOfWeek(ws)
.plus(6, DateTimeUnit.DAY) .plus(6, DateTimeUnit.DAY)
val range = dayRange(first, last, zone) val range = dayRange(first, last, zone)
@@ -305,7 +304,7 @@ class MonthViewModel @Inject constructor(
* silently list a date the grid isn't pointing at. * silently list a date the grid isn't pointing at.
*/ */
fun selectDate(date: LocalDate) { fun selectDate(date: LocalDate) {
val target = YearMonth(date.year, date.month) val target = date.yearMonth
_selectedDate.value = if (target == _month.value && _selectedDate.value == date) null else date _selectedDate.value = if (target == _month.value && _selectedDate.value == date) null else date
if (target != _month.value) _month.value = target if (target != _month.value) _month.value = target
} }
@@ -320,12 +319,12 @@ class MonthViewModel @Inject constructor(
} else { } else {
val next = selected.plus(days, DateTimeUnit.DAY) val next = selected.plus(days, DateTimeUnit.DAY)
_selectedDate.value = next _selectedDate.value = next
_month.value = YearMonth(next.year, next.month) _month.value = next.yearMonth
} }
} }
fun goToToday() { fun goToToday() {
_month.value = YearMonth(todayDate.year, todayDate.month) _month.value = todayDate.yearMonth
_selectedDate.value = null _selectedDate.value = null
} }
@@ -336,8 +335,8 @@ class MonthViewModel @Inject constructor(
*/ */
private fun followToday(old: LocalDate, new: LocalDate) { private fun followToday(old: LocalDate, new: LocalDate) {
if (viewStyle.value.isScrolling) return if (viewStyle.value.isScrolling) return
if (_month.value != YearMonth(old.year, old.month)) return if (_month.value != old.yearMonth) return
_month.value = YearMonth(new.year, new.month) _month.value = new.yearMonth
_selectedDate.value = null _selectedDate.value = null
} }
@@ -357,7 +356,7 @@ class MonthViewModel @Inject constructor(
/** Jump to the month containing [date] (drawer jump-to-date). */ /** Jump to the month containing [date] (drawer jump-to-date). */
fun goToDate(date: LocalDate) { fun goToDate(date: LocalDate) {
_month.value = YearMonth(date.year, date.month) _month.value = date.yearMonth
_selectedDate.value = date _selectedDate.value = date
} }
@@ -407,7 +406,7 @@ internal fun layoutMonthWeeks(
byDay: DayIndex, byDay: DayIndex,
zone: TimeZone, zone: TimeZone,
): List<MonthWeek> { ): List<MonthWeek> {
val gridStart = firstOfMonth(ym).startOfWeek(weekStart) val gridStart = ym.firstDay.startOfWeek(weekStart)
val weekCount = weekRowsInMonth(ym, weekStart) val weekCount = weekRowsInMonth(ym, weekStart)
return (0 until weekCount).map { row -> return (0 until weekCount).map { row ->
@@ -528,8 +527,7 @@ internal fun monthGridRange(
weekStart: DayOfWeek, weekStart: DayOfWeek,
zone: TimeZone, zone: TimeZone,
): ClosedRange<Instant> { ): ClosedRange<Instant> {
val firstOfMonth = LocalDate(ym.year, ym.month, 1) val gridStart = ym.firstDay.startOfWeek(weekStart)
val gridStart = firstOfMonth.startOfWeek(weekStart)
return dayRange(gridStart, gridStart.plus(41, DateTimeUnit.DAY), zone) return dayRange(gridStart, gridStart.plus(41, DateTimeUnit.DAY), zone)
} }
@@ -558,7 +556,7 @@ internal fun edgeForPad(pad: Int): Int = minOf(WINDOW_EDGE, pad - 1).coerceAtLea
* the first of the visible month. * the first of the visible month.
*/ */
internal fun agendaStartForMonth(month: YearMonth, today: LocalDate): LocalDate = internal fun agendaStartForMonth(month: YearMonth, today: LocalDate): LocalDate =
if (YearMonth(today.year, today.month) == month) { if (today.yearMonth == month) {
today today
} else { } else {
LocalDate(month.year, month.month, 1) LocalDate(month.year, month.month, 1)
@@ -626,8 +624,6 @@ internal fun clampMonthWindow(window: IntRange): IntRange {
return window.first.coerceIn(0, last)..window.last.coerceIn(0, last) return window.first.coerceIn(0, last)..window.last.coerceIn(0, last)
} }
internal fun firstOfMonth(ym: YearMonth): LocalDate = LocalDate(ym.year, ym.month, 1)
/** /**
* The Dense style addresses *weeks* by absolute index instead: it has no month * The Dense style addresses *weeks* by absolute index instead: it has no month
* blocks to key by, and a row's identity has to survive the months around it * blocks to key by, and a row's identity has to survive the months around it
@@ -658,7 +654,7 @@ internal fun monthIndexForWeek(weekIndex: Int, weekStart: DayOfWeek): Int {
// The row's midweek day: a boundary week is reported as whichever month owns // The row's midweek day: a boundary week is reported as whichever month owns
// most of it, the same rule the title uses. // most of it, the same rule the title uses.
val midweek = weekStartForIndex(weekIndex, weekStart).plus(3, DateTimeUnit.DAY) val midweek = weekStartForIndex(weekIndex, weekStart).plus(3, DateTimeUnit.DAY)
return monthIndexOf(YearMonth(midweek.year, midweek.month)) return monthIndexOf(midweek.yearMonth)
} }
/** /**
@@ -667,19 +663,16 @@ internal fun monthIndexForWeek(weekIndex: Int, weekStart: DayOfWeek): Int {
* laid out too, rather than flickering as placeholders. * laid out too, rather than flickering as placeholders.
*/ */
internal fun weekWindowFor(window: IntRange, weekStart: DayOfWeek): IntRange { internal fun weekWindowFor(window: IntRange, weekStart: DayOfWeek): IntRange {
val first = weekIndexOf(firstOfMonth(yearMonthForIndex(window.first)), weekStart) - 1 val first = weekIndexOf(yearMonthForIndex(window.first).firstDay, weekStart) - 1
val lastMonthEnd = firstOfMonth(yearMonthForIndex(window.last)) val lastMonthEnd = yearMonthForIndex(window.last).lastDay
.plus(1, DateTimeUnit.MONTH)
.minus(1, DateTimeUnit.DAY)
return first..(weekIndexOf(lastMonthEnd, weekStart) + 1) return first..(weekIndexOf(lastMonthEnd, weekStart) + 1)
} }
/** How many week rows [ym] needs under [weekStart] — 4 (a non-leap February), 5 or 6. */ /** How many week rows [ym] needs under [weekStart] — 4 (a non-leap February), 5 or 6. */
internal fun weekRowsInMonth(ym: YearMonth, weekStart: DayOfWeek): Int { internal fun weekRowsInMonth(ym: YearMonth, weekStart: DayOfWeek): Int {
val first = firstOfMonth(ym) val first = ym.firstDay
val leadOffset = ((first.dayOfWeek.ordinal - weekStart.ordinal) + 7) % 7 val leadOffset = ((first.dayOfWeek.ordinal - weekStart.ordinal) + 7) % 7
val daysInMonth = java.time.YearMonth.of(ym.year, ym.month.ordinal + 1).lengthOfMonth() return (leadOffset + ym.numberOfDays + 6) / 7
return (leadOffset + daysInMonth + 6) / 7
} }
/** /**
@@ -56,6 +56,7 @@ import de.jeanlucmakiola.floret.components.ReorderableRowHeight
import de.jeanlucmakiola.floret.locale.currentLocale import de.jeanlucmakiola.floret.locale.currentLocale
import kotlinx.datetime.DayOfWeek import kotlinx.datetime.DayOfWeek
import java.time.format.TextStyle as JavaTextStyle import java.time.format.TextStyle as JavaTextStyle
import kotlinx.datetime.toJavaDayOfWeek
/** /**
* Views: everything that changes how a calendar view reads, grouped by the view * Views: everything that changes how a calendar view reads, grouped by the view
@@ -504,7 +505,7 @@ private val WEEK_START_OPTIONS: List<WeekStartPref> =
internal fun weekStartLabel(pref: WeekStartPref): String = when (pref) { internal fun weekStartLabel(pref: WeekStartPref): String = when (pref) {
WeekStartPref.Auto -> stringResource(R.string.settings_week_start_auto) WeekStartPref.Auto -> stringResource(R.string.settings_week_start_auto)
// Localised weekday name, so no per-day string resource is needed. // Localised weekday name, so no per-day string resource is needed.
is WeekStartPref.Day -> java.time.DayOfWeek.of(pref.day.ordinal + 1) is WeekStartPref.Day -> pref.day.toJavaDayOfWeek()
.getDisplayName(JavaTextStyle.FULL, currentLocale()) .getDisplayName(JavaTextStyle.FULL, currentLocale())
} }
@@ -202,10 +202,12 @@ import kotlinx.coroutines.launch
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone
import kotlinx.datetime.plus import kotlinx.datetime.plus
import kotlinx.datetime.toJavaDayOfWeek
import kotlinx.datetime.toJavaLocalDate import kotlinx.datetime.toJavaLocalDate
import kotlinx.datetime.toLocalDateTime import kotlinx.datetime.toLocalDateTime
import java.time.format.TextStyle as JavaTextStyle import java.time.format.TextStyle as JavaTextStyle
import java.util.Locale import java.util.Locale
import kotlinx.datetime.yearMonth
/** One lane of the all-day strip, sized to a trimmed bar line (#190). */ /** One lane of the all-day strip, sized to a trimmed bar line (#190). */
private val ALL_DAY_ROW_HEIGHT = 20.dp private val ALL_DAY_ROW_HEIGHT = 20.dp
@@ -1084,7 +1086,7 @@ private fun WeekDayHeader(
.fillMaxWidth() .fillMaxWidth()
.padding(top = HEADER_TOP_PADDING, bottom = HEADER_BOTTOM_PADDING, end = TIMELINE_CONTENT_END_INSET), .padding(top = HEADER_TOP_PADDING, bottom = HEADER_BOTTOM_PADDING, end = TIMELINE_CONTENT_END_INSET),
) { date -> ) { date ->
val javaDow = java.time.DayOfWeek.of(date.dayOfWeek.ordinal + 1) val javaDow = date.dayOfWeek.toJavaDayOfWeek()
val isToday = date == today val isToday = date == today
Column( Column(
modifier = Modifier modifier = Modifier
@@ -1958,7 +1960,7 @@ private fun formatWeekTitle(
abbreviated: Boolean = false, abbreviated: Boolean = false,
): String { ): String {
return formatCalendarTitle( return formatCalendarTitle(
date = java.time.LocalDate.of(weekStart.year, weekStart.month.ordinal + 1, 1), date = weekStart.yearMonth.firstDay.toJavaLocalDate(),
locale = locale, locale = locale,
currentYear = currentYear, currentYear = currentYear,
skeleton = if (abbreviated) "LLL" else "LLLL", skeleton = if (abbreviated) "LLL" else "LLLL",
@@ -1974,7 +1976,7 @@ private fun formatDayTitle(
abbreviated: Boolean = false, abbreviated: Boolean = false,
): String = ): String =
formatCalendarTitle( formatCalendarTitle(
date = java.time.LocalDate.of(date.year, date.month.ordinal + 1, date.day), date = date.toJavaLocalDate(),
locale = locale, locale = locale,
currentYear = currentYear, currentYear = currentYear,
skeleton = if (abbreviated) "dMMM" else "EEEdMMM", skeleton = if (abbreviated) "dMMM" else "EEEdMMM",
@@ -121,8 +121,8 @@ class ContinuousMonthIndexTest {
val weeks = weekWindowFor(window, ws) val weeks = weekWindowFor(window, ws)
window.forEach { index -> window.forEach { index ->
val ym = yearMonthForIndex(index) val ym = yearMonthForIndex(index)
val first = weekIndexOf(firstOfMonth(ym), ws) val first = weekIndexOf(ym.firstDay, ws)
val last = weekIndexOf(firstOfMonth(yearMonthForIndex(index + 1)).minus(1, DateTimeUnit.DAY), ws) val last = weekIndexOf(yearMonthForIndex(index + 1).firstDay.minus(1, DateTimeUnit.DAY), ws)
assertThat(weeks.contains(first)).isTrue() assertThat(weeks.contains(first)).isTrue()
assertThat(weeks.contains(last)).isTrue() assertThat(weeks.contains(last)).isTrue()
} }
@@ -162,8 +162,8 @@ class ContinuousMonthIndexTest {
} }
@Test @Test
fun `firstOfMonth is the 1st`() { fun `firstDay is the 1st`() {
assertThat(firstOfMonth(jun26)).isEqualTo(LocalDate(2026, 6, 1)) assertThat(jun26.firstDay).isEqualTo(LocalDate(2026, 6, 1))
} }
@Test @Test