Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/dailyhoroscope/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ Below is the dictionary the applet uses when displaying the current moon phase a
| VIR | Virgo |

## Version history
**Version 1.2**
- Fixed moon phase and sign calculations on major moon phase days

**Version 1.1.1** *(Current)*
**Version 1.1.1**
- Fixed long words being cut off by hyphenating them at their final syllable
- Fixed certain apostrophes not showing in horoscope text

Expand Down
208 changes: 205 additions & 3 deletions apps/dailyhoroscope/daily_horoscope.star
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Summary: See your daily horoscope
Description: Displays the daily horoscope for a specific sign from USA Today.
Author: frame-shift

Version 1.1.1
Version 1.2
"""

load("encoding/json.star", "json")
Expand All @@ -31,6 +31,7 @@ load("images/waning_crescent.webp", WANING_CRESCENT_ICON_ASSET = "file")
load("images/waning_gibbous.webp", WANING_GIBBOUS_ICON_ASSET = "file")
load("images/waxing_crescent.webp", WAXING_CRESCENT_ICON_ASSET = "file")
load("images/waxing_gibbous.webp", WAXING_GIBBOUS_ICON_ASSET = "file")
load("math.star", "math")
load("re.star", "re")
load("render.star", "render")
load("schema.star", "schema")
Expand Down Expand Up @@ -365,14 +366,215 @@ def get_moon_info(date):
raw_phase = moon_html.find("table").eq(0).find("td").eq(5).text().strip().lower() # moon phase e.g. 'waxing gibbous'
raw_sign = moon_html.find("table").eq(0).find("td").eq(6).text().strip().lower() # moon sign e.g. 'libra'

# Remove superfluous time string if phase is New Moon / Full Moon
# When "at " is present, astro-seek sign is for the event moment, *not* the calendar day like it should be
has_event_time = re.findall(r"at\s", raw_phase)

# Remove superfluous time string
edit_phase = re.sub(r"at.*", "", raw_phase)

moon_phase = MPHASES[edit_phase]
moon_sign = MSIGNS[raw_sign]

if has_event_time:
# Parse the event time (astro-seek uses UT/GMT) and compare to
# noon UTC to determine actual phase at the reference time
noon_utc = time.parse_time(
date.format("2006-01-02") + "T12:00:00Z",
format = "2006-01-02T15:04:05Z",
)

# Extract "HH:MM" from e.g. "new moon at 01:23"
event_hm = re.findall(r"\d+:\d+", raw_phase)
if event_hm:
event_time = time.parse_time(
date.format("2006-01-02") + "T" + event_hm[0] + ":00Z",
format = "2006-01-02T15:04:05Z",
)
noon_after_event = noon_utc.unix > event_time.unix
else:
# If cannot parse time, then assume event already passed by noon
noon_after_event = True

# The minor phase that follows or precedes each major phase event
phase_after = {
"new moon": "waxing crescent",
"first quarter": "waxing gibbous",
"full moon": "waning gibbous",
"last quarter": "waning crescent",
}
phase_before = {
"new moon": "waning crescent",
"first quarter": "waxing crescent",
"full moon": "waxing gibbous",
"last quarter": "waning gibbous",
}

if noon_after_event:
moon_phase = MPHASES[phase_after.get(edit_phase, edit_phase)]
else:
moon_phase = MPHASES[phase_before.get(edit_phase, edit_phase)]

moon_sign = MSIGNS[_meeus_moon_sign(noon_utc)]
else:
moon_sign = MSIGNS[raw_sign]

return moon_phase, moon_sign

# --- Helpers that use Meeus lunar longitude algorithms (Ch 47) ---
_DEG2RAD = math.pi / 180.0

_ZODIAC_SIGNS = [
"aries",
"taurus",
"gemini",
"cancer",
"leo",
"virgo",
"libra",
"scorpio",
"sagittarius",
"capricorn",
"aquarius",
"pisces",
]

# Periodic terms for the moon longitude (Sigma-l) (Meeus Table 47.A)
# Each row: [D, M, M', F, coefficient (millionths of a degree)]
_MOON_LON_TERMS = [
[0, 0, 1, 0, 6288774],
[2, 0, -1, 0, 1274027],
[2, 0, 0, 0, 658314],
[0, 0, 2, 0, 213618],
[0, 1, 0, 0, -185116],
[0, 0, 0, 2, -114332],
[2, 0, -2, 0, 58793],
[2, -1, -1, 0, 57066],
[2, 0, 1, 0, 53322],
[2, -1, 0, 0, 45758],
[0, 1, -1, 0, -40923],
[1, 0, 0, 0, -34720],
[0, 1, 1, 0, -30383],
[2, 0, 0, -2, 15327],
[0, 0, 1, 2, -12528],
[0, 0, 1, -2, 10980],
[4, 0, -1, 0, 10675],
[0, 0, 3, 0, 10034],
[4, 0, -2, 0, 8548],
[2, 1, -1, 0, -7888],
[2, 1, 0, 0, -6766],
[1, 0, -1, 0, -5163],
[1, 1, 0, 0, 4987],
[2, -1, 1, 0, 4036],
[2, 0, 2, 0, 3994],
[4, 0, 0, 0, 3861],
[2, 0, -3, 0, 3665],
[0, 1, -2, 0, -2689],
[2, 0, -1, 2, -2602],
[2, -1, -2, 0, 2390],
[1, 0, 1, 0, -2348],
[2, -2, 0, 0, 2236],
[0, 1, 2, 0, -2120],
[0, 2, 0, 0, -2069],
[2, -2, -1, 0, 2048],
[2, 0, 1, -2, -1773],
[2, 0, 0, 2, -1595],
[4, -1, -1, 0, 1215],
[0, 0, 2, 2, -1110],
[3, 0, -1, 0, -892],
[2, 1, 1, 0, -810],
[4, -1, -2, 0, 759],
[0, 2, -1, 0, -713],
[2, 2, -1, 0, -700],
[2, 1, -2, 0, 691],
[2, -1, 0, -2, 596],
[4, 0, 1, 0, 549],
[0, 0, 4, 0, 537],
[4, -1, 0, 0, 520],
[1, 0, -2, 0, -487],
[2, 1, 0, -2, -399],
[0, 0, 2, -2, -381],
[1, 1, 1, 0, 351],
[3, 0, -2, 0, -340],
[4, 0, -3, 0, 330],
[2, -1, 2, 0, 327],
[0, 2, 1, 0, -323],
[1, 1, -1, 0, 299],
[2, 0, 3, 0, 294],
]
Comment on lines +425 to +502
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve readability and maintainability, move the _ZODIAC_SIGNS and _MOON_LON_TERMS constants to a separate file (e.g., lunar_data.star) and load them. This aligns with the repository's general rule to keep large data structures out of the main application logic.

References
  1. To improve readability and maintainability, move large data structures into a separate file and load them into the main application logic.


def _normalize_deg(d):
# Reduce an angle to [0, 360)
d = d % 360.0
if d < 0:
d = d + 360.0
Comment on lines +507 to +508
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This if block is redundant. In Starlark, the modulo operator % with a positive divisor ensures a non-negative result. d % 360.0 will always be in the range [0.0, 360.0), so this check can be removed.

return d

def _julian_day(year, month, day, hour, minute, second):
# Compute Julian Day Number from Gregorian calendar date (Meeus Ch 7)
y = year
m = month
if m <= 2:
y = y - 1
m = m + 12
a = int(y // 100)
b = 2 - a + int(a // 4)
day_frac = day + (hour + minute / 60.0 + second / 3600.0) / 24.0
return int(365.25 * (y + 4716)) + int(30.6001 * (m + 1)) + day_frac + b - 1524.5

def _meeus_moon_sign(t):
# Return zodiac sign moon is in at time t (Meeus Ch 47)
year = int(t.format("2006"))
month = int(t.format("1"))
day = int(t.format("2"))
hour = int(t.format("15"))
minute = int(t.format("4"))
second = int(t.format("5"))
Comment on lines +525 to +530
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Instead of formatting the time object to a string and parsing components, access them directly via attributes like t.year, t.month, etc. This is more direct and readable.

Suggested change
year = int(t.format("2006"))
month = int(t.format("1"))
day = int(t.format("2"))
hour = int(t.format("15"))
minute = int(t.format("4"))
second = int(t.format("5"))
year = t.year
month = t.month
day = t.day
hour = t.hour
minute = t.minute
second = t.second


jd = _julian_day(year, month, day, hour, minute, second)
T = (jd - 2451545.0) / 36525.0
T2 = T * T
T3 = T2 * T
T4 = T3 * T

LP = _normalize_deg(218.3164477 + 481267.88123421 * T - 0.0015786 * T2 + T3 / 538841.0 - T4 / 65194000.0)
D = _normalize_deg(297.8501921 + 445267.1114034 * T - 0.0018819 * T2 + T3 / 545868.0 - T4 / 113065000.0)
M = _normalize_deg(357.5291092 + 35999.0502909 * T - 0.0001536 * T2 + T3 / 24490000.0)
MP = _normalize_deg(134.9633964 + 477198.8675055 * T + 0.0087414 * T2 + T3 / 69699.0 - T4 / 14712000.0)
F = _normalize_deg(93.2720950 + 483202.0175233 * T - 0.0036539 * T2 - T3 / 3526000.0 + T4 / 863310000.0)

A1 = _normalize_deg(119.75 + 131.849 * T)
A2 = _normalize_deg(53.09 + 479264.290 * T)

E = 1.0 - 0.002516 * T - 0.0000074 * T2
E2 = E * E

sigma_l = 0.0
for row in _MOON_LON_TERMS:
d_c = row[0]
m_c = row[1]
mp_c = row[2]
f_c = row[3]
coeff = row[4]
arg = (d_c * D + m_c * M + mp_c * MP + f_c * F) * _DEG2RAD
term = coeff * math.sin(arg)
m_abs = m_c
if m_abs < 0:
m_abs = -m_abs
if m_abs == 1:
term = term * E
elif m_abs == 2:
term = term * E2
sigma_l = sigma_l + term

sigma_l = sigma_l + 3958.0 * math.sin(A1 * _DEG2RAD)
sigma_l = sigma_l + 1962.0 * math.sin((LP - F) * _DEG2RAD)
sigma_l = sigma_l + 318.0 * math.sin(A2 * _DEG2RAD)

lon = _normalize_deg(LP + sigma_l / 1000000.0)
sign_index = int(lon / 30.0)
if sign_index > 11:
sign_index = 0
return _ZODIAC_SIGNS[sign_index]

def get_schema():
# Options menu
return schema.Schema(
Expand Down