natural-time-js 2.0.6 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -29
- package/dist/NaturalDate.d.ts +61 -0
- package/dist/NaturalDate.js +138 -0
- package/dist/ephemeris.d.ts +47 -0
- package/dist/ephemeris.js +111 -0
- package/dist/index.d.ts +2 -80
- package/dist/index.js +3 -91
- package/package.json +3 -14
- package/dist/astronomy/celestial.d.ts +0 -108
- package/dist/astronomy/celestial.js +0 -244
- package/dist/astronomy/celestial.js.map +0 -1
- package/dist/core/NaturalDate.d.ts +0 -156
- package/dist/core/NaturalDate.js +0 -260
- package/dist/core/NaturalDate.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/types/index.d.ts +0 -118
- package/dist/types/index.js +0 -12
- package/dist/types/index.js.map +0 -1
- package/dist/utils/validators.d.ts +0 -140
- package/dist/utils/validators.js +0 -231
- package/dist/utils/validators.js.map +0 -1
package/README.md
CHANGED
|
@@ -46,21 +46,35 @@ The core class for working with Natural Time:
|
|
|
46
46
|
```javascript
|
|
47
47
|
import { NaturalDate } from 'natural-time-js';
|
|
48
48
|
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
// Default (precision=0): longitude is truncated to the nearest integer zone
|
|
50
|
+
// Paris 2.3522° → calculated as NT+2
|
|
51
|
+
const naturalDate = new NaturalDate(new Date(), 2.3522);
|
|
52
|
+
console.log(naturalDate.toString()); // "013)04)01 113°00 NT+2"
|
|
53
|
+
|
|
54
|
+
// Analog mode (precision=Infinity): uses exact longitude
|
|
55
|
+
const naturalDateAnalog = new NaturalDate(new Date(), 2.3522, Infinity);
|
|
52
56
|
```
|
|
53
57
|
|
|
58
|
+
The third argument `precision` controls how many decimal places of longitude are used for calculations:
|
|
59
|
+
|
|
60
|
+
| `precision` | Effective longitude | Zones |
|
|
61
|
+
|-------------|--------------------:|------:|
|
|
62
|
+
| `0` (default) | `Math.trunc(lon)` → e.g. 2.3° → 2° | 360 |
|
|
63
|
+
| `1` | Truncated to 0.1° → e.g. 2.3522° → 2.3° | 3600 |
|
|
64
|
+
| `Infinity` | Exact longitude — fully analog | ∞ |
|
|
65
|
+
|
|
54
66
|
#### Properties
|
|
55
67
|
|
|
56
68
|
```javascript
|
|
57
69
|
// Time Properties
|
|
58
|
-
naturalDate.unixTime;
|
|
59
|
-
naturalDate.time;
|
|
60
|
-
naturalDate.nadir;
|
|
70
|
+
naturalDate.unixTime; // Artificial gregorian date (UNIX timestamp)
|
|
71
|
+
naturalDate.time; // Current time in degrees (0-359.999...)
|
|
72
|
+
naturalDate.nadir; // Beginning of the day at current longitude (UNIX timestamp)
|
|
61
73
|
|
|
62
74
|
// Location
|
|
63
|
-
naturalDate.longitude;
|
|
75
|
+
naturalDate.longitude; // Raw longitude as provided (-180° to +180°)
|
|
76
|
+
naturalDate.precision; // Decimal places used in calculations (default: 0)
|
|
77
|
+
naturalDate.effectiveLongitude;// Longitude actually used in calculations (truncated to precision)
|
|
64
78
|
|
|
65
79
|
// Year Properties
|
|
66
80
|
naturalDate.year; // Current year (year 1: 2012/2013)
|
|
@@ -89,10 +103,12 @@ NaturalDate.MILLISECONDS_PER_DAY; // 86400000 (24*60*60*1000)
|
|
|
89
103
|
#### Utility Methods
|
|
90
104
|
|
|
91
105
|
```javascript
|
|
92
|
-
naturalDate.toString(); // "004)04)01 113°00 NT+5
|
|
106
|
+
naturalDate.toString(); // "004)04)01 113°00 NT+5" (precision=0 default)
|
|
93
107
|
naturalDate.toDateString(); // "004)04)01"
|
|
94
108
|
naturalDate.toTimeString(); // "113°00"
|
|
95
|
-
naturalDate.
|
|
109
|
+
naturalDate.toTimeString(0); // "113°" (cut, never rounded)
|
|
110
|
+
naturalDate.toLongitudeString(); // "NT+5" (matches precision)
|
|
111
|
+
naturalDate.toLongitudeString(1); // "NT+5.2" (explicit decimals)
|
|
96
112
|
naturalDate.toYearString(); // "004"
|
|
97
113
|
naturalDate.toMoonString(); // "04"
|
|
98
114
|
naturalDate.toDayOfMoonString(); // "01"
|
|
@@ -103,13 +119,18 @@ naturalDate.toDayOfMoonString(); // "01"
|
|
|
103
119
|
Calculate precise celestial events and positions:
|
|
104
120
|
|
|
105
121
|
```javascript
|
|
106
|
-
import { NaturalSunEvents, NaturalMoonPosition } from 'natural-time-js';
|
|
122
|
+
import { NaturalDate, NaturalSunPosition, NaturalSunEvents, NaturalMoonPosition } from 'natural-time-js';
|
|
107
123
|
|
|
108
124
|
// Giza Pyramid coordinates
|
|
109
125
|
const latitude = 29.9791;
|
|
110
126
|
const longitude = 31.1341;
|
|
111
127
|
const naturalDate = new NaturalDate(new Date(), longitude);
|
|
112
128
|
|
|
129
|
+
// Get current sun position
|
|
130
|
+
const sunPosition = NaturalSunPosition(naturalDate, latitude);
|
|
131
|
+
console.log(sunPosition.azimuth); // 178.421° (clockwise from north)
|
|
132
|
+
console.log(sunPosition.elevation); // 64.323° (negative below horizon)
|
|
133
|
+
|
|
113
134
|
// Get sun events for the day
|
|
114
135
|
const sunEvents = NaturalSunEvents(naturalDate, latitude);
|
|
115
136
|
console.log(sunEvents.sunrise); // 70.567° (time in natural degrees)
|
|
@@ -117,29 +138,13 @@ console.log(sunEvents.sunset); // 287.910°
|
|
|
117
138
|
|
|
118
139
|
// Get current moon position and phase
|
|
119
140
|
const moonInfo = NaturalMoonPosition(naturalDate, latitude);
|
|
120
|
-
console.log(moonInfo.phase); // 74.861° (
|
|
141
|
+
console.log(moonInfo.phase); // 74.861° (0° new moon, 180° full moon)
|
|
121
142
|
console.log(moonInfo.altitude); // 64.323° (degrees above horizon)
|
|
122
143
|
```
|
|
123
144
|
|
|
124
|
-
## Documentation
|
|
125
|
-
|
|
126
|
-
The library is fully documented using JSDoc. Generate the documentation with:
|
|
127
|
-
|
|
128
|
-
```bash
|
|
129
|
-
npm run docs
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
For live documentation updates during development:
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
npm run docs:dev
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
This creates HTML documentation in the `docs` directory.
|
|
139
|
-
|
|
140
145
|
## Contribute
|
|
141
146
|
|
|
142
|
-
Natural time is open to contributions from free-thinking minds. Find your way through Github or email at: [
|
|
147
|
+
Natural time is open to contributions from free-thinking minds. Find your way through Github or email at: [sylvain@biquette.xyz](mailto:sylvain@biquette.xyz) to get in touch.
|
|
143
148
|
|
|
144
149
|
## License
|
|
145
150
|
|
|
@@ -158,4 +163,4 @@ It was a real pleasure to give birth of natural time during the spring/summer of
|
|
|
158
163
|
|
|
159
164
|
🌍 One love 🌎
|
|
160
165
|
|
|
161
|
-
🏔 🐐 🌞 🌈
|
|
166
|
+
🏔 🐐 🌞 🌈
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export declare function cached<T>(key: string, compute: () => T): T;
|
|
2
|
+
export declare function resetCaches(): void;
|
|
3
|
+
export declare class NaturalDate {
|
|
4
|
+
static readonly MILLISECONDS_PER_DAY = 86400000;
|
|
5
|
+
/** 2012-12-21 12:00 UTC: year 001 begins, artificial time ends. */
|
|
6
|
+
static readonly END_OF_ARTIFICIAL_TIME: number;
|
|
7
|
+
readonly unixTime: number;
|
|
8
|
+
/** As given, east positive. */
|
|
9
|
+
readonly longitude: number;
|
|
10
|
+
readonly precision: number;
|
|
11
|
+
/** The longitude the clock runs on: `longitude` cut to `precision` decimals. */
|
|
12
|
+
readonly effectiveLongitude: number;
|
|
13
|
+
readonly year: number;
|
|
14
|
+
/** 1 to 13, and 14 for the Rainbow day(s). */
|
|
15
|
+
readonly moon: number;
|
|
16
|
+
/** 1 to 53. */
|
|
17
|
+
readonly week: number;
|
|
18
|
+
/** 1 to 4. */
|
|
19
|
+
readonly weekOfMoon: number;
|
|
20
|
+
/** Days since END_OF_ARTIFICIAL_TIME. */
|
|
21
|
+
readonly day: number;
|
|
22
|
+
/** 1 to 366. */
|
|
23
|
+
readonly dayOfYear: number;
|
|
24
|
+
/** 1 to 28. */
|
|
25
|
+
readonly dayOfMoon: number;
|
|
26
|
+
/** 1 to 7. */
|
|
27
|
+
readonly dayOfWeek: number;
|
|
28
|
+
readonly isRainbowDay: boolean;
|
|
29
|
+
/** Degrees since natural midnight, 0 to 360: 180 is noon. */
|
|
30
|
+
readonly time: number;
|
|
31
|
+
/** Unix ms. */
|
|
32
|
+
readonly yearStart: number;
|
|
33
|
+
/** 365 or 366 days. */
|
|
34
|
+
readonly yearDuration: number;
|
|
35
|
+
/** Natural midnight that opened the day, unix ms. */
|
|
36
|
+
readonly nadir: number;
|
|
37
|
+
/**
|
|
38
|
+
* @param date `null` or `undefined` for now.
|
|
39
|
+
* @param longitude Degrees, -180 to 180, east positive.
|
|
40
|
+
* @param precision Decimals of longitude the clock runs on, cut and never rounded:
|
|
41
|
+
* 0 gives 360 zones (2.3522 runs on 2), 1 gives 3600 (2.3), `Infinity` the exact sun.
|
|
42
|
+
*/
|
|
43
|
+
constructor(date?: Date | number | string | null, longitude?: number, precision?: number);
|
|
44
|
+
/** Degrees of `event` within this natural day, or false if it falls outside. */
|
|
45
|
+
getTimeOfEvent(event: Date | number | string): number | false;
|
|
46
|
+
/** `013)04)01 113°00 NT+2` */
|
|
47
|
+
toString(): string;
|
|
48
|
+
/** `013)04)01`, `013)RAINBOW`, `013)RAINBOW+` */
|
|
49
|
+
toDateString(separator?: string): string;
|
|
50
|
+
/** `202°99`: cut to `decimals`, never rounded. It is 202° until the sun reaches 203°. */
|
|
51
|
+
toTimeString(decimals?: number): string;
|
|
52
|
+
/**
|
|
53
|
+
* `NT+2.3`, `NT-73`, `NTZ` when nothing is left once cut. Decimals default to
|
|
54
|
+
* `precision` (0 when exact): the label names the longitude the clock runs on.
|
|
55
|
+
*/
|
|
56
|
+
toLongitudeString(decimals?: number): string;
|
|
57
|
+
/** `013`, `-042` before year 1. */
|
|
58
|
+
toYearString(): string;
|
|
59
|
+
toMoonString(): string;
|
|
60
|
+
toDayOfMoonString(): string;
|
|
61
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Seasons } from 'astronomy-engine';
|
|
2
|
+
// One cache for everything the sky makes us compute twice. It forgets its oldest
|
|
3
|
+
// entry beyond 512: a clock left open for years, or a longitude slider dragged
|
|
4
|
+
// around the Earth, stays small.
|
|
5
|
+
const cache = new Map();
|
|
6
|
+
export function cached(key, compute) {
|
|
7
|
+
if (cache.has(key))
|
|
8
|
+
return cache.get(key);
|
|
9
|
+
const value = compute();
|
|
10
|
+
if (cache.size >= 512)
|
|
11
|
+
cache.delete(cache.keys().next().value);
|
|
12
|
+
cache.set(key, value);
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
export function resetCaches() {
|
|
16
|
+
cache.clear();
|
|
17
|
+
}
|
|
18
|
+
const DAY = 86400000;
|
|
19
|
+
// Floats cannot hold 73.57: times 100 it is 7356.999999999999, and a plain cut would
|
|
20
|
+
// move the observer to 73.56. One part in a million million, added before the cut,
|
|
21
|
+
// puts it back. C and Python add the very same hair.
|
|
22
|
+
const HAIR = 1.000000000001;
|
|
23
|
+
// The natural year opens at noon UTC on the day of the winter solstice (the next
|
|
24
|
+
// day if the solstice falls after noon), then waits for the sun to reach the longitude.
|
|
25
|
+
const newYear = (artificialYear) => {
|
|
26
|
+
const solstice = Seasons(artificialYear).dec_solstice.date;
|
|
27
|
+
return Date.UTC(solstice.getUTCFullYear(), solstice.getUTCMonth(), solstice.getUTCDate() + (solstice.getUTCHours() >= 12 ? 1 : 0), 12);
|
|
28
|
+
};
|
|
29
|
+
const yearContext = (artificialYear, longitude) => cached(`YEAR_${artificialYear}_${longitude}`, () => {
|
|
30
|
+
const start = newYear(artificialYear);
|
|
31
|
+
return {
|
|
32
|
+
start: Math.trunc(start + (-longitude + 180) * DAY / 360),
|
|
33
|
+
duration: (newYear(artificialYear + 1) - start) / DAY
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
export class NaturalDate {
|
|
37
|
+
/**
|
|
38
|
+
* @param date `null` or `undefined` for now.
|
|
39
|
+
* @param longitude Degrees, -180 to 180, east positive.
|
|
40
|
+
* @param precision Decimals of longitude the clock runs on, cut and never rounded:
|
|
41
|
+
* 0 gives 360 zones (2.3522 runs on 2), 1 gives 3600 (2.3), `Infinity` the exact sun.
|
|
42
|
+
*/
|
|
43
|
+
constructor(date, longitude, precision = 0) {
|
|
44
|
+
// NaN compares false to everything: only a positive test refuses it
|
|
45
|
+
if (longitude !== undefined && !(typeof longitude === 'number' && longitude >= -180 && longitude <= 180)) {
|
|
46
|
+
throw new Error('Longitude must be between -180 and +180');
|
|
47
|
+
}
|
|
48
|
+
// ?? and not ||: 0 is a moment like any other
|
|
49
|
+
const unixTime = new Date(date ?? Date.now()).getTime();
|
|
50
|
+
if (isNaN(unixTime)) {
|
|
51
|
+
throw new Error('Invalid date provided');
|
|
52
|
+
}
|
|
53
|
+
this.unixTime = unixTime;
|
|
54
|
+
this.longitude = longitude || 0;
|
|
55
|
+
this.precision = precision;
|
|
56
|
+
this.effectiveLongitude = precision === Infinity
|
|
57
|
+
? this.longitude
|
|
58
|
+
: Math.trunc(this.longitude * Math.pow(10, precision) * HAIR) / Math.pow(10, precision);
|
|
59
|
+
// The natural year began at the last winter solstice, or at the one closing this artificial year
|
|
60
|
+
const artificialYear = new Date(unixTime).getUTCFullYear();
|
|
61
|
+
let context = yearContext(artificialYear - 1, this.effectiveLongitude);
|
|
62
|
+
if (unixTime - context.start >= context.duration * DAY) {
|
|
63
|
+
context = yearContext(artificialYear, this.effectiveLongitude);
|
|
64
|
+
}
|
|
65
|
+
this.yearStart = context.start;
|
|
66
|
+
this.yearDuration = context.duration;
|
|
67
|
+
// C and Python repeat these operations in this order: same floats, same answers
|
|
68
|
+
const elapsed = (unixTime - this.yearStart) / DAY;
|
|
69
|
+
const days = Math.floor(elapsed);
|
|
70
|
+
const weeks = Math.floor(elapsed / 7);
|
|
71
|
+
this.year = new Date(this.yearStart).getUTCFullYear() - 2011;
|
|
72
|
+
this.moon = Math.floor(elapsed / 28) + 1;
|
|
73
|
+
this.week = weeks + 1;
|
|
74
|
+
this.weekOfMoon = weeks % 4 + 1;
|
|
75
|
+
this.day = Math.floor((unixTime - (NaturalDate.END_OF_ARTIFICIAL_TIME + (-this.effectiveLongitude + 180) * DAY / 360)) / DAY);
|
|
76
|
+
this.dayOfYear = days + 1;
|
|
77
|
+
this.dayOfMoon = days % 28 + 1;
|
|
78
|
+
this.dayOfWeek = days % 7 + 1;
|
|
79
|
+
this.isRainbowDay = this.dayOfYear > 13 * 28;
|
|
80
|
+
this.nadir = this.yearStart + days * DAY;
|
|
81
|
+
this.time = (unixTime - this.nadir) * 360 / DAY;
|
|
82
|
+
}
|
|
83
|
+
/** Degrees of `event` within this natural day, or false if it falls outside. */
|
|
84
|
+
getTimeOfEvent(event) {
|
|
85
|
+
const eventTime = new Date(event).getTime();
|
|
86
|
+
if (eventTime < this.nadir || eventTime > this.nadir + DAY)
|
|
87
|
+
return false;
|
|
88
|
+
// 360 / DAY first, unlike `time`: the copies in C and Python do the same
|
|
89
|
+
return (eventTime - this.nadir) * (360 / DAY);
|
|
90
|
+
}
|
|
91
|
+
/** `013)04)01 113°00 NT+2` */
|
|
92
|
+
toString() {
|
|
93
|
+
return `${this.toDateString()} ${this.toTimeString()} ${this.toLongitudeString()}`;
|
|
94
|
+
}
|
|
95
|
+
/** `013)04)01`, `013)RAINBOW`, `013)RAINBOW+` */
|
|
96
|
+
toDateString(separator = ')') {
|
|
97
|
+
if (this.isRainbowDay) {
|
|
98
|
+
return `${this.toYearString()}${separator}RAINBOW${this.dayOfYear === 366 ? '+' : ''}`;
|
|
99
|
+
}
|
|
100
|
+
return `${this.toYearString()}${separator}${this.toMoonString()}${separator}${this.toDayOfMoonString()}`;
|
|
101
|
+
}
|
|
102
|
+
/** `202°99`: cut to `decimals`, never rounded. It is 202° until the sun reaches 203°. */
|
|
103
|
+
toTimeString(decimals = 2) {
|
|
104
|
+
// The hair added before the cut repairs floats (20263.999999999996 is 20264),
|
|
105
|
+
// it is far thinner than a millisecond of sun
|
|
106
|
+
const scale = Math.pow(10, Math.max(0, Math.trunc(decimals)));
|
|
107
|
+
const total = Math.floor(this.time * scale + 1e-6);
|
|
108
|
+
const whole = Math.floor(total / scale) % 360;
|
|
109
|
+
const fraction = scale > 1 ? (total % scale).toString().padStart(Math.trunc(decimals), '0') : '';
|
|
110
|
+
return `${whole.toString().padStart(3, '0')}°${fraction}`;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* `NT+2.3`, `NT-73`, `NTZ` when nothing is left once cut. Decimals default to
|
|
114
|
+
* `precision` (0 when exact): the label names the longitude the clock runs on.
|
|
115
|
+
*/
|
|
116
|
+
toLongitudeString(decimals = Number.isFinite(this.precision) ? this.precision : 0) {
|
|
117
|
+
const scale = Math.pow(10, Math.max(0, Math.trunc(decimals)));
|
|
118
|
+
const scaled = Math.trunc(Math.abs(this.longitude) * scale * HAIR);
|
|
119
|
+
if (scaled === 0)
|
|
120
|
+
return 'NTZ';
|
|
121
|
+
const sign = this.longitude >= 0 ? '+' : '-';
|
|
122
|
+
const fraction = scale > 1 ? '.' + (scaled % scale).toString().padStart(Math.trunc(decimals), '0') : '';
|
|
123
|
+
return `NT${sign}${Math.floor(scaled / scale)}${fraction}`;
|
|
124
|
+
}
|
|
125
|
+
/** `013`, `-042` before year 1. */
|
|
126
|
+
toYearString() {
|
|
127
|
+
return `${this.year < 0 ? '-' : ''}${Math.abs(this.year).toString().padStart(3, '0')}`;
|
|
128
|
+
}
|
|
129
|
+
toMoonString() {
|
|
130
|
+
return this.moon.toString().padStart(2, '0');
|
|
131
|
+
}
|
|
132
|
+
toDayOfMoonString() {
|
|
133
|
+
return this.dayOfMoon.toString().padStart(2, '0');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
NaturalDate.MILLISECONDS_PER_DAY = DAY;
|
|
137
|
+
/** 2012-12-21 12:00 UTC: year 001 begins, artificial time ends. */
|
|
138
|
+
NaturalDate.END_OF_ARTIFICIAL_TIME = Date.UTC(2012, 11, 21, 12);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { NaturalDate } from './NaturalDate.js';
|
|
2
|
+
/** Azimuth clockwise from north; elevation signed, altitude clamped to the horizon. */
|
|
3
|
+
export declare function NaturalSunPosition(naturalDate: NaturalDate, latitude: number): {
|
|
4
|
+
azimuth: number;
|
|
5
|
+
elevation: number;
|
|
6
|
+
altitude: number;
|
|
7
|
+
highestAltitude: number;
|
|
8
|
+
isAboveHorizon: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare function NaturalSunAltitude(naturalDate: NaturalDate, latitude: number): {
|
|
11
|
+
altitude: number;
|
|
12
|
+
highestAltitude: number;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* When the sun rises, sets, crosses -12° (night) and 6° (golden hour) this natural day.
|
|
16
|
+
* Where it never does, the day runs from 0° to 360° in summer and shrinks to 180° in winter.
|
|
17
|
+
*/
|
|
18
|
+
export declare function NaturalSunEvents(naturalDate: NaturalDate, latitude: number): {
|
|
19
|
+
sunrise: number;
|
|
20
|
+
sunset: number;
|
|
21
|
+
nightStart: number;
|
|
22
|
+
nightEnd: number;
|
|
23
|
+
morningGoldenHour: number;
|
|
24
|
+
eveningGoldenHour: number;
|
|
25
|
+
};
|
|
26
|
+
/** Altitude clamped to the horizon; phase 0° new moon, 180° full. */
|
|
27
|
+
export declare function NaturalMoonPosition(naturalDate: NaturalDate, latitude: number): {
|
|
28
|
+
altitude: number;
|
|
29
|
+
phase: number;
|
|
30
|
+
};
|
|
31
|
+
/** Moonrise and moonset this natural day, 0 when the moon skips one. */
|
|
32
|
+
export declare function NaturalMoonEvents(naturalDate: NaturalDate, latitude: number): {
|
|
33
|
+
moonrise: number;
|
|
34
|
+
moonset: number;
|
|
35
|
+
highestAltitude: number;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The mustaches: sunrise and sunset at both solstices of the year, seen from NT 0,
|
|
39
|
+
* and how far they swing on average (0 to 90).
|
|
40
|
+
*/
|
|
41
|
+
export declare function MustachesRange(naturalDate: NaturalDate, latitude: number): {
|
|
42
|
+
winterSunrise: number;
|
|
43
|
+
winterSunset: number;
|
|
44
|
+
summerSunrise: number;
|
|
45
|
+
summerSunset: number;
|
|
46
|
+
averageMustacheAngle: number;
|
|
47
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Body, Equator, Horizon, MoonPhase, Observer, SearchAltitude, SearchHourAngle, SearchRiseSet, Seasons } from 'astronomy-engine';
|
|
2
|
+
import { NaturalDate, cached } from './NaturalDate.js';
|
|
3
|
+
// Every function here answers for a NaturalDate seen from a latitude in degrees.
|
|
4
|
+
// Results are angles in degrees: events in natural degrees of the day (0 at
|
|
5
|
+
// midnight, 180 at noon), positions as the sky gives them.
|
|
6
|
+
function observer(naturalDate, latitude) {
|
|
7
|
+
if (!(naturalDate instanceof NaturalDate)) {
|
|
8
|
+
throw new Error(`Invalid naturalDate: ${naturalDate}. Expected NaturalDate instance`);
|
|
9
|
+
}
|
|
10
|
+
if (!(typeof latitude === 'number' && latitude >= -90 && latitude <= 90)) {
|
|
11
|
+
throw new Error(`Invalid latitude: ${latitude}. Expected number between -90 and 90`);
|
|
12
|
+
}
|
|
13
|
+
// The sky is seen from the longitude as given: precision shapes the clock, not the Earth
|
|
14
|
+
return new Observer(latitude, naturalDate.longitude, 0);
|
|
15
|
+
}
|
|
16
|
+
const at = (naturalDate, found, otherwise) => found ? naturalDate.getTimeOfEvent(found.date) || 0 : otherwise;
|
|
17
|
+
/** Azimuth clockwise from north; elevation signed, altitude clamped to the horizon. */
|
|
18
|
+
export function NaturalSunPosition(naturalDate, latitude) {
|
|
19
|
+
const place = observer(naturalDate, latitude);
|
|
20
|
+
const date = new Date(naturalDate.unixTime);
|
|
21
|
+
const sun = Equator(Body.Sun, date, place, true, false);
|
|
22
|
+
const { azimuth, altitude } = Horizon(date, place, sun.ra, sun.dec);
|
|
23
|
+
return {
|
|
24
|
+
azimuth,
|
|
25
|
+
elevation: altitude,
|
|
26
|
+
altitude: Math.max(altitude, 0),
|
|
27
|
+
highestAltitude: SearchHourAngle(Body.Sun, place, 0, new Date(naturalDate.nadir)).hor.altitude,
|
|
28
|
+
isAboveHorizon: altitude >= 0
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function NaturalSunAltitude(naturalDate, latitude) {
|
|
32
|
+
const { altitude, highestAltitude } = NaturalSunPosition(naturalDate, latitude);
|
|
33
|
+
return { altitude, highestAltitude };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* When the sun rises, sets, crosses -12° (night) and 6° (golden hour) this natural day.
|
|
37
|
+
* Where it never does, the day runs from 0° to 360° in summer and shrinks to 180° in winter.
|
|
38
|
+
*/
|
|
39
|
+
export function NaturalSunEvents(naturalDate, latitude) {
|
|
40
|
+
const place = observer(naturalDate, latitude);
|
|
41
|
+
// Two precisions of one longitude share a date but not a midnight: the nadir tells them apart
|
|
42
|
+
return cached(`SUN_${naturalDate.nadir}_${latitude}_${naturalDate.longitude}`, () => {
|
|
43
|
+
const nadir = new Date(naturalDate.nadir);
|
|
44
|
+
// Natural days 91 to 273 are summer in the north; both ends belong to both hemispheres
|
|
45
|
+
const d = naturalDate.dayOfYear;
|
|
46
|
+
const summer = latitude >= 0 ? d >= 91 && d <= 273 : d <= 91 || d >= 273;
|
|
47
|
+
const rising = summer ? 0 : 180;
|
|
48
|
+
const setting = summer ? 360 : 180;
|
|
49
|
+
const crossing = (direction, altitude) => at(naturalDate, SearchAltitude(Body.Sun, place, direction, nadir, 2, altitude), direction > 0 ? rising : setting);
|
|
50
|
+
return {
|
|
51
|
+
sunrise: at(naturalDate, SearchRiseSet(Body.Sun, place, +1, nadir, 1), rising),
|
|
52
|
+
sunset: at(naturalDate, SearchRiseSet(Body.Sun, place, -1, nadir, 1), setting),
|
|
53
|
+
nightStart: crossing(-1, -12),
|
|
54
|
+
nightEnd: crossing(+1, -12),
|
|
55
|
+
morningGoldenHour: crossing(+1, 6),
|
|
56
|
+
eveningGoldenHour: crossing(-1, 6)
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/** Altitude clamped to the horizon; phase 0° new moon, 180° full. */
|
|
61
|
+
export function NaturalMoonPosition(naturalDate, latitude) {
|
|
62
|
+
const place = observer(naturalDate, latitude);
|
|
63
|
+
const date = new Date(naturalDate.unixTime);
|
|
64
|
+
const moon = Equator(Body.Moon, date, place, true, false);
|
|
65
|
+
return {
|
|
66
|
+
altitude: Math.max(Horizon(date, place, moon.ra, moon.dec).altitude, 0),
|
|
67
|
+
phase: MoonPhase(date)
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** Moonrise and moonset this natural day, 0 when the moon skips one. */
|
|
71
|
+
export function NaturalMoonEvents(naturalDate, latitude) {
|
|
72
|
+
const place = observer(naturalDate, latitude);
|
|
73
|
+
return cached(`MOON_${naturalDate.nadir}_${latitude}_${naturalDate.longitude}`, () => {
|
|
74
|
+
const nadir = new Date(naturalDate.nadir);
|
|
75
|
+
return {
|
|
76
|
+
moonrise: at(naturalDate, SearchRiseSet(Body.Moon, place, +1, nadir, 1), 0),
|
|
77
|
+
moonset: at(naturalDate, SearchRiseSet(Body.Moon, place, -1, nadir, 1), 0),
|
|
78
|
+
highestAltitude: SearchHourAngle(Body.Moon, place, 0, nadir).hor.altitude
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The mustaches: sunrise and sunset at both solstices of the year, seen from NT 0,
|
|
84
|
+
* and how far they swing on average (0 to 90).
|
|
85
|
+
*/
|
|
86
|
+
export function MustachesRange(naturalDate, latitude) {
|
|
87
|
+
observer(naturalDate, latitude);
|
|
88
|
+
// UTC year: the sky does not care which time zone the machine was sold in
|
|
89
|
+
const year = new Date(naturalDate.unixTime).getUTCFullYear();
|
|
90
|
+
try {
|
|
91
|
+
return cached(`MUSTACHES_${year}_${latitude}`, () => {
|
|
92
|
+
const seasons = Seasons(year);
|
|
93
|
+
const winter = NaturalSunEvents(new NaturalDate(seasons.dec_solstice.date, 0), latitude);
|
|
94
|
+
const summer = NaturalSunEvents(new NaturalDate(seasons.jun_solstice.date, 0), latitude);
|
|
95
|
+
const swing = latitude >= 0
|
|
96
|
+
? (winter.sunrise - summer.sunrise + summer.sunset - winter.sunset) / 4
|
|
97
|
+
: (summer.sunrise - winter.sunrise + winter.sunset - summer.sunset) / 4;
|
|
98
|
+
return {
|
|
99
|
+
winterSunrise: winter.sunrise,
|
|
100
|
+
winterSunset: winter.sunset,
|
|
101
|
+
summerSunrise: summer.sunrise,
|
|
102
|
+
summerSunset: summer.sunset,
|
|
103
|
+
averageMustacheAngle: Math.min(Math.max(swing, 0), 90)
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// Should the engine give up on a search, a clock without mustaches is still a clock (C does the same)
|
|
109
|
+
return { winterSunrise: 0, winterSunset: 0, summerSunrise: 0, summerSunset: 0, averageMustacheAngle: 0 };
|
|
110
|
+
}
|
|
111
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,80 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* @description Natural time is a fresh, elegant, and coherent way of measuring the movements of time here on the Earth.
|
|
4
|
-
* This new time standard is based on common sense and the observation of natural cycles.
|
|
5
|
-
* Our current Gregorian calendar is an artificial construct disconnected from natural cycles.
|
|
6
|
-
* Natural time realigns us with the observable patterns of the cosmos:
|
|
7
|
-
* It's time to return to the cosmic rhythms that have guided humanity for millennia.
|
|
8
|
-
* Learn more: https://naturaltime.app
|
|
9
|
-
*
|
|
10
|
-
* # Natural Time JavaScript Library
|
|
11
|
-
*
|
|
12
|
-
* This JavaScript library translates Gregorian (artificial) datetime to natural datetime.
|
|
13
|
-
* Natural time is defined by:
|
|
14
|
-
*
|
|
15
|
-
* - 360° circular time - replacing arbitrary hours with continuous movement
|
|
16
|
-
* - Make the sun back as the main source of time
|
|
17
|
-
* - Years beginning at the winter solstice
|
|
18
|
-
* - 13 perfect moons of 28 days = 364 days
|
|
19
|
-
* - "Rainbow day(s)" - Day out of time to make the year 365 or 366 days
|
|
20
|
-
*
|
|
21
|
-
* This is mathematical beauty aligned with astronomical reality.
|
|
22
|
-
*
|
|
23
|
-
* ## Basic Usage
|
|
24
|
-
*
|
|
25
|
-
* ```typescript
|
|
26
|
-
* import { NaturalDate } from 'natural-time-js';
|
|
27
|
-
*
|
|
28
|
-
* // Create a natural date for the current time at longitude 0
|
|
29
|
-
* const naturalDate = new NaturalDate(new Date(), 0);
|
|
30
|
-
*
|
|
31
|
-
* // Display the natural date
|
|
32
|
-
* console.log(naturalDate.toString());
|
|
33
|
-
* // Example output: "011)04)15 113°25 NTZ"
|
|
34
|
-
*
|
|
35
|
-
* // Get specific components
|
|
36
|
-
* console.log(`Year: ${naturalDate.year}`);
|
|
37
|
-
* console.log(`Moon: ${naturalDate.moon}`);
|
|
38
|
-
* console.log(`Day of moon: ${naturalDate.dayOfMoon}`);
|
|
39
|
-
* console.log(`Time: ${naturalDate.time}°`);
|
|
40
|
-
* ```
|
|
41
|
-
*
|
|
42
|
-
* ## Astronomical Functions
|
|
43
|
-
*
|
|
44
|
-
* The library provides astronomical functions for calculating sun and moon positions:
|
|
45
|
-
*
|
|
46
|
-
* ```typescript
|
|
47
|
-
* import { NaturalDate, NaturalSunEvents, NaturalMoonPosition } from 'natural-time-js';
|
|
48
|
-
*
|
|
49
|
-
* const naturalDate = new NaturalDate(new Date(), 0);
|
|
50
|
-
* const latitude = 45; // 45° North
|
|
51
|
-
*
|
|
52
|
-
* // Get sun events for the day
|
|
53
|
-
* const sunEvents = NaturalSunEvents(naturalDate, latitude);
|
|
54
|
-
* console.log(`Sunrise: ${sunEvents.sunrise}°`);
|
|
55
|
-
* console.log(`Sunset: ${sunEvents.sunset}°`);
|
|
56
|
-
*
|
|
57
|
-
* // Get moon position and phase
|
|
58
|
-
* const moonInfo = NaturalMoonPosition(naturalDate, latitude);
|
|
59
|
-
* console.log(`Moon phase: ${moonInfo.phase}°`);
|
|
60
|
-
* ```
|
|
61
|
-
*
|
|
62
|
-
* @author Sylvain 441
|
|
63
|
-
* @license CC0-1.0
|
|
64
|
-
* @version 2.0.2
|
|
65
|
-
*/
|
|
66
|
-
import { resetAstronomyCache } from './astronomy/celestial.js';
|
|
67
|
-
export { NaturalDate, resetYearContextCache } from './core/NaturalDate.js';
|
|
68
|
-
/**
|
|
69
|
-
* Astronomical functions and constants for natural time calculations
|
|
70
|
-
*/
|
|
71
|
-
export { NaturalSunAltitude, NaturalSunEvents, NaturalMoonPosition, NaturalMoonEvents, MustachesRange, HEMISPHERES, SEASONS, ANGLES } from './astronomy/celestial.js';
|
|
72
|
-
export { resetAstronomyCache };
|
|
73
|
-
/**
|
|
74
|
-
* Resets all internal caches used by the library.
|
|
75
|
-
*/
|
|
76
|
-
export declare function resetCaches(): void;
|
|
77
|
-
/**
|
|
78
|
-
* Utility functions for validation and error handling
|
|
79
|
-
*/
|
|
80
|
-
export { isValidLatitude, isValidLongitude, isValidAngle, isValidTimestamp, isValidNaturalDate, isValidCacheKey, throwValidationError } from './utils/validators.js';
|
|
1
|
+
export { NaturalDate, resetCaches } from './NaturalDate.js';
|
|
2
|
+
export { NaturalSunPosition, NaturalSunAltitude, NaturalSunEvents, NaturalMoonPosition, NaturalMoonEvents, MustachesRange } from './ephemeris.js';
|
package/dist/index.js
CHANGED
|
@@ -1,91 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* This new time standard is based on common sense and the observation of natural cycles.
|
|
5
|
-
* Our current Gregorian calendar is an artificial construct disconnected from natural cycles.
|
|
6
|
-
* Natural time realigns us with the observable patterns of the cosmos:
|
|
7
|
-
* It's time to return to the cosmic rhythms that have guided humanity for millennia.
|
|
8
|
-
* Learn more: https://naturaltime.app
|
|
9
|
-
*
|
|
10
|
-
* # Natural Time JavaScript Library
|
|
11
|
-
*
|
|
12
|
-
* This JavaScript library translates Gregorian (artificial) datetime to natural datetime.
|
|
13
|
-
* Natural time is defined by:
|
|
14
|
-
*
|
|
15
|
-
* - 360° circular time - replacing arbitrary hours with continuous movement
|
|
16
|
-
* - Make the sun back as the main source of time
|
|
17
|
-
* - Years beginning at the winter solstice
|
|
18
|
-
* - 13 perfect moons of 28 days = 364 days
|
|
19
|
-
* - "Rainbow day(s)" - Day out of time to make the year 365 or 366 days
|
|
20
|
-
*
|
|
21
|
-
* This is mathematical beauty aligned with astronomical reality.
|
|
22
|
-
*
|
|
23
|
-
* ## Basic Usage
|
|
24
|
-
*
|
|
25
|
-
* ```typescript
|
|
26
|
-
* import { NaturalDate } from 'natural-time-js';
|
|
27
|
-
*
|
|
28
|
-
* // Create a natural date for the current time at longitude 0
|
|
29
|
-
* const naturalDate = new NaturalDate(new Date(), 0);
|
|
30
|
-
*
|
|
31
|
-
* // Display the natural date
|
|
32
|
-
* console.log(naturalDate.toString());
|
|
33
|
-
* // Example output: "011)04)15 113°25 NTZ"
|
|
34
|
-
*
|
|
35
|
-
* // Get specific components
|
|
36
|
-
* console.log(`Year: ${naturalDate.year}`);
|
|
37
|
-
* console.log(`Moon: ${naturalDate.moon}`);
|
|
38
|
-
* console.log(`Day of moon: ${naturalDate.dayOfMoon}`);
|
|
39
|
-
* console.log(`Time: ${naturalDate.time}°`);
|
|
40
|
-
* ```
|
|
41
|
-
*
|
|
42
|
-
* ## Astronomical Functions
|
|
43
|
-
*
|
|
44
|
-
* The library provides astronomical functions for calculating sun and moon positions:
|
|
45
|
-
*
|
|
46
|
-
* ```typescript
|
|
47
|
-
* import { NaturalDate, NaturalSunEvents, NaturalMoonPosition } from 'natural-time-js';
|
|
48
|
-
*
|
|
49
|
-
* const naturalDate = new NaturalDate(new Date(), 0);
|
|
50
|
-
* const latitude = 45; // 45° North
|
|
51
|
-
*
|
|
52
|
-
* // Get sun events for the day
|
|
53
|
-
* const sunEvents = NaturalSunEvents(naturalDate, latitude);
|
|
54
|
-
* console.log(`Sunrise: ${sunEvents.sunrise}°`);
|
|
55
|
-
* console.log(`Sunset: ${sunEvents.sunset}°`);
|
|
56
|
-
*
|
|
57
|
-
* // Get moon position and phase
|
|
58
|
-
* const moonInfo = NaturalMoonPosition(naturalDate, latitude);
|
|
59
|
-
* console.log(`Moon phase: ${moonInfo.phase}°`);
|
|
60
|
-
* ```
|
|
61
|
-
*
|
|
62
|
-
* @author Sylvain 441
|
|
63
|
-
* @license CC0-1.0
|
|
64
|
-
* @version 2.0.2
|
|
65
|
-
*/
|
|
66
|
-
import { resetYearContextCache } from './core/NaturalDate.js';
|
|
67
|
-
import { resetAstronomyCache } from './astronomy/celestial.js';
|
|
68
|
-
// Export the core NaturalDate class
|
|
69
|
-
export { NaturalDate, resetYearContextCache } from './core/NaturalDate.js';
|
|
70
|
-
/**
|
|
71
|
-
* Astronomical functions and constants for natural time calculations
|
|
72
|
-
*/
|
|
73
|
-
export { NaturalSunAltitude, NaturalSunEvents, NaturalMoonPosition, NaturalMoonEvents, MustachesRange, HEMISPHERES, SEASONS, ANGLES } from './astronomy/celestial.js';
|
|
74
|
-
export { resetAstronomyCache };
|
|
75
|
-
/**
|
|
76
|
-
* Resets all internal caches used by the library.
|
|
77
|
-
*/
|
|
78
|
-
export function resetCaches() {
|
|
79
|
-
// Directly call the exported reset functions to clear caches
|
|
80
|
-
resetYearContextCache();
|
|
81
|
-
resetAstronomyCache();
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Utility functions for validation and error handling
|
|
85
|
-
*/
|
|
86
|
-
export { isValidLatitude, isValidLongitude, isValidAngle, isValidTimestamp, isValidNaturalDate, isValidCacheKey, throwValidationError } from './utils/validators.js';
|
|
87
|
-
// NOTE: Consumers using Node ESM require explicit .js extensions for relative imports.
|
|
88
|
-
// Keep all internal re-exports using the ".js" suffix to ensure SSR compatibility.
|
|
89
|
-
// NOTE: Consumers using Node ESM require explicit .js extensions for relative imports.
|
|
90
|
-
// Keep all internal re-exports using the ".js" suffix to ensure SSR compatibility.
|
|
91
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
// Relative imports keep their .js: Node ESM (and SSR) will not guess it
|
|
2
|
+
export { NaturalDate, resetCaches } from './NaturalDate.js';
|
|
3
|
+
export { NaturalSunPosition, NaturalSunAltitude, NaturalSunEvents, NaturalMoonPosition, NaturalMoonEvents, MustachesRange } from './ephemeris.js';
|