Try Astrologer API

Subscribe to support and grow the project.

Moon Phase Details #

The MoonPhaseDetailsFactory generates a rich lunar context from any astrological subject. This page shows practical examples covering common use cases.

Basic Usage #

from kerykeion import AstrologicalSubjectFactory, MoonPhaseDetailsFactory

subject = AstrologicalSubjectFactory.from_birth_data(
    "Example", 2025, 4, 1, 7, 51,
    lng=-0.1276, lat=51.5074, tz_str="Europe/London",
    online=False,
)

overview = MoonPhaseDetailsFactory.from_subject(subject)

print(f"Phase:        {overview.moon.phase_name} {overview.moon.emoji}")
print(f"Illumination: {overview.moon.illumination}")
print(f"Stage:        {overview.moon.stage}")
print(f"Major Phase:  {overview.moon.major_phase}")
print(f"Age:          {overview.moon.age_days} days")
print(f"Cycle:        {overview.moon.lunar_cycle}")

Output:

Phase:        Waxing Crescent 🌒
Illumination: 12%
Stage:        waxing
Major Phase:  New Moon
Age:          3 days
Cycle:        11.068%

Upcoming Phases #

Find the last and next occurrence of each major phase:

if overview.moon.detailed and overview.moon.detailed.upcoming_phases:
    phases = overview.moon.detailed.upcoming_phases

    for label, window in [
        ("New Moon",      phases.new_moon),
        ("First Quarter", phases.first_quarter),
        ("Full Moon",     phases.full_moon),
        ("Last Quarter",  phases.last_quarter),
    ]:
        if window is None:
            continue
        last = window.last.datestamp if window.last else "N/A"
        nxt  = window.next.datestamp if window.next else "N/A"
        print(f"{label:15s}  Last: {last}")
        print(f"{'':15s}  Next: {nxt}")
        print()

Output:

New Moon         Last: Sat, 29 Mar 2025 ...
                 Next: Sun, 27 Apr 2025 ...

First Quarter    Last: ...
                 Next: ...
...

Eclipse Information #

# Next Lunar Eclipse
if overview.moon.next_lunar_eclipse:
    ecl = overview.moon.next_lunar_eclipse
    print(f"Next Lunar Eclipse: {ecl.datestamp}")
    print(f"Type: {ecl.type}")

# Next Solar Eclipse
if overview.sun and overview.sun.next_solar_eclipse:
    ecl = overview.sun.next_solar_eclipse
    print(f"Next Solar Eclipse: {ecl.datestamp}")
    print(f"Type: {ecl.type}")

Sun Times and Position #

if overview.sun:
    sun = overview.sun

    # sunrise/sunset/solar_noon are timezone-aware datetime objects; day_length is a timedelta
    print(f"Sunrise:    {sun.sunrise:%H:%M}")
    print(f"Sunset:     {sun.sunset:%H:%M}")
    print(f"Solar Noon: {sun.solar_noon:%H:%M}")
    print(f"Day Length: {sun.day_length}")

    if sun.position:
        print(f"Altitude:   {sun.position.altitude:.2f}°")
        print(f"Azimuth:    {sun.position.azimuth:.2f}°")

Generating a Text Report #

Pass the overview directly to ReportGenerator for a formatted ASCII report:

from kerykeion import ReportGenerator

ReportGenerator(overview).print_report()

Output:

=====================================================
Moon Phase Overview — Tue, 01 Apr 2025 06:51:00 +0000
=====================================================

+Moon Summary--+--------------------+
| Field        | Value              |
+--------------+--------------------+
| Phase Name   | Waxing Crescent 🌒 |
| Major Phase  | New Moon           |
| Stage        | Waxing             |
| Illumination | 12%                |
| Age (days)   | 3                  |
| ...          | ...                |
+--------------+--------------------+

+Illumination Details-------+
| Field            | Value  |
+------------------+--------+
| Percentage       | 12.0%  |
| Visible Fraction | 0.1161 |
| Phase Angle      | 39.85° |
+------------------+--------+

...

JSON Serialization #

import json

# Compact JSON (no None fields)
json_str = overview.model_dump_json(exclude_none=True, indent=2)
print(json_str)

# Or as a Python dict
data = overview.model_dump(exclude_none=True)

Zodiac Signs #

if overview.moon.zodiac:
    print(f"Sun Sign:  {overview.moon.zodiac.sun_sign}")
    print(f"Moon Sign: {overview.moon.zodiac.moon_sign}")

Location Metadata #

The factory can annotate whether a default location was used (useful for API consumers):

overview = MoonPhaseDetailsFactory.from_subject(
    subject,
    using_default_location=True,
    location_precision=2,
)

loc = overview.location
print(f"Lat: {loc.latitude}, Lng: {loc.longitude}")
print(f"Default location: {loc.using_default_location}")