Almanac fields

weewx-loopdata manual · weewx-loopdata on GitHub · Report an issue


Any WeeWX report almanac tag can be listed as a field (5.0): write the tag as it would appear in a Cheetah template, with the $ removed. The values are computed with whatever almanac WeeWX has registered — weewx-skyfield for the full Skyfield experience, PyEphem if installed, or WeeWX’s built-in fallback (sunrise, sunset and moon phase only) — and are converted and formatted per the report that declared the field, exactly as the report tag would render.

Examples:

almanac.sunrise                                      06:47 (formatted per the report)
almanac.sunrise.raw                                  1593611225 (unix epoch seconds)
almanac.sunset.raw
almanac.moon_phase                                   Waxing gibbous
almanac.moon_index                                   the 0-7 moon phase index
almanac.moon.phase                                   percent of the moon illuminated
almanac.sun.az                                       sun azimuth in decimal degrees
almanac.sun.alt                                      sun altitude in decimal degrees
almanac.sun.transit.raw
almanac.sun.visible.raw                              length of daylight, in the report's units
almanac.sun.visible.second.raw                       length of daylight, pinned to seconds
almanac.sunrise.unix_epoch.raw                       sunrise, pinned to epoch seconds
almanac.moon.rise.raw
almanac.mars.earth_distance                          in AU, as in reports
almanac.next_full_moon.raw
almanac.next_solstice.raw
almanac(horizon=-6).sun(use_center=1).rise.raw       civil dawn
almanac(horizon=-6).sun(use_center=1).set.raw        civil dusk
almanac(horizon=-12).sun(use_center=1).rise.raw      nautical dawn
almanac(horizon=-18).sun(use_center=1).rise.raw      astronomical dawn

A complete example: a live sky

weewx-celestial is a whole page built from these fields: its Geocentric panel — every body (sun, moon, the eight planets, Proxima Centauri) placed by compass bearing and distance, the moon at its true phase, odometer distance readouts ticking between loop packets — is drawn entirely from loopdata almanac fields. It runs no service and computes nothing itself; loopdata evaluates the almanac tags on every loop packet, and the page reads loop-data.txt. See it live at www.paloaltoweather.com.

The days=±N extension

One loopdata extension to the report grammar: almanac(days=±N) evaluates the almanac at the same wall-clock time N local calendar days away. For example, almanac(days=1).sunrise.raw is tomorrow’s sunrise and almanac(days=-1).sun.visible.raw is yesterday’s length of day. (Reports express this with $almanac(almanac_time=$time_ts+86400), which needs Cheetah variables that a config line doesn’t have; days= is also DST-correct where ±86400 is not.)

Pinning units

By default almanac values are converted per the declaring report, exactly as the report tag would render — including any [Units] [[Groups]] overrides the report carries. That is right for formatted values, but it means a .raw field can change meaning with the report’s settings: a report that sets group_deltatime = hour makes almanac.sun.visible.raw emit hours instead of seconds.

A unit segment pins the unit regardless — the same override observation fields (day.outTemp.avg.degree_C) and station fields (station.altitude.meter.raw) already take:

almanac.sunrise.unix_epoch.raw               epoch seconds, whatever the report's units
almanac.sun.visible.second.raw               seconds of daylight, whatever the report's units
almanac.sun.visible.hour.round(2).raw        hours, rounded — the unit sits
                                             before round(n) and the format spec

Pin the unit on any .raw field your javascript consumes numerically.

Rules and limits

  • Almanac fields are current-only: they take no period prefix and no aggregate (10m.almanac... and almanac.sunrise.max are not valid).
  • .raw/.formatted/.ordinal_compass work on tags that return formatted values (times, and angles like almanac.sun.azimuth); .raw on a plain number (e.g., almanac.moon_index.raw) is allowed and returns the number unchanged.
  • The formatting calls and round(n) work here too, exactly as on report almanac tags: almanac.sunrise.format("%H:%M"), almanac.sun.azimuth.format("%.1f", add_label=False), almanac.sun.azimuth.round(1).raw.
  • almanac.sun.azimuth and almanac.sun.altitude need WeeWX 5.0 or later. almanac.sun.az and almanac.sun.alt work on every WeeWX version LoopData supports (with PyEphem or weewx-skyfield, which any position in the sky needs), but they are plain numbers of degrees, so they take .raw and neither the formatting calls nor round(n).
  • An event that does not happen today, such as a sunset under the midnight sun or the end of civil twilight on a high-latitude summer night, has no time. Its formatted field is published as the report reads it (N/A, in the report’s language), and its .raw field is left out. So to tell “no sunset today” from a sunset, declare the .raw beside the formatted one and test for the .raw key, never for the text.
  • The json key is the field entry verbatim, so element ids can match keys as usual.
  • A call with more than one keyword contains a comma, so the entry must be quoted in the declaration: sun = almanac.sunrise, "almanac(pressure=0, horizon=-8).sun.rise.raw". None of the standard entries above need quoting.
  • A field whose endpoint is a tuple of scalars is emitted as a json array — e.g. a field ending in moon_phases.
  • Temperature and pressure for refraction come from the current loop packet (the report analog: Cheetah uses the latest archive record).

Cost is managed automatically

Almanac fields touch no accumulators and are evaluated on LoopData’s own thread, off the WeeWX engine thread, with per-field caching so only values that actually change are recomputed on every loop packet:

Cache tier What Recomputed
continuous positions (az/alt), distances, moon phase every loop packet
day rise, set, transit, antitransit, visible, visible_change, sunrise, sunset once per local day
event previous_* events once per local day (the instant is always in the past; the value only changes at the next occurrence)
event next_* events when the event’s own instant passes (since 6.9; see below)

Fields naming the same next_* event — say a satellite pass’s rise, set and maximum altitude — are computed together and expire together, at the latest time-typed value among them. For a pass that is the set time, so an in-progress pass keeps serving until it ends; the very next packet then carries the following occurrence, every field switching at once. Recomputes stay rare by construction, because each one returns the next occurrence, in the future, re-arming the cache: next_full_moon recomputes once per lunar month, a satellite’s next_pass once per pass. A group with no time-typed field cannot see its event end and keeps the once-per-day refresh instead — include a pass time field to get event-time expiry.

A day- or event-tier evaluation that yields no data — say, a satellite whose orbital elements have not been downloaded yet — is not cached: every packet retries, so the field picks up its value the moment the almanac has data (since 6.9).

The tiers make almanac.sun.rise and almanac.sun.next_rising genuinely different fields: rise shows today’s sunrise for the whole day, while next_rising rolls forward to tomorrow’s the moment the sun rises. Pick the one that matches the page’s intent.

The same distinction applies to events with no day-scoped spelling. One packet after the full moon, almanac.next_full_moon truthfully reads a lunar month ahead — exactly what a report page regenerated at that moment would say. A page that wants a “full moon tonight at 18:23” badge for the rest of the day pairs it with almanac.previous_full_moon, which is day-rolled: list both (pinned for numeric comparison, e.g. almanac.next_full_moon.unix_epoch.raw and almanac.previous_full_moon.unix_epoch.raw), and when previous_full_moon’s local date is today, show it; otherwise show next_full_moon. Together they cost two recomputes a month.

Migrating from weewx-celestial’s loop fields

weewx-celestial ≤ 5.x injected celestial loop fields via a service; since loopdata 5.0 (and celestial 6.0) those are almanac fields instead. Every current.<field> it emitted has an almanac equivalent:

Old celestial loop field Almanac field
current.sunrise.raw almanac.sunrise.unix_epoch.raw
current.civilTwilightStart.raw almanac(horizon=-6).sun(use_center=1).rise.unix_epoch.raw
current.daylightDur.raw almanac.sun.visible.second.raw
current.tomorrowSunrise.raw almanac(days=1).sunrise.unix_epoch.raw

The pinned unit segments keep the old fields’ fixed meanings (epoch seconds, seconds of daylight) no matter how the report’s units are set — celestial’s own loop fields never followed report units, so a faithful migration pins.

The only derivation left to the page is waxing/waning: the moon is waxing when almanac.next_full_moon.unix_epoch.raw < almanac.next_new_moon.unix_epoch.raw (pinned, as for any .raw field the page compares numerically). Note that distances arrive in AU (as reports show them) rather than miles/km.


weewx-loopdata is Copyright © 2020–2026 John A Kline and is licensed under the GNU Public License v3.