├── .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 | julianDay |
30 | |
31 |
32 |
33 | lunarAge |
34 | |
35 |
36 |
37 | lunarAgePercent |
38 | |
39 |
40 |
41 | lunarPhase |
42 | |
43 |
44 |
45 | lunarPhaseEmoji |
46 | |
47 |
48 |
49 | lunationNumber |
50 | |
51 |
52 |
53 | lunarDistance |
54 | |
55 |
56 |
57 | isWaning |
58 | |
59 |
60 |
61 | isWaxing |
62 | |
63 |
64 |
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 |
--------------------------------------------------------------------------------