Opening hours need the right time zone
A shop that opens at 9:00 opens at 9:00 where the shop is. MapSocket now finds each geofence's time zone from its location, offline, in microseconds.
Geofences can have operating hours: a store open 9:00 AM to 9:00 PM, a restaurant open for lunch and dinner, a depot
open around the clock on weekdays. Each geofence reports currentlyOperating, and with one project setting on it
sends no events while it’s closed.
Hours only mean something in a time zone. Until now you chose one, and the dashboard defaulted to your browser’s. That breaks quietly as soon as the person drawing a geofence isn’t where the geofence is: a team in Lisbon setting up stores in Tokyo would get stores that open at 9:00 Lisbon time, 5:00 PM in Tokyo.
Ask the map, not the user
The answer is simple: the time zone should come from the geofence’s location. When a geofence is saved, the API looks up the IANA time zone at its location and stores it with the geofence:
- Circles and isochrones use their center.
- Polygons use a point inside the shape. On update we ask PostGIS for
ST_PointOnSurface, which is always inside, even for a crescent. - Open sea, which no zone covers, gets its nautical zone,
Etc/GMT±nby longitude.
Move a geofence and its zone is looked up again at the new place. You can still pass timezone explicitly through the
API to override it, and sending null on an update switches back to the location’s zone.
Offline, and fast
The lookup has to be cheap. It runs on every save and every import (an import can be 10,000 rows), and calling an external service for each would be slow and one more thing that can fail.
We use tzf-rs, a Rust library that ships time-zone boundary polygons inside the binary. The boundaries come from the timezone-boundary-builder project, which draws them from OpenStreetMap. A lookup is a point-in-polygon test against a pre-built index, in microseconds. There’s no network call, no API key and no database table to load. Updating the boundaries is a dependency bump.
Zones change. Countries move their borders and their rules, and daylight-saving dates get rewritten. That’s why we
store the zone name (Asia/Tokyo) rather than an offset (+09:00). The offset for a given moment is worked out
when it’s needed, from the time-zone database, so a geofence stays right across DST changes without anyone touching
it.
The editor, too
The dashboard’s hours editor has been rebuilt too. There’s a switch to add operating hours, then one row per day: unticked is closed; ticked starts as 24 hours, with an Edit link for specific times; each day can have several ranges, such as 9:00 AM to 3:00 PM and 5:00 PM to 10:00 PM. A closing time earlier than the opening time runs past midnight, so a bar open 8:00 PM to 2:00 AM is one range. The time-zone box is gone. Instead, the editor tells you which zone it found: “Times are in Asia/Tokyo, from the geofence’s location.”
The API format is unchanged:
"operatingHours": {
"sun": [["9:00 AM", "3:00 PM"], ["5:00 PM", "10:00 PM"]],
"tue": [["00:00", "24:00"]]
}
Days that aren’t listed are closed. No operatingHours at all means always open.