One computed civil day — the return type of GreenCal.day/3.
All DateTime fields are UTC unless a :time_zone option was given.
A nil event time means the event does not occur within the civil day;
check the matching :state field to know why.
Instants versus scalars
The day holds two kinds of value, and they are not sampled the same way.
Instants — sun.rise, twilight.dawn, moon.set, moon.node.at,
moon.phase_change.at … — are exact: each is searched for within the
civil day and reported to the second.
Scalars are a snapshot taken at sampled_at, the middle of the civil
day — local noon on a 24 h day, half an hour off it on the two DST days
a year that run 23 or 25 hours. That covers every number
and label the day carries: moon.phase, moon.elongation,
moon.illuminated_fraction, moon.distance_km, moon.declination,
moon.ecliptic_latitude, the three *_trend fields (computed from ±12 h
around sampled_at), and constellation / element / organ.
So illuminated_fraction displayed at 23:00 is the value it had at noon,
not at 23:00 — the Moon's illumination moves by about 1.5 points over a
day. For a value at an arbitrary instant, call GreenCal.Astro.phase/2
or GreenCal.Astro.moon/2 directly.
:events — the day's chronology
:events is the ordered view of the instants this struct already
holds, not a second computation. Every entry has a canonical home in a
field (sun.rise, twilight.dusk, moon.node, moon.apsis…) and the
list is a projection of those fields, so the two cannot drift apart.
for e <- day.events, do: {e.family, e.type, e.at}
#=> [{:twilight, :dawn, ...}, {:sun, :rise, ...}, {:moon, :set, ...},
# {:phase, :full_moon, ...}, {:sun, :set, ...}, ...]Its contract:
- Sorted by
:at, ascending. Guaranteed, not incidental. - Uniform shape: every entry is exactly
%{family: atom, type: atom, at: DateTime.t()}— no optional keys, ever. The azimuth of a sunrise, the distance of a perigee, the declination of a standstill live in the structured field, which is made for them. - Same time zone as the rest of the struct.
- Always present. There is no option to skip it, so
[]means the day holds nothing, never "not computed".
An absent event is not a non-event
A missing {:sun, :rise} entry can mean polar day or polar night, and
the flat list cannot tell you which — sun.state can
(:always_above / :always_below), and so can twilight.state and
moon.state. Reach for :state before rendering a dash: above the
Arctic circle in June the honest label is "the Sun does not set", not
"—".
Being a projection, :events also inherits what the struct chooses to
report: on the rare high-latitude day holding two moonrises, the struct
keeps one, and so does the list.
Families: topocentric and geocentric
| Family | Types | |
|---|---|---|
| Topocentric — depends on the location | :sun | :rise, :transit, :set |
:twilight | :dawn, :dusk | |
:moon | :rise, :transit, :set | |
| Geocentric — the same everywhere on Earth | :phase | :new_moon, :first_quarter, :full_moon, :last_quarter |
:apsis | :perigee, :apogee | |
:node | :ascending, :descending | |
:standstill | :northernmost, :southernmost |
The split is worth knowing when you compute several places at once: over
a set of plots on the same farm, only the topocentric families change.
The geocentric ones are the same instants for everyone — compute them
once with GreenCal.lunar_timeline/2 rather than once per plot.
Summary
Types
One entry of GreenCal.Day :events — see the module doc for the
families and their types, and for why the shape carries nothing else.
Types
@type event() :: %{family: atom(), type: atom(), at: DateTime.t()}
One entry of GreenCal.Day :events — see the module doc for the
families and their types, and for why the shape carries nothing else.
@type t() :: %GreenCal.Day{ constellation: String.t(), date: Date.t(), element: :fire | :earth | :air | :water, events: [event()], location: GreenCal.location(), moon: GreenCal.Day.Moon.t(), organ: :fruit | :root | :flower | :leaf, sampled_at: DateTime.t(), sun: GreenCal.Day.Sun.t(), twilight: GreenCal.Day.Twilight.t() }