Source

earth/moon/phases.ts

import { Day, JulianDay } from '@/types'
import { MOON_PHASE_UPPER_LIMITS, MOON_SYNODIC_PERIOD, MoonPhase, MoonPhaseQuarter } from '@/constants'
import { getDecimalYear } from '@/times'
import { fmod } from '@/utils'

// The value of K must be an integer
function getK (jd: JulianDay): number {
  const decimalYear = getDecimalYear(jd)
  const decimalK = 12.3685 * (decimalYear - 2000)
  return decimalK >= 0 ? Math.floor(decimalK) : Math.ceil(decimalK)
}

function getPhaseK (jd: JulianDay, phase: MoonPhaseQuarter): number {
  let k = getK(jd)
  if (phase === MoonPhaseQuarter.FirstQuarter) {
    k = k + 0.25
  } else if (phase == MoonPhaseQuarter.Full) {
    k = k + 0.5
  } else if (phase == MoonPhaseQuarter.LastQuarter) {
    k = k + 0.75
  }
  return k
}

/**
 * The time of a given Moon phase.
 * Results are already corrected for the Sun's aberration and by the Moon's light-time.
 * @param {JulianDay} jd The julian day
 * @param {MoonPhase} phase The requested phase
 * @return {JulianDay}
 * @memberof module:Earth
 */
export function getTimeOfMeanPhase (jd: JulianDay, phase: MoonPhaseQuarter): JulianDay {
  const k = getPhaseK(jd, phase)
  const T = k / 1236.85
  return 2451_550.097_66
    + 29.530_588_861 * k
    + 0.000_154_37 * Math.pow(T, 2)
    - 0.000_000_150 * Math.pow(T, 3)
    + 0.000_000_000_73 * Math.pow(T, 4)
}

/**
 * The age of the Moon cycle (0 = New Moon, MOON_SYNODIC_PERIOD/2 = Full Moon).
 * This is a low-accuracy age of the moon, using the average moon synodic period.
 * @param {JulianDay} jd The julian day
 * @return {JulianDay}
 * @memberof module:Earth
 */
export function getAge (jd: JulianDay): Day {
  let jdNewMoon = getTimeOfMeanPhase(jd - MOON_SYNODIC_PERIOD, MoonPhaseQuarter.New)
  if (jdNewMoon > jd) {
    jdNewMoon = jdNewMoon - MOON_SYNODIC_PERIOD
  }
  return fmod(jd - jdNewMoon, MOON_SYNODIC_PERIOD)
}

/**
 * The age name of the Moon cycle (New, WaxingCresent, FirstQuarter etc)
 * @param {JulianDay} jd The julian day
 * @return {MoonPhase} The moon phase name
 * @memberof module:Earth
 */
export function getAgeName (jd: JulianDay): MoonPhase {
  const frac = getAge(jd) / MOON_SYNODIC_PERIOD
  // Order matter since we wrote down only upper limits.
  if (frac <= (MOON_PHASE_UPPER_LIMITS[MoonPhase.New])) {
    return MoonPhase.New
  } else if (frac <= (MOON_PHASE_UPPER_LIMITS[MoonPhase.WaxingCrescent])) {
    return MoonPhase.WaxingCrescent
  } else if (frac <= (MOON_PHASE_UPPER_LIMITS[MoonPhase.FirstQuarter])) {
    return MoonPhase.FirstQuarter
  } else if (frac <= (MOON_PHASE_UPPER_LIMITS[MoonPhase.WaxingGibbous])) {
    return MoonPhase.WaxingGibbous
  } else if (frac <= (MOON_PHASE_UPPER_LIMITS[MoonPhase.Full])) {
    return MoonPhase.Full
  } else if (frac <= (MOON_PHASE_UPPER_LIMITS[MoonPhase.WaningGibbous])) {
    return MoonPhase.WaningGibbous
  } else if (frac <= (MOON_PHASE_UPPER_LIMITS[MoonPhase.LastQuarter])) {
    return MoonPhase.LastQuarter
  } else if (frac <= (MOON_PHASE_UPPER_LIMITS[MoonPhase.WaningCrescent])) {
    return MoonPhase.WaningCrescent
  } else {
    return MoonPhase.New
  }
}