diff --git a/myApp/HaruDanim/CLAUDE.md b/myApp/HaruDanim/CLAUDE.md index 4437598..13934d3 100644 --- a/myApp/HaruDanim/CLAUDE.md +++ b/myApp/HaruDanim/CLAUDE.md @@ -105,6 +105,7 @@ xcrun xcstringstool sync Widgets/Localizable.xcstrings --stringsdata "${widget_f ### 4.1 논리적 하루 (DayMath) - 설정 "하루 시작 시간"(`dayStartMinutes`)·"주 시작 요일"(`weekStartWeekday`, 기본 월요일)이 모든 집계의 기준 - `dayKey(for:)`: 시각 → 그 시각이 속한 논리적 하루의 달력일 자정. 경계 이전 시각은 전날 귀속 +- ⚠️ **자정 Date(=키)를 dayKey(for:)나 ~Range(containing:)에 다시 넣지 말 것** — 하루 시작 시간이 자정보다 늦으면 전날로 밀린다(일기 달력이 매달 1일에 이전 달을 그리고, 내보내기 '날짜 지정'이 전날 일기를 담던 버그). 달력 UI에서 고른 달력일은 `calendar.startOfDay`를 키로 직접 쓰고, 키로 시각 연산이 필요하면 `dayRange(forKey:).lowerBound`로 변환해 넘긴다(기록·통계 탭 패턴) - 세션은 실제 시각 그대로 하나로 저장하고, **집계는 구간 겹침(overlap)으로 자동 분할** — 하루 경계를 걸친 세션이 날짜별로 나뉘어 계산됨. `Aggregator.seconds/count`가 이 규칙의 단일 구현이며 화면·위젯·내보내기 모두 이를 공유 ### 4.2 다짐 진행률 (QuestProgress) diff --git a/myApp/HaruDanim/IOS/Views/DiaryExportSheet.swift b/myApp/HaruDanim/IOS/Views/DiaryExportSheet.swift index fc01299..0241b81 100644 --- a/myApp/HaruDanim/IOS/Views/DiaryExportSheet.swift +++ b/myApp/HaruDanim/IOS/Views/DiaryExportSheet.swift @@ -59,17 +59,21 @@ struct DiaryExportSheet: View { LocalPrefs.orderedActions(allActionsQuery, raw: actionOrderRaw) } - /// 선택 조건에 해당하고 내용이 있는 일기 (날짜순) + /// 선택 조건에 해당하고 내용이 있는 일기 (날짜순). + /// 날짜 선택 UI에서 고른 달력일은 곧 일기 키의 달력일이다 — DiaryEntry.dayKey가 + /// "해당 달력일의 자정"이므로 자정(startOfDay)을 키로 직접 쓴다. + /// ⚠️ 자정 Date를 dayKey(for:)에 넣으면 하루 시작 시간이 자정보다 늦을 때 + /// 전날로 밀려, 고른 날짜의 전날 일기가 내보내지는 버그가 있었다. private var targetEntries: [DiaryEntry] { let keys: Set switch mode { case .range: - let startKey = math.dayKey(for: min(startDate, endDate)) - let endKey = math.dayKey(for: max(startDate, endDate)) + let startKey = math.calendar.startOfDay(for: min(startDate, endDate)) + let endKey = math.calendar.startOfDay(for: max(startDate, endDate)) keys = Set(diaryEntries.map(\.dayKey).filter { $0 >= startKey && $0 <= endKey }) case .picks: keys = Set(picks.compactMap { components in - math.calendar.date(from: components).map { math.dayKey(for: $0) } + math.calendar.date(from: components).map { math.calendar.startOfDay(for: $0) } }) } return diaryEntries diff --git a/myApp/HaruDanim/IOS/Views/DiaryView.swift b/myApp/HaruDanim/IOS/Views/DiaryView.swift index c46cdea..3b0e64f 100644 --- a/myApp/HaruDanim/IOS/Views/DiaryView.swift +++ b/myApp/HaruDanim/IOS/Views/DiaryView.swift @@ -243,7 +243,9 @@ struct DiaryRootView: View { } private var monthSummary: some View { - let range = math.monthRange(containing: monthAnchor) + // monthAnchor는 자정 '키'라 monthRange(containing:)에 그대로 넣으면 하루 시작 시간이 + // 자정보다 늦을 때 전날(= 매달 1일이면 이전 달)로 밀린다 — 키를 실제 시각으로 변환해 전달 + let range = math.monthRange(containing: math.dayRange(forKey: monthAnchor).lowerBound) let count = entriesByDay.keys.filter { range.contains(math.dayRange(forKey: $0).lowerBound) }.count return HStack(spacing: 6) { Image(systemName: "pencil.and.scribble") @@ -419,7 +421,8 @@ private struct DiaryCalendarGrid: View { /// 이 달 그리드 셀 (앞쪽 빈칸은 nil) private var cells: [Date?] { - let range = math.monthRange(containing: monthAnchor) + // monthAnchor(자정 키)를 실제 시각으로 변환해 전달 — monthSummary와 동일한 이유 + let range = math.monthRange(containing: math.dayRange(forKey: monthAnchor).lowerBound) let keys = math.dayKeys(in: range) guard let first = keys.first else { return [] } let firstWeekday = calendar.component(.weekday, from: first)