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 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
- // Convert current time at longitude 5.2° E
50
- const naturalDate = new NaturalDate(new Date(), 5.2);
51
- console.log(naturalDate.toString()); // "004)04)01 113°00 NT+5.2"
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; // Artificial gregorian date (UNIX timestamp)
59
- naturalDate.time; // Current time in degrees (0-359.999...)
60
- naturalDate.nadir; // Beginning of the day at current longitude (UNIX timestamp)
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; // Longitude (-180° to +180°)
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.2"
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.toLongitudeString(); // "NT+5.2"
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° (percentage of lunar cycle)
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: [sylvain441@proton.me](mailto:sylvain441@proton.me) to get in touch.
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
- * @module natural-time-js
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
- * @module natural-time-js
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 { 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';