Lunar Phase Calculator

Lunar Phase Calculator

2021, Jan 18    

I’ve always been interested in the planets, stars, and moon up in space. Calculating how the moon will look from your exact spot on earth sound really interesting to me. The more and more I looked into different aspects of the moon the more excited I became.

Check the project out live for today’s LunarPhase.info for Culver city, California.


In case you were wondering, LunarPhase.info is:

Designed and coded on a PC in Culver city, California. Coded with Visual Studio Code. Built on Jekyll, the open source static site generator. Hosted on GitHub Pages.


Calculating Lunar phase

This solution had multiple problems to solve.

  • Know where ‘you’, the Observer() are at on the Earth
  • Find out current date and time
  • Find out how many days are in the current month
  • Determine what percent of illumination for each date


There are the libraries to reference:

Python lib: Calendar Python lib: Math PyEphem


Here is a Jupyter Notebook:


In [1]:

import ephem
import calendar
from datetime import datetime

from math import radians as rad, degrees as deg

In [2]:

# set month key variables
months = ('January','February','March','April','May','June', 'July','August','September','October','November','December')
jan=0
feb=1
mar=2
apr=3
may=4
jun=5
jul=6
aug=7
sep=8
oct=9
nov=10
dec=11

In [3]:

now = datetime.today()
#year = now.year # for current year
#month = now.month - 1 # for currnet month

year=2021
month=feb

#print(month)

In [4]:

# Find number of days per month of year
days_per_month = {}
dpm = days_per_month
for i in range(0,12):
    k = months[i]
    v = calendar.monthrange(year, i+1)
    days_per_month[i] = (k, v[1])
# print(months[month], year)

In [5]:

g = ephem.Observer()
g.name = 'Los Angeles'
g.lat=rad(34.0211)
g.long=rad(-118.3965)

m = ephem.Moon()
i = 0
days=1
try:
    for i in range(0, dpm[month][days]):
            g.date = datetime(year,month+1, i+1)
            g.date -= ephem.hour
            m.compute(g)
            print(dpm[month][0][:3],i+1, year,"- " "%.1f" % m.phase)
except ValueError:
    if i > dpm[month][days]:
        i - 1
        print('error')

[ OUTPUT ]:

Feb 1 2021 - 88.0
Feb 2 2021 - 79.7
Feb 3 2021 - 69.9
Feb 4 2021 - 58.9
Feb 5 2021 - 47.6
Feb 6 2021 - 36.4
Feb 7 2021 - 25.9
Feb 8 2021 - 16.7
Feb 9 2021 - 9.3
Feb 10 2021 - 4.0
Feb 11 2021 - 0.9
Feb 12 2021 - 0.2
Feb 13 2021 - 1.7
Feb 14 2021 - 5.1
Feb 15 2021 - 10.4
Feb 16 2021 - 17.0
Feb 17 2021 - 24.7
Feb 18 2021 - 33.3
Feb 19 2021 - 42.4
Feb 20 2021 - 51.8
Feb 21 2021 - 61.2
Feb 22 2021 - 70.4
Feb 23 2021 - 79.0
Feb 24 2021 - 86.7
Feb 25 2021 - 92.9
Feb 26 2021 - 97.4
Feb 27 2021 - 99.6
Feb 28 2021 - 99.3

Putting it all together

Now that the current observation is ready, lets create the html file.


# # # # # # # # # # # # # 
#    CREATE HTML PAGE
# # # # # # # # # # # # # 
def create_page(lunar_info):
    # # # # # # # # # # # # # 
    #    CREATE HTML FILE CONSTRUCTOR
    doc_start ="""
        <!DOCTYPE html>
        <html lang="en">
        """.encode()

    # # # # # # # # # # # # # 
    #    HEADER
    #    Page title and CSS
    head = """
        <head>
        <!-- Required meta tags -->
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <!-- Bootstrap CSS -->
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
         integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
        <link rel="stylesheet" type="text/css" href="css/style.css" />
        <title>LunarPhase.info for Los Angeles, California</title>
        </head>
        """.encode()

    # # # # # # # # # # # # # 
    #    CONTAINER
    start_of_body = """
        <div class="container text-center" style="font-family:'Trebuchet MS', 'Lucida Sans Unicode',
         'Lucida Grande', 'Lucida Sans', Arial, sans-serif">
        <body style="background-color: #070719;" class="text-white">
        """.encode()

    # # # # # # # # # # # # # 
    #    LOCATION TITLE
    start_page_title = """
        <p class="pt-3">Lunarphase.info for Culver city, California</p>
        """.encode()

    # # # # # # # # # # # # # 
    #  HERO IMAGE
    #    Start of HERO HTML and style
    start_current_phase_symbol = """
        <!-- Current-lunar-phase-symbol -->
        <div>
            <div id="contain-all-moon">
                <svg viewBox="0 0 400 400">
                <style type="text/css">
                    .st0{fill:#EBEBEB;fill-opacity:0.16;}
                    .st1{fill:#FFFFFF;}
                    .st2{opacity:0.45;}
                    .st3{fill:#D9D9BE;}
                    .st4{display:none;}
                </style>
        """.encode()

    # # # # # # # # # # # # # 
    #    CREATE HERO IMG SVG GROUP
    start_moon_group = """
        <!-- start of moon group-->
        <g id="moon-group">
        """.encode()        

    # # # # # # # # # # # # # 
    #    AURA IMG SVG GROUP
    start_aura_group = """
        <g id="aura-group">
            <path id="aura-1" class="st0" d="M91.1,197.5c0-60.7,49.6-110.3,110.3-110.3s110.3,49.6,110.3,110.3s-49.6,110.3-110.3,110.3
                S91.1,258.3,91.1,197.5z"/>
            <path id="aura-2" class="st0" d="M70.9,197.5c0-71.9,57.7-130.6,130.6-130.6c71.9,0,130.6,57.7,130.6,130.6
                c0,71.9-57.7,130.6-130.6,130.6C129.6,328.1,70.9,270.4,70.9,197.5z"/>
            <path id="aura-3" class="st0" d="M51.6,197.5c0-83,66.8-149.8,149.8-149.8s150.8,65.8,150.8,149.8s-66.8,149.8-149.8,149.8
                S51.6,280.5,51.6,197.5z"/>
        </g>
        """.encode()

    # # # # # # # # # # # # # 
    #    Current SVG MOON PHASE
    start_current_phase = """	<path id="waxing_x5F_crescent_x5F_fill" 
    class="st1" d="M294.7,199.7c0,51.6-41.8,93.4-93.4,93.4c-21.8,0,56-18.7,56-93.4
            s-77.8-93.4-56-93.4C252.9,106.3,294.7,148.2,294.7,199.7z"/>
        """.encode()

    # # # # # # # # # # # # # 
    #    SVG MOON CRATERS GROUP
    start_moon_craters = """
        <g id="crater-group_2_" class="st2">
            <path id="_x31_" class="st3" 
            d="M162.2,243.7c0.6,2.3-0.8,4.7-3.2,5.3c-2.3,0.6-4.7-0.8-5.3-3.2c-0.6-2.4,0.8-4.7,3.2-5.3
                C159.2,239.9,161.6,241.4,162.2,243.7z"/>
            <path id="_x32_" class="st3" 
            d="M148.1,232.8c1,4.1-1.5,8.3-5.6,9.4c-4.1,1-8.3-1.5-9.4-5.6c-1-4.1,1.5-8.3,5.6-9.4
                C142.9,226.1,147.1,228.7,148.1,232.8z"/>
            <path id="_x33_" class="st3" 
            d="M164.7,200.9c2.2,9-3.3,18.2-12.3,20.4c-9,2.2-18.2-3.3-20.4-12.3c-2.2-9,3.3-18.2,12.3-20.4
                C153.3,186.4,162.5,191.9,164.7,200.9z"/>
            <path id="_x34_" class="st3" 
            d="M148.9,167.5c3.4,5.8,1.4,13.3-4.4,16.7c-5.8,3.4-13.3,1.4-16.7-4.4c-3.4-5.8-1.4-13.3,4.4-16.7
                C138,159.7,145.5,161.7,148.9,167.5z"/>
            <path id="_x35_" class="st3" 
            d="M181.3,144c6.4,11,2.7,25.2-8.3,31.6c-11,6.4-25.2,2.7-31.6-8.3c-6.4-11-2.7-25.2,8.3-31.6
                C160.7,129.3,174.9,133,181.3,144z"/>
            <path id="_x36_" class="st3" 
            d="M191.7,161.8c1.5,2.6,0.6,5.9-1.9,7.5c-2.6,1.5-5.9,0.6-7.5-1.9c-1.5-2.6-0.6-5.9,1.9-7.5
                C186.8,158.3,190.2,159.2,191.7,161.8z"/>
            <path id="_x37_" class="st3" 
            d="M228.2,146c4.7,8,1.9,18.4-6.1,23.1c-8,4.7-18.4,1.9-23.1-6.1c-4.7-8-1.9-18.4,6.1-23.1
                S223.5,138,228.2,146z"/>
            <path id="_x38_" class="st3" 
            d="M259.6,175.6c0.7,2.9-1.1,5.9-4,6.6c-2.9,0.7-5.9-1.1-6.6-4c-0.7-2.9,1.1-5.9,4-6.6
                C255.9,170.9,258.9,172.6,259.6,175.6z"/>
            <path id="_x39_" class="st3" 
            d="M252.9,185.1c1.6,6.5-2.4,13.2-8.9,14.8c-6.5,1.6-13.2-2.4-14.8-8.9c-1.6-6.5,2.4-13.2,8.9-14.8
                C244.6,174.6,251.2,178.6,252.9,185.1z"/>
            <path id="_x31_0" class="st3" 
            d="M261.5,199.9c1.2,4.7-1.7,9.5-6.5,10.7c-4.7,1.2-9.5-1.7-10.7-6.5c-1.2-4.7,1.7-9.5,6.5-10.7
                C255.4,192.3,260.3,195.2,261.5,199.9z"/>
            <path id="_x31_1" class="st3" 
            d="M259.6,225.6c0.7,2.9-1.1,5.9-4,6.6c-2.9,0.7-5.9-1.1-6.6-4c-0.7-2.9,1.1-5.9,4-6.6
                C255.9,220.9,258.9,222.7,259.6,225.6z"/>
        """.encode()

    end_of_moon_group = """
        </g>
        <!-- end of moon group-->
        """.encode()
        # # # # # # # # # # # # # end MOON SVG GROUP

    # # # # # # # # # # # # # 
    #    SVG Mountains group
    start_mountains_image_group = """
        <!-- start of mountain group-->
        <g id="mountain-group">
            <path id="mountain-5" class="st4" 
            d="M299.7,339.3l11.1-20.2l19.2,32.4l19.2,32.4H79l19.2-32.4l19.2-32.4l11.1,20.2l7.1-16.2
                l4,10.1l17.2-38.5l20.2,45.6l7.1,15.2l14.2-24.3l15.2,27.3l11.1,19.2l17.2-37.5l20.2-45.6l17.2,38.5l4-10.1l7.1,16.2H299.7z"/>
            <path id="mountain-4" d="M393.8,349.4l7.1,50.6H167l19.2-35.4l19.2-35.4l11.1,22.3l7.1-17.2l4,11.1l18.2,42.5l21.3-89.1l7.1,16.2
                l14.2,26.3l16.2,29.4l11.1,21.3l17.2-40.5l21.3-49.6l18.2,42.5l4-11.1l7.1,17.2L393.8,349.4z"/>
            <path id="mountain-3" d="M0,400h98.2L79,302.8l-19.2-97.2l-11.1,59.7l-7.1-47.6l-4,30.4L19.2,133.8L0,222.8V400z"/>
            <path id="mountain-2" d="M404.9,400H299.7l20.2-80l20.2-80l12.1,48.6l8.1-38.5l5.1,24.3l19.2-94.1l15.2,74.9V400H404.9z"/>
            <path id="mountain-1" d="M329,330.1l17.2-30.4l28.3,49.6l25.7,50.6H0l28.3-49.6l28.3-49.6l17.2,30.4L86,305.9l7.1,15.2l26.3-57.7
                l31.4,67.8l10.1,22.3l21.3-36.4l23.3,40.5l16.2,29.4l25.3-55.7l31.4-67.8l26.3,57.7l7.1-15.2l11.1,24.3L329,330.1L329,330.1z"/>
        </g>
        """.encode()

    end_of_symbol_group = """
        </svg>
        </div>
        """.encode()
        # # # # # # # # # # # # # # end HERO IMAGE

    # # # # # # # # # # # # # # 
    #    HEADLINE
    #    specific lunar phase and percent
    start_lunar_card_title = """
        <!-- Lunar-Card-title -->
        <div">
        <span class="h1">""".encode() + str(lunar_info['pf_phase']).encode()
    
    start_lunar_card_title += """ | """.encode() + str(lunar_info['pf_percent']).encode() 
    
    start_lunar_card_title +="""</span>
        </div>
        <!-- Horizontal Rule 1-->
        <div>
        <hr style="border:0;border-bottom: 1px dashed #ccc"/>
        </div>
        """.encode()
        # # # # # # # # # # # # # # # end HEADLINE

    # # # # # # # # # # # # # # # 
    #    Group 1 - Row 1
    #    n Relevant Moon Phase icons
    start_relevant_phase_info = """
        <div>
        <!-- Relevant Moon Phases info-->
        <div class="row mb-2">
        """.encode()

    start_relevant_phase_icons = """
        <!-- Relevant Moon Phases icons-->
        """.encode()

    # # # # # # # # # # # # # 
    #    4 Moon Phase Icons
    #    SVG Icons
    phase_spot_first_quarter = """
        <div class="col-3">
        <!-- first quarter moon -->
        <svg viewBox="0 0 6 6">
            <path style="opacity:0.541;fill:#FFCC00;enable-background:new" 
            d="M3,6c1.7,0,3-1.3,3-3S4.7,0,3,0S0,1.3,0,3S1.3,6,3,6z M3,5.7C1.5,5.7,0.3,4.5,0.3,3S1.5,0.3,3,0.3"/>
        </svg>
        </div>
        """.encode()    
    
    phase_spot_full_moon = """
        <div class="col-3">
        <!-- full moon -->
        <svg viewbox="0 0 6 6">
            <circle
            r="3"
            cy="3"
            cx="3"
            style="display:inline;opacity:0.95;fill:#fff;fill-opacity:1;stroke:none;
            stroke-width:7.61669302;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
        </svg>
        </div>
        """.encode()
    
    phase_spot_third_quarter = """
        <div class="col-3">
        <!-- third quarter moon -->
        <svg viewBox="0 0 6 6" style="enable-background:new 0 0 6 6;">
        <path style="fill:#FFCC00;opacity:0.541" 
        d="M3,0C1.3,0,0,1.3,0,3s1.3,3,3,3s3-1.3,3-3S4.7,0,3,0z M3,0.3c1.5,0,2.7,1.2,2.7,2.7S4.5,5.7,3,5.7"/>
        </svg>
        </div>
        """.encode()

    phase_spot_new_moon = """
        <div class="col-3">
        <!-- new moon -->
        <svg viewbox="0 0 6 6">
            <circle
            r="3"
            cy="3"
            cx="3"
            style="display:inline;opacity:0.54100001;fill:rgb(99, 99, 85);
            fill-opacity:.5;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
        </svg>
        </div>
        """.encode()
  
    if lunar_info['phase_spot_1_title'] == "New Moon":
        phase_spot_1 = phase_spot_new_moon
        phase_spot_2 = phase_spot_first_quarter
        phase_spot_3 = phase_spot_full_moon
        phase_spot_4 = phase_spot_third_quarter
        
    
    elif lunar_info['phase_spot_1_title'] == "First Quarter":
        phase_spot_1 = phase_spot_first_quarter
        phase_spot_2 = phase_spot_full_moon
        phase_spot_3 = phase_spot_third_quarter
        phase_spot_4 = phase_spot_new_moon
    
    elif lunar_info['phase_spot_1_title'] == "Full Moon":
        phase_spot_1 = phase_spot_full_moon
        phase_spot_2 = phase_spot_third_quarter
        phase_spot_3 = phase_spot_new_moon
        phase_spot_4 = phase_spot_first_quarter
    
    elif lunar_info['phase_spot_1_title'] == "Third Quarter":
        phase_spot_1 = phase_spot_third_quarter
        phase_spot_2 = phase_spot_new_moon
        phase_spot_3 = phase_spot_first_quarter
        phase_spot_4 = phase_spot_full_moon 

    end_relevant_phase_icons = """
        </div>   
        <!-- end of relevant phase icon-->
        """.encode()

    # # # # # # # # # # # # # 
    #  Group 1 - Row 2
    #  n Relevant Moon phase titles 
    start_relevant_phase_titles = """
        <!-- START Relevant Phase Titles -->
        <div class='row'>
        """.encode()

    phase_spot_1_title = """
        <div class="col-3">
        <!-- Phase Spot 1 title -->
        <p>""".encode() + str(lunar_info['phase_spot_1_title']).encode() + """</p>
        </div>""".encode()

    phase_spot_2_title = """
        <div class="col-3">
        <!-- Phase Spot 2 title -->
        <p>""".encode() + str(lunar_info['phase_spot_2_title']).encode() + """</p>
        </div>""".encode()

    phase_spot_3_title = """
        <div class="col-3">
        <!-- Phase Spot 3 title -->
        <p>""".encode() + str(lunar_info['phase_spot_3_title']).encode() + """</p>
        </div>""".encode()

    phase_spot_4_title = """
        <div class="col-3">
        <!-- Phase Spot 4 title-->
        <p>""".encode() + str(lunar_info['phase_spot_4_title']).encode() + """</p>
        </div>""".encode()

    end_relevant_phase_titles = """
        <!-- END Relevant Phase Titles-->
        </div>
        """.encode()

    # # # # # # # # # # # # # 
    #  Grid 1 - Row 3
    #  n Relevant Moon phase dates
    start_relevant_phase_dates = """
        <!-- START Relevant Phase Dates -->
        <div class="row">
        """.encode()

    phase_spot_1_date = """
        <!-- Phase Spot 1 date -->
        <p class="col-3">""".encode() + str(lunar_info['phase_spot_1_date']).encode() + """</p>
        """.encode()

    phase_spot_2_date = """
        <!-- Phase Spot 2 date -->
        <p class="col-3">""".encode() + str(lunar_info['phase_spot_2_date']).encode() + """</p>
        """.encode()

    phase_spot_3_date = """
        <!-- Phase Spot 3 date -->
        <p class="col-3">""".encode() + str(lunar_info['phase_spot_3_date']).encode() + """</p>
        """.encode()

    phase_spot_4_date = """
        <!-- Phase Spot 4 date -->
        <p class="col-3">""".encode() + str(lunar_info['phase_spot_4_date']).encode() + """</p>
        """.encode()

    end_relevant_phase_dates = """
        <!-- END Relevant Phase Dates-->	
        </div>
        """.encode()

    end_relevant_phase_info = """
        </div>
        <!-- END Relevant Phase info -->
        <div>
        <hr style="border:0;border-bottom: 1px dashed #ccc"/>
        </div>
        """.encode()
        # # # # # # # # # # # # # end of Grid 1

    # # # # # # # # # # # #
    #    Grid 2 - row 1
    #    sunrise and sunset titles
    start_sunset_sunrise_info = """
        <!-- START Sunset / Sunrise Info-->
        <div class='row'>
        """.encode()

    # # # # # # # # # # # # # 
    #    Grid 2 - col 1
    #    sunrise for today
    start_sunset_info = """
        <!-- START Sunset Info-->
        <div class="col-6">
        <p>Sunset Today</p>
        <p>""".encode() + str(lunar_info['pf_sunset']).encode() + """
        </p>
        </div>
        """.encode()

    # # # # # # # # # # # # # 
    #    Grid 2 - col 2
    #    sunrise for today
    start_sunrise_info = """
        <!-- START Sunrise Info-->
        <div class='col-6'>
        <p>Sunrise Today</p>
        <p>""".encode() + str(lunar_info['pf_sunrise']).encode() + """
        </p>
        """.encode()

    end_sunset_sunrise_info = """
        </div>
        </div>
        <!-- END Sunrise / Sunset info -->
        """.encode()
        # # # # # # # # # # # # # end of Grid 2

    # # # # # # # # # # # # # 
    #    Data Update info
    data_update_info = """
        <!-- Site last updated info -->
        <p>Last updated """.encode() + str(lunar_info['pf_todaydate']).encode() + """</p>
        """.encode()

    # # # # # # # # # # # # # 
    #    Footer
    footer_info = """
        <footer>
        <p>Made by: <a href="http://jpromano.com">JPROMANO.COM</a></p>
        </footer>
        """.encode()

    # # # # # # # # # # # # # 
    #    File Closing tags
    file_closing_tags = """
        </div>
        </div>
            
        <!-- Optional JavaScript -->
        <!-- jQuery first, then Popper.js, then Bootstrap JS -->
        <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" 
        integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" 
        integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" 
        integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
        </body>
        </div>
        </html>
        """.encode()

    # # # # # # # # # # # # # 
    #    CREATE HTML OUTPUT
    # # # # # # # # # # # # # 
    #
    # PAGE CREATION
    output = doc_start + head + start_of_body + start_page_title
    
    # HERO IMAGE
    output += start_current_phase_symbol
    output += start_moon_group + start_aura_group + start_current_phase + start_moon_craters + end_of_moon_group
    
    # MOUNTAINS GROUP
    output += start_mountains_image_group + end_of_symbol_group
    
    # HEADLINE Title
    output += start_lunar_card_title
    
    # HEADLINE Phase Percent
    output += start_relevant_phase_info
    
    # GRID 1
    output += start_relevant_phase_icons
    
    # GRID 1: ROW 1 - Col 1, Col 2, Col 3, Col 4
    output += phase_spot_1 + phase_spot_2 + phase_spot_3 + phase_spot_4
    output += end_relevant_phase_icons
    
    # GRID 1: ROW 2 - Col 1, Col 2, Col 3, Col 4
    output += start_relevant_phase_titles + phase_spot_1_title + phase_spot_2_title + phase_spot_3_title + phase_spot_4_title
    output += end_relevant_phase_titles
    
    # GRID 1: ROW 3 - Col 1, Col 2, Col 3, Col 4
    output += start_relevant_phase_dates + phase_spot_1_date + phase_spot_2_date + phase_spot_3_date + phase_spot_4_date
    output += end_relevant_phase_dates + end_relevant_phase_info
    
    # GRID 2: ROW 1 - Col 1, Col 2
    output += start_sunset_sunrise_info + start_sunset_info
    
    # GRID 2: ROW 2 - Col 1, Col 2
    output += start_sunrise_info + end_sunset_sunrise_info
    
    # SITE INFO
    output += data_update_info
    
    # FOOTER
    output += footer_info

    # Script Files

    # CLOSING FILE TAGES
    output += file_closing_tags

    page_name = 'index'
    file_type = '.html'
    file_name = page_name + file_type

    f = open(file_name,'wb')
    f.write(output)
    f.close()
    webbrowser.open_new_tab(file_name)

And then send it out to Github.

import os
os.chdir('z:\\lunarphase')
os.popen('git add index.html')
os.popen('git commit -m today')
os.popen('git push')