A native macOS world clock, calendar, and meeting planner for people who work across time zones.
Track any number of cities at a glance, then select and confirm a new macOS system time zone when you land.
![]() |
![]() |
![]() |
| World clocks | Calendar | Meeting planner |
Most world-clock utilities show you what time it is elsewhere. The tedious part of actually travelling is the other direction: you land, and your Mac is still on the time zone you left. TravelTime keeps both halves in one place — a multi-zone panel to read, and a safe, confirmed switch to act on.
No Electron, no telemetry, no account, and no runtime third-party dependencies. The release binary is about 2 MB.
| World clocks | Local time in the menu bar and every tracked city in a clean panel. Each row shows Today / Yesterday / Tomorrow relative to the selected system zone, plus its live UTC offset. |
| Safe zone switching | Select a city, then confirm with the Switch button. TravelTime uses the standard macOS authorization dialog; your password is never stored or seen by the app. |
| Searchable city picker | Add from common cities or search the full macOS IANA time-zone catalogue. Duplicate IANA IDs are handled correctly — Berlin and Frankfurt can coexist as separate rows. |
| Adaptive panel | The window grows with normal content, keeps its footer fixed, hides native scroll tracks, and only scrolls the middle region when the display is genuinely too short. |
| IP geolocation | "Detect current location" resolves your city and zone, previews it, and switches on confirmation. |
| Conflict detection | If macOS Set time zone automatically is on, it will silently revert manual switches. The panel warns you and deep-links to the relevant System Settings pane. |
| Custom avatar | Drop in any image; stored under ~/Library/Application Support/TravelTime/. |
| In-app updates | Checks GitHub Releases and verifies the download against a SHA-256 published in the release notes before installing. Works for both the current and the legacy TimeZoneBar.app-named assets. |
| Meeting-time planner | Move a time slider and see the corresponding time and working-hours status across every saved city. |
| Solar + lunar calendar | Browse months with Gregorian dates, Chinese lunar dates, solar terms, today/selection states, and event markers in a complete six-week grid. |
| ICS calendars | Import .ics files, expand common recurring-event rules, mark event days, and inspect the selected day's agenda. |
| Offline public holidays | Enable bundled 2026 holidays for China, Hong Kong, Singapore, Malaysia, and Thailand. Region-colored dots distinguish overlapping holidays, and each holiday includes a concise explanation. No API key, account, or network connection is required. |
- macOS 14.0 (Sonoma) or later — Apple silicon or Intel
- To build: Swift 5.9+ toolchain (Xcode 15+ or Command Line Tools)
-
Download the latest
.app.zipfrom the Releases page. -
Unzip and move
TravelTime.appto/Applications. -
The build is ad-hoc signed and not notarized, so Gatekeeper will block the first launch. Right-click the app → Open → Open to allow it, or:
xattr -dr com.apple.quarantine /Applications/TravelTime.app
git clone https://github.com/susunola/TravelTime.git
cd TravelTime
swift build -c release --disable-sandbox # --disable-sandbox: see note below
python3 make_icon.py Resources # generates AppIcon.icns, stdlib only
./build.sh # bundle + sign + install to /ApplicationsNotes:
--disable-sandboxis required on recent macOS, where SwiftPM's own sandbox fails withsandbox_apply: Operation not permittedwhile compiling the manifest.build.shalready passes it.build.shsigns with a self-signed identity namedTravelTime Developerand installs straight to/Applications. If you do not have that certificate in your keychain, either create one in Keychain Access (Certificate Assistant → Create a Certificate, type: Code Signing) or change the identity to-for ad-hoc signing.dist/is emptied after a successful build on purpose: Launchpad indexes every.appand every zip it finds and would otherwise show ghost duplicates.
./build.sh releaseThis builds, installs, packages release/TravelTime-<version>.app.zip, and
prints its SHA-256. The release notes must contain a line
SHA256: <64 hex chars> — the updater verifies against it and aborts the install
if it is missing or does not match, so a release published without it cannot be
installed by existing users.
| Action | Result |
|---|---|
| Click the Dock icon (or the menu bar clock) | Show / hide the panel |
| Click a zone row | Select it and reveal the Switch action |
| Switch to city | Change the macOS system time zone (authorization required) |
| Right-click a zone row | Switch to or remove that city (the current zone cannot be removed) |
| Add city | Search common cities or the complete IANA time-zone catalogue |
| Detect current location | IP geolocation → preview → confirm to switch |
| Click the avatar | Choose a custom image |
| Calendar tab | Browse Gregorian/lunar dates and inspect events or holidays |
| Meeting planner tab | Compare a candidate time and working-hours status across cities |
| Settings → Software Update | Check GitHub Releases and update in place |
| Settings → Calendar & Holidays | Import .ics calendars and enable offline holidays by region |
| Settings → Uninstall | Remove the app and all local data |
| Quit TravelTime | Quit |
main.swift NSApplication bootstrap
AppDelegate.swift status item, panel window, 1 Hz tick (repaints on minute change)
MenuPanelView.swift three-tab SwiftUI panel, adaptive content, shared footer
PanelHeader.swift current time, displayed-zone date, avatar, rotating quote
CalendarCardView.swift Gregorian/lunar month grid and event/holiday details
EventStore.swift ICS parsing, recurrence expansion, persistence, queries
HolidayStore.swift bundled offline holiday catalogue and region selection
SettingsView.swift preferences, cities, calendars, updater, uninstall
ZonePicker.swift searchable common-city and IANA time-zone picker
TimeZoneStore.swift @MainActor state, persistence, solar day/night, DST, day-offset
SystemZoneSwitcher privileged `systemsetup -settimezone` via osascript
LocationDetector IP geolocation with fallback endpoint
Updater.swift GitHub Releases check, SHA-256 verify, in-place replace
Design notes worth knowing before you touch the code:
- UI is AppKit-hosted SwiftUI. An
NSWindowwith.utilityWindowstyle hosting anNSHostingView. Borderless and transparent-titlebar variants are deliberately avoided: on macOS 26 they trip a FrontBoard scene fence for self-signed apps and simply never render. - Privilege boundary. Switching zones shells out to
/usr/sbin/systemsetup -settimezonethroughosascript ... with administrator privileges, on a background queue with a 15 s timeout so an ignored dialog cannot hang the UI or leak a child process. The zone identifier is validated againstTimeZone.knownTimeZoneIdentifiersbefore it reaches a shell string — geolocation responses are untrusted input on a privileged path. - Both geo endpoints are HTTPS (
ipwho.is, falling back toipapi.co) for the same reason: a spoofable plaintext response would feed a privileged call. ip-api.com is not used — its free tier has no HTTPS, and ATS blocks plain HTTP. - Updates fail closed. If no SHA-256 is found in the release notes, or it does
not match the download, installation is aborted. The post-extraction bundle is
located by
.appextension (not by name), so legacyTimeZoneBar.appand currentTravelTime.appreleases both upgrade in place. The install replaces the running bundle wherever it lives (~/Applicationsincluded). - Zone rows are identified by UUID, not IANA ID, because IANA IDs are not
unique per city (Berlin and Frankfurt share
Europe/Berlin). Persisted payloads from before the UUID field migrate on decode. - Auto-timezone detection reads the on-disk plist only and assumes off when
absent. On macOS 26
defaults readreturns a stale cfprefsd value — observed reportingActive = 1long after the setting was turned off. - The minute tick is deduplicated. The 1 Hz timer only publishes when the minute actually changes, collapsing 60 redraws per minute into one.
- Public holidays are bundled data. They never call timeanddate or another holiday API. Enabled regions are materialized as local all-day event sources; the current catalogue covers 2026 and is refreshed through app updates.
- Calendar markers preserve provenance. Holiday source names carry a stable region code, which drives the same low-saturation color in Settings, month markers, and event details. Imported ICS calendars use the app accent.
Documented rather than hidden:
- Not notarized. Every release needs the Gatekeeper step above.
- Day/night is approximate. Latitude/longitude come from a 23-entry lookup table; zones outside it fall back to a longitude derived from the UTC offset at the equator. Good enough for a badge, not for astronomy.
- IP geolocation is city-level at best, and wrong on VPNs. Pick the zone manually when it matters.
- Bundled public-holiday coverage currently ends in 2026. Install a newer TravelTime release when a later catalogue becomes available. Malaysia data contains federal holidays only; state-only holidays are omitted.
Every switch asks for my password. Why? Changing the system time zone is a privileged operation on macOS. The app uses the system authorization dialog and never stores or sees the password. Cancelling is treated as a deliberate "no" and reports no error.
My switch was reverted. System Settings → General → Date & Time → Set time zone automatically takes precedence. The panel detects this and links you straight there; turn it off and the warning clears within 30 seconds.
No icon in the menu bar? On macOS 26 the scene manager may refuse a status item for self-signed builds with no Team ID. TravelTime is a regular Dock app, so click the Dock icon instead — nothing is lost.
Two TravelTime icons in Launchpad.
Launchpad indexes bundle metadata inside zip files too, so a release zip sitting
in ~/Downloads shows up as a second entry. Rename or remove the zip, then
killall Dock.
I uninstalled but the icon is still there.
Launchpad caches its tile list. Run killall Dock or log out and back in.
Bug reports and pull requests are welcome — see CONTRIBUTING.md. For anything security-related, please read SECURITY.md first.
MIT © susunola


