Calculate Today's Moon Phase Using Python, ephem, and unittest - A Comprehensive Guide

Have you ever wondered how to calculate the moon phase for today or any specific date using Python? In this article, I will guide you through the process of fetching the moon phase using the ephem library and creating a well-structured Python project using poetry for dependency management and packaging. I will also write tests for our code using the unittest library.

Project Setup

First, let's set up a new Python project using poetry. Follow these steps:

  1. Install Poetry if you haven't already:
curl -sSL https://install.python-poetry.org | python3 -
  1. Create a new project and navigate to the project directory:
poetry new moon_phase
cd moon_phase
  1. Add the ephem library as a dependency:
poetry add ephem

Now that we have our project set up, let's dive into writing the code.

Calculating Moon Phase

We'll create a function called get_moon_phase() in a new Python file named moon_phase.py. This function takes an optional date parameter; if no date is provided, it will use the current date.

import ephem
import datetime

def get_moon_phase(date=None):
    """
    This function returns the moon phase for a given date.
    If no date is provided, the current date is used.

    :param date: The date for which the moon phase is fetched.
    :type date: datetime.date, optional
    :return: The moon phase (0: New Moon, 0.5: First Quarter, 1.0: Full Moon)
    :rtype: float
    """
    if date is None:
        date = datetime.datetime.now()

    moon = ephem.Moon(date)
    phase = moon.moon_phase
    return phase

Here's a breakdown of what the code does:

  1. Import the required libraries - ephem for moon phase calculations and datetime to handle dates.

  2. Define the get_moon_phase() function, which takes an optional date parameter.

  3. If no date is provided, use the current date.

  4. Create a ephem.Moon object for the given date.

  5. Retrieve the moon_phase attribute of the Moon object, which is a float between 0 and 1.

  6. Return the moon phase.

Now, if you want to fetch the moon phase for today, you can simply call the get_moon_phase() function:

moon_phase = get_moon_phase()
print(f"Today's moon phase is: {moon_phase:.2f}")

Writing Unit Tests

We'll write unit tests for our get_moon_phase() function using Python's built-in unittest library. Create a new Python file named test_moon_phase.py and add the following code:

import unittest
from moon_phase import get_moon_phase
import datetime

class TestMoonPhase(unittest.TestCase):

    def test_get_moon_phase_known_date(self):
        # Test with a known date, January 1, 2000
        test_date = datetime.date(2000, 1, 1)
        expected_phase = 0.04  # Known moon phase for this date
        actual_phase = get_moon_phase_phase(test_date)
        self.assertAlmostEqual(expected_phase, actual_phase, places=2)
    def test_get_moon_phase_today(self):
        # Test with the current date
        test_date = datetime.datetime.now()
        actual_phase = get_moon_phase(test_date)
        self.assertIsInstance(actual_phase, float)
        self.assertTrue(0 <= actual_phase <= 1)

if name == 'main':
    unittest.main()

In our test suite, we have two test cases:

  1. test_get_moon_phase_known_date: This test checks if the function returns the correct moon phase for a known date (January 1, 2000, in this case). We compare the expected moon phase with the actual moon phase returned by the function using assertAlmostEqual with a precision of 2 decimal places.

  2. test_get_moon_phase_today: This test checks if the function returns a valid moon phase for the current date. We first assert that the returned value is a float, and then check that the value is between 0 and 1 (inclusive), as expected for a moon phase.

To run the tests, use poetry:

poetry run python -m unittest test_moon_phase.py

If everything is implemented correctly, all tests should pass. You can now run the moon_phase.py script to fetch the current moon phase using poetry run python moon_phase.py.

Conclusion

In this article, we demonstrated how to calculate the moon phase for any given date using Python and the ephem library. We also used poetry for dependency management and packaging, and wrote unit tests with Python's built-in unittest library.

Now you have a solid foundation for expanding your project, such as creating a web application or a command-line tool to fetch the moon phase for any date. Happy coding!

Did you find this article valuable?

Support Theo van der Sluijs by becoming a sponsor. Any amount is appreciated!