Contents

mannylopez/tinymoon

A tiny Swift library to calculate the moon phase for any given date, works super fast, and works completely offline.

Quick start

// Get the moon phase for right now
let moon = TinyMoon.calculateMoonPhase()

Installation

  1. Open your existing Xcode project or create a new one
  2. Open the Swift Packages Manager

- In the project navigator, select your project file to open the project settings. - Navigate to the the Package Dependencies tab

  1. Add the Tiny Moon Package

- Click the + button at the bottom of the tab - In the dialog box that appears, enter the URL for Tiny Moon: https://github.com/mannylopez/TinyMoon.git

  1. Specify version rules

- Xcode will prompt you to specify version rules for the package. "Up to Next Major Version" ensures compatibility with future updates that don't introduce breaking changes. - Click Add Package

[Xcode package dialog box]

Usage

Now that Tiny Moon is added to your project, import it and simply pass in the the Date and TimeZone for which you'd like to know the Moon phase for. If no date is passed in, then your system's current Date will be used.

An example app using Tiny Moon

import SwiftUI
import TinyMoon

struct SimpleMoonView: View {

  private let moon = TinyMoon.calculateMoonPhase()

  var body: some View {
    VStack(spacing: 16) {
      Text(moon.date.toString())
      Text(moon.emoji)
      Text(moon.name)
      Text("Illumination: \(moon.illuminatedFraction)")
      Text("\(moon.ageOfMoon.days) days, \(moon.ageOfMoon.hours) hours, \(moon.ageOfMoon.minutes) minutes")
      Text("Full Moon in \(moon.daysTillFullMoon) days")
      Text("New Moon in \(moon.daysTillNewMoon) days")
    }
  }
}

#Preview {
  SimpleMoonView()
}

[Simple Moon View]

API

Entry point

The main entry point into the library is the TinyMoon name space.

From there, you can access func's calculateMoonPhase and calculateExactMoonPhase

public enum TinyMoon {
  public static func calculateMoonPhase(
    _ date: Date = Date(),
    timeZone: TimeZone = TimeZone.current)
    -> Moon
  {
    Moon(date: date, timeZone: timeZone)
  }

  public static func calculateExactMoonPhase(_ date: Date = Date()) -> ExactMoon {
    ExactMoon(date: date)
  }
}
Moon vs ExactMoon

The Moon object prioritizes the major phases (new moon, first quarter, full moon, last quarter) if it happens to land within a specific day.

The ExactMoon object always returns the exact values for the specific date and time passed in.

For example, given that the full moon occurs on August 19, 2024 at 13:25 UTC

import Foundation

// The Date we will query for is August 19, 2024 at 02:00 UTC
let isoFormatter = ISO8601DateFormatter()
isoFormatter.timeZone = TimeZone(secondsFromGMT: 0)
let date = isoFormatter.date(from: "2024-08-19T02:00:00Z")!

let moon = TinyMoon.calculateMoonPhase(date, timeZone: isoFormatter.timeZone) // If you don't specify a TimeZone, it will default to the system's TimeZone
print(moon.name)	// Full Moon
print(moon.emoji)	// πŸŒ•

let exactMoon = TinyMoon.calculateExactMoonPhase(date)
print(exactMoon.name)	// Waxing Gibbous
print(exactMoon.emoji)	// πŸŒ”

ExactMoon will return .waxingGibbous because that is a more accurate representation of the moon phase at 02:00 UTC time. Moon will return .fullMoon since a Full Moon happens during that day.

Moon properties

| Property | Type | Description | | -- | -- | -- | | moonPhase | MoonPhase | Enum with all 8 moon phases | | name | String | String representation of MoonPhase case | | emoji | String | Emoji for moon phase | | date | Date | Date passed in for the Moon object | | julianDay | Double | Continuous count of day since the beginning of Julian period. Used in astronomical calculations (wikipedia) | | daysTillFullMoon | Int | Returns 0 if the current date is a full moon | | daysTillNewMoon | Int | Returns 0 if the current date is a new moon | | daysElapsedInCycle | Double | Number of days elapsed into the synodic cycle, represented as a fraction | | ageOfMoon | (days: Int, hours: Int, minutes: Int) | Age of the moon in days, minutes, hours | | illuminatedFraction | Double | Illuminated portion of the Moon, where 0.0 = new and 1.00 = full | | phaseFraction | Double | Phase of the Moon, represented as a fraction. See table below. | | distanceFromCenterOfEarth | Double | Distance of moon from the center of the Earth, in kilometers | | fullMoonName | String? | Returns the coloquial full moon name |

Values for phaseFraction and corresponding emoji

| phaseFraction | Name | Emoji | | -- | -- | -- | | 0.0 | New Moon | πŸŒ‘ | || Waxing Crescent | πŸŒ’ | | 0.25 | First Quarter | πŸŒ“ | || Waxing Gibbous | πŸŒ” | | 0.5 | Full Moon | πŸŒ• | || Waning Gibbous | πŸŒ– | | 0.75 | Last Quarter | πŸŒ— | || Waning Crescent | 🌘 |

Demos

The following are built with this Tiny Moon Swift Package

MacOS

A MacOS app (available in the App Store) that displays the current moon phase as an emoji in your toolbar.

Source code: https://github.com/mannylopez/TinyMoonApp

[Toolbar] [Detail view]

iOS

A simple iOS app to showcase how to use Tiny Moon Swift Package

Source code: https://github.com/mannylopez/TinyMoonMobile

[Mobile app]

Appreciation

Big thank you to suncalc, a tiny JavaScript for calculating the moon position and lunar phase for the given time (sun position and sunlight phases as well). It helped me understand the underlying formulas needed for calculating the moon phase.

Great resources I found via suncalc include, but are not limited to:

A big big thank you to John Walker who wrote Moontool for Windows and released the C source code into the public domain. This is where the main formula for calculating the moon phase comes from, and I've found it to be extremely accurate.

Package Metadata

Repository: mannylopez/tinymoon

Default branch: main

README: README.md