🌐 Homepage: https://koke1997.github.io/timenow/
GitHub Pages setup: Go to Settings → Pages → Source: GitHub Actions to enable deployment.
The 1884 problem: Railroads needed trains to run on schedule. To prevent collisions on single-track lines, 80+ local times were collapsed into 4 zones in one afternoon — November 18, 1883. That political fix is still running your clock.
What we are building: Every point on Earth can now calculate its own true solar time from GPS coordinates and an accurate clock. No zones. No DST. 12:00:00 means the sun is at its highest — everywhere, always, honestly. The API also quantifies social jetlag (the gap between biological and civil time) and derives prayer times (Islamic, Jewish, Christian) from solar geometry — not lookup tables.
Two deployment targets:
- IoT device — runs at a fixed location, reads GPS, calculates solar time, shares it with nearby devices. A network of these makes time zones obsolete.
- Web — any browser, any coordinate, any historical or future instant. Explore, compare, understand how far your civil clock diverges from your sun.
For any location, the structural offset between civil time and solar time is:
mean_offset_min = UTC_offset_hours × 60 − longitude_deg × 4
This is stable year-round. The Equation of Time (Earth's orbital eccentricity +
axial tilt) adds ±16 minutes of seasonal variation on top. The API field
civil_solar_offset_min returns the full value at any instant.
Selected deviations (verifiable with GET /solar):
| Location | Longitude | Civil UTC | Mean solar UTC | Mean offset |
|---|---|---|---|---|
| Kashgar, Xinjiang | 76.0°E | +8 | +5.07 | +2 h 56 m |
| Urumqi, Xinjiang | 87.6°E | +8 | +5.84 | +2 h 09 m |
| Shanghai | 121.5°E | +8 | +8.10 | −6 m (aligned) |
| Vladivostok | 131.9°E | +10 | +8.79 | +1 h 12 m |
| Madrid (winter) | 3.7°W | +1 | −0.25 | +1 h 14 m |
| Madrid (summer) | 3.7°W | +2 | −0.25 | +2 h 14 m |
| Trans-Siberian (Vladivostok end) | 131.9°E | Moscow +7 | +8.79 | solar noon ≈ 02:30 Moscow time on ticket |
The Kashgar–Shanghai pair is the starkest: both cities share UTC+8, yet their mean solar offsets differ by 3 hours and 2 minutes. The civil clock cannot represent this difference — not approximately, not with DST, not ever.
Civil-solar misalignment is the structural component of "social jetlag" — the difference between your biological clock and your civil clock. Peer-reviewed findings:
| Study | Finding |
|---|---|
| Giuntella & Mazzonna, J. Health Economics (2019) | US counties on western timezone edges: higher obesity, diabetes, shorter sleep — using timezone borders as natural experiment |
| Roenneberg et al., Nature Reviews Endocrinology (2023) | Social jetlag associated with obesity, type 2 diabetes, tobacco/alcohol consumption |
| Holz et al., Sleep 46(12) (2023), n=6,335 adolescents | Higher social jetlag → lower crystallized intelligence, worse school grades, altered brain connectivity |
| Qian et al., European J. Medical Research (2026) | Social jetlag associated with higher 10-year Framingham cardiovascular risk |
Civil timezone boundaries change the clock in integer-hour steps at political lines. Solar time changes continuously at 4 minutes per degree of longitude. Any vehicle moving east or west accumulates solar displacement that no civil clock tracks until the next zone boundary.
Eastbound travel shortens the solar day (moving against the Sun). Westbound lengthens it. Aviation already accounts for this: FAA 14 CFR Part 117 requires stricter crew rest for eastbound flights due to circadian desynchronisation. Railways, ships, and long-haul trucking have the same physics.
~2 billion people observe prayer times defined entirely by solar position:
Islamic Fajr–Isha, Jewish Shacharit–Ma'ariv, and Christian canonical hours.
Civil zones shift when these obligations fall — sometimes by hours. GET /solar/prayer
returns prayer windows from solar geometry, not lookup tables.
Solar time does not replace UTC. UTC is the coordination layer — trains, flights, international calls. Solar time is the personal biological layer. A device shows both: "Solar noon (UTC 11:47)". Every person on Earth knows when UTC 11:47 is for them. No zone conversion needed.
# Clone and start
git clone https://github.com/koke1997/timenow.git && cd timenow
podman-compose up --build # or: docker compose up --buildOpen http://localhost:8090 — the web UI, deviation map, and all API endpoints are ready.
The first build downloads dependencies and compiles (~3–5 min). Subsequent starts use the image cache.
IoT mode with fixed GPS coordinates (no serial GPS device required):
TIMENOW_PROFILE=iot TIMENOW_GPS_COORDS="51.5074,-0.1278" podman-compose upIoT mode with a real NMEA serial device — add to podman-compose.yml:
devices:
- /dev/ttyUSB0:/dev/ttyUSB0
environment:
- TIMENOW_GPS_PORT=/dev/ttyUSB0IERS / Orekit EOP data is persisted in a named volume (orekit-data) and refreshed automatically every 6 hours.
Requires: JDK 17+, sbt 1.x on PATH.
./start.sh # auto-detects a free port starting at 8090Or:
cd backend && sbt runOpen http://localhost:8090 for the web UI, /globe.html for the world
deviation map, /docs for the API schema.
make dev # hot-reload backend on every save (~2 s cycle)
make test # all backend tests
make fmt # format all Scala sources
make check # format-check (CI-safe)
make playwright # E2E smoke tests
make health # curl /health pretty-printedThe server starts with 198 bundled cities. Additional data loads on demand:
curl "http://localhost:8090/geo/fetch?cc=DE" # Germany cities (~1–2 MB)
curl "http://localhost:8090/geo/fetch?type=geoip" # IP geolocation (~7 MB)
curl "http://localhost:8090/geo/status" # what is loadedAll data is cached in ~/.timenow/data/ and survives restarts.
GET /docs returns the full OpenAPI 3.0 schema.
| Endpoint | Params | Description |
|---|---|---|
GET /solar |
lat, lng[, dt] |
Solar time, sun position, day boundaries, civil offset |
GET /world/deviation-map |
— | 2° global grid: [lat, lng, deviation_hours] |
GET /health |
— | Server status, IERS EOP freshness |
GET /geocode |
q= |
City search |
GET /locate |
— | IP geolocation |
GET /geo/status · GET /geo/fetch |
cc= |
Offline geo data status / load |
| Endpoint | Params | Description |
|---|---|---|
GET /reform |
lat, lng |
Honest UTC offset; clock reform calculator |
GET /solar/prayer |
lat, lng, date |
Prayer times (Islamic/Jewish/Christian) from solar position |
GET /solar/jetlag |
lat, lng |
Quantified social jetlag score and health risk level |
GET /solar/almanac |
lat, lng, year |
Solstices, equinoxes, EoT extremes for the year |
GET /solar/timetable |
lat1, lng1, lat2, lng2, depart_utc |
Solar time journey for travel |
GET /solar/ical |
lat, lng, year |
RFC 5545 iCalendar feed |
GET /share |
lat, lng |
Shareable social card with OG meta tags |
GET /analemma |
lat, lng, year |
365 sun positions (figure-8 visualization) |
| Endpoint | Params | Description |
|---|---|---|
GET /iot/stream |
— | SSE live feed, 1 s cadence |
GET /mesh/solar |
lat, lng |
Peer mesh solar comparison |
GET /mesh/coverage |
— | Peer coverage map |
GET /.well-known/solar-time |
— | RFC 9557 IXDTF discovery — dut1_sec, tai_utc_offset_sec, node_public_key, signature |
{
"solar_time": {
"local_solar_time": "13:51:04",
"solar_noon_utc": "2026-04-13T12:00:52Z",
"equation_of_time_min": -0.47
},
"sun_position": {
"elevation_deg": 41.97,
"azimuth_deg": 218.5,
"distance_au": 1.0027,
"declination_deg": 9.18
},
"day": {
"sunrise_utc": "...", "sunset_utc": "...",
"day_length_hours": 13.75,
"is_polar_day": false, "is_polar_night": false
},
"civil_time": {
"timezone": "Europe/London",
"utc_offset_hours": 1.0,
"civil_time_local": "14:51:04",
"civil_solar_offset_min": 60.87
}
}| Layer | Method | Accuracy |
|---|---|---|
| Sun position | NREL SPA — VSOP87 truncated series (Reda & Andreas 2004) | ±0.0003° |
| ΔT (TT − UT1) | Orekit EOP from IERS finals2000A.all | < 0.1 s |
| Earth rotation | Orekit IAU 2006 precession + IAU 2000B nutation | sub-arcsecond |
| IERS EOP | finals2000A.all from USNO, refreshed weekly | published values |
| Timezone offset | timeshape polygon lookup, tzdata 2025b | exact IANA tz |
| Atmospheric refraction | Bennett (1982) | < 0.1 arcmin |
timenow/
├── backend/ Scala 3 — calculation engine + HTTP API
│ └── src/main/scala/solar/
│ ├── calc/ Pure math (no IO): NREL SPA, VSOP87, EoT, refraction
│ ├── domain/ Opaque units: Degrees, AU, Minutes — zero runtime cost
│ ├── service/ IO orchestration: SolarService, DeviationMapService
│ ├── infra/ Orekit init, IERS download, timezone lookup, port scan
│ └── api/ HTTP routes + Circe encoders — thin, no logic
├── frontend/ HTML/JS — no framework
│ ├── index.html Solar clock, city search, EoT badge, civil offset
│ ├── globe.html World deviation map (Leaflet + OpenStreetMap tiles)
│ ├── stories.html Case studies: Spain, Xinjiang, Trans-Siberian, health
│ ├── prayer.html Prayer times across Islamic/Jewish/Christian traditions
│ └── timetable.html Solar time journey for trains/flights
├── agent/ A2A device-to-device protocol (in progress)
├── llm/ Solar-time knowledge distillation (research)
└── tests/ Playwright E2E smoke tests
The backend is a self-contained JAR (sbt assembly). It runs on a Raspberry Pi. It works
offline after the first IERS data sync. This is intentional — IoT deployment
requires no cloud dependency.
Container image: A Containerfile (OCI/Podman-compatible) and podman-compose.yml
are included for zero-toolchain deployment. See Quick start above.
cd backend && sbt test # 54 tests
npm test # Playwright E2E tests (requires backend running)Backend test suites:
JulianCalendarSuite— JD/century conversions against NOAA referenceSunParamsSuite— NOAA orbital geometrySolarTimeSuite— Equation of Time, true solar timeDayBoundariesSuite— sunrise/sunset/polar, Boulder CO, SvalbardSolarPositionSuite— elevation, azimuth, refractionSpaSuite— NREL SPA vs NREL TP-560-34302 reference (Oct 17 2003, ΔT=67s)RoutesSuite— HTTP integration tests (health, solar, deviation-map, docs)
| Component | Library | Version |
|---|---|---|
| Language | Scala 3 | 3.7.4 |
| HTTP | http4s Ember | 0.23.30 |
| Effects | Cats Effect | 3.5.7 |
| JSON | Circe | 0.14.10 |
| Astrodynamics | Orekit | 13.1.4 |
| Timezone DB | timeshape | 2025b.26 |
| Geo data | GeoIP2 Java | 4.2.0 |
| Tests | munit + munit-cats-effect | 1.0.4 / 2.0.0 |
| Map | Leaflet.js | 1.9.4 |
| E2E | Playwright | latest |
| Container | Podman / Docker (OCI) | Containerfile |
See docs/rfcs.md for a full analysis of the existing IETF
standards for time (RFC 3339, RFC 9557/IXDTF, RFC 7808/TZDIST, RFC 9636/TZif,
RFC 5905/NTPv4) and the gap that TimeNow fills: no RFC exists for solar time
representation, calculation, or distribution.
The docs/draft-timenow-solar-time-ixdtf-00.md
document is an Internet-Draft proposing registration of solar as an
IXDTF extension key (RFC 9557 §3.2). A full RFC 7991 xml2rfc XML version is at
docs/draft-timenow-solar-time-ixdtf-00.xml,
ready for IETF Datatracker submission (see docs/IETF_SUBMISSION.md).
The format:
2026-04-13T12:00:52Z[Europe/London][solar/51.5000,-0.1000]
UTC is authoritative. The [solar/...] suffix is an elective annotation that
any receiver not implementing this spec MUST ignore (per RFC 9557 §3.3).
The GET /.well-known/solar-time endpoint is the IoT discovery URL that
the draft normalises. It exposes dut1_sec, tai_utc_offset_sec, node_public_key,
and a signature so constrained devices can apply IERS corrections and verify peer
identity without downloading finals2000A.all.
| Source | Used for |
|---|---|
| NOAA Solar Calculation Details | GMST polynomial: mean longitude, anomaly, EoT, sunrise/sunset hour angle |
| NREL SPA — Reda & Andreas (2004) | VSOP87 truncated series; ±0.0003° — current Sun position engine |
| VSOP87 — Bretagnon & Francou (1988) | Earth heliocentric position theory |
| Jean Meeus — Astronomical Algorithms, 2nd ed. (1998) | JD/JC, nutation, orbital terms |
| Bennett (1982) — atmospheric refraction | Refraction correction; < 0.1 arcmin for elevations > −0.575° |
| IAU SOFA | Reference for IAU 2006 precession and IAU 2000B nutation |
| JPL Horizons | Independent validation: sub-arcsecond Sun ephemeris |
| Source | Used for |
|---|---|
| IERS Conventions 2010 (TN 36) | IAU 2006 precession + IAU 2000B nutation; UT1–UTC definition |
| IERS finals2000A.all — USNO | Published UT1–UTC, polar motion; fetched weekly |
| USNO tai-utc.dat | Leap-second history |
| Library | Role |
|---|---|
| Orekit 13.1.4 | IAU 2006/2000B, IERS EOP, UTC/UT1/TT time scales, ITRF→TOD |
| timeshape 2025b | Timezone polygon lookup |
| OpenStreetMap | Map basemap + country borders (© contributors, ODbL) |
| ColorBrewer RdBu | Diverging colormap for deviation overlay |
| GeoNames | City database, CC BY 4.0 |
| MaxMind GeoLite2 | IP geolocation, CC BY-SA 4.0 |
- Giuntella & Mazzonna — Sunset Time and the Health Effects of Social Jetlag, J. Health Economics (2019)
- Roenneberg et al. — Social jetlag review, Nature Reviews Endocrinology (2023)
- Holz et al. — Effects of sleep-corrected social jetlag, Sleep 46(12) (2023)
- Qian et al. — Social jetlag + cardiovascular risk, European J. Medical Research (2026)
- Manfredini et al. — Social jetlag review, Nutrients 13(12) (2021)
- Railroads create the first time zones — November 18, 1883
- Harvard Gazette — America's first time zone — 1853 Valley Falls collision
- FAA 14 CFR Part 117 — eastbound flight crew rest requirements
- Trans-Siberian time zones — 9,289 km on Moscow Time; solar noon at Vladivostok ≈ 02:30 Moscow ticket time
- Wikipedia — Time zone, Solar time, Equation of Time