Skip to content
Closed
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
34 changes: 20 additions & 14 deletions KeyStats/ActivityHeatmapView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,24 @@ final class ActivityHeatmapView: NSView {
private var gridOffsetX: CGFloat = 0
private var gridWidth: CGFloat = 0

// MARK: - 缓存
private lazy var calendar = Calendar.current
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use autoupdating calendar for cached date computations

Caching Calendar.current in a lazy property captures a snapshot of calendar settings at first access, so long-running sessions can render stale weekday alignment after the user changes region/first-weekday or time zone in macOS settings. recalculateLayout() and drawWeekdayLabels() now depend on this cached value, which can misplace cells and labels until restart, whereas the previous code re-read Calendar.current on each call. Use Calendar.autoupdatingCurrent (or refresh the cache on locale/time-zone change notifications) to preserve correct rendering.

Useful? React with 👍 / 👎.

private lazy var monthFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "MMM"
return formatter
}()
private lazy var weekdaySymbols: [String] = {
let formatter = DateFormatter()
formatter.locale = Locale.current
return formatter.shortWeekdaySymbols ?? ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
}()
Comment on lines +36 to +40
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The weekdaySymbols lazy property currently initializes a DateFormatter solely to retrieve shortWeekdaySymbols. While this is lazy, DateFormatter is an expensive object to create. The Calendar object, which is already cached as self.calendar, provides shortWeekdaySymbols directly. Using self.calendar.shortWeekdaySymbols would be more efficient and avoid an unnecessary DateFormatter instantiation.

    private lazy var weekdaySymbols: [String] = {
        return calendar.shortWeekdaySymbols ?? ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
    }()

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jules fix

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! I've simplified weekdaySymbols to use the cached calendar object instead of creating a new DateFormatter instance. I've also pre-calculated the 3-character prefixes and converted them to String once during initialization to avoid redundant work in the draw loop.

private lazy var tooltipDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter
}()

// MARK: - 翻转坐标系
override var isFlipped: Bool { true }

Expand Down Expand Up @@ -111,7 +129,6 @@ final class ActivityHeatmapView: NSView {
gridOffsetX = max(0, floor((bounds.width - contentWidth) / 2))

cellRects.removeAll()
let calendar = Calendar.current

for (index, activity) in activityData.enumerated() {
let weekday = calendar.component(.weekday, from: activity.date)
Expand Down Expand Up @@ -218,10 +235,6 @@ final class ActivityHeatmapView: NSView {
}

private func drawMonthLabels() {
let calendar = Calendar.current
let monthFormatter = DateFormatter()
monthFormatter.dateFormat = "MMM"

var lastMonth = -1
var lastLabelMaxX: CGFloat = -CGFloat.greatestFiniteMagnitude
for (index, activity) in activityData.enumerated() {
Expand All @@ -244,11 +257,6 @@ final class ActivityHeatmapView: NSView {
}

private func drawWeekdayLabels() {
let calendar = Calendar.current
let formatter = DateFormatter()
formatter.locale = Locale.current
let symbols = formatter.shortWeekdaySymbols ?? ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]

let displayRows = [0, 2, 4]
let attrs: [NSAttributedString.Key: Any] = [
.font: NSFont.systemFont(ofSize: 9),
Expand All @@ -257,7 +265,7 @@ final class ActivityHeatmapView: NSView {

for row in displayRows {
let symbolIndex = (row + calendar.firstWeekday - 1) % 7
let label = String(symbols[symbolIndex].prefix(3))
let label = String(weekdaySymbols[symbolIndex].prefix(3))
let y = monthLabelHeight + CGFloat(row) * (cellSize + cellSpacing)
label.draw(at: NSPoint(x: gridOffsetX, y: y + 1), withAttributes: attrs)
}
Expand Down Expand Up @@ -312,9 +320,7 @@ final class ActivityHeatmapView: NSView {
let activity = activityData[index]
let tooltip = ensureTooltipView(in: host)

let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
let dateStr = dateFormatter.string(from: activity.date)
let dateStr = tooltipDateFormatter.string(from: activity.date)
let totalFormat = NSLocalizedString("heatmap.totalFormat", comment: "")
let detailFormat = NSLocalizedString("heatmap.detailFormat", comment: "")

Expand Down