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.
@@ -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":"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"}
@@ -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": "1.1.1",
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
- "test": "echo \"Error: no test specified\" && exit 1"
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
- }