- Swift 93.2%
- Shell 3.7%
- Python 3.1%
| Data/holiday-cn | ||
| docs | ||
| Resources | ||
| Sources | ||
| Tests/OffPeakKitTests | ||
| Tools | ||
| .gitignore | ||
| build-app.sh | ||
| Package.swift | ||
| README.md | ||
| test.sh | ||
DSAliTimer
A macOS menu bar app that shows whether right now is off-peak for DeepSeek and Alibaba Cloud Model Studio. Requires macOS 14 or later.
Off-peak rules
| Provider | Schedule | Weekends and holidays |
|---|---|---|
| DeepSeek | Working days are peak 01:00–04:00 and 06:00–10:00 UTC, which is 09:00–12:00 and 14:00–18:00 in Beijing. Every other hour of a working day is off-peak. | Off-peak all day. The day is the Beijing date. |
| Alibaba Model Studio | Off-peak 22:00–08:00 UTC+8, which is 14:00–00:00 UTC, every day. | No change. |
China declares some weekends as make-up workdays (调休) to fund long holiday runs. Weekends stay
off-peak, so a make-up workday counts as a weekend. Set
OffPeakPolicy.makeupWorkdaysCountAsWorkingDays for the stricter reading.
Holiday data covers 2025 and 2026, generated from the State Council 放假安排 notices in
Data/holiday-cn/. From 2027 on, days fall back to weekday and weekend rules until the schedule is
published and the table regenerated:
python3 Tools/update_holidays.py --fetch 2027
Menu
The first row is the clock you are reading against. Each provider then gets two lines, its mark beside both:
DeepSeek ○ in 2h 0m
Peak · next off-peak 04:00 → 06:00
Alibaba Model Studio ● 7h 36m left
Off-peak · ends 02:00 · next off-peak 16:00
- The first line counts down the state you are in.
- The second names that state and gives the clock times: when this run ends, and when the next off-peak starts.
- A time carries a weekday label only once it has crossed midnight in the zone you are viewing.
--status prints the longer rule text, the reasoning, and the UTC and Beijing readings.
The open menu is built when you click it and does not tick, so the countdown is accurate to the moment you opened it. The menu bar updates every second, and immediately when the system clock changes.
Preferences
Settings, above Quit DSAliTimer, holds the two preferences.
Menu bar icon → Text labels swaps the marks for DS ● · ALI ○. Both carry the same
information, and the app falls back to text on its own when a mark is missing. Text mode is stored
as menuBarLabelStyle = text in the dev.ruben.dsalibtimer domain. Logos are the default, and
that default is stored by deleting the key:
defaults delete dev.ruben.dsalibtimer menuBarLabelStyle
Times shown in → picks the zone the menu and --status print in: your Mac's zone, a pinned
list, or every identifier macOS knows, each labelled with its real current offset so DST is
visible. This is display only. The DeepSeek windows stay UTC and the Alibaba windows stay UTC+8
whatever you select. To go back to the system zone:
defaults delete dev.ruben.dsalibtimer displayTimeZoneIdentifier
Install and run
macOS 14 or later, and a Swift 6 toolchain (the package requires Swift tools 6.2), from Xcode or the Command Line Tools.
./build-app.sh # release build -> dist/DSAliTimer.app
open dist/DSAliTimer.app # lives in the menu bar; quit from its menu
./dist/DSAliTimer.app/Contents/MacOS/DSAliTimer --status
swift run DSAliTimer # the same app unbundled, from the repo
The bundle is ad-hoc signed and not notarised, so a copy that arrives through a browser needs
xattr -d com.apple.quarantine dist/DSAliTimer.app. To start it at login, move the bundle somewhere
stable first, then add it under System Settings → General → Login Items.
Development
./test.sh
./test.sh -c release
test.sh exists because a Command Line Tools install ships Swift Testing without putting it on
SwiftPM's framework search path, rpath, or dyld cache, and ships no XCTest at all. It wires those
up, and behaves like plain swift test under a full Xcode.
Everything a user reads lives in OffPeakKit, wording included, so all of it is testable without
AppKit. The suite sweeps every minute of the day for both providers, checks window edges and the
Beijing-date basis against real 2026 holidays, and asserts the literal menu strings.
Sources/OffPeakKit/ is the logic: minute windows, civil dates, day kinds, the holiday table, the
two provider rules, evaluation, time formatting, and menu copy. Sources/DSAliTimer/ is the menu
bar and nothing else.
The verdict path does not use Calendar. An instant's Beijing date is integer arithmetic on
epoch + 28800, so the day cannot follow your Mac's locale, its DST rules, or the zone you picked
for display.
Brand marks
Resources/BrandIcons/ holds two SVGs that Tools/get_brand_icons.sh fetches from
@lobehub/icons-static-svg on npm:
./Tools/get_brand_icons.sh # default version, currently 1.95.1
./Tools/get_brand_icons.sh 1.96.0 # pin another
A file with the same name in ~/Library/Application Support/DSAliTimer/icons/ takes precedence
over the bundled one.
Upstream sizes every icon width="1em" height="1em". AppKit resolves em to 1 pt, which in a
status item is a one-point-wide invisible icon. BrandArtwork.normalizeSVG substitutes the viewBox
numbers at load time, so the shipped files stay byte-identical to upstream.
The DeepSeek and Alibaba Cloud marks are trademarks of their owners. Upstream's MIT claim covers the icon collection, not the logos. See Resources/BrandIcons/NOTICE.md.
