Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(CalendarWindowMath.sundayWeekViewStart(containing: today))
monthPickerMonth = today
pendingScroll = .scrollToNowOrPulse
rebuildPresentation()
Expand Down Expand Up @@ -922,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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,54 @@ 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 {
let parts = timeZoneId.split(separator: "/")
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("/")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ final class ParityCaptureGeometryTests: XCTestCase {
XCTAssertEqual(ParityCaptureGeometry.contentTrackWidth, 871, accuracy: 0.1)
XCTAssertEqual(ParityCaptureGeometry.visibleDayCount, 5)
}

}
18 changes: 18 additions & 0 deletions apps/calendar-macos/CompassKitTests/TimeZoneFormattingTests.swift
Original file line number Diff line number Diff line change
@@ -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")
}
}
Loading