Skip to content

Concepts

Daily windows (local standard time)

A daily high or low belongs to the station’s local calendar day, not a UTC day. Mostly Right buckets every observation by station-local date using the IANA tz from the registry — so daily aggregation is correct without any extra work on your side.

A station’s “day” in settlement terms is the station-local calendar day in local standard time — never daylight-savings shifted. Two reasons:

  1. Consistency across DST transitions. A daylight-savings-aware “local day” would be 23 or 25 hours twice a year. A 24-hour day with a fixed UTC offset is unambiguous.
  2. Matches how daily climate records are kept. The NWS daily climate report for KNYC dated 2025-07-15 covers the high °F observed in EST 2025-07-15 00:00 → 24:00, regardless of whether NYC is observing EDT that day.

The station’s IANA tz from STATIONS carries the LST offset — America/New_York is UTC−5 (EST), even during summer.

The canonical mapping is settlement_date_for(observed_at, station):

from mostlyright import snapshot
# Observation at UTC 2025-07-15T02:00 — that's EST 2025-07-14T21:00 (still 2025-07-14)
date = snapshot.settlement_date_for("2025-07-15T02:00:00Z", "KNYC")
print(date) # "2025-07-14"
# Observation at UTC 2025-07-15T07:00 — that's EST 2025-07-15T02:00 (rolled into 2025-07-15)
date = snapshot.settlement_date_for("2025-07-15T07:00:00Z", "KNYC")
print(date) # "2025-07-15"

The function takes an ISO-8601 UTC timestamp and the station code; it returns the settlement date as a string.

Consider a temperature high at KNYC of 95°F observed at UTC 2025-07-15T22:51:00 (EST 17:51, 5:51 PM):

BucketingBuckets toBelongs to (local day)
UTC day (naive)2025-07-15Same — works for this row
Station LST (correct)2025-07-15Same

Now consider the same temperature observed at UTC 2025-07-16T03:51:00 (EST 22:51, 10:51 PM — still the same local day):

BucketingBuckets toBelongs to (local day)
UTC day (naive)2025-07-16Wrong — the high belongs to 2025-07-15
Station LST (correct)2025-07-15Same

UTC bucketing silently mis-routes the highest observations of the day for any station east of UTC; symmetric mis-routing for stations west of UTC during the early-morning hours. research() uses LST internally to avoid this.

The pre-rollup fetch widens by ±1 UTC day

Section titled “The pre-rollup fetch widens by ±1 UTC day”

To guarantee no tz-edge observation is missed, obs() and research() fetch [from_date - 1 UTC day, to_date + 1 UTC day] internally. After bucketing by LST, the output is clipped back to the caller’s window:

caller: research("KNYC", "2025-07-15", "2025-07-15")
fetcher: [2025-07-14T00Z, 2025-07-17T00Z) ← widened ±1 UTC day
bucket: group rows by LST date = "2025-07-15"
output: one row keyed "2025-07-15"

The widening is invisible to the caller; the LST clip happens at the rollup boundary.

research() output rows carry:

  • date — the LST day (string "YYYY-MM-DD")
  • obs_high_at — ISO-8601 UTC timestamp of the observation that produced obs_high_f
  • obs_low_at — same for the low
  • market_close_utc — ISO-8601 UTC market-close timestamp (populated for market-linked workflows; null otherwise)

obs_high_at and obs_low_at are UTC — they’re the actual observation time, not bucketed. The LST date is in date.

Same logic applies. Tokyo (RJTT) uses Asia/Tokyo (UTC+9). A UTC 2025-07-14T15:00 observation is Tokyo-local 2025-07-15T00:00 — it rolls into the 2025-07-15 settlement bucket.

date = snapshot.settlement_date_for("2025-07-14T15:00:00Z", "RJTT")
print(date) # "2025-07-15"

See International stations.

A “tail observation” is one near the local-day boundary — e.g. KNYC at UTC 2025-07-16T04:00 (EST 23:00, end of local 2025-07-15). To include these correctly:

  • research() widens by ±1 UTC day — done automatically.
  • obs() with strategy="exact_window" returns only observations within the exact requested UTC range. Manual LST grouping is your responsibility.

Most callers want research(). Reach for obs() only when you need raw rows.