feat: accurate location to timezone API#1038
Conversation
…ookup Replace the longitude/15 approximation for DX local time with accurate IANA timezone lookup via the new geo-tz library and /api/geo-time endpoint. Changes: - New server: geoTz.js wrapper, geo-time.js route (express pattern) - DXLocalTime.jsx: uses Intl.DateTimeFormat with real IANA timezone, persists UTC/local preference to localStorage - useTimeState: fetches DX timezone from API, switches to Intl.format() - Modern/Classic layouts: integrate DXLocalTime with fetched timezone - Add GEO_TZ_DATA_PATH to .env.example - 16 language files: new dxTime.showLocal/showUtc i18n keys
Weird, I must have borked it for the Dockable view, apparently its setup differently to Modern and Classic. Ill fix it, test it out in Modern/Classic for now |
|
You can click the arrow icon or the timezone text |
|
I have fixed the Dockable display mode. As for the Manila vs Perth difference, you are not using the same data package. In my PR body I talked a bit about this, because I thought some users might find some edge cases where they find the timezone name being displayed confusing, even if correct. We could just start using the 1970 variant, or even the As for the offline situation, nice catch! Re-adding the legacy code as a fallback would work to solve this, tho that would fail silently and OHC doesnt have a dedicated visual indicator for the user to know they lost connection to the server, so it might be a bit weird. (ofc on local deployments, the server doesnt really go down by itself) |
accius
left a comment
There was a problem hiding this comment.
Three things to fix before this can land:
geo-tz needs to be pinned (^x.y.z) instead of *. Wildcard lets npm land us on a breaking major between dev and prod.
The DXLocalTime UTC toggle is a clickable <span>, which we're trying to move away from. Heads up since this just landed yesterday and isn't documented anywhere yet: any clickable thing should be a real <button> so it's keyboard-focusable and screen readers announce it as a control. A clickable <span> is invisible to keyboard navigation and not announced as interactive. Joshua just did the conversion for ContestPanel in #1037 closing the corresponding a11y issue #1030. For this one it'd be:
<button
type="button"
onClick={() => setIsLocal((prev) => !prev)}
aria-pressed={isLocal}
style={{ background: 'none', border: 'none', padding: 0, color: 'inherit', font: 'inherit', cursor: 'pointer' }}
>
({isLocal ? timezone : 'UTC'}) ⇄
</button>The /api/geo-time fetch in useTimeState has no AbortController, so rapid DX target changes can resolve out of order. Standard signal/abort cleanup in the effect's return fixes it.
On Michael's Manila/Perth catch, +1 to bumping to the 1970 data package, the 10MB is worth not having every user in a country with a quirky tz overlap reporting it. For the stale-when-offline case I'd fall back to the lon/15 approximation client-side so the panel is at least always showing something approximately right.
|
Thanks for the review, those accessibility details are probably worth adding to a section in CONTRIBUTING.md so its not easy to miss for future work. I'll change it now, and keep it in mind for future work. I'll add the out-of-order control logic as well as the offline fallback client-side. geo-tz choicesFor the version pinning, could we only pin to ti Major.Minor? If you check the releases, they maintain 8.1 and then every revision is just updating TZ data. This way we would avoid major or minor changes but ensure we keep TZ up to date. So something like I think we should commit to the extra 4MB to go with the
These are just a few I caught in that area of the world in 5 minutes of poking around File sizes for reference. ❯ du --all node_modules/geo-tz/data/
25M node_modules/geo-tz/data/timezones-1970.geojson.geo.dat
868K node_modules/geo-tz/data/timezones-1970.geojson.index.json
708K node_modules/geo-tz/data/timezones-now.geojson.index.json
29M node_modules/geo-tz/data/timezones.geojson.geo.dat
14M node_modules/geo-tz/data/timezones-now.geojson.geo.dat
916K node_modules/geo-tz/data/timezones.geojson.index.json
70M node_modules/geo-tz/data/I'm not very well versed in the node/js ecosystem but I think npm doesnt have the concept of subpackages like python does (would be something like |
…navailable - implement abortcontroller and timeout logic for extra reliability
…rget timezone indicator
|
Yeah the Minus/Plus sign is that weird tzdata standard I mentioned yesterday. I suppose I should add some code to flip it for display, because its what most people expect. Hmmm the UTC display for the DX-Target seems kinda useless....maybe we should just remove the ability to toggle it completely? Perhaps we could always add the offset to the line as just a 2-3 character thing, alongside the timezone name? Thoughts? |
highly confusing as GMT+1 = UTC+1 (within a leap second), and if it's correct that that equals Etc/GMT-1 .. then argggh.
you mean remove the toggle and on one line display the local time, the timezone name and the UTC conversion? |
|
I'll write a utility function to convert the Etc/GMT timezones to a simpler UTC offset, so like: As for the toggle, yeah remove the button feature completely and just display something like that, or even Hmm thinking of other possible formats 🤔 |
fea4d22 to
2fc7d6d
Compare
|
😰 that is horrifying I'm leaning towards just letting the user infer the offset from the given time to their own time or to UTC, as they prefer, and only displaying the IANA zone name (or even adding the offset as a bonus to the hover tooltip?) |
|
I've added a reusable filtering function to convert tzdata Etc/GMT timezones to UTC with the correct ISO8601 signs. When you select ocean areas or terra nullius you should now see the correct offset based on UTC. Straight On the topic of getting rid of the toggle completely, should I go ahead with the change? |
accius
left a comment
There was a problem hiding this comment.
All four of my earlier points are addressed:
geo-tzis pinned at^8.1— good.- The DXLocalTime toggle is a real
<button>now witharia-labeland the unstyled-button pattern. useTimeStatehas an AbortController plus a 2s timeout — that's actually nicer than the 5s I had in mind for a hung server.- Bumped to
geo-tz/allfor the 1970 dataset, with a solarlon/15fallback so the DX panel always shows something even when the server is unreachable. The Etc/GMT sign-flip handling informatGmtUtcfor the Manila/Perth ISO display is a clean addition.
One small non-blocker for a followup: the catch in useTimeState setDxTimezone(null) on every rejection including AbortError. When the user rapidly changes DX target, the cleanup's controller.abort() fires before the new effect runs, so the old fetch rejects with AbortError → state briefly nulls before the new value lands → visible UI flicker (timezone label flashes to fallback ⚠ marker). Standard fix is:
.catch((err) => {
if (err.name === 'AbortError') return;
setDxTimezone(null);
});Not a blocker — happy to land this as-is and clean up later.
K0CJH








This PR adds a new API that serves as a unified way to obtain accurate timezone information on locations. It can receive either a lat/lon pair or a grid locator and returns the correct (but unionized) timezone.
To do this it uses the
[node-geo-tz](https://github.com/evansiroky/node-geo-tz)library which makes use of the standard[timezone-boudary-builder](https://github.com/evansiroky/timezone-boundary-builder)[timezone-boudary-builder](https://github.com/evansiroky/timezone-boundary-builder)project data. It even handles Oceans and Terra Nullius correctly.Since we only really care about accuracy for current or future timezones, I have selected to use the
nowdata product which is smaller than the alternatives (it currently sits at ~14MB versus 24MB for the next smallest alternative)The library also implements lazy-loading of geojson areas with in-memory caching to avoid I/O and having to fully load the full dataset on startup.
I have left the package.json to always use the latest version of this library as that is important to ensure continued accuracy over time (Usually tz data sees at most a couple of updates per year)
With this PR I have modified the DX - TARGET panel to make use of this new API instead of the lon/15 solar time approximation. I have also added a local storage key so the browser remembers the user's preference on showing either local or UTC time for this panel.
Currently the local time text shows the chosen unionized timezone instead of the previous
(local)indicator, which is informational and aids in finding issues, but users that are not that into timezones might be confused by the indicated timezone not matching the exact location (in the past, non-unionized timezones were the standard, but that is currently not the case) - regardless the displayed time is guaranteed to be correct as the unionized timezones take DST and other details into account. Let me know if we should change this.The solar Sunrise and Sunset logic remains the same as it is the nice approach for what it wants to achieve - even so I migrated the existing code from
toLocaleStringtoIntl.DateTimeFormat.format()with undefined locale (it uses the browser locale) for consistency and to put us in a better place for i18n etc.. (Tho I suppose for HH:MM display it might not matter much)I have also migrated the DE local time in the page Header to
IntlWe could make use of this to set the timezone for the user station as well, but I decided not to modify this behavior as it seems adequate and also to keep the PR as targeted as possible - in the future we could take a look to see if its worth implementing considering the tradeoffs to the existing implementation
In terms of performance, the library itself is pretty fast, but after considering the needs of the main OHC instance I concluded the following:
This new API should make it really easy to integrate any future features that require access to accurate time information (I am currently implementing the callsign lookup popup feature which will make good use of it if available)
This should fix #1034
Type of change
How to test
Checklist
server.js: caches have TTLs and size caps (we serve 2,000+ concurrent users)var(--accent-cyan), etc.).bak,.old,console.logdebug lines, or test scripts includedScreenshots (if visual change)
Before:


After: