A multi-timezone display for broadcast control rooms. Runs offline on a Raspberry Pi 5 with a few hundred lines of Python.
Live broadcast rooms juggle multiple timezones at once — every live event references "local time" in a different city. The off-the-shelf options are either network-dependent (a non-starter where control hardware doesn't touch the internet) or $300+ commercial units that do less than a Pi 5 and a few hundred lines of Python could.
The worldclock is a simple and small physical display showing the timezones we actually use, mounted in line of sight, no network surface, nothing to maintain.
It sits on the desk where I work, in daily use.
[Pi 5 boots]
↓
systemd starts worldclock service
↓
Python reads local time + applies configured timezone offsets
↓
Renders the UTC hero clock + configured timezone tiles to the display
↓
Refresh loop redraws the screen
- Time source: Pi's hardware clock — backed by a DS3231 RTC. No NTP, no WiFi.
- Display: Raspberry Pi Touch Display 2 (reports as 1280x720 in fullscreen)
- Service: runs under systemd, auto-starts on boot, auto-restarts on crash.
- Timezone configuration: each tile can be edited on the touchscreen with choices from
timezones.py, saved toconfig.jsonso it persists across reboots.
| Component | Part |
|---|---|
| Single-board computer | Raspberry Pi 5 (1GB) |
| Storage | microSD 32GB |
| Display | Raspberry Pi Touch Display 2 |
| Cooling | Official Raspberry Pi 5 Active Cooler |
| RTC backup | DS3231 module (I²C, CR2032 coin cell) |
| Power | Official Pi 5 27W USB-C PSU |
| Case | Waveshare Protective Case → 3D Printed Case Soon |
Note on the RTC: The DS3231 is a HAT-style module but I connected it with four short female-to-female jumper wires rather than seating it directly on the header — seating it directly was not possible with the small case, and the active cooler covers most of the board. Wiring only the four I²C pins keeps it loose inside the case. See RTC setup below.
Tested on Raspberry Pi OS (64-bit), Python 3.13.
Confirm your OS release with
cat /etc/os-releaseand your Python withpython3 --versionbefore reporting issues — the systemd unit and config paths below assume a/boot/firmware/layout (Bookworm and newer).
git clone https://github.com/seb-caw/worldclock.git
cd worldclock
# Create venv and install dependencies
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# Run once to verify
python -m src.clockrequirements.txt:
pygame
tzdata
tzdata ships the IANA timezone database so zone math and DST transitions are correct without relying on the system zoneinfo being present.
sudo raspi-config
# Interface Options → I2C → Enable → Finish → rebootWire four pins from the DS3231 to the Pi's GPIO header:
| DS3231 pin | Pi physical pin | Function |
|---|---|---|
| VCC | Pin 1 | 3.3V |
| SDA | Pin 3 | I²C data |
| SCL | Pin 5 | I²C clock |
| GND | Pin 6 (or 9) | Ground |
Confirm the Pi can see the chip — it should appear at address 0x68:
sudo i2cdetect -y 1Register the RTC by adding the overlay to /boot/firmware/config.txt (under [all]):
dtoverlay=i2c-rtc,ds3231
Reboot, then write the current (correct) system time into the RTC and read it back:
sudo hwclock -w # write system time → RTC
sudo hwclock -r # read RTC back
timedatectl # RTC time should match local timeWith a coin cell installed the DS3231 holds time through power cycles with negligible drift, so the device keeps accurate time fully offline.
sudo cp systemd/worldclock.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now worldclock
sudo systemctl status worldclockThe unit waits briefly for the desktop session before launching fullscreen, restarts on failure, and starts automatically on every boot:
[Unit]
Description=World Clock
After=graphical.target
[Service]
User=pi
WorkingDirectory=/home/pi/worldclock
Environment=DISPLAY=:0
Environment=XAUTHORITY=/home/pi/.Xauthority
ExecStartPre=/bin/sleep 5
ExecStart=/home/pi/worldclock/venv/bin/python -m src.clock
Restart=on-failure
RestartSec=5
[Install]
WantedBy=graphical.targetTo stop it temporarily (e.g. to access the desktop over SSH):
sudo systemctl stop worldclockNo WiFi, no NTP — by choice, not by limitation. The Pi 5 has WiFi built in. I'm deliberately not using it. The environment this lives in doesn't expose control hardware to the internet, and an offline-by-design appliance has a far gentler failure mode (a few seconds of drift per year from the DS3231) than a networked one ("WiFi died at 2am and the clock has now been stuck for hours"). When the clock does need its time corrected, it's a deliberate manual act: plug in ethernet, let NTP sync once, unplug.
Pi 5 over a microcontroller. Tempting to use an ESP32 or Pico for the "right tool for the job" reasons — lower power, faster boot, no OS to maintain. I went with the Pi because I wanted hands-on use with a Pi and for this to be my first real-world Python project.
A locked-down network surface. Even though it normally runs offline, I stripped the device back to the minimum: disabled the printing (cups), discovery (avahi), and rpcbind services so the only listening port is SSH, which itself is hardened (non-default port, root login disabled, limited auth attempts). The goal is that if it ever does touch a network, it presents almost nothing.
- The touchscreen was the real fight, not the clock logic. The official display reports touches as mouse events, not pygame finger events, so my first scroll/drag implementation silently did nothing on the hardware while working fine on my laptop. Reworking selection around a tap-vs-drag distance threshold (and adding an explicit Confirm step so a mis-tap can't change a zone) took longer than writing the timezone rendering.
- Physical constraints bit harder than software ones. The active cooler covers the GPIO header and the RTC didn't fit the case — the "just plug in an RTC" step turned into removing the cooler, sourcing short jumper wires, and wiring only four pins. If I rebuilt it I'd choose the case, cooler, and RTC together as one mechanical decision rather than three separate orders.
- Dev environment friction. Moving files between a Windows PC, a Mac, and the Pi surfaced every
scp/SSH quirk going (WSL hijackingscp, a macOS password-prompt bug). Settling on one machine andrsyncover SSH early would have saved a lot of dead time.
MIT — see LICENSE
World map background: BlankMap-World-v2 (public domain / CC0).
