Grant Emerson

Lunar Phases

Lunar Phases — SwiftMoji Entry #13

Almost every app across the Apple ecosystem relies on an essential framework called Foundation. In Foundation, Apple provides developers with many useful types and methods. One such type is the Calendar struct which offers a simple API for doing complex calendrical calculations without the need to account for leap years or daylight savings. This is incredibly useful when determining the difference in time between two dates. In the following example, Calendar’s dateComponents(_:from:to:) method is used in conjunction with some magic math to calculate the current lunar phase to a certain degree of accuracy. The daysSinceStart constant is the number of days between now and the first new moon of 1970. That is multiplied by 86,400 to convert to seconds. The offset of 12,300 seconds accounts for the first new moon of 1970 occurring slightly before midnight. Then the total seconds elapsed in the current lunar phase can be calculated by taking the remainder after dividing by 2,551,443 (the number of seconds in a lunar month). The seconds elapsed since the start of the current phase can then be divided by 637,861 to get the current quarter. Modulo 637861 returns the seconds elapsed in the current quarter. If that time interval is greater than a day, the index of the current phase is rounded up.

Credits: The algorithm was taken from this post and adapted for Swift.

import Foundation

extension Date {
    static let dateFormatter: DateFormatter = {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "MM/dd/yyyy"
        return dateFormatter
    }()
    
    init(_ text: String) {
        self = Self.dateFormatter.date(from: text)!
    }
}

let lunarPhaseStart = Date("01/07/1970")
let daysSinceStart = Calendar.current.dateComponents(
    [.day],
    from: lunarPhaseStart,
    to: Date()
).day!

let seconds = daysSinceStart * 86400 + 12300
let lunarMonths = seconds % 2551443
let lunarMonthPart = lunarMonths / 637861
let secondsSinceMainPhase = lunarMonths % 637861

let index = 2 * lunarMonthPart + (86400 <= secondsSinceMainPhase ? 1 : 0)

let lunarPhases = ["🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"]
let lunarPhase = lunarPhases[index]

let currentDate = Date.dateFormatter.string(from: Date())
print("\(currentDate) - \(lunarPhase)") // 10/17/19 - 🌖
Tagged with: