natural-time-js 1.1.1 → 2.0.2
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/CHANGELOG.md +27 -0
- package/README.md +99 -107
- package/babel.config.cjs +9 -0
- package/dist/astronomy/celestial.d.ts +104 -0
- package/{context.js → dist/astronomy/celestial.js} +99 -176
- package/dist/astronomy/celestial.js.map +1 -0
- package/dist/core/NaturalDate.d.ts +164 -0
- package/dist/core/NaturalDate.js +254 -0
- package/dist/core/NaturalDate.js.map +1 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +118 -0
- package/dist/types/index.js +12 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/validators.d.ts +140 -0
- package/dist/utils/validators.js +231 -0
- package/dist/utils/validators.js.map +1 -0
- package/jest.config.cjs +29 -0
- package/package.json +23 -3
- package/.github/natural-time-app.jpg +0 -0
- package/index.js +0 -241
- package/utils/validators.js +0 -136
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module natural-time-js/validators
|
|
3
|
+
* @description Validation utilities for natural time calculations.
|
|
4
|
+
* This module provides functions to validate inputs for natural time operations,
|
|
5
|
+
* ensuring that values are within acceptable ranges and of the correct types.
|
|
6
|
+
*/
|
|
7
|
+
import { NaturalDate } from '../core/NaturalDate';
|
|
8
|
+
/**
|
|
9
|
+
* Validates if a value is a finite number.
|
|
10
|
+
*
|
|
11
|
+
* This is a utility function that checks if a value is a valid number
|
|
12
|
+
* (not NaN, not Infinity, and actually a number type).
|
|
13
|
+
*
|
|
14
|
+
* @param value - Value to validate
|
|
15
|
+
* @returns True if value is a valid number
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* isValidNumber(42); // true
|
|
19
|
+
* isValidNumber('42'); // false (string, not number)
|
|
20
|
+
* isValidNumber(NaN); // false
|
|
21
|
+
* isValidNumber(Infinity); // false
|
|
22
|
+
*/
|
|
23
|
+
export declare const isValidNumber: (value: unknown) => value is number;
|
|
24
|
+
/**
|
|
25
|
+
* Validates if a latitude value is within the valid range.
|
|
26
|
+
*
|
|
27
|
+
* Latitude must be a number between -90° (South Pole) and 90° (North Pole).
|
|
28
|
+
*
|
|
29
|
+
* @param latitude - Latitude in degrees to validate
|
|
30
|
+
* @returns True if latitude is valid
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* isValidLatitude(45.5); // true
|
|
34
|
+
* isValidLatitude(-90); // true (South Pole)
|
|
35
|
+
* isValidLatitude(90); // true (North Pole)
|
|
36
|
+
* isValidLatitude(91); // false (out of range)
|
|
37
|
+
*/
|
|
38
|
+
export declare const isValidLatitude: (latitude: unknown) => latitude is number;
|
|
39
|
+
/**
|
|
40
|
+
* Validates if a longitude value is within the valid range.
|
|
41
|
+
*
|
|
42
|
+
* Longitude must be a number between -180° and 180°, where:
|
|
43
|
+
* - 0° is the Prime Meridian (Greenwich)
|
|
44
|
+
* - Positive values are East longitude
|
|
45
|
+
* - Negative values are West longitude
|
|
46
|
+
*
|
|
47
|
+
* @param longitude - Longitude in degrees to validate
|
|
48
|
+
* @returns True if longitude is valid
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* isValidLongitude(0); // true (Prime Meridian)
|
|
52
|
+
* isValidLongitude(180); // true (International Date Line)
|
|
53
|
+
* isValidLongitude(-180); // true (International Date Line)
|
|
54
|
+
* isValidLongitude(181); // false (out of range)
|
|
55
|
+
*/
|
|
56
|
+
export declare const isValidLongitude: (longitude: unknown) => longitude is number;
|
|
57
|
+
/**
|
|
58
|
+
* Validates if an angle value is within the valid range.
|
|
59
|
+
*
|
|
60
|
+
* Angles in natural time are measured in degrees from 0° to 360°,
|
|
61
|
+
* representing a full circle. This is used for time of day and
|
|
62
|
+
* celestial positions.
|
|
63
|
+
*
|
|
64
|
+
* @param angle - Angle in degrees to validate
|
|
65
|
+
* @returns True if angle is valid
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* isValidAngle(180); // true (half circle)
|
|
69
|
+
* isValidAngle(360); // true (full circle)
|
|
70
|
+
* isValidAngle(0); // true (beginning of circle)
|
|
71
|
+
* isValidAngle(361); // false (out of range)
|
|
72
|
+
* isValidAngle(-1); // false (out of range)
|
|
73
|
+
*/
|
|
74
|
+
export declare const isValidAngle: (angle: unknown) => angle is number;
|
|
75
|
+
/**
|
|
76
|
+
* Validates if a timestamp is a valid Unix time.
|
|
77
|
+
*
|
|
78
|
+
* Unix timestamps represent the number of milliseconds since
|
|
79
|
+
* January 1, 1970, 00:00:00 UTC (the Unix epoch).
|
|
80
|
+
* Valid timestamps must be positive numbers.
|
|
81
|
+
*
|
|
82
|
+
* @param timestamp - Unix timestamp in milliseconds
|
|
83
|
+
* @returns True if timestamp is valid
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* isValidTimestamp(Date.now()); // true
|
|
87
|
+
* isValidTimestamp(0); // false (Unix epoch exactly)
|
|
88
|
+
* isValidTimestamp(-1); // false (before Unix epoch)
|
|
89
|
+
*/
|
|
90
|
+
export declare const isValidTimestamp: (timestamp: unknown) => timestamp is number;
|
|
91
|
+
/**
|
|
92
|
+
* Validates if an object is a properly initialized NaturalDate instance.
|
|
93
|
+
*
|
|
94
|
+
* This function performs a comprehensive check of a NaturalDate object,
|
|
95
|
+
* verifying that all properties are present and within valid ranges.
|
|
96
|
+
* It's used to ensure that only valid NaturalDate objects are used in calculations.
|
|
97
|
+
*
|
|
98
|
+
* @param date - Value to validate
|
|
99
|
+
* @returns True if value is a valid NaturalDate instance
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* const naturalDate = new NaturalDate(new Date(), 0);
|
|
103
|
+
* isValidNaturalDate(naturalDate); // true
|
|
104
|
+
*
|
|
105
|
+
* isValidNaturalDate({}); // false (not a NaturalDate instance)
|
|
106
|
+
* isValidNaturalDate(null); // false
|
|
107
|
+
*/
|
|
108
|
+
export declare const isValidNaturalDate: (date: unknown) => date is NaturalDate;
|
|
109
|
+
/**
|
|
110
|
+
* Validates if a string is a valid cache key.
|
|
111
|
+
*
|
|
112
|
+
* Cache keys are used for storing and retrieving calculated values
|
|
113
|
+
* to improve performance. Valid keys must be non-empty strings.
|
|
114
|
+
*
|
|
115
|
+
* @param key - Cache key to validate
|
|
116
|
+
* @returns True if key is valid
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* isValidCacheKey('SUN_2023_45.5_0'); // true
|
|
120
|
+
* isValidCacheKey(''); // false (empty string)
|
|
121
|
+
* isValidCacheKey(123); // false (not a string)
|
|
122
|
+
*/
|
|
123
|
+
export declare const isValidCacheKey: (key: unknown) => key is string;
|
|
124
|
+
/**
|
|
125
|
+
* Throws a formatted error for invalid parameters.
|
|
126
|
+
*
|
|
127
|
+
* This utility function creates consistent error messages for validation failures,
|
|
128
|
+
* making it easier to identify and fix issues with invalid inputs.
|
|
129
|
+
*
|
|
130
|
+
* @param paramName - Name of the invalid parameter
|
|
131
|
+
* @param value - Invalid value
|
|
132
|
+
* @param expectedType - Description of expected type/format
|
|
133
|
+
* @throws {Error} Formatted validation error
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* // If latitude is invalid:
|
|
137
|
+
* throwValidationError('latitude', 95, 'number between -90 and 90');
|
|
138
|
+
* // Throws: "Error: Invalid latitude: 95. Expected number between -90 and 90"
|
|
139
|
+
*/
|
|
140
|
+
export declare const throwValidationError: (paramName: string, value: unknown, expectedType: string) => never;
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module natural-time-js/validators
|
|
3
|
+
* @description Validation utilities for natural time calculations.
|
|
4
|
+
* This module provides functions to validate inputs for natural time operations,
|
|
5
|
+
* ensuring that values are within acceptable ranges and of the correct types.
|
|
6
|
+
*/
|
|
7
|
+
import { NaturalDate } from '../core/NaturalDate';
|
|
8
|
+
/**
|
|
9
|
+
* Valid range for latitude values in degrees
|
|
10
|
+
*/
|
|
11
|
+
const LATITUDE_RANGE = {
|
|
12
|
+
MIN: -90,
|
|
13
|
+
MAX: 90
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Valid range for longitude values in degrees
|
|
17
|
+
*/
|
|
18
|
+
const LONGITUDE_RANGE = {
|
|
19
|
+
MIN: -180,
|
|
20
|
+
MAX: 180
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Valid range for angle values in degrees
|
|
24
|
+
*/
|
|
25
|
+
const ANGLE_RANGE = {
|
|
26
|
+
MIN: 0,
|
|
27
|
+
MAX: 360
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Valid ranges for natural date components
|
|
31
|
+
*/
|
|
32
|
+
const NATURAL_DATE_RANGES = {
|
|
33
|
+
/** Moon (month) number range (1-14) */
|
|
34
|
+
MOON: { MIN: 1, MAX: 14 },
|
|
35
|
+
/** Week of year range (1-53) */
|
|
36
|
+
WEEK: { MIN: 1, MAX: 53 },
|
|
37
|
+
/** Week within moon range (1-4) */
|
|
38
|
+
WEEK_OF_MOON: { MIN: 1, MAX: 4 },
|
|
39
|
+
/** Day of moon range (1-28) */
|
|
40
|
+
DAY_OF_MOON: { MIN: 1, MAX: 28 },
|
|
41
|
+
/** Day of week range (1-7) */
|
|
42
|
+
DAY_OF_WEEK: { MIN: 1, MAX: 7 }
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Validates if a value is a finite number.
|
|
46
|
+
*
|
|
47
|
+
* This is a utility function that checks if a value is a valid number
|
|
48
|
+
* (not NaN, not Infinity, and actually a number type).
|
|
49
|
+
*
|
|
50
|
+
* @param value - Value to validate
|
|
51
|
+
* @returns True if value is a valid number
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* isValidNumber(42); // true
|
|
55
|
+
* isValidNumber('42'); // false (string, not number)
|
|
56
|
+
* isValidNumber(NaN); // false
|
|
57
|
+
* isValidNumber(Infinity); // false
|
|
58
|
+
*/
|
|
59
|
+
export const isValidNumber = (value) => {
|
|
60
|
+
return typeof value === 'number' && Number.isFinite(value) && !Number.isNaN(value);
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Validates if a latitude value is within the valid range.
|
|
64
|
+
*
|
|
65
|
+
* Latitude must be a number between -90° (South Pole) and 90° (North Pole).
|
|
66
|
+
*
|
|
67
|
+
* @param latitude - Latitude in degrees to validate
|
|
68
|
+
* @returns True if latitude is valid
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* isValidLatitude(45.5); // true
|
|
72
|
+
* isValidLatitude(-90); // true (South Pole)
|
|
73
|
+
* isValidLatitude(90); // true (North Pole)
|
|
74
|
+
* isValidLatitude(91); // false (out of range)
|
|
75
|
+
*/
|
|
76
|
+
export const isValidLatitude = (latitude) => {
|
|
77
|
+
return isValidNumber(latitude) &&
|
|
78
|
+
latitude >= LATITUDE_RANGE.MIN &&
|
|
79
|
+
latitude <= LATITUDE_RANGE.MAX;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Validates if a longitude value is within the valid range.
|
|
83
|
+
*
|
|
84
|
+
* Longitude must be a number between -180° and 180°, where:
|
|
85
|
+
* - 0° is the Prime Meridian (Greenwich)
|
|
86
|
+
* - Positive values are East longitude
|
|
87
|
+
* - Negative values are West longitude
|
|
88
|
+
*
|
|
89
|
+
* @param longitude - Longitude in degrees to validate
|
|
90
|
+
* @returns True if longitude is valid
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* isValidLongitude(0); // true (Prime Meridian)
|
|
94
|
+
* isValidLongitude(180); // true (International Date Line)
|
|
95
|
+
* isValidLongitude(-180); // true (International Date Line)
|
|
96
|
+
* isValidLongitude(181); // false (out of range)
|
|
97
|
+
*/
|
|
98
|
+
export const isValidLongitude = (longitude) => {
|
|
99
|
+
return isValidNumber(longitude) &&
|
|
100
|
+
longitude >= LONGITUDE_RANGE.MIN &&
|
|
101
|
+
longitude <= LONGITUDE_RANGE.MAX;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Validates if an angle value is within the valid range.
|
|
105
|
+
*
|
|
106
|
+
* Angles in natural time are measured in degrees from 0° to 360°,
|
|
107
|
+
* representing a full circle. This is used for time of day and
|
|
108
|
+
* celestial positions.
|
|
109
|
+
*
|
|
110
|
+
* @param angle - Angle in degrees to validate
|
|
111
|
+
* @returns True if angle is valid
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* isValidAngle(180); // true (half circle)
|
|
115
|
+
* isValidAngle(360); // true (full circle)
|
|
116
|
+
* isValidAngle(0); // true (beginning of circle)
|
|
117
|
+
* isValidAngle(361); // false (out of range)
|
|
118
|
+
* isValidAngle(-1); // false (out of range)
|
|
119
|
+
*/
|
|
120
|
+
export const isValidAngle = (angle) => {
|
|
121
|
+
return isValidNumber(angle) &&
|
|
122
|
+
angle >= ANGLE_RANGE.MIN &&
|
|
123
|
+
angle <= ANGLE_RANGE.MAX;
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Validates if a timestamp is a valid Unix time.
|
|
127
|
+
*
|
|
128
|
+
* Unix timestamps represent the number of milliseconds since
|
|
129
|
+
* January 1, 1970, 00:00:00 UTC (the Unix epoch).
|
|
130
|
+
* Valid timestamps must be positive numbers.
|
|
131
|
+
*
|
|
132
|
+
* @param timestamp - Unix timestamp in milliseconds
|
|
133
|
+
* @returns True if timestamp is valid
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* isValidTimestamp(Date.now()); // true
|
|
137
|
+
* isValidTimestamp(0); // false (Unix epoch exactly)
|
|
138
|
+
* isValidTimestamp(-1); // false (before Unix epoch)
|
|
139
|
+
*/
|
|
140
|
+
export const isValidTimestamp = (timestamp) => {
|
|
141
|
+
return isValidNumber(timestamp) && timestamp > 0;
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Validates if an object is a properly initialized NaturalDate instance.
|
|
145
|
+
*
|
|
146
|
+
* This function performs a comprehensive check of a NaturalDate object,
|
|
147
|
+
* verifying that all properties are present and within valid ranges.
|
|
148
|
+
* It's used to ensure that only valid NaturalDate objects are used in calculations.
|
|
149
|
+
*
|
|
150
|
+
* @param date - Value to validate
|
|
151
|
+
* @returns True if value is a valid NaturalDate instance
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* const naturalDate = new NaturalDate(new Date(), 0);
|
|
155
|
+
* isValidNaturalDate(naturalDate); // true
|
|
156
|
+
*
|
|
157
|
+
* isValidNaturalDate({}); // false (not a NaturalDate instance)
|
|
158
|
+
* isValidNaturalDate(null); // false
|
|
159
|
+
*/
|
|
160
|
+
export const isValidNaturalDate = (date) => {
|
|
161
|
+
if (!(date instanceof NaturalDate)) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
// Basic property validations
|
|
165
|
+
if (!isValidTimestamp(date.unixTime) ||
|
|
166
|
+
!isValidLongitude(date.longitude) ||
|
|
167
|
+
!isValidNumber(date.year)) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
// Natural calendar validations
|
|
171
|
+
return (isValidNumber(date.moon) &&
|
|
172
|
+
date.moon >= NATURAL_DATE_RANGES.MOON.MIN &&
|
|
173
|
+
date.moon <= NATURAL_DATE_RANGES.MOON.MAX &&
|
|
174
|
+
isValidNumber(date.week) &&
|
|
175
|
+
date.week >= NATURAL_DATE_RANGES.WEEK.MIN &&
|
|
176
|
+
date.week <= NATURAL_DATE_RANGES.WEEK.MAX &&
|
|
177
|
+
isValidNumber(date.weekOfMoon) &&
|
|
178
|
+
date.weekOfMoon >= NATURAL_DATE_RANGES.WEEK_OF_MOON.MIN &&
|
|
179
|
+
date.weekOfMoon <= NATURAL_DATE_RANGES.WEEK_OF_MOON.MAX &&
|
|
180
|
+
isValidNumber(date.dayOfMoon) &&
|
|
181
|
+
date.dayOfMoon >= NATURAL_DATE_RANGES.DAY_OF_MOON.MIN &&
|
|
182
|
+
date.dayOfMoon <= NATURAL_DATE_RANGES.DAY_OF_MOON.MAX &&
|
|
183
|
+
isValidNumber(date.dayOfWeek) &&
|
|
184
|
+
date.dayOfWeek >= NATURAL_DATE_RANGES.DAY_OF_WEEK.MIN &&
|
|
185
|
+
date.dayOfWeek <= NATURAL_DATE_RANGES.DAY_OF_WEEK.MAX &&
|
|
186
|
+
isValidNumber(date.dayOfYear) &&
|
|
187
|
+
date.dayOfYear >= 1 &&
|
|
188
|
+
date.dayOfYear <= date.yearDuration &&
|
|
189
|
+
typeof date.isRainbowDay === 'boolean' &&
|
|
190
|
+
isValidAngle(date.time) &&
|
|
191
|
+
isValidTimestamp(date.yearStart) &&
|
|
192
|
+
isValidNumber(date.yearDuration) &&
|
|
193
|
+
isValidTimestamp(date.nadir));
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* Validates if a string is a valid cache key.
|
|
197
|
+
*
|
|
198
|
+
* Cache keys are used for storing and retrieving calculated values
|
|
199
|
+
* to improve performance. Valid keys must be non-empty strings.
|
|
200
|
+
*
|
|
201
|
+
* @param key - Cache key to validate
|
|
202
|
+
* @returns True if key is valid
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* isValidCacheKey('SUN_2023_45.5_0'); // true
|
|
206
|
+
* isValidCacheKey(''); // false (empty string)
|
|
207
|
+
* isValidCacheKey(123); // false (not a string)
|
|
208
|
+
*/
|
|
209
|
+
export const isValidCacheKey = (key) => {
|
|
210
|
+
return typeof key === 'string' && key.length > 0;
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* Throws a formatted error for invalid parameters.
|
|
214
|
+
*
|
|
215
|
+
* This utility function creates consistent error messages for validation failures,
|
|
216
|
+
* making it easier to identify and fix issues with invalid inputs.
|
|
217
|
+
*
|
|
218
|
+
* @param paramName - Name of the invalid parameter
|
|
219
|
+
* @param value - Invalid value
|
|
220
|
+
* @param expectedType - Description of expected type/format
|
|
221
|
+
* @throws {Error} Formatted validation error
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* // If latitude is invalid:
|
|
225
|
+
* throwValidationError('latitude', 95, 'number between -90 and 90');
|
|
226
|
+
* // Throws: "Error: Invalid latitude: 95. Expected number between -90 and 90"
|
|
227
|
+
*/
|
|
228
|
+
export const throwValidationError = (paramName, value, expectedType) => {
|
|
229
|
+
throw new Error(`Invalid ${paramName}: ${value}. Expected ${expectedType}`);
|
|
230
|
+
};
|
|
231
|
+
//# sourceMappingURL=validators.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validators.js","sourceRoot":"","sources":["../../src/utils/validators.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD;;GAEG;AACH,MAAM,cAAc,GAAG;IACnB,GAAG,EAAE,CAAC,EAAE;IACR,GAAG,EAAE,EAAE;CACD,CAAC;AAEX;;GAEG;AACH,MAAM,eAAe,GAAG;IACpB,GAAG,EAAE,CAAC,GAAG;IACT,GAAG,EAAE,GAAG;CACF,CAAC;AAEX;;GAEG;AACH,MAAM,WAAW,GAAG;IAChB,GAAG,EAAE,CAAC;IACN,GAAG,EAAE,GAAG;CACF,CAAC;AAEX;;GAEG;AACH,MAAM,mBAAmB,GAAG;IACxB,uCAAuC;IACvC,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;IACzB,gCAAgC;IAChC,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;IACzB,mCAAmC;IACnC,YAAY,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE;IAChC,+BAA+B;IAC/B,WAAW,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE;IAChC,8BAA8B;IAC9B,WAAW,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE;CACzB,CAAC;AAEX;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAc,EAAmB,EAAE;IAC7D,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AACvF,CAAC,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,QAAiB,EAAsB,EAAE;IACrE,OAAO,aAAa,CAAC,QAAQ,CAAC;QACvB,QAAQ,IAAI,cAAc,CAAC,GAAG;QAC9B,QAAQ,IAAI,cAAc,CAAC,GAAG,CAAC;AAC1C,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,SAAkB,EAAuB,EAAE;IACxE,OAAO,aAAa,CAAC,SAAS,CAAC;QACxB,SAAS,IAAI,eAAe,CAAC,GAAG;QAChC,SAAS,IAAI,eAAe,CAAC,GAAG,CAAC;AAC5C,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAc,EAAmB,EAAE;IAC5D,OAAO,aAAa,CAAC,KAAK,CAAC;QACpB,KAAK,IAAI,WAAW,CAAC,GAAG;QACxB,KAAK,IAAI,WAAW,CAAC,GAAG,CAAC;AACpC,CAAC,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,SAAkB,EAAuB,EAAE;IACxE,OAAO,aAAa,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,CAAC,CAAC;AACrD,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,IAAa,EAAuB,EAAE;IACrE,IAAI,CAAC,CAAC,IAAI,YAAY,WAAW,CAAC,EAAE,CAAC;QACjC,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,6BAA6B;IAC7B,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC;QAChC,CAAC,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC;QACjC,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5B,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,+BAA+B;IAC/B,OAAO,CACH,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;QACxB,IAAI,CAAC,IAAI,IAAI,mBAAmB,CAAC,IAAI,CAAC,GAAG;QACzC,IAAI,CAAC,IAAI,IAAI,mBAAmB,CAAC,IAAI,CAAC,GAAG;QAEzC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;QACxB,IAAI,CAAC,IAAI,IAAI,mBAAmB,CAAC,IAAI,CAAC,GAAG;QACzC,IAAI,CAAC,IAAI,IAAI,mBAAmB,CAAC,IAAI,CAAC,GAAG;QAEzC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC;QAC9B,IAAI,CAAC,UAAU,IAAI,mBAAmB,CAAC,YAAY,CAAC,GAAG;QACvD,IAAI,CAAC,UAAU,IAAI,mBAAmB,CAAC,YAAY,CAAC,GAAG;QAEvD,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC;QAC7B,IAAI,CAAC,SAAS,IAAI,mBAAmB,CAAC,WAAW,CAAC,GAAG;QACrD,IAAI,CAAC,SAAS,IAAI,mBAAmB,CAAC,WAAW,CAAC,GAAG;QAErD,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC;QAC7B,IAAI,CAAC,SAAS,IAAI,mBAAmB,CAAC,WAAW,CAAC,GAAG;QACrD,IAAI,CAAC,SAAS,IAAI,mBAAmB,CAAC,WAAW,CAAC,GAAG;QAErD,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC;QAC7B,IAAI,CAAC,SAAS,IAAI,CAAC;QACnB,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,YAAY;QAEnC,OAAO,IAAI,CAAC,YAAY,KAAK,SAAS;QACtC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;QACvB,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC;QAChC,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC;QAChC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAC/B,CAAC;AACN,CAAC,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,GAAY,EAAiB,EAAE;IAC3D,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;AACrD,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,SAAiB,EAAE,KAAc,EAAE,YAAoB,EAAS,EAAE;IACnG,MAAM,IAAI,KAAK,CAAC,WAAW,SAAS,KAAK,KAAK,cAAc,YAAY,EAAE,CAAC,CAAC;AAChF,CAAC,CAAC"}
|
package/jest.config.cjs
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
preset: 'ts-jest',
|
|
3
|
+
testEnvironment: 'node',
|
|
4
|
+
extensionsToTreatAsEsm: ['.ts'],
|
|
5
|
+
moduleNameMapper: {
|
|
6
|
+
'^(\\.{1,2}/.*)\\.js$': '$1',
|
|
7
|
+
},
|
|
8
|
+
transform: {
|
|
9
|
+
'^.+\\.tsx?$': ['ts-jest', {
|
|
10
|
+
useESM: true,
|
|
11
|
+
}],
|
|
12
|
+
},
|
|
13
|
+
testMatch: [
|
|
14
|
+
'**/__tests__/**/*.test.ts',
|
|
15
|
+
'**/__tests__/**/*.test.js',
|
|
16
|
+
'**/src/**/__tests__/**/*.test.ts',
|
|
17
|
+
'**/src/**/__tests__/**/*.test.js'
|
|
18
|
+
],
|
|
19
|
+
collectCoverageFrom: [
|
|
20
|
+
'src/**/*.ts',
|
|
21
|
+
'src/**/*.js',
|
|
22
|
+
'!src/index.ts',
|
|
23
|
+
'!src/index.js',
|
|
24
|
+
'!**/node_modules/**',
|
|
25
|
+
'!**/dist/**'
|
|
26
|
+
],
|
|
27
|
+
coverageDirectory: 'coverage',
|
|
28
|
+
verbose: true,
|
|
29
|
+
};
|
package/package.json
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "natural-time-js",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "Natural time is a fresh, elegant, and coherent way of measuring the movements of time here on the Earth. This new time standard is based on common sense and the observation of natural cycles. Learn more: https://naturaltime.app. This JavaScript Class translates gregorian artificial datetime to natural datetime",
|
|
5
|
-
"main": "index.js",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
6
7
|
"type": "module",
|
|
7
8
|
"scripts": {
|
|
8
|
-
"
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"build:watch": "tsc --watch",
|
|
11
|
+
"test": "jest",
|
|
12
|
+
"test:watch": "jest --watch",
|
|
13
|
+
"test:coverage": "jest --coverage",
|
|
14
|
+
"docs": "jsdoc -c jsdoc.json",
|
|
15
|
+
"docs:dev": "jsdoc -c jsdoc.json -w",
|
|
16
|
+
"typecheck": "tsc --noEmit"
|
|
9
17
|
},
|
|
10
18
|
"repository": {
|
|
11
19
|
"type": "git",
|
|
@@ -26,5 +34,17 @@
|
|
|
26
34
|
"homepage": "https://github.com/sylvain441/natural-time-js#readme",
|
|
27
35
|
"dependencies": {
|
|
28
36
|
"astronomy-engine": "^2.1.19"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@babel/core": "^7.26.9",
|
|
40
|
+
"@babel/preset-env": "^7.26.9",
|
|
41
|
+
"@types/jest": "^29.5.14",
|
|
42
|
+
"@types/node": "^22.13.9",
|
|
43
|
+
"babel-jest": "^29.7.0",
|
|
44
|
+
"clean-jsdoc-theme": "^4.3.0",
|
|
45
|
+
"jest": "^29.7.0",
|
|
46
|
+
"jsdoc": "^4.0.4",
|
|
47
|
+
"ts-jest": "^29.2.6",
|
|
48
|
+
"typescript": "^5.8.2"
|
|
29
49
|
}
|
|
30
50
|
}
|
|
Binary file
|
package/index.js
DELETED
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module natural-time-js
|
|
3
|
-
* @description Natural time implementation for JavaScript - translates artificial dates to natural dates
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Seasons } from 'astronomy-engine';
|
|
7
|
-
|
|
8
|
-
const yearContextCache = new Map();
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Calculates year context for natural date calculations
|
|
12
|
-
* @param {number} artificialYear - Gregorian calendar year
|
|
13
|
-
* @param {number} longitude - Longitude in degrees (-180 to 180)
|
|
14
|
-
* @returns {{
|
|
15
|
-
* start: number,
|
|
16
|
-
* duration: number
|
|
17
|
-
* }} Year context with start timestamp and duration
|
|
18
|
-
* @private
|
|
19
|
-
*/
|
|
20
|
-
const calculateYearContext = (artificialYear, longitude) => {
|
|
21
|
-
const cacheKey = `${artificialYear}_${longitude}`;
|
|
22
|
-
|
|
23
|
-
if (yearContextCache.has(cacheKey)) {
|
|
24
|
-
return yearContextCache.get(cacheKey);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const startSolstice = Seasons(artificialYear).dec_solstice.date;
|
|
28
|
-
const endSolstice = Seasons(artificialYear + 1).dec_solstice.date;
|
|
29
|
-
|
|
30
|
-
const startNewYear = Date.UTC(
|
|
31
|
-
startSolstice.getUTCFullYear(),
|
|
32
|
-
startSolstice.getUTCMonth(),
|
|
33
|
-
startSolstice.getUTCDate() + (startSolstice.getUTCHours() >= 12 ? 1 : 0),
|
|
34
|
-
12, 0, 0
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
const endNewYear = Date.UTC(
|
|
38
|
-
endSolstice.getUTCFullYear(),
|
|
39
|
-
endSolstice.getUTCMonth(),
|
|
40
|
-
endSolstice.getUTCDate() + (endSolstice.getUTCHours() >= 12 ? 1 : 0),
|
|
41
|
-
12, 0, 0
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
const context = {
|
|
45
|
-
start: parseInt(startNewYear + (-longitude + 180) * NaturalDate.MILLISECONDS_PER_DAY/360),
|
|
46
|
-
duration: (endNewYear - startNewYear) / NaturalDate.MILLISECONDS_PER_DAY
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
yearContextCache.set(cacheKey, context);
|
|
50
|
-
return context;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Natural date class for converting artificial (Gregorian) dates to natural time
|
|
55
|
-
* @class
|
|
56
|
-
*/
|
|
57
|
-
export class NaturalDate {
|
|
58
|
-
/**
|
|
59
|
-
* Number of milliseconds in a day
|
|
60
|
-
* @constant {number}
|
|
61
|
-
*/
|
|
62
|
-
static MILLISECONDS_PER_DAY = 86400000; // 24 * 60 * 60 * 1000
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* End date of the artificial time era (December 21, 2012 12:00 UTC)
|
|
66
|
-
* @constant {number}
|
|
67
|
-
*/
|
|
68
|
-
static END_OF_ARTIFICIAL_TIME = Date.UTC(2012, 11, 21, 12, 0, 0);
|
|
69
|
-
|
|
70
|
-
unixTime; // Artificial gregorian date (UNIX timestamp)
|
|
71
|
-
longitude; // Longitude (between -180° to +180°)
|
|
72
|
-
|
|
73
|
-
year; // Current year (year 1: 2012/2013)
|
|
74
|
-
moon; // Current moon (between 1 and 14)
|
|
75
|
-
|
|
76
|
-
week; // Current week (between 1 and 53)
|
|
77
|
-
weekOfMoon; // Current week of the moon (between 1 and 4)
|
|
78
|
-
|
|
79
|
-
day; // Number of days passed since END_OF_ARTIFICIAL_TIME
|
|
80
|
-
dayOfYear; // Current day of the year (between 1 and 366)
|
|
81
|
-
dayOfMoon; // Current day of the moon (between 1 and 28)
|
|
82
|
-
dayOfWeek; // Current day of the week (between 1 and 7)
|
|
83
|
-
|
|
84
|
-
isRainbowDay; // True if current day is rainbow day
|
|
85
|
-
|
|
86
|
-
time; // Current time (between 0 and 359°999999...)
|
|
87
|
-
|
|
88
|
-
yearStart; // Beginning of the year at the current longitude (UNIX timestamp)
|
|
89
|
-
yearDuration; // Numbers of days in the current year (between 365 and 366)
|
|
90
|
-
nadir; // Beginning of the day at the current longitude (UNIX timestamp)
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Creates a new natural date instance
|
|
94
|
-
* @param {Date|number} date - JavaScript Date object or Unix timestamp
|
|
95
|
-
* @param {number} longitude - Longitude in degrees (-180 to 180)
|
|
96
|
-
* @throws {Error} If inputs are invalid
|
|
97
|
-
*/
|
|
98
|
-
constructor(date, longitude) {
|
|
99
|
-
// Validate longitude
|
|
100
|
-
if (longitude !== undefined && (typeof longitude !== 'number' || longitude < -180 || longitude > 180)) {
|
|
101
|
-
throw new Error('Longitude must be between -180 and +180');
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
date = new Date(date || Date.now());
|
|
105
|
-
if (isNaN(date.getTime())) {
|
|
106
|
-
throw new Error('Invalid date provided');
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
this.unixTime = date.getTime();
|
|
110
|
-
this.longitude = longitude || 0;
|
|
111
|
-
|
|
112
|
-
if (Number.isFinite(this.unixTime)) {
|
|
113
|
-
// YEAR START & DURATION
|
|
114
|
-
let yearContext = calculateYearContext(date.getUTCFullYear()-1, longitude);
|
|
115
|
-
|
|
116
|
-
// Correction if between the beginning of natural year and the end of the artificial year
|
|
117
|
-
if(this.unixTime - yearContext.start >= yearContext.duration * NaturalDate.MILLISECONDS_PER_DAY)
|
|
118
|
-
yearContext = calculateYearContext(date.getUTCFullYear(), longitude);
|
|
119
|
-
|
|
120
|
-
this.yearStart = yearContext.start;
|
|
121
|
-
this.yearDuration = yearContext.duration;
|
|
122
|
-
|
|
123
|
-
let timeSinceLocalYearStart = (this.unixTime - this.yearStart) / NaturalDate.MILLISECONDS_PER_DAY;
|
|
124
|
-
|
|
125
|
-
// YEAR
|
|
126
|
-
this.year = new Date(this.yearStart).getUTCFullYear() - new Date(NaturalDate.END_OF_ARTIFICIAL_TIME).getUTCFullYear()+ 1;
|
|
127
|
-
|
|
128
|
-
// MOON
|
|
129
|
-
this.moon = parseInt(timeSinceLocalYearStart / 28) + 1;
|
|
130
|
-
|
|
131
|
-
// HEPTAD
|
|
132
|
-
this.week = parseInt(timeSinceLocalYearStart / 7) + 1;
|
|
133
|
-
this.weekOfMoon = parseInt(timeSinceLocalYearStart / 7) % 4 + 1;
|
|
134
|
-
|
|
135
|
-
// DAY
|
|
136
|
-
this.day = parseInt((this.unixTime - (NaturalDate.END_OF_ARTIFICIAL_TIME + (-longitude + 180) * NaturalDate.MILLISECONDS_PER_DAY/360)) / NaturalDate.MILLISECONDS_PER_DAY);
|
|
137
|
-
this.dayOfYear = parseInt(timeSinceLocalYearStart) + 1;
|
|
138
|
-
this.dayOfMoon = parseInt(timeSinceLocalYearStart) % 28 + 1;
|
|
139
|
-
this.dayOfWeek = parseInt(timeSinceLocalYearStart) % 7 + 1;
|
|
140
|
-
|
|
141
|
-
// NADIR (i.e day start, midnight)
|
|
142
|
-
this.nadir = this.yearStart + parseInt(timeSinceLocalYearStart) * NaturalDate.MILLISECONDS_PER_DAY;
|
|
143
|
-
|
|
144
|
-
// TIME
|
|
145
|
-
this.time = (this.unixTime - this.nadir) * 360 / NaturalDate.MILLISECONDS_PER_DAY;
|
|
146
|
-
|
|
147
|
-
// RAINBOW DAY
|
|
148
|
-
this.isRainbowDay = this.dayOfYear > 13*28;
|
|
149
|
-
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
throw 'Argument must be a Date object or a Unix timestamp';
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Gets the time of an astronomical event in natural degrees
|
|
158
|
-
* @param {Date|number} event - Event timestamp
|
|
159
|
-
* @returns {number|false} Event time in natural degrees (0-360) or false if out of range
|
|
160
|
-
*/
|
|
161
|
-
getTimeOfEvent(event) {
|
|
162
|
-
// Make sure it's a unix timestamp
|
|
163
|
-
event = new Date(event).getTime();
|
|
164
|
-
|
|
165
|
-
// Check if not out of range
|
|
166
|
-
if(event < this.nadir || event > this.nadir + NaturalDate.MILLISECONDS_PER_DAY)
|
|
167
|
-
return false;
|
|
168
|
-
|
|
169
|
-
return (event - this.nadir) * (360 / NaturalDate.MILLISECONDS_PER_DAY);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Exports current date as a full ISO formatted string
|
|
174
|
-
* @returns ex: "004)04)01 113°00 NT+95"
|
|
175
|
-
*/
|
|
176
|
-
toString() {
|
|
177
|
-
return `${this.toDateString()} ${this.toTimeString()} ${this.toLongitudeString()}`;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Exports current date as a formatted string
|
|
182
|
-
* @param {String} separator Default: ')'
|
|
183
|
-
* @returns ex: "004)04)01"
|
|
184
|
-
*/
|
|
185
|
-
toDateString(separator = ')') {
|
|
186
|
-
if(this.isRainbowDay) {
|
|
187
|
-
return `${this.toYearString()}${separator}RAINBOW${this.dayOfYear === 366 ? "+" : ""}`;
|
|
188
|
-
}
|
|
189
|
-
return `${this.toYearString()}${separator}${this.toMoonString()}${separator}${this.toDayOfMoonString()}`;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Exports current time as a formatted string
|
|
194
|
-
* @param {*} decimals Number of decimals (precision) Default: 2
|
|
195
|
-
* @param {*} rounding Allows granular incrementation of time (ex: 100°10 => 100°15 => 100°20) Default: 1
|
|
196
|
-
* @returns ex: "113°00" "202°" "63°1234"
|
|
197
|
-
*/
|
|
198
|
-
toTimeString(decimals = 2, rounding = 1) {
|
|
199
|
-
let timeUnity = parseInt(this.time);
|
|
200
|
-
let timeDecimals = parseInt((this.time % 1) * Math.pow(10, decimals));
|
|
201
|
-
timeDecimals = Math.floor(timeDecimals / rounding) * rounding;
|
|
202
|
-
|
|
203
|
-
return String(timeUnity).padStart(3, '0') + "°" + (decimals ? String(timeDecimals).padStart(decimals, '0') : '');
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Exports current longitude as a formatted string
|
|
208
|
-
* @param {Number} decimals Number of decimals (precision) Default: 1
|
|
209
|
-
* @returns ex: "NT+95.4" "NT-78"
|
|
210
|
-
*/
|
|
211
|
-
toLongitudeString(decimals = 1) {
|
|
212
|
-
if(Math.abs(Math.round(this.longitude)) == 0)
|
|
213
|
-
return "NTZ"
|
|
214
|
-
|
|
215
|
-
return "NT" + (this.longitude > 0 ? "+" : "") + this.longitude.toFixed(decimals) + "";
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Exports current year as a formatted string
|
|
220
|
-
* @returns ex: "004"
|
|
221
|
-
*/
|
|
222
|
-
toYearString() {
|
|
223
|
-
return (this.year < 0 ? "-" : "") + String(Math.abs(this.year)).padStart(3, '0');
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Exports current moon as a formatted string
|
|
228
|
-
* @returns ex: "04"
|
|
229
|
-
*/
|
|
230
|
-
toMoonString() {
|
|
231
|
-
return String(this.moon).padStart(2, '0');
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Exports current day of the moon as a formatted string
|
|
236
|
-
* @returns ex: "01"
|
|
237
|
-
*/
|
|
238
|
-
toDayOfMoonString() {
|
|
239
|
-
return String(this.dayOfMoon).padStart(2, '0');
|
|
240
|
-
}
|
|
241
|
-
}
|