-
It is not practical for a short function to predict Moon Phase times with absolute precision.
- There are too many "periodic" variables due to planetary interactions etc. for a short program
- At the time of writing, it is difficult to find precision algorhithms for selected factors (lunar tidal drift, leap-seconds etc.)
- The Meeus formula appears to have an accuracy that could be measured in minutes, calendars ususally attribute phases to a day.
- This version of the formula is intended for dates after 1900.
- Changing from UT to local time can change the date of a lunar phase prediction. This is correct behaviour, occurring when the time is before midnight in one timezone and after midnight in the other.
Public Shared Function MoonPhases(yyyy As Integer, UseLocalTime As Boolean) As List(Of PhaseDate)
' collect all moon phases for year and return result as List(of PhaseDate)
' The Structure for PhaseDate and Enum for PhaseName are defined within this code
' Moon phase calculations based on "Calendrical Calculations (Meeus)" in PhasesOfTheMoon function
' yyyy = required year
' If UseLocalTime = False Then all times wil be Universal Time (also known as UT, GMT, Greenwich Mean Time or Time-Zone 0)
' If UseLocalTime = True Then all times will be in host computers local time (adjusted for PC time zone, summer-time etc.)
' Estimate k at start of year
Dim estimate_k As Double = (yyyy - 1900) * 12.37 ' 12.37 is approx average lunar cycles per year
Dim k As Integer = CInt(Math.Floor(estimate_k - 1))
' the -2 is because we do not want to miss first cycle of year. we guessed k and must allow small margin of error
Dim pd As PhaseDate : pd.phase = PhaseName.NewMoon : pd.dat = New Date(yyyy, 1, 1, 0, 0, 0, 0, 0)
' dummy value - set to ensure While starts first loop and no issues with undefined variable
Dim ret As New List(Of PhaseDate)
While pd.dat.Year <= yyyy
pd = PhasesOfTheMoon(k + 0, UseLocalTime)
If pd.dat.Year = yyyy Then ret.Add(pd)
pd = PhasesOfTheMoon(k + 0.25, UseLocalTime)
If pd.dat.Year = yyyy Then ret.Add(pd)
pd = PhasesOfTheMoon(k + 0.5, UseLocalTime)
If pd.dat.Year = yyyy Then ret.Add(pd)
pd = PhasesOfTheMoon(k + 0.75, UseLocalTime)
If pd.dat.Year = yyyy Then ret.Add(pd)
k += 1
End While
Return ret
End Function
Private Shared Function PhasesOfTheMoon(k As Double, UseLocalTime As Boolean) As PhaseDate
' This function based on Calendrical Calculations (Meeus) Chapter 32
' it finds the moon phase dates of a lunar cycle
' valid from 1 Jan 1900
' k = lunar cycle number (always integer) + required moon phase (double)
' required moon phase always 0, 0.25, 0.5 or 0.75
' where 0=New .25=Q1 .5=Full .75=Q3
' If UseLocalTime = False Then date/time will be Universal Time (also known as UT, GMT, Greenwich Mean Time or Time-Zone 0)
' If UseLocalTime = True Then date/time will be in host computers local time (adjusted for PC time zone, summer-time etc.)
' Times accurate to within a few minutes but moany commercial tables only quote the day
Dim corr, F, JD, M, Mo, T, temp As Double
Dim moonType As PhaseName
Dim ret As PhaseDate
Dim JDE_Greg As Date
' Make certain k is exact multiple 0.25
k = Math.Round(k * 4) / 4
' determine moon type from k
temp = k - Math.Floor(k)
Select Case temp ' allow a little margin to avoid trivial rounding error problems
Case 0.125 To 0.374 : moonType = PhaseName.FirstQuarterMoon
Case 0.375 To 0.625 : moonType = PhaseName.FullMoon
Case 0.625 To 0.874 : moonType = PhaseName.ThirdQuarterMoon
Case Else : moonType = PhaseName.NewMoon
End Select
T = k / 1236.85
JD = 2415020.75933 + 29.53058868 * k + 0.0001178 * T * T - 0.000000155 * T * T * T
JD += 0.00033 * SinDeg((166.56 + 132.87 * T - 0.009173 * T * T) Mod 360)
M = (359.2242 + 29.10535608 * k - 0.0000333 * T * T - 0.00000347 * T * T * T) Mod 360
Mo = (306.0253 + 385.81691806 * k + 0.0107306 * T * T - 0.00001236 * T * T * T) Mod 360
F = (21.2964 + 390.67050646 * k - 0.0016528 * T * T - 0.00000239 * T * T * T) Mod 360
Select Case moonType
Case PhaseName.NewMoon, PhaseName.FullMoon
corr += (0.1734 - 0.000393 * T) * SinDeg(M)
corr += 0.0021 * SinDeg(2 * M)
corr -= 0.4068 * SinDeg(Mo)
corr += 0.0161 * SinDeg(2 * Mo)
corr -= 0.0004 * SinDeg(3 * Mo)
corr += 0.0104 * SinDeg(2 * F)
corr -= 0.0051 * SinDeg(M + Mo)
corr -= 0.0074 * SinDeg(M - Mo)
corr += 0.0004 * SinDeg(2 * F + M)
corr -= 0.0004 * SinDeg(2 * F - M)
corr -= 0.0006 * SinDeg(2 * F + Mo)
corr += 0.001 * SinDeg(2 * F - Mo)
corr += 0.0005 * SinDeg(M + 2 * Mo)
Case PhaseName.FirstQuarterMoon, PhaseName.ThirdQuarterMoon
corr += (0.1721 - 0.0004 * T) * SinDeg(M)
corr += 0.0021 * SinDeg(2 * M)
corr -= 0.628 * SinDeg(Mo)
corr += 0.0089 * SinDeg(2 * Mo)
corr -= 0.0004 * SinDeg(3 * Mo)
corr += 0.0079 * SinDeg(2 * F)
corr -= 0.0119 * SinDeg(M + Mo)
corr -= 0.0047 * SinDeg(M - Mo)
corr += 0.0003 * SinDeg(2 * F + M)
corr -= 0.0004 * SinDeg(2 * F - M)
corr -= 0.0006 * SinDeg(2 * F + Mo)
corr += 0.0021 * SinDeg(2 * F - Mo)
corr += 0.0003 * SinDeg(M + 2 * Mo)
corr += 0.0004 * SinDeg(M - 2 * Mo)
corr -= 0.0003 * SinDeg(2 * M + Mo)
End Select
If moonType = PhaseName.FirstQuarterMoon Then
corr += 0.0028 - 0.0004 * CosDeg(M) + 0.0003 * CosDeg(Mo)
End If
If moonType = PhaseName.ThirdQuarterMoon Then
corr += -0.0028 + 0.0004 * CosDeg(M) - 0.0003 * CosDeg(Mo)
End If
ret.phase = moonType
JDE_Greg = New Date(1858, 11, 17, 0, 0, 0, 0, 0)
ret.dat = JDE_Greg.AddDays(JD + corr - 2400000.5)
If UseLocalTime Then ret.dat = ret.dat.ToLocalTime
Return ret
End Function
Private Shared Function SinDeg(a As Double) As Double
Return Math.Sin(a * Math.PI / 180)
End Function
Private Shared Function CosDeg(a As Double) As Double
Return Math.Cos(a * Math.PI / 180)
End Function
Public Structure PhaseDate
Dim phase As PhaseName
Dim dat As Date
End Structure
Public Enum PhaseName
NewMoon
FirstQuarterMoon
FullMoon
ThirdQuarterMoon
End Enum
How to Call Function
Obtain list of every Moon Phase in the year 2026. Results should be in Universal Time
Dim ListPhaseDate As List(Of PhaseDate) = MoonPhases(2026, False)
Obtain list of every Moon Phase in the year 2027. Results should be in the local time zone of the host computer
Dim ListPhaseDate As List(Of PhaseDate) = MoonPhases(2027, True)
DigitalDan.co.uk