From d01905b65c2e9825f6367bb1d07b3f757040180a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 20 Oct 2026 00:12:06 +0000 Subject: [PATCH 1/2] fix(macos): align parity week anchor with web Sunday start Co-authored-by: Tyler Dane --- .../Sources/CompassData/NativeCalendarRootModel.swift | 4 +--- .../CompassKit/Layout/ParityCaptureGeometry.swift | 11 +++++++++++ .../CompassKitTests/ParityCaptureGeometryTests.swift | 8 ++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/apps/calendar-macos/CompassData/Sources/CompassData/NativeCalendarRootModel.swift b/apps/calendar-macos/CompassData/Sources/CompassData/NativeCalendarRootModel.swift index b86eebf5d..7ff96df61 100644 --- a/apps/calendar-macos/CompassData/Sources/CompassData/NativeCalendarRootModel.swift +++ b/apps/calendar-macos/CompassData/Sources/CompassData/NativeCalendarRootModel.swift @@ -372,9 +372,7 @@ public final class NativeCalendarRootModel { contentTrackWidth = CGFloat(ParityCaptureGeometry.contentTrackWidth) viewStore.setVisibleDayCount(ParityCaptureGeometry.visibleDayCount) let today = referenceNow - let calendar = EffectiveTimeZone.calendar - let weekStart = calendar.dateInterval(of: .weekOfYear, for: today)?.start ?? today - viewStore.setAnchorDate(weekStart) + viewStore.setAnchorDate(ParityCaptureGeometry.weekViewStart(containing: today)) monthPickerMonth = today pendingScroll = .scrollToNowOrPulse rebuildPresentation() diff --git a/apps/calendar-macos/CompassKit/Sources/CompassKit/Layout/ParityCaptureGeometry.swift b/apps/calendar-macos/CompassKit/Sources/CompassKit/Layout/ParityCaptureGeometry.swift index 1b8253ae7..8c02bd3c0 100644 --- a/apps/calendar-macos/CompassKit/Sources/CompassKit/Layout/ParityCaptureGeometry.swift +++ b/apps/calendar-macos/CompassKit/Sources/CompassKit/Layout/ParityCaptureGeometry.swift @@ -21,4 +21,15 @@ public enum ParityCaptureGeometry { marginLeft: gridMarginLeft ) } + + /// Web week view uses Day.js `startOf("week")` (Sunday) in the demo timezone. + public static func weekViewStart(containing date: Date) -> Date { + var calendar = EffectiveTimeZone.calendar + calendar.firstWeekday = 1 + calendar.minimumDaysInFirstWeek = 1 + let startOfDay = calendar.startOfDay(for: date) + let weekday = calendar.component(.weekday, from: startOfDay) + let daysFromSunday = (weekday - calendar.firstWeekday + 7) % 7 + return calendar.date(byAdding: .day, value: -daysFromSunday, to: startOfDay) ?? startOfDay + } } diff --git a/apps/calendar-macos/CompassKitTests/ParityCaptureGeometryTests.swift b/apps/calendar-macos/CompassKitTests/ParityCaptureGeometryTests.swift index 36f67e497..e73343f37 100644 --- a/apps/calendar-macos/CompassKitTests/ParityCaptureGeometryTests.swift +++ b/apps/calendar-macos/CompassKitTests/ParityCaptureGeometryTests.swift @@ -7,4 +7,12 @@ final class ParityCaptureGeometryTests: XCTestCase { XCTAssertEqual(ParityCaptureGeometry.contentTrackWidth, 871, accuracy: 0.1) XCTAssertEqual(ParityCaptureGeometry.visibleDayCount, 5) } + + func testWeekViewStartMatchesWebSundayWeek() throws { + EffectiveTimeZone.identifier = "UTC" + let reference = try XCTUnwrap( + CompassDateParsing.parseInEffectiveTimeZone("2026-06-10T15:00:00.000Z")) + let weekStart = ParityCaptureGeometry.weekViewStart(containing: reference) + XCTAssertEqual(CompassDateParsing.formatCalendarDay(weekStart), "2026-06-07") + } } From d96e42fc7388bd288f6b66a85e1adeabce9aeaef Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 20 Oct 2026 00:17:13 +0000 Subject: [PATCH 2/2] fix(macos): use Sunday week start and UTC labels like web Co-authored-by: Tyler Dane --- .../CompassData/NativeCalendarRootModel.swift | 7 ++- .../Calendar/CalendarWindowMath.swift | 11 +++++ .../Layout/ParityCaptureGeometry.swift | 11 ----- .../Timezone/TimeZoneFormatting.swift | 45 +++++++++++++++++-- .../CalendarWindowMathTests.swift | 7 +++ .../ParityCaptureGeometryTests.swift | 7 --- .../TimeZoneFormattingTests.swift | 18 ++++++++ 7 files changed, 80 insertions(+), 26 deletions(-) create mode 100644 apps/calendar-macos/CompassKitTests/TimeZoneFormattingTests.swift diff --git a/apps/calendar-macos/CompassData/Sources/CompassData/NativeCalendarRootModel.swift b/apps/calendar-macos/CompassData/Sources/CompassData/NativeCalendarRootModel.swift index 7ff96df61..ddbb68a21 100644 --- a/apps/calendar-macos/CompassData/Sources/CompassData/NativeCalendarRootModel.swift +++ b/apps/calendar-macos/CompassData/Sources/CompassData/NativeCalendarRootModel.swift @@ -174,7 +174,7 @@ public final class NativeCalendarRootModel { let anchor: Date = { let today = demoPresentation?.referenceNow ?? now() - return EffectiveTimeZone.calendar.dateInterval(of: .weekOfYear, for: today)?.start ?? today + return CalendarWindowMath.sundayWeekViewStart(containing: today) }() viewStore = ViewStore( view: .week, @@ -372,7 +372,7 @@ public final class NativeCalendarRootModel { contentTrackWidth = CGFloat(ParityCaptureGeometry.contentTrackWidth) viewStore.setVisibleDayCount(ParityCaptureGeometry.visibleDayCount) let today = referenceNow - viewStore.setAnchorDate(ParityCaptureGeometry.weekViewStart(containing: today)) + viewStore.setAnchorDate(CalendarWindowMath.sundayWeekViewStart(containing: today)) monthPickerMonth = today pendingScroll = .scrollToNowOrPulse rebuildPresentation() @@ -920,8 +920,7 @@ public final class NativeCalendarRootModel { if viewStore.visibleDayCount == 1 { viewStore.setAnchorDate(today) } else { - let weekStart = calendar.dateInterval(of: .weekOfYear, for: today)?.start ?? today - viewStore.setAnchorDate(weekStart) + viewStore.setAnchorDate(CalendarWindowMath.sundayWeekViewStart(containing: today)) } monthPickerMonth = today rebuildPresentation() diff --git a/apps/calendar-macos/CompassKit/Sources/CompassKit/Calendar/CalendarWindowMath.swift b/apps/calendar-macos/CompassKit/Sources/CompassKit/Calendar/CalendarWindowMath.swift index 5bd3f31f2..8b8ee28aa 100644 --- a/apps/calendar-macos/CompassKit/Sources/CompassKit/Calendar/CalendarWindowMath.swift +++ b/apps/calendar-macos/CompassKit/Sources/CompassKit/Calendar/CalendarWindowMath.swift @@ -3,6 +3,17 @@ import Foundation public enum CalendarWindowMath { public static let weekDayCount = 7 + /// Web Day.js `startOf("week")`: Sunday 00:00 in the effective timezone. + public static func sundayWeekViewStart(containing date: Date) -> Date { + var calendar = EffectiveTimeZone.calendar + calendar.firstWeekday = 1 + calendar.minimumDaysInFirstWeek = 1 + let startOfDay = calendar.startOfDay(for: date) + let weekday = calendar.component(.weekday, from: startOfDay) + let daysFromSunday = (weekday - calendar.firstWeekday + 7) % 7 + return calendar.date(byAdding: .day, value: -daysFromSunday, to: startOfDay) ?? startOfDay + } + /// Seven local days from the anchor's start of day; `end` is exclusive next midnight. public static func weekEventsQueryWindow(anchor: Date) -> DateRange { let calendar = EffectiveTimeZone.calendar diff --git a/apps/calendar-macos/CompassKit/Sources/CompassKit/Layout/ParityCaptureGeometry.swift b/apps/calendar-macos/CompassKit/Sources/CompassKit/Layout/ParityCaptureGeometry.swift index 8c02bd3c0..1b8253ae7 100644 --- a/apps/calendar-macos/CompassKit/Sources/CompassKit/Layout/ParityCaptureGeometry.swift +++ b/apps/calendar-macos/CompassKit/Sources/CompassKit/Layout/ParityCaptureGeometry.swift @@ -21,15 +21,4 @@ public enum ParityCaptureGeometry { marginLeft: gridMarginLeft ) } - - /// Web week view uses Day.js `startOf("week")` (Sunday) in the demo timezone. - public static func weekViewStart(containing date: Date) -> Date { - var calendar = EffectiveTimeZone.calendar - calendar.firstWeekday = 1 - calendar.minimumDaysInFirstWeek = 1 - let startOfDay = calendar.startOfDay(for: date) - let weekday = calendar.component(.weekday, from: startOfDay) - let daysFromSunday = (weekday - calendar.firstWeekday + 7) % 7 - return calendar.date(byAdding: .day, value: -daysFromSunday, to: startOfDay) ?? startOfDay - } } diff --git a/apps/calendar-macos/CompassKit/Sources/CompassKit/Timezone/TimeZoneFormatting.swift b/apps/calendar-macos/CompassKit/Sources/CompassKit/Timezone/TimeZoneFormatting.swift index dd8a19ed2..fd9986d53 100644 --- a/apps/calendar-macos/CompassKit/Sources/CompassKit/Timezone/TimeZoneFormatting.swift +++ b/apps/calendar-macos/CompassKit/Sources/CompassKit/Timezone/TimeZoneFormatting.swift @@ -5,12 +5,22 @@ public enum TimeZoneFormatting { TimeZone(identifier: identifier) != nil } + /// Matches web `formatTimeZoneAbbreviation` (Intl short, offset fallback). public static func abbreviation(for timeZoneId: String, at date: Date = Date()) -> String { guard let zone = TimeZone(identifier: timeZoneId) else { return timeZoneId } - let formatter = DateFormatter() - formatter.timeZone = zone - formatter.dateFormat = "z" - return formatter.string(from: date) + if timeZoneId == "UTC" { + return "UTC" + } + + let short = timeZoneNamePart(timeZone: zone, at: date, style: .short) + if let short, !looksLikeIanaId(short) { + return short + } + + if let offset = timeZoneNamePart(timeZone: zone, at: date, style: .shortOffset) { + return offset + } + return short ?? timeZoneId } public static func cityName(for timeZoneId: String) -> String { @@ -18,4 +28,31 @@ public enum TimeZoneFormatting { guard let city = parts.last else { return timeZoneId } return city.replacingOccurrences(of: "_", with: " ") } + + private enum NameStyle { + case short + case shortOffset + } + + private static func timeZoneNamePart( + timeZone: TimeZone, + at date: Date, + style: NameStyle + ) -> String? { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "en_US") + formatter.timeZone = timeZone + switch style { + case .short: + formatter.dateFormat = "zzz" + case .shortOffset: + formatter.dateFormat = "ZZZZZ" + } + let value = formatter.string(from: date) + return value.isEmpty ? nil : value + } + + private static func looksLikeIanaId(_ value: String) -> Bool { + value.contains("/") + } } diff --git a/apps/calendar-macos/CompassKitTests/CalendarWindowMathTests.swift b/apps/calendar-macos/CompassKitTests/CalendarWindowMathTests.swift index df862fda0..c1420df43 100644 --- a/apps/calendar-macos/CompassKitTests/CalendarWindowMathTests.swift +++ b/apps/calendar-macos/CompassKitTests/CalendarWindowMathTests.swift @@ -25,6 +25,13 @@ final class CalendarWindowMathTests: XCTestCase { ) } + func testSundayWeekViewStartMatchesWeb() throws { + let reference = try XCTUnwrap( + CompassDateParsing.parseInEffectiveTimeZone("2026-06-10T15:00:00.000Z")) + let weekStart = CalendarWindowMath.sundayWeekViewStart(containing: reference) + XCTAssertEqual(CompassDateParsing.formatCalendarDay(weekStart), "2026-06-07") + } + func testDayEventQueryRangeUsesExclusiveEnd() throws { let anchor = try XCTUnwrap( CompassDateParsing.calendarDateInEffectiveTimeZone("2026-06-28") diff --git a/apps/calendar-macos/CompassKitTests/ParityCaptureGeometryTests.swift b/apps/calendar-macos/CompassKitTests/ParityCaptureGeometryTests.swift index e73343f37..f346efec5 100644 --- a/apps/calendar-macos/CompassKitTests/ParityCaptureGeometryTests.swift +++ b/apps/calendar-macos/CompassKitTests/ParityCaptureGeometryTests.swift @@ -8,11 +8,4 @@ final class ParityCaptureGeometryTests: XCTestCase { XCTAssertEqual(ParityCaptureGeometry.visibleDayCount, 5) } - func testWeekViewStartMatchesWebSundayWeek() throws { - EffectiveTimeZone.identifier = "UTC" - let reference = try XCTUnwrap( - CompassDateParsing.parseInEffectiveTimeZone("2026-06-10T15:00:00.000Z")) - let weekStart = ParityCaptureGeometry.weekViewStart(containing: reference) - XCTAssertEqual(CompassDateParsing.formatCalendarDay(weekStart), "2026-06-07") - } } diff --git a/apps/calendar-macos/CompassKitTests/TimeZoneFormattingTests.swift b/apps/calendar-macos/CompassKitTests/TimeZoneFormattingTests.swift new file mode 100644 index 000000000..19880cc48 --- /dev/null +++ b/apps/calendar-macos/CompassKitTests/TimeZoneFormattingTests.swift @@ -0,0 +1,18 @@ +import CompassKit +import XCTest + +final class TimeZoneFormattingTests: XCTestCase { + func testUTCLabelMatchesWeb() throws { + let at = try XCTUnwrap( + CompassDateParsing.parseInEffectiveTimeZone("2026-07-15T12:00:00.000Z")) + XCTAssertEqual(TimeZoneFormatting.abbreviation(for: "UTC", at: at), "UTC") + } + + func testNamedZoneUsesShortAbbreviation() throws { + let summer = try XCTUnwrap( + CompassDateParsing.parseInEffectiveTimeZone("2026-07-15T18:00:00.000Z")) + XCTAssertEqual( + TimeZoneFormatting.abbreviation(for: "America/Denver", at: summer), + "MDT") + } +}