├── .editorconfig ├── .eslintrc ├── .github └── workflows │ ├── publish.yml │ └── static.yml ├── .gitignore ├── .npmrc ├── .prettierrc ├── LICENSE ├── README.md ├── index.html ├── index.js ├── package-lock.json ├── package.json ├── public └── moon.png ├── src ├── Julian.ts ├── Moon.ts ├── MoonOptions.ts ├── constants │ ├── Hemisphere.ts │ ├── LunarEmoji.ts │ ├── LunarMonth.ts │ ├── LunarPhase.ts │ ├── Time.ts │ └── Unit.ts ├── factory │ └── defaultOptions.ts ├── index.ts └── utils │ └── MathUtil.ts ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── vite.demo.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint", "prettier"], 5 | "extends": [ 6 | "eslint:recommended", 7 | "plugin:@typescript-eslint/eslint-recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "prettier" 10 | ], 11 | "env": { 12 | "browser": true, 13 | "node": true 14 | }, 15 | "rules": { 16 | "@typescript-eslint/no-empty-function": "off", 17 | "no-empty-function": "off", 18 | "prettier/prettier": "error" 19 | }, 20 | "ignorePatterns": ["*.d.ts"] 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package to npmjs 2 | on: 3 | release: 4 | types: [published] 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | contents: read 11 | packages: write 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-node@v4 15 | with: 16 | node-version: '20' 17 | registry-url: 'https://registry.npmjs.org' 18 | - run: npm ci 19 | - run: npm run build 20 | - run: npm publish 21 | env: 22 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 23 | -------------------------------------------------------------------------------- /.github/workflows/static.yml: -------------------------------------------------------------------------------- 1 | # Deploy Vite build to GitHub Pages 2 | name: Deploy Vite to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["main"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Single deploy job since we're just deploying 26 | deploy: 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v4 34 | - name: Setup Node 35 | uses: actions/setup-node@v3 36 | with: 37 | node-version: 20 38 | cache: 'npm' 39 | - name: Install dependencies 40 | run: npm install 41 | - name: Build 42 | run: npm run build:demo 43 | - name: Setup Pages 44 | uses: actions/configure-pages@v4 45 | - name: Upload artifact 46 | uses: actions/upload-pages-artifact@v3 47 | with: 48 | # Upload dist repository 49 | path: './dist' 50 | - name: Deploy to GitHub Pages 51 | id: deployment 52 | uses: actions/deploy-pages@v4 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # production 5 | build 6 | dist 7 | docs 8 | 9 | # debug 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # local environment 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry = "https://registry.npmjs.com/" 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "proseWrap": "never" 4 | } 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Jason Sturges 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lunar phase 2 | Calculate phase of the moon using Julian date. 3 | 4 |

5 | 6 |

7 | 8 | 9 | # Getting Started 10 | 11 | To install, execute: 12 | 13 | npm i lunarphase-js 14 | 15 | Then, import into a project and use as: 16 | 17 | ```js 18 | import { Moon } from "lunarphase-js"; 19 | 20 | const phase = Moon.lunarPhase(); 21 | ``` 22 | 23 | This package provides for following distributables: 24 | - CJS: CommonJS 25 | - ESM: ES Modules 26 | - UMD: Universal Module Definition (browser) 27 | - IIFE: Immediately invoked function expression 28 | - TypeScript declaration types 29 | 30 | 31 | # Usage 32 | 33 | In the lunar calendar there are 8 phases each synodic month, which is the number of days to complete the cycle. This time between two identical syzygies is equivalent to 29.53059 Earth days. 34 | 35 | Lunar phases, in order: 36 | 37 | | Phase | Northern Hemisphere | Southern Hemisphere | 38 | | --------------- | ------------------- | ------------------- | 39 | | New | 🌑 | 🌑 | 40 | | Waxing Crescent | 🌒 | 🌘 | 41 | | First Quarter | 🌓 | 🌗 | 42 | | Waxing Gibbous | 🌔 | 🌖 | 43 | | Full | 🌕 | 🌕 | 44 | | Waning Gibbous | 🌖 | 🌔 | 45 | | Last Quarter | 🌗 | 🌓 | 46 | | Waning Crescent | 🌘 | 🌒 | 47 | 48 | 49 | # API Reference: 50 | 51 | The following functions are exported from `Moon`: 52 | 53 | | Function | Output | Description | 54 | | ----------------- | ------------------- | ----------------------------------------------------- | 55 | | lunarPhase() | Waxing Gibbous | Get lunar phase for a given date | 56 | | lunarPhaseEmoji() | 🌖 | Get emoji of lunar phase for a given date | 57 | | isWaxing() | true | Whether the moon is waxing | 58 | | isWaning() | false | Whether the moon is waning | 59 | | lunarAge() | 11.367344279004676 | Earth days since the last new moon | 60 | | lunarAgePercent() | 0.38497186542446116 | Percentage through the lunar synodic month | 61 | | lunarDistance() | 56.04166690080031 | Distance to the moon measured in units of Earth radii | 62 | | lunationNumber() | 1217 | Brown Lunation Number (BLN) | 63 | 64 | Visit this repository's GitHub Pages for a live example: https://jasonsturges.github.io/lunarphase-js/ 65 | 66 | All functions default to the current date, as in "now": 67 | 68 | ```js 69 | import { Moon } from "lunarphase-js"; 70 | 71 | const phase = Moon.lunarPhase(); 72 | ``` 73 | 74 | Otherwise, pass a date object to each function for a specific date. 75 | 76 | ```js 77 | import { Moon } from "lunarphase-js"; 78 | 79 | const date = new Date(); 80 | const phase = Moon.lunarPhase(date); 81 | ``` 82 | 83 | 84 | ### Lunar Phase 85 | 86 | To get the current lunar phase from the `LunarPhase` enum (ex: "FULL") 87 | 88 | ```js 89 | const phase = Moon.lunarPhase(); 90 | ``` 91 | 92 | 93 | ### Lunar Phase Emoji 94 | 95 | To get the current lunar phase emoji from the `LunarEmoji` (ex: "🌕"): 96 | 97 | ```js 98 | const phaseEmoji = Moon.lunarPhaseEmoji(); 99 | ``` 100 | 101 | As phases are inverted between Northern and Southern Hemispheres, optionally pass `Hemisphere` options. 102 | 103 | ```js 104 | import { Hemisphere, Moon } from "lunarphase-js"; 105 | 106 | const date = new Date(); 107 | Moon.lunarPhaseEmoji(date, { 108 | hemisphere: Hemisphere.SOUTHERN, 109 | }); 110 | ``` 111 | 112 | To get emoji for other lunar phases, pass a `LunarPhase` enum: 113 | 114 | ```js 115 | import { LunarPhase, Moon } from "lunarphase-js"; 116 | 117 | const emoji = Moon.emojiForLunarPhase(LunarPhase.FULL); 118 | ``` 119 | 120 | Optionally pass a `Hemisphere` to the function: 121 | 122 | ```js 123 | import { Hemisphere, LunarPhase, Moon } from "lunarphase-js"; 124 | 125 | const emoji = Moon.emojiForLunarPhase(LunarPhase.FULL, { 126 | hemisphere: Hemisphere.SOUTHERN, 127 | }); 128 | 129 | ``` 130 | 131 | 132 | ### Waxing 133 | 134 | Whether the moon is waxing (ex: false) 135 | 136 | ```js 137 | const waxing = Moon.isWaxing(); 138 | ``` 139 | 140 | 141 | ### Waning 142 | 143 | Whether the moon is waning (ex: true) 144 | 145 | ```js 146 | const waning = Moon.isWaning(); 147 | ``` 148 | 149 | 150 | ### Lunar Age 151 | 152 | Age in Earth days through the current lunar cycle, equivalent to 29.53059 Earth days, based on Mean Synodic Month, 2000 AD mean solar days. 153 | 154 | | Phase | Start | Event | End | 155 | | --------------- | -------------- | -------------- | -------------- | 156 | | New | | 0 | 1.84566173161 | 157 | | Waxing Crescent | 1.84566173161 | 3.69132346322 | 5.53698519483 | 158 | | First Quarter | 5.53698519483 | 7.38264692644 | 9.22830865805 | 159 | | Waxing Gibbous | 9.22830865805 | 11.07397038966 | 12.91963212127 | 160 | | Full | 12.91963212127 | 14.76529385288 | 16.61095558449 | 161 | | Waning Gibbous | 16.61095558449 | 18.4566173161 | 20.30227904771 | 162 | | Last Quarter | 20.30227904771 | 22.14794077932 | 23.99360251093 | 163 | | Waning Crescent | 23.99360251093 | 25.83926424254 | 27.68492597415 | 164 | | New | 27.68492597415 | 29.53058770576 | | 165 | 166 | To get the lunar age (ex: 16.54412413414952) 167 | 168 | ```js 169 | const age = Moon.lunarAge(); 170 | ``` 171 | 172 | 173 | ### Lunar Age Percent 174 | 175 | To get the percentage through the lunar cycle (ex: 0.5602368519132597) 176 | 177 | ```js 178 | const agePercent = Moon.lunarAgePercent(); 179 | ``` 180 | 181 | 182 | ### Lunation Number 183 | 184 | Brown Lunation Number (BLN), per Ernest William Brown's lunar theory, defining Lunation 1 as the first new moon of 1923 at approximately 02:41 UTC, January 17, 1923 185 | 186 | ```js 187 | const lunationNumber = Moon.lunationNumber(); 188 | ``` 189 | 190 | 191 | ### Lunar Distance 192 | 193 | Distance to the moon measured in units of Earth radii, with perigee at 56 and apogee at 63.8. 194 | 195 | ```js 196 | const distance = Moon.lunarDistance(); 197 | ``` 198 | 199 | 200 | ## Julian 201 | 202 | Convert to and from Gregorian Dates to Julian Days via the `Julian` module. 203 | 204 | API Reference: 205 | 206 | | Function | Output | Description | 207 | | ---------- | ------------------------ | -------------------------- | 208 | | fromDate() | 2459357.5380029744 | Convert date to Julian Day | 209 | | toDate() | 2021-05-23T05:56:10.418Z | Convert Julian Day to date | 210 | 211 | To convert a date to Julian day: 212 | 213 | ```js 214 | import { Julian } from "lunarphase-js"; 215 | 216 | const date = new Date(); 217 | const julian = Julian.fromDate(date); 218 | ``` 219 | 220 | To convert a Julian day to a date: 221 | 222 | ```js 223 | import { Julian } from "lunarphase-js"; 224 | 225 | const julian = 2459356.529302257; 226 | const date = Julian.toDate(julian); 227 | ``` 228 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Moon Lunar Phase 8 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
julianDay
lunarAge
lunarAgePercent
lunarPhase
lunarPhaseEmoji
lunationNumber
lunarDistance
isWaning
isWaxing
65 |
66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { Moon } from "./src"; 2 | import { Julian } from "./src"; 3 | 4 | const update = () => { 5 | document.querySelector("#julianDay").innerHTML = Julian.fromDate().toFixed(10); 6 | document.querySelector("#lunarAge").innerHTML = Moon.lunarAge().toFixed(10); 7 | document.querySelector("#lunarAgePercent").innerHTML = Moon.lunarAgePercent().toFixed(10); 8 | document.querySelector("#lunationNumber").innerHTML = Moon.lunationNumber().toFixed(0); 9 | document.querySelector("#lunarDistance").innerHTML = Moon.lunarDistance().toFixed(10); 10 | document.querySelector("#lunarPhase").innerHTML = Moon.lunarPhase(); 11 | document.querySelector("#lunarPhaseEmoji").innerHTML = Moon.lunarPhaseEmoji(); 12 | document.querySelector("#isWaxing").innerHTML = Moon.isWaxing(); 13 | document.querySelector("#isWaning").innerHTML = Moon.isWaning(); 14 | requestAnimationFrame(update); 15 | }; 16 | update(); 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lunarphase-js", 3 | "version": "2.0.3", 4 | "description": "Calculate phase of the moon using Julian date", 5 | "author": "Jason Sturges (https://jasonsturges.com)", 6 | "homepage": "https://jasonsturges.github.io/lunarphase-js/", 7 | "repository": "github:jasonsturges/lunarphase-js", 8 | "publishConfig": { 9 | "access": "public", 10 | "registry": "https://registry.npmjs.org/", 11 | "scope": "jasonsturges" 12 | }, 13 | "license": "ISC", 14 | "keywords": [ 15 | "astronomy", 16 | "moon", 17 | "lunar", 18 | "phase", 19 | "julian" 20 | ], 21 | "main": "dist/index.cjs.js", 22 | "module": "dist/index.es.js", 23 | "types": "dist/index.d.ts", 24 | "files": [ 25 | "dist" 26 | ], 27 | "scripts": { 28 | "dev": "tsc && vite build --watch", 29 | "start": "vite --host --open", 30 | "build": "tsc && vite build", 31 | "build:demo": "tsc && vite build --config vite.demo.config.ts", 32 | "lint:scripts": "eslint . --ext .ts" 33 | }, 34 | "devDependencies": { 35 | "@typescript-eslint/eslint-plugin": "^7.1.0", 36 | "@typescript-eslint/parser": "^7.1.0", 37 | "dts-bundle-generator": "^9.3.1", 38 | "eslint": "^8.57.0", 39 | "eslint-config-prettier": "^9.1.0", 40 | "eslint-plugin-prettier": "^5.1.3", 41 | "prettier": "^3.2.5", 42 | "ts-node": "^10.9.2", 43 | "tslib": "^2.6.2", 44 | "typescript": "^5.3.3", 45 | "vite": "^5.1.4", 46 | "vite-plugin-dts": "^3.7.3" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /public/moon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jasonsturges/lunarphase-js/0ed6b65982f271c8349b0d42e5fdb3894c74f9d5/public/moon.png -------------------------------------------------------------------------------- /src/Julian.ts: -------------------------------------------------------------------------------- 1 | import { EPOCH } from "./constants/Time"; 2 | 3 | /** 4 | * Julian calendar, chronological days since noon Universal Time on January 1, 4713 BC 5 | */ 6 | export class Julian { 7 | /** 8 | * Julian day from Gregorian date. 9 | */ 10 | static fromDate(date = new Date()): number { 11 | const time = date.getTime(); 12 | return time / 86400000 - date.getTimezoneOffset() / 1440 + EPOCH; 13 | } 14 | 15 | /** 16 | * Gregorian date from Julian day 17 | */ 18 | static toDate(julian: number): Date { 19 | const date = new Date(); 20 | date.setTime((julian - EPOCH + date.getTimezoneOffset() / 1440) * 86400000); 21 | 22 | return date; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Moon.ts: -------------------------------------------------------------------------------- 1 | import { Julian } from "./Julian"; 2 | import { ANOMALISTIC_MONTH, LUNATION_BASE_JULIAN_DAY, SYNODIC_MONTH } from "./constants/Time"; 3 | import { Hemisphere } from "./constants/Hemisphere"; 4 | import { LunarPhase } from "./constants/LunarPhase"; 5 | import { MoonOptions } from "./MoonOptions"; 6 | import { NorthernHemisphereLunarEmoji, SouthernHemisphereLunarEmoji } from "./constants/LunarEmoji"; 7 | import { defaultOptions } from "./factory/defaultOptions"; 8 | import { normalize } from "./utils/MathUtil"; 9 | 10 | /** 11 | * Calculations relating to Earth's moon. 12 | */ 13 | export class Moon { 14 | /** 15 | * Moon's age, or Earth days since the last new moon, 16 | * normalized within a 29.53059 Earth days calendar. 17 | */ 18 | static lunarAge(date = new Date()) { 19 | const percent = Moon.lunarAgePercent(date); 20 | return percent * SYNODIC_MONTH; 21 | } 22 | 23 | /** 24 | * Percentage through the lunar synodic month. 25 | */ 26 | static lunarAgePercent(date = new Date()) { 27 | return normalize((Julian.fromDate(date) - 2451550.1) / SYNODIC_MONTH); 28 | } 29 | 30 | /** 31 | * Brown Lunation Number (BLN), per Ernest William Brown's lunar theory, 32 | * defining Lunation 1 as the first new moon of 1923 at 33 | * approximately 02:41 UTC, January 17, 1923. 34 | */ 35 | static lunationNumber(date = new Date()) { 36 | return Math.round((Julian.fromDate(date) - LUNATION_BASE_JULIAN_DAY) / SYNODIC_MONTH) + 1; 37 | } 38 | 39 | /** 40 | * Distance to the moon measured in units of Earth radii, with 41 | * perigee at 56 and apogee at 63.8. 42 | */ 43 | static lunarDistance(date = new Date()) { 44 | const julian = Julian.fromDate(date); 45 | const agePercent = Moon.lunarAgePercent(date); 46 | const radians = agePercent * 2 * Math.PI; 47 | const percent = 2 * Math.PI * normalize((julian - 2451562.2) / ANOMALISTIC_MONTH); 48 | 49 | return 60.4 - 3.3 * Math.cos(percent) - 0.6 * Math.cos(2 * radians - percent) - 0.5 * Math.cos(2 * radians); 50 | } 51 | 52 | /** 53 | * Name of the lunar phase per date submitted. 54 | */ 55 | static lunarPhase(date = new Date(), options?: Partial) { 56 | options = { 57 | ...defaultOptions, 58 | ...options, 59 | }; 60 | 61 | const age = Moon.lunarAge(date); 62 | 63 | if (age < 1.84566173161) return LunarPhase.NEW; 64 | else if (age < 5.53698519483) return LunarPhase.WAXING_CRESCENT; 65 | else if (age < 9.22830865805) return LunarPhase.FIRST_QUARTER; 66 | else if (age < 12.91963212127) return LunarPhase.WAXING_GIBBOUS; 67 | else if (age < 16.61095558449) return LunarPhase.FULL; 68 | else if (age < 20.30227904771) return LunarPhase.WANING_GIBBOUS; 69 | else if (age < 23.99360251093) return LunarPhase.LAST_QUARTER; 70 | else if (age < 27.68492597415) return LunarPhase.WANING_CRESCENT; 71 | 72 | return LunarPhase.NEW; 73 | } 74 | 75 | /** 76 | * Emoji of the lunar phase per date submitted. 77 | */ 78 | static lunarPhaseEmoji(date = new Date(), options?: Partial) { 79 | options = { 80 | ...defaultOptions, 81 | ...options, 82 | }; 83 | 84 | const phase = Moon.lunarPhase(date); 85 | 86 | return Moon.emojiForLunarPhase(phase, options); 87 | } 88 | 89 | /** 90 | * Emoji for specified lunar phase. 91 | */ 92 | static emojiForLunarPhase(phase: LunarPhase, options?: Partial) { 93 | const { hemisphere } = { 94 | ...defaultOptions, 95 | ...options, 96 | }; 97 | 98 | let emoji; 99 | 100 | if (hemisphere === Hemisphere.SOUTHERN) { 101 | emoji = SouthernHemisphereLunarEmoji; 102 | } else { 103 | emoji = NorthernHemisphereLunarEmoji; 104 | } 105 | 106 | switch (phase) { 107 | case LunarPhase.WANING_CRESCENT: 108 | return emoji["WANING_CRESCENT"]; 109 | case LunarPhase.LAST_QUARTER: 110 | return emoji["LAST_QUARTER"]; 111 | case LunarPhase.WANING_GIBBOUS: 112 | return emoji["WANING_GIBBOUS"]; 113 | case LunarPhase.FULL: 114 | return emoji["FULL"]; 115 | case LunarPhase.WAXING_GIBBOUS: 116 | return emoji["WAXING_GIBBOUS"]; 117 | case LunarPhase.FIRST_QUARTER: 118 | return emoji["FIRST_QUARTER"]; 119 | case LunarPhase.WAXING_CRESCENT: 120 | return emoji["WAXING_CRESCENT"]; 121 | 122 | default: 123 | case LunarPhase.NEW: 124 | return emoji["NEW"]; 125 | } 126 | } 127 | 128 | /** 129 | * Whether the moon is currently waxing (growing). 130 | */ 131 | static isWaxing(date = new Date()) { 132 | const age = Moon.lunarAge(date); 133 | return age <= 14.765; 134 | } 135 | 136 | /** 137 | * Whether the moon is currently waning (shrinking). 138 | */ 139 | static isWaning(date = new Date()) { 140 | const age = Moon.lunarAge(date); 141 | return age > 14.765; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/MoonOptions.ts: -------------------------------------------------------------------------------- 1 | import { Hemisphere } from "./constants/Hemisphere"; 2 | 3 | export type MoonOptions = { 4 | hemisphere?: Hemisphere; 5 | }; 6 | -------------------------------------------------------------------------------- /src/constants/Hemisphere.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Earth's hemispheres. 3 | */ 4 | export enum Hemisphere { 5 | NORTHERN = "Northern", 6 | SOUTHERN = "Southern", 7 | } 8 | -------------------------------------------------------------------------------- /src/constants/LunarEmoji.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Enumeration of lunar phases as emoji for the Northern Hemisphere. 3 | */ 4 | export enum NorthernHemisphereLunarEmoji { 5 | NEW = "🌑", 6 | WAXING_CRESCENT = "🌒", 7 | FIRST_QUARTER = "🌓", 8 | WAXING_GIBBOUS = "🌔", 9 | FULL = "🌕", 10 | WANING_GIBBOUS = "🌖", 11 | LAST_QUARTER = "🌗", 12 | WANING_CRESCENT = "🌘", 13 | } 14 | 15 | /** 16 | * Enumeration of lunar phases as emoji for the Southern Hemisphere. 17 | */ 18 | export enum SouthernHemisphereLunarEmoji { 19 | NEW = "🌑", 20 | WAXING_CRESCENT = "🌘", 21 | FIRST_QUARTER = "🌗", 22 | WAXING_GIBBOUS = "🌖", 23 | FULL = "🌕", 24 | WANING_GIBBOUS = "🌔", 25 | LAST_QUARTER = "🌓", 26 | WANING_CRESCENT = "🌒", 27 | } 28 | -------------------------------------------------------------------------------- /src/constants/LunarMonth.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Lunar month, time between two successive syzygies of the 3 | * same type: new moons or full moons 4 | */ 5 | export enum LunarMonth { 6 | ANOMALISTIC = "Anomalistic", 7 | DRACONIC = "Draconic", 8 | SIDEREAL = "Sidereal", 9 | SYNODIC = "Synodic", 10 | TROPICAL = "Tropical", 11 | } 12 | -------------------------------------------------------------------------------- /src/constants/LunarPhase.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Enumeration of lunar phases 3 | */ 4 | export enum LunarPhase { 5 | NEW = "New", 6 | WAXING_CRESCENT = "Waxing Crescent", 7 | FIRST_QUARTER = "First Quarter", 8 | WAXING_GIBBOUS = "Waxing Gibbous", 9 | FULL = "Full", 10 | WANING_GIBBOUS = "Waning Gibbous", 11 | LAST_QUARTER = "Last Quarter", 12 | WANING_CRESCENT = "Waning Crescent", 13 | } 14 | -------------------------------------------------------------------------------- /src/constants/Time.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Timestamp epoch, January 1, 1970, in Julian Days. 3 | * @type {number} 4 | */ 5 | export const EPOCH = 2440587.5; 6 | 7 | /** 8 | * Lunation 1 as the first new moon of 1923 at approximately 9 | * 02:41 UTC, January 17, 1923 per Ernest William Brown's lunar theory. 10 | */ 11 | export const LUNATION_BASE_JULIAN_DAY = 2423436.6115277777; 12 | 13 | /** 14 | * Length of one phase (1/8 of a synodic month) in Earth days. 15 | */ 16 | export const PHASE_LENGTH = 3.69132346322; 17 | 18 | /** 19 | * Orbital period of the Moon from perigee to apogee and back to perigee 20 | */ 21 | export const ANOMALISTIC_MONTH = 27.55454988; 22 | 23 | /** 24 | * Length of one synodic month - lunation, or days for the phases to complete a cycle. 25 | * Time between two identical syzygies, equivalent of 29.53059 Earth days. 26 | * 27 | * Based on Mean Synodic Month, 2000 AD mean solar days. 28 | */ 29 | export const SYNODIC_MONTH = 29.53058770576; 30 | -------------------------------------------------------------------------------- /src/constants/Unit.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Units of measure 3 | */ 4 | export enum Unit { 5 | EARTH_RADII = "Earth Radii", 6 | KILOMETERS = "km", 7 | MILES = "m", 8 | } 9 | -------------------------------------------------------------------------------- /src/factory/defaultOptions.ts: -------------------------------------------------------------------------------- 1 | import { Hemisphere } from "../constants/Hemisphere"; 2 | import { MoonOptions } from "../MoonOptions"; 3 | 4 | /** Default moon options factory */ 5 | export const defaultOptions: Partial = { 6 | hemisphere: Hemisphere.NORTHERN, 7 | }; 8 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { Hemisphere } from "./constants/Hemisphere"; 2 | export { NorthernHemisphereLunarEmoji, SouthernHemisphereLunarEmoji } from "./constants/LunarEmoji"; 3 | export { LunarMonth } from "./constants/LunarMonth"; 4 | export { LunarPhase } from "./constants/LunarPhase"; 5 | export type { MoonOptions } from "./MoonOptions"; 6 | export { Julian } from "./Julian"; 7 | export { Moon } from "./Moon"; 8 | export { Unit } from "./constants/Unit"; 9 | -------------------------------------------------------------------------------- /src/utils/MathUtil.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Normalization utility for percentage calculations. 3 | */ 4 | export const normalize = (value: number): number => { 5 | value -= Math.floor(value); 6 | if (value < 0) value += 1; 7 | 8 | return value; 9 | }; 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./src", 4 | "rootDir": "./src", 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleResolution": "Node", 8 | "jsx": "react-jsx", 9 | "lib": ["ESNext", "DOM", "DOM.Iterable"], 10 | "types": ["vite/client", "node"], 11 | "allowJs": false, 12 | "allowSyntheticDefaultImports": true, 13 | "useDefineForClassFields": true, 14 | "strict": true, 15 | "skipLibCheck": false, 16 | "sourceMap": true, 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "esModuleInterop": true, 20 | "noEmit": true, 21 | "noUnusedLocals": false, 22 | "noUnusedParameters": false, 23 | "noImplicitReturns": false, 24 | "forceConsistentCasingInFileNames": true 25 | }, 26 | "include": ["src"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import dts from "vite-plugin-dts"; 2 | import path from "path"; 3 | import { defineConfig, UserConfig } from "vite"; 4 | 5 | export default defineConfig({ 6 | base: "./", 7 | plugins: [dts({ rollupTypes: true })], 8 | build: { 9 | sourcemap: true, 10 | lib: { 11 | entry: path.resolve(__dirname, "src/index.ts"), 12 | name: "lunarphase", 13 | formats: ["es", "cjs", "umd", "iife"], 14 | fileName: (format) => `index.${format}.js`, 15 | }, 16 | rollupOptions: { 17 | external: [], 18 | output: { 19 | globals: {}, 20 | }, 21 | }, 22 | }, 23 | } satisfies UserConfig); 24 | -------------------------------------------------------------------------------- /vite.demo.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, UserConfig } from "vite"; 2 | 3 | export default defineConfig({ 4 | base: "/lunarphase-js", 5 | build: { 6 | sourcemap: true, 7 | }, 8 | } satisfies UserConfig); 9 | --------------------------------------------------------------------------------