siderust-js 0.1.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/.github/workflows/ci.yml +166 -0
- package/.gitmodules +9 -0
- package/CHANGELOG.md +26 -0
- package/LICENSE +661 -0
- package/README.md +138 -0
- package/package.json +12 -0
- package/qtty-js/.github/workflows/ci.yml +151 -0
- package/qtty-js/.gitmodules +3 -0
- package/qtty-js/CHANGELOG.md +31 -0
- package/qtty-js/LICENSE +661 -0
- package/qtty-js/README.md +132 -0
- package/qtty-js/package.json +20 -0
- package/qtty-js/qtty/.github/workflows/ci.yml +155 -0
- package/qtty-js/qtty/CHANGELOG.md +120 -0
- package/qtty-js/qtty/Cargo.lock +1462 -0
- package/qtty-js/qtty/Cargo.toml +12 -0
- package/qtty-js/qtty/LICENSE +661 -0
- package/qtty-js/qtty/README.md +9 -0
- package/qtty-js/qtty/qtty/Cargo.toml +41 -0
- package/qtty-js/qtty/qtty/README.md +8 -0
- package/qtty-js/qtty/qtty/examples/angles.rs +14 -0
- package/qtty-js/qtty/qtty/examples/astronomy.rs +17 -0
- package/qtty-js/qtty/qtty/examples/dimensional_arithmetic.rs +83 -0
- package/qtty-js/qtty/qtty/examples/python_integration.rs +61 -0
- package/qtty-js/qtty/qtty/examples/quickstart.rs +15 -0
- package/qtty-js/qtty/qtty/examples/ratios.rs +12 -0
- package/qtty-js/qtty/qtty/examples/serde_with_unit.rs +234 -0
- package/qtty-js/qtty/qtty/examples/serialization.rs +141 -0
- package/qtty-js/qtty/qtty/examples/serialization_advanced.rs +155 -0
- package/qtty-js/qtty/qtty/src/f32.rs +108 -0
- package/qtty-js/qtty/qtty/src/f64.rs +30 -0
- package/qtty-js/qtty/qtty/src/i128.rs +111 -0
- package/qtty-js/qtty/qtty/src/i16.rs +111 -0
- package/qtty-js/qtty/qtty/src/i32.rs +111 -0
- package/qtty-js/qtty/qtty/src/i64.rs +111 -0
- package/qtty-js/qtty/qtty/src/i8.rs +111 -0
- package/qtty-js/qtty/qtty/src/lib.rs +238 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std/Cargo.lock +83 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std/Cargo.toml +10 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std/src/lib.rs +7 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std-alloc/Cargo.lock +83 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std-alloc/Cargo.toml +10 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std-alloc/src/lib.rs +7 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-std/Cargo.lock +83 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-std/Cargo.toml +10 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-std/src/lib.rs +5 -0
- package/qtty-js/qtty/qtty/tests/integration_tests.rs +529 -0
- package/qtty-js/qtty/qtty/tests/qtty_vec_feature_matrix.rs +58 -0
- package/qtty-js/qtty/qtty-core/Cargo.toml +41 -0
- package/qtty-js/qtty/qtty-core/README.md +8 -0
- package/qtty-js/qtty/qtty-core/examples/diesel_integration.rs +145 -0
- package/qtty-js/qtty/qtty-core/examples/quantity_db_serde.rs +215 -0
- package/qtty-js/qtty/qtty-core/src/dimension.rs +249 -0
- package/qtty-js/qtty/qtty-core/src/feature_diesel.rs +318 -0
- package/qtty-js/qtty/qtty-core/src/feature_pyo3.rs +27 -0
- package/qtty-js/qtty/qtty-core/src/feature_serde.rs +203 -0
- package/qtty-js/qtty/qtty-core/src/feature_tiberius.rs +28 -0
- package/qtty-js/qtty/qtty-core/src/lib.rs +744 -0
- package/qtty-js/qtty/qtty-core/src/macros.rs +93 -0
- package/qtty-js/qtty/qtty-core/src/quantity.rs +810 -0
- package/qtty-js/qtty/qtty-core/src/scalar.rs +1742 -0
- package/qtty-js/qtty/qtty-core/src/unit.rs +332 -0
- package/qtty-js/qtty/qtty-core/src/units/angular.rs +1228 -0
- package/qtty-js/qtty/qtty-core/src/units/area.rs +243 -0
- package/qtty-js/qtty/qtty-core/src/units/frequency.rs +179 -0
- package/qtty-js/qtty/qtty-core/src/units/length.rs +1270 -0
- package/qtty-js/qtty/qtty-core/src/units/mass.rs +488 -0
- package/qtty-js/qtty/qtty-core/src/units/mod.rs +26 -0
- package/qtty-js/qtty/qtty-core/src/units/power.rs +324 -0
- package/qtty-js/qtty/qtty-core/src/units/time.rs +667 -0
- package/qtty-js/qtty/qtty-core/src/units/unitless.rs +212 -0
- package/qtty-js/qtty/qtty-core/src/units/velocity.rs +210 -0
- package/qtty-js/qtty/qtty-core/src/units/volume.rs +269 -0
- package/qtty-js/qtty/qtty-core/tests/core.rs +628 -0
- package/qtty-js/qtty/qtty-core/tests/diesel.rs +461 -0
- package/qtty-js/qtty/qtty-core/tests/integers.rs +632 -0
- package/qtty-js/qtty/qtty-core/tests/no_cross_unit_ops.rs +35 -0
- package/qtty-js/qtty/qtty-core/tests/pyo3.rs +334 -0
- package/qtty-js/qtty/qtty-core/tests/quantity_f32.rs +276 -0
- package/qtty-js/qtty/qtty-core/tests/scalar_decimal.rs +258 -0
- package/qtty-js/qtty/qtty-core/tests/scalar_f32.rs +286 -0
- package/qtty-js/qtty/qtty-core/tests/scalar_f64_real.rs +287 -0
- package/qtty-js/qtty/qtty-core/tests/scalar_rational.rs +260 -0
- package/qtty-js/qtty/qtty-core/tests/serde.rs +256 -0
- package/qtty-js/qtty/qtty-core/tests/tiberius.rs +208 -0
- package/qtty-js/qtty/qtty-derive/Cargo.toml +23 -0
- package/qtty-js/qtty/qtty-derive/README.md +8 -0
- package/qtty-js/qtty/qtty-derive/src/lib.rs +340 -0
- package/qtty-js/qtty/qtty-ffi/ARCHITECTURE.md +3 -0
- package/qtty-js/qtty/qtty-ffi/Cargo.toml +31 -0
- package/qtty-js/qtty/qtty-ffi/README.md +9 -0
- package/qtty-js/qtty/qtty-ffi/build.rs +326 -0
- package/qtty-js/qtty/qtty-ffi/cbindgen.toml +105 -0
- package/qtty-js/qtty/qtty-ffi/include/qtty_ffi.h +1126 -0
- package/qtty-js/qtty/qtty-ffi/src/ffi.rs +1251 -0
- package/qtty-js/qtty/qtty-ffi/src/ffi_serde.rs +294 -0
- package/qtty-js/qtty/qtty-ffi/src/helpers.rs +310 -0
- package/qtty-js/qtty/qtty-ffi/src/lib.rs +229 -0
- package/qtty-js/qtty/qtty-ffi/src/macros.rs +121 -0
- package/qtty-js/qtty/qtty-ffi/src/registry.rs +274 -0
- package/qtty-js/qtty/qtty-ffi/src/types.rs +620 -0
- package/qtty-js/qtty/qtty-ffi/tests/integration_tests.rs +842 -0
- package/qtty-js/qtty/qtty-ffi/units.csv +156 -0
- package/qtty-js/qtty/qtty-ffi/units.csv.md +3 -0
- package/qtty-js/qtty-node/.prettierignore +6 -0
- package/qtty-js/qtty-node/.prettierrc.json +6 -0
- package/qtty-js/qtty-node/README.md +250 -0
- package/qtty-js/qtty-node/c8.config.json +11 -0
- package/qtty-js/qtty-node/eslint.config.js +31 -0
- package/qtty-js/qtty-node/examples/arithmetic.mjs +64 -0
- package/qtty-js/qtty-node/examples/astronomy.mjs +90 -0
- package/qtty-js/qtty-node/examples/quickstart.mjs +36 -0
- package/qtty-js/qtty-node/examples/serialization.mjs +125 -0
- package/qtty-js/qtty-node/examples/unit_factories.mjs +74 -0
- package/qtty-js/qtty-node/index.d.ts +219 -0
- package/qtty-js/qtty-node/index.js +323 -0
- package/qtty-js/qtty-node/lib/DerivedQuantity.js +122 -0
- package/qtty-js/qtty-node/lib/Quantity.js +151 -0
- package/qtty-js/qtty-node/lib/backend.js +25 -0
- package/qtty-js/qtty-node/native.cjs +306 -0
- package/qtty-js/qtty-node/package-lock.json +3223 -0
- package/qtty-js/qtty-node/package.json +70 -0
- package/qtty-js/qtty-node/units.d.ts +299 -0
- package/qtty-js/qtty-node/units.js +210 -0
- package/qtty-js/qtty-web/Cargo.lock +767 -0
- package/qtty-js/qtty-web/Cargo.toml +21 -0
- package/qtty-js/qtty-web/index.d.ts +140 -0
- package/qtty-js/qtty-web/index.js +20 -0
- package/qtty-js/qtty-web/lib/DerivedQuantity.js +58 -0
- package/qtty-js/qtty-web/lib/Quantity.js +75 -0
- package/qtty-js/qtty-web/lib/backend.js +80 -0
- package/qtty-js/qtty-web/package.json +45 -0
- package/qtty-js/qtty-web/src/lib.rs +111 -0
- package/qtty-js/scripts/ci.sh +73 -0
- package/scripts/ci.sh +123 -0
- package/siderust-core/Cargo.lock +787 -0
- package/siderust-core/Cargo.toml +18 -0
- package/siderust-core/DEDUPLICATION.md +124 -0
- package/siderust-core/src/body.rs +120 -0
- package/siderust-core/src/events.rs +184 -0
- package/siderust-core/src/lib.rs +20 -0
- package/siderust-core/src/observer.rs +55 -0
- package/siderust-core/src/position.rs +213 -0
- package/siderust-node/.prettierignore +7 -0
- package/siderust-node/.prettierrc.json +6 -0
- package/siderust-node/Cargo.lock +906 -0
- package/siderust-node/Cargo.toml +29 -0
- package/siderust-node/README.md +109 -0
- package/siderust-node/__test__/index.test.mjs +248 -0
- package/siderust-node/build.rs +5 -0
- package/siderust-node/c8.config.json +3 -0
- package/siderust-node/eslint.config.js +31 -0
- package/siderust-node/examples/01_basic_coordinates.mjs +24 -0
- package/siderust-node/examples/02_coordinate_transformations.mjs +25 -0
- package/siderust-node/examples/03_all_frames_conversions.mjs +26 -0
- package/siderust-node/examples/04_all_center_conversions.mjs +24 -0
- package/siderust-node/examples/05_target_tracking.mjs +22 -0
- package/siderust-node/examples/06_night_events.mjs +18 -0
- package/siderust-node/examples/07_moon_properties.mjs +21 -0
- package/siderust-node/examples/08_solar_system.mjs +19 -0
- package/siderust-node/examples/09_star_observability.mjs +22 -0
- package/siderust-node/examples/10_time_periods.mjs +9 -0
- package/siderust-node/examples/11_serialization.mjs +31 -0
- package/siderust-node/examples/12_runtime_ephemeris.mjs +27 -0
- package/siderust-node/examples/13_coordinate_operations.mjs +20 -0
- package/siderust-node/index.d.ts +623 -0
- package/siderust-node/index.js +79 -0
- package/siderust-node/lib/Observer.js +112 -0
- package/siderust-node/lib/Star.js +118 -0
- package/siderust-node/lib/backend.js +63 -0
- package/siderust-node/lib/wrappers.js +566 -0
- package/siderust-node/main.js +20 -0
- package/siderust-node/native.cjs +360 -0
- package/siderust-node/package-lock.json +3261 -0
- package/siderust-node/package.json +71 -0
- package/siderust-node/src/body.rs +74 -0
- package/siderust-node/src/coordinates.rs +372 -0
- package/siderust-node/src/ephemeris.rs +462 -0
- package/siderust-node/src/events.rs +577 -0
- package/siderust-node/src/lib.rs +43 -0
- package/siderust-node/src/observer.rs +132 -0
- package/siderust-node/src/phase.rs +218 -0
- package/siderust-node/src/position.rs +292 -0
- package/siderust-node/src/star.rs +200 -0
- package/siderust-web/Cargo.lock +855 -0
- package/siderust-web/Cargo.toml +34 -0
- package/siderust-web/README.md +100 -0
- package/siderust-web/__test__/index.test.mjs +118 -0
- package/siderust-web/examples/github-pages/README.md +31 -0
- package/siderust-web/examples/github-pages/index.html +135 -0
- package/siderust-web/index.d.ts +311 -0
- package/siderust-web/index.js +66 -0
- package/siderust-web/lib/Observer.js +103 -0
- package/siderust-web/lib/Star.js +116 -0
- package/siderust-web/lib/backend.js +400 -0
- package/siderust-web/lib/wrappers.js +512 -0
- package/siderust-web/package.json +55 -0
- package/siderust-web/src/body.rs +69 -0
- package/siderust-web/src/coordinates.rs +302 -0
- package/siderust-web/src/ephemeris.rs +456 -0
- package/siderust-web/src/events.rs +520 -0
- package/siderust-web/src/lib.rs +51 -0
- package/siderust-web/src/observer.rs +117 -0
- package/siderust-web/src/phase.rs +190 -0
- package/siderust-web/src/position.rs +291 -0
- package/siderust-web/src/star.rs +178 -0
- package/tempoch-js/.github/workflows/ci.yml +142 -0
- package/tempoch-js/.gitmodules +3 -0
- package/tempoch-js/CHANGELOG.md +25 -0
- package/tempoch-js/LICENSE +661 -0
- package/tempoch-js/README.md +126 -0
- package/tempoch-js/package.json +20 -0
- package/tempoch-js/scripts/ci.sh +73 -0
- package/tempoch-js/tempoch/.github/workflows/ci.yml +113 -0
- package/tempoch-js/tempoch/CHANGELOG.md +82 -0
- package/tempoch-js/tempoch/Cargo.lock +947 -0
- package/tempoch-js/tempoch/Cargo.toml +3 -0
- package/tempoch-js/tempoch/LICENSE +661 -0
- package/tempoch-js/tempoch/README.md +76 -0
- package/tempoch-js/tempoch/tempoch/Cargo.toml +27 -0
- package/tempoch-js/tempoch/tempoch/examples/periods.rs +45 -0
- package/tempoch-js/tempoch/tempoch/examples/quickstart.rs +13 -0
- package/tempoch-js/tempoch/tempoch/src/lib.rs +49 -0
- package/tempoch-js/tempoch/tempoch/tests/integration.rs +57 -0
- package/tempoch-js/tempoch/tempoch-core/Cargo.toml +24 -0
- package/tempoch-js/tempoch/tempoch-core/src/delta_t.rs +345 -0
- package/tempoch-js/tempoch/tempoch-core/src/instant.rs +811 -0
- package/tempoch-js/tempoch/tempoch-core/src/julian_date_ext.rs +142 -0
- package/tempoch-js/tempoch/tempoch-core/src/lib.rs +81 -0
- package/tempoch-js/tempoch/tempoch-core/src/period.rs +1168 -0
- package/tempoch-js/tempoch/tempoch-core/src/scales.rs +779 -0
- package/tempoch-js/tempoch/tempoch-ffi/Cargo.lock +889 -0
- package/tempoch-js/tempoch/tempoch-ffi/Cargo.toml +26 -0
- package/tempoch-js/tempoch/tempoch-ffi/build.rs +24 -0
- package/tempoch-js/tempoch/tempoch-ffi/cbindgen.toml +30 -0
- package/tempoch-js/tempoch/tempoch-ffi/src/error.rs +19 -0
- package/tempoch-js/tempoch/tempoch-ffi/src/lib.rs +82 -0
- package/tempoch-js/tempoch/tempoch-ffi/src/period.rs +101 -0
- package/tempoch-js/tempoch/tempoch-ffi/src/time.rs +711 -0
- package/tempoch-js/tempoch/tempoch-ffi/tests/ffi.rs +265 -0
- package/tempoch-js/tempoch-node/.prettierignore +6 -0
- package/tempoch-js/tempoch-node/.prettierrc.json +6 -0
- package/tempoch-js/tempoch-node/Cargo.lock +496 -0
- package/tempoch-js/tempoch-node/Cargo.toml +29 -0
- package/tempoch-js/tempoch-node/README.md +265 -0
- package/tempoch-js/tempoch-node/__test__/index.test.mjs +598 -0
- package/tempoch-js/tempoch-node/build.rs +5 -0
- package/tempoch-js/tempoch-node/c8.config.json +3 -0
- package/tempoch-js/tempoch-node/eslint.config.js +31 -0
- package/tempoch-js/tempoch-node/examples/periods.mjs +79 -0
- package/tempoch-js/tempoch-node/examples/quickstart.mjs +71 -0
- package/tempoch-js/tempoch-node/examples/timescales.mjs +92 -0
- package/tempoch-js/tempoch-node/index.d.ts +280 -0
- package/tempoch-js/tempoch-node/index.js +32 -0
- package/tempoch-js/tempoch-node/lib/JulianDate.js +176 -0
- package/tempoch-js/tempoch-node/lib/ModifiedJulianDate.js +156 -0
- package/tempoch-js/tempoch-node/lib/Period.js +133 -0
- package/tempoch-js/tempoch-node/lib/backend.js +38 -0
- package/tempoch-js/tempoch-node/lib/qttyCompat.js +92 -0
- package/tempoch-js/tempoch-node/native.cjs +317 -0
- package/tempoch-js/tempoch-node/package-lock.json +3223 -0
- package/tempoch-js/tempoch-node/package.json +56 -0
- package/tempoch-js/tempoch-node/src/lib.rs +573 -0
- package/tempoch-js/tempoch-web/Cargo.toml +23 -0
- package/tempoch-js/tempoch-web/index.d.ts +95 -0
- package/tempoch-js/tempoch-web/index.js +27 -0
- package/tempoch-js/tempoch-web/lib/JulianDate.js +170 -0
- package/tempoch-js/tempoch-web/lib/ModifiedJulianDate.js +145 -0
- package/tempoch-js/tempoch-web/lib/Period.js +121 -0
- package/tempoch-js/tempoch-web/lib/backend.js +118 -0
- package/tempoch-js/tempoch-web/package.json +46 -0
- package/tempoch-js/tempoch-web/src/lib.rs +184 -0
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { dirname, join } from 'node:path';
|
|
7
|
+
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const {
|
|
11
|
+
JulianDate,
|
|
12
|
+
ModifiedJulianDate,
|
|
13
|
+
Period,
|
|
14
|
+
jdToMjd,
|
|
15
|
+
mjdToJd,
|
|
16
|
+
julianCenturies,
|
|
17
|
+
julianYears,
|
|
18
|
+
jdFromDate,
|
|
19
|
+
mjdFromDate,
|
|
20
|
+
jdToDate,
|
|
21
|
+
mjdToDate,
|
|
22
|
+
jdDifference,
|
|
23
|
+
mjdDifference,
|
|
24
|
+
version,
|
|
25
|
+
} = require(join(__dirname, '..', 'index.js'));
|
|
26
|
+
|
|
27
|
+
// ── Constants ──────────────────────────────────────────────────────────────
|
|
28
|
+
const J2000_JD = 2_451_545.0;
|
|
29
|
+
const J2000_MJD = 51_544.5;
|
|
30
|
+
const EPSILON = 1e-9; // relative tolerance for day-precision values
|
|
31
|
+
|
|
32
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
33
|
+
// JulianDate
|
|
34
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
35
|
+
|
|
36
|
+
describe('JulianDate', () => {
|
|
37
|
+
// ── constructor ─────────────────────────────────────────────────────
|
|
38
|
+
describe('constructor', () => {
|
|
39
|
+
it('stores the raw value', () => {
|
|
40
|
+
const jd = new JulianDate(J2000_JD);
|
|
41
|
+
assert.equal(jd.value, J2000_JD);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('accepts zero', () => {
|
|
45
|
+
assert.equal(new JulianDate(0).value, 0);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('accepts negative values (pre-Julian epoch)', () => {
|
|
49
|
+
assert.ok(new JulianDate(-10_000).value < 0);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('throws on NaN', () => {
|
|
53
|
+
assert.throws(() => new JulianDate(NaN), /finite/i);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('throws on +Infinity', () => {
|
|
57
|
+
assert.throws(() => new JulianDate(Infinity), /finite/i);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('throws on -Infinity', () => {
|
|
61
|
+
assert.throws(() => new JulianDate(-Infinity), /finite/i);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// ── factories ───────────────────────────────────────────────────────
|
|
66
|
+
describe('j2000()', () => {
|
|
67
|
+
it('returns JD 2 451 545.0', () => {
|
|
68
|
+
assert.equal(JulianDate.j2000().value, J2000_JD);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('fromDate()', () => {
|
|
73
|
+
it('round-trips a well-known UTC date', () => {
|
|
74
|
+
// J2000 UTC is ~11:58:56Z due to ΔT; create a date from that back-converted ts
|
|
75
|
+
const jd0 = JulianDate.j2000();
|
|
76
|
+
const d = jd0.toDate();
|
|
77
|
+
const jdRT = JulianDate.fromDate(d);
|
|
78
|
+
assert.ok(Math.abs(jdRT.value - J2000_JD) < 1e-6);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('preserves millisecond precision on round-trip', () => {
|
|
82
|
+
const now = new Date(Date.UTC(2025, 5, 15, 10, 30, 45, 500));
|
|
83
|
+
const jd = JulianDate.fromDate(now);
|
|
84
|
+
const back = jd.toDate();
|
|
85
|
+
assert.ok(Math.abs(back.getTime() - now.getTime()) < 2); // ≤2 ms
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('fromUtc()', () => {
|
|
90
|
+
it('matches fromDate for the same UTC instant', () => {
|
|
91
|
+
// 2025-06-15 10:30:00 UTC
|
|
92
|
+
const d = new Date(Date.UTC(2025, 5, 15, 10, 30, 0));
|
|
93
|
+
const jd1 = JulianDate.fromDate(d);
|
|
94
|
+
const jd2 = JulianDate.fromUtc(2025, 6, 15, 10, 30, 0);
|
|
95
|
+
assert.ok(Math.abs(jd1.value - jd2.value) < 1e-10);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('supports sub-second precision via fractional second', () => {
|
|
99
|
+
const jd1 = JulianDate.fromUtc(2025, 6, 15, 0, 0, 0.5);
|
|
100
|
+
const jd2 = JulianDate.fromUtc(2025, 6, 15, 0, 0, 0);
|
|
101
|
+
const diffDays = jd1.value - jd2.value;
|
|
102
|
+
// 0.5 s / 86400 s·day⁻¹; f64 precision at JD ~2461037 allows ~2e-10 abs error
|
|
103
|
+
assert.ok(Math.abs(diffDays - 0.5 / 86400) < 1e-9);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('throws on invalid components', () => {
|
|
107
|
+
assert.throws(() => JulianDate.fromUtc(2025, 13, 1, 0, 0, 0), /invalid/i);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// ── conversions ─────────────────────────────────────────────────────
|
|
112
|
+
describe('toMjd()', () => {
|
|
113
|
+
it('converts J2000 correctly', () => {
|
|
114
|
+
const mjd = JulianDate.j2000().toMjd();
|
|
115
|
+
assert.ok(Math.abs(mjd.value - J2000_MJD) < EPSILON);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('returns a ModifiedJulianDate instance', () => {
|
|
119
|
+
const mjd = new JulianDate(J2000_JD).toMjd();
|
|
120
|
+
assert.equal(typeof mjd.value, 'number');
|
|
121
|
+
assert.equal(typeof mjd.toJd, 'function');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('toDate()', () => {
|
|
126
|
+
it('returns a JavaScript Date', () => {
|
|
127
|
+
const d = JulianDate.j2000().toDate();
|
|
128
|
+
assert.ok(d instanceof Date);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('J2000.0 UTC is approximately 2000-01-01T11:58:56Z (ΔT ≈ 63.8 s)', () => {
|
|
132
|
+
const d = JulianDate.j2000().toDate();
|
|
133
|
+
assert.equal(d.getUTCFullYear(), 2000);
|
|
134
|
+
assert.equal(d.getUTCMonth(), 0); // January
|
|
135
|
+
assert.equal(d.getUTCDate(), 1);
|
|
136
|
+
assert.equal(d.getUTCHours(), 11);
|
|
137
|
+
// ΔT puts the minute at 58 (63+ seconds before noon TT)
|
|
138
|
+
assert.equal(d.getUTCMinutes(), 58);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// ── epoch quantities ─────────────────────────────────────────────────
|
|
143
|
+
describe('julianCenturies()', () => {
|
|
144
|
+
it('is exactly 0 for J2000.0', () => {
|
|
145
|
+
const q = JulianDate.j2000().julianCenturies();
|
|
146
|
+
assert.equal(q.value, 0);
|
|
147
|
+
assert.equal(q.unit, 'JulianCentury');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('is 1.0 for J2100.0 (JD + 36525)', () => {
|
|
151
|
+
const jd2100 = new JulianDate(J2000_JD + 36_525.0);
|
|
152
|
+
const q = jd2100.julianCenturies();
|
|
153
|
+
assert.ok(Math.abs(q.value - 1.0) < 1e-12);
|
|
154
|
+
assert.equal(q.unit, 'JulianCentury');
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('julianYears()', () => {
|
|
159
|
+
it('is 0 for J2000.0', () => {
|
|
160
|
+
const q = JulianDate.j2000().julianYears();
|
|
161
|
+
assert.equal(q.value, 0);
|
|
162
|
+
assert.equal(q.unit, 'JulianYear');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('is 1 for J2001.0 (JD + 365.25)', () => {
|
|
166
|
+
const jd2001 = new JulianDate(J2000_JD + 365.25);
|
|
167
|
+
const q = jd2001.julianYears();
|
|
168
|
+
assert.ok(Math.abs(q.value - 1.0) < 1e-12);
|
|
169
|
+
assert.equal(q.unit, 'JulianYear');
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// ── arithmetic ───────────────────────────────────────────────────────
|
|
174
|
+
describe('addDays()', () => {
|
|
175
|
+
it('adds a positive duration', () => {
|
|
176
|
+
const jd = new JulianDate(J2000_JD);
|
|
177
|
+
assert.ok(Math.abs(jd.addDays(1).value - (J2000_JD + 1)) < EPSILON);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('adds a negative duration (subtract)', () => {
|
|
181
|
+
const jd = new JulianDate(J2000_JD);
|
|
182
|
+
assert.ok(Math.abs(jd.addDays(-1).value - (J2000_JD - 1)) < EPSILON);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('returns a new JulianDate (immutable)', () => {
|
|
186
|
+
const jd = new JulianDate(J2000_JD);
|
|
187
|
+
const jd2 = jd.addDays(10);
|
|
188
|
+
assert.equal(jd.value, J2000_JD); // original unchanged
|
|
189
|
+
assert.equal(jd2.value, J2000_JD + 10);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('throws on NaN days', () => {
|
|
193
|
+
assert.throws(() => new JulianDate(J2000_JD).addDays(NaN), /finite/i);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('difference()', () => {
|
|
198
|
+
it('self − self is 0', () => {
|
|
199
|
+
const jd = new JulianDate(J2000_JD);
|
|
200
|
+
const d = jd.difference(jd);
|
|
201
|
+
assert.equal(d.value, 0);
|
|
202
|
+
assert.equal(d.unit, 'Day');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('later − earlier is positive', () => {
|
|
206
|
+
const jd1 = new JulianDate(J2000_JD + 10);
|
|
207
|
+
const jd2 = new JulianDate(J2000_JD);
|
|
208
|
+
assert.ok(Math.abs(jd1.difference(jd2).value - 10) < EPSILON);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('earlier − later is negative', () => {
|
|
212
|
+
const jd1 = new JulianDate(J2000_JD);
|
|
213
|
+
const jd2 = new JulianDate(J2000_JD + 10);
|
|
214
|
+
assert.ok(Math.abs(jd1.difference(jd2).value + 10) < EPSILON);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// ── formatting ───────────────────────────────────────────────────────
|
|
219
|
+
describe('format()', () => {
|
|
220
|
+
it('returns a non-empty string containing the value', () => {
|
|
221
|
+
const s = JulianDate.j2000().format();
|
|
222
|
+
assert.ok(typeof s === 'string' && s.length > 0);
|
|
223
|
+
assert.ok(s.includes('2451545'));
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
229
|
+
// ModifiedJulianDate
|
|
230
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
231
|
+
|
|
232
|
+
describe('ModifiedJulianDate', () => {
|
|
233
|
+
describe('constructor', () => {
|
|
234
|
+
it('stores raw value', () => {
|
|
235
|
+
const mjd = new ModifiedJulianDate(J2000_MJD);
|
|
236
|
+
assert.equal(mjd.value, J2000_MJD);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('throws on NaN', () => {
|
|
240
|
+
assert.throws(() => new ModifiedJulianDate(NaN), /finite/i);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('throws on Infinity', () => {
|
|
244
|
+
assert.throws(() => new ModifiedJulianDate(Infinity), /finite/i);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe('fromDate()', () => {
|
|
249
|
+
it('round-trips a well-known date', () => {
|
|
250
|
+
const now = new Date(Date.UTC(2025, 0, 1, 0, 0, 0));
|
|
251
|
+
const mjd = ModifiedJulianDate.fromDate(now);
|
|
252
|
+
const back = mjd.toDate();
|
|
253
|
+
assert.ok(Math.abs(back.getTime() - now.getTime()) < 2);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe('fromUtc()', () => {
|
|
258
|
+
it('MJD epoch (1858-11-17 00:00:00 UTC) is MJD 0', () => {
|
|
259
|
+
const mjd = ModifiedJulianDate.fromUtc(1858, 11, 17, 0, 0, 0);
|
|
260
|
+
// ΔT in 1858 is large (>5 min), so only verify rough order of magnitude
|
|
261
|
+
assert.ok(Math.abs(mjd.value) < 1);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
describe('toJd()', () => {
|
|
266
|
+
it('converts J2000 MJD back to J2000 JD', () => {
|
|
267
|
+
const jd = new ModifiedJulianDate(J2000_MJD).toJd();
|
|
268
|
+
assert.ok(Math.abs(jd.value - J2000_JD) < EPSILON);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('toDate()', () => {
|
|
273
|
+
it('restores the original date on round-trip', () => {
|
|
274
|
+
const d = new Date(Date.UTC(2020, 3, 15, 8, 0, 0));
|
|
275
|
+
const mjd = ModifiedJulianDate.fromDate(d);
|
|
276
|
+
const back = mjd.toDate();
|
|
277
|
+
assert.ok(Math.abs(back.getTime() - d.getTime()) < 2);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe('addDays()', () => {
|
|
282
|
+
it('adds fractional days', () => {
|
|
283
|
+
const mjd = new ModifiedJulianDate(J2000_MJD);
|
|
284
|
+
const mjd2 = mjd.addDays(0.5);
|
|
285
|
+
assert.ok(Math.abs(mjd2.value - (J2000_MJD + 0.5)) < EPSILON);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
describe('difference()', () => {
|
|
290
|
+
it('1 day apart gives 1', () => {
|
|
291
|
+
const a = new ModifiedJulianDate(J2000_MJD + 1);
|
|
292
|
+
const b = new ModifiedJulianDate(J2000_MJD);
|
|
293
|
+
const d = a.difference(b);
|
|
294
|
+
assert.ok(Math.abs(d.value - 1) < EPSILON);
|
|
295
|
+
assert.equal(d.unit, 'Day');
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
describe('format()', () => {
|
|
300
|
+
it('includes the MJD value', () => {
|
|
301
|
+
const s = new ModifiedJulianDate(J2000_MJD).format();
|
|
302
|
+
assert.ok(typeof s === 'string' && s.includes('51544'));
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
308
|
+
// Period
|
|
309
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
310
|
+
|
|
311
|
+
describe('Period', () => {
|
|
312
|
+
describe('constructor', () => {
|
|
313
|
+
it('creates a valid period', () => {
|
|
314
|
+
const p = new Period(J2000_MJD, J2000_MJD + 1);
|
|
315
|
+
assert.equal(p.startMjd, J2000_MJD);
|
|
316
|
+
assert.equal(p.endMjd, J2000_MJD + 1);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('accepts zero-length period (start === end)', () => {
|
|
320
|
+
const p = new Period(J2000_MJD, J2000_MJD);
|
|
321
|
+
assert.equal(p.durationDays(), 0);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('throws when start > end', () => {
|
|
325
|
+
assert.throws(() => new Period(J2000_MJD + 1, J2000_MJD), /start.*end|end.*start/i);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('throws on non-finite endpoints', () => {
|
|
329
|
+
assert.throws(() => new Period(NaN, J2000_MJD), /finite/i);
|
|
330
|
+
assert.throws(() => new Period(J2000_MJD, Infinity), /finite/i);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
describe('fromDates()', () => {
|
|
335
|
+
it('creates a period from two JS Dates', () => {
|
|
336
|
+
const start = new Date(Date.UTC(2020, 0, 1));
|
|
337
|
+
const end = new Date(Date.UTC(2021, 0, 1));
|
|
338
|
+
const p = Period.fromDates(start, end);
|
|
339
|
+
// The MJD-based duration may differ from the calendar duration by a small
|
|
340
|
+
// ΔT correction (~0.1 s/yr). Verify against the free-function equivalent.
|
|
341
|
+
const expected = mjdFromDate(end) - mjdFromDate(start);
|
|
342
|
+
assert.ok(Math.abs(p.durationDays() - expected) < 1e-9);
|
|
343
|
+
// Sanity: roughly 366 days (2020 is a leap year)
|
|
344
|
+
assert.ok(p.durationDays() > 365.9 && p.durationDays() < 366.1);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('throws when start > end', () => {
|
|
348
|
+
const start = new Date(Date.UTC(2021, 0, 1));
|
|
349
|
+
const end = new Date(Date.UTC(2020, 0, 1));
|
|
350
|
+
assert.throws(() => Period.fromDates(start, end), /start.*end|end.*start/i);
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
describe('accessors', () => {
|
|
355
|
+
it('start returns a ModifiedJulianDate', () => {
|
|
356
|
+
const p = new Period(J2000_MJD, J2000_MJD + 5);
|
|
357
|
+
const s = p.start;
|
|
358
|
+
assert.equal(s.value, J2000_MJD);
|
|
359
|
+
assert.equal(typeof s.toJd, 'function');
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('end returns a ModifiedJulianDate', () => {
|
|
363
|
+
const p = new Period(J2000_MJD, J2000_MJD + 5);
|
|
364
|
+
assert.equal(p.end.value, J2000_MJD + 5);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
describe('durationDays()', () => {
|
|
369
|
+
it('returns correct duration', () => {
|
|
370
|
+
const p = new Period(J2000_MJD, J2000_MJD + 7.5);
|
|
371
|
+
assert.ok(Math.abs(p.durationDays() - 7.5) < EPSILON);
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
describe('contains()', () => {
|
|
376
|
+
it('includes start (closed left)', () => {
|
|
377
|
+
const p = new Period(J2000_MJD, J2000_MJD + 1);
|
|
378
|
+
assert.ok(p.contains(J2000_MJD));
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('includes interior points', () => {
|
|
382
|
+
const p = new Period(J2000_MJD, J2000_MJD + 1);
|
|
383
|
+
assert.ok(p.contains(J2000_MJD + 0.5));
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('excludes end (open right)', () => {
|
|
387
|
+
const p = new Period(J2000_MJD, J2000_MJD + 1);
|
|
388
|
+
assert.ok(!p.contains(J2000_MJD + 1));
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('excludes points before start', () => {
|
|
392
|
+
const p = new Period(J2000_MJD, J2000_MJD + 1);
|
|
393
|
+
assert.ok(!p.contains(J2000_MJD - 0.1));
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
describe('intersection()', () => {
|
|
398
|
+
it('returns overlapping sub-period', () => {
|
|
399
|
+
const a = new Period(J2000_MJD, J2000_MJD + 2);
|
|
400
|
+
const b = new Period(J2000_MJD + 1, J2000_MJD + 3);
|
|
401
|
+
const i = a.intersection(b);
|
|
402
|
+
assert.notEqual(i, null);
|
|
403
|
+
assert.ok(Math.abs(i.startMjd - (J2000_MJD + 1)) < EPSILON);
|
|
404
|
+
assert.ok(Math.abs(i.endMjd - (J2000_MJD + 2)) < EPSILON);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('returns null for non-overlapping periods', () => {
|
|
408
|
+
const a = new Period(J2000_MJD, J2000_MJD + 1);
|
|
409
|
+
const b = new Period(J2000_MJD + 2, J2000_MJD + 3);
|
|
410
|
+
assert.equal(a.intersection(b), null);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('returns null for adjacent periods (shared endpoint only)', () => {
|
|
414
|
+
const a = new Period(J2000_MJD, J2000_MJD + 1);
|
|
415
|
+
const b = new Period(J2000_MJD + 1, J2000_MJD + 2);
|
|
416
|
+
assert.equal(a.intersection(b), null);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('intersection of a period with itself is itself', () => {
|
|
420
|
+
const p = new Period(J2000_MJD, J2000_MJD + 5);
|
|
421
|
+
const i = p.intersection(p);
|
|
422
|
+
assert.notEqual(i, null);
|
|
423
|
+
assert.ok(Math.abs(i.durationDays() - 5) < EPSILON);
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
describe('toUtc()', () => {
|
|
428
|
+
it('returns startMs and endMs as numbers', () => {
|
|
429
|
+
const p = new Period(J2000_MJD, J2000_MJD + 1);
|
|
430
|
+
const { startMs, endMs } = p.toUtc();
|
|
431
|
+
assert.equal(typeof startMs, 'number');
|
|
432
|
+
assert.equal(typeof endMs, 'number');
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it('endMs − startMs equals durationDays × 86400000', () => {
|
|
436
|
+
const p = new Period(J2000_MJD, J2000_MJD + 2);
|
|
437
|
+
const { startMs, endMs } = p.toUtc();
|
|
438
|
+
const diffDays = (endMs - startMs) / 86_400_000;
|
|
439
|
+
assert.ok(Math.abs(diffDays - 2) < 1e-6);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('startMs reconstructs a valid Date', () => {
|
|
443
|
+
const p = new Period(J2000_MJD, J2000_MJD + 1);
|
|
444
|
+
const { startMs } = p.toUtc();
|
|
445
|
+
const d = new Date(startMs);
|
|
446
|
+
assert.ok(d instanceof Date && !isNaN(d));
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
describe('format()', () => {
|
|
451
|
+
it('returns a string containing the MJD values', () => {
|
|
452
|
+
const p = new Period(J2000_MJD, J2000_MJD + 1);
|
|
453
|
+
const s = p.format();
|
|
454
|
+
assert.ok(typeof s === 'string');
|
|
455
|
+
assert.ok(s.includes('51544'));
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
461
|
+
// Free functions
|
|
462
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
463
|
+
|
|
464
|
+
describe('jdToMjd() / mjdToJd()', () => {
|
|
465
|
+
it('jdToMjd(J2000) = 51544.5', () => {
|
|
466
|
+
assert.ok(Math.abs(jdToMjd(J2000_JD) - J2000_MJD) < EPSILON);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it('mjdToJd(51544.5) = J2000_JD', () => {
|
|
470
|
+
assert.ok(Math.abs(mjdToJd(J2000_MJD) - J2000_JD) < EPSILON);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it('round-trips', () => {
|
|
474
|
+
const jd = 2_460_000.5;
|
|
475
|
+
assert.ok(Math.abs(mjdToJd(jdToMjd(jd)) - jd) < EPSILON);
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
describe('julianCenturies() / julianYears()', () => {
|
|
480
|
+
it('julianCenturies(J2000) = 0', () => {
|
|
481
|
+
assert.equal(julianCenturies(J2000_JD), 0);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it('julianCenturies(J2000 + 36525) = 1', () => {
|
|
485
|
+
assert.ok(Math.abs(julianCenturies(J2000_JD + 36_525) - 1) < 1e-12);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('julianYears(J2000) = 0', () => {
|
|
489
|
+
assert.equal(julianYears(J2000_JD), 0);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it('julianYears(J2000 + 365.25) = 1', () => {
|
|
493
|
+
assert.ok(Math.abs(julianYears(J2000_JD + 365.25) - 1) < 1e-12);
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
describe('jdFromDate() / mjdFromDate()', () => {
|
|
498
|
+
it('jdFromDate round-trips via jdToDate', () => {
|
|
499
|
+
const d = new Date(Date.UTC(2025, 0, 1, 0, 0, 0));
|
|
500
|
+
const jd = jdFromDate(d);
|
|
501
|
+
assert.equal(typeof jd, 'number');
|
|
502
|
+
assert.ok(isFinite(jd));
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it('mjdFromDate returns expected MJD for a known date', () => {
|
|
506
|
+
const d = new Date(Date.UTC(2025, 0, 1, 0, 0, 0));
|
|
507
|
+
const mjd = mjdFromDate(d);
|
|
508
|
+
// J2025.0 is approx MJD 60676 (rough check)
|
|
509
|
+
assert.ok(mjd > 60_000 && mjd < 70_000);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it('jdFromDate and JulianDate.fromDate agree', () => {
|
|
513
|
+
const d = new Date(Date.UTC(2010, 5, 15, 12, 0, 0));
|
|
514
|
+
const jd1 = jdFromDate(d);
|
|
515
|
+
const jd2 = JulianDate.fromDate(d).value;
|
|
516
|
+
assert.ok(Math.abs(jd1 - jd2) < EPSILON);
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
describe('jdToDate() / mjdToDate()', () => {
|
|
521
|
+
it('jdToDate returns a JS Date', () => {
|
|
522
|
+
const d = jdToDate(J2000_JD);
|
|
523
|
+
assert.ok(d instanceof Date);
|
|
524
|
+
assert.ok(!isNaN(d));
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it('mjdToDate returns a JS Date', () => {
|
|
528
|
+
const d = mjdToDate(J2000_MJD);
|
|
529
|
+
assert.ok(d instanceof Date);
|
|
530
|
+
assert.ok(!isNaN(d));
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it('jdToDate and mjdToDate agree for the same epoch', () => {
|
|
534
|
+
const d1 = jdToDate(J2000_JD);
|
|
535
|
+
const d2 = mjdToDate(J2000_MJD);
|
|
536
|
+
// Both represent the same instant; allow ≤ 1 ms rounding
|
|
537
|
+
assert.ok(Math.abs(d1.getTime() - d2.getTime()) <= 1);
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
describe('jdDifference() / mjdDifference()', () => {
|
|
542
|
+
it('jdDifference(a, b) = a − b', () => {
|
|
543
|
+
assert.ok(Math.abs(jdDifference(J2000_JD + 5, J2000_JD) - 5) < EPSILON);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it('jdDifference is antisymmetric', () => {
|
|
547
|
+
const d = jdDifference(J2000_JD, J2000_JD + 3);
|
|
548
|
+
assert.ok(Math.abs(d + 3) < EPSILON);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it('mjdDifference(a, b) = a − b', () => {
|
|
552
|
+
assert.ok(Math.abs(mjdDifference(J2000_MJD + 2, J2000_MJD) - 2) < EPSILON);
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
describe('version()', () => {
|
|
557
|
+
it('returns a semver string', () => {
|
|
558
|
+
const v = version();
|
|
559
|
+
assert.match(v, /^\d+\.\d+\.\d+/);
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
564
|
+
// End-to-end / cross-type workflows
|
|
565
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
566
|
+
|
|
567
|
+
describe('End-to-end workflows', () => {
|
|
568
|
+
it('JD → MJD → Date → MJD round-trip preserves value', () => {
|
|
569
|
+
const jd = new JulianDate(J2000_JD + 100);
|
|
570
|
+
const mjd = jd.toMjd();
|
|
571
|
+
const d = mjd.toDate();
|
|
572
|
+
const mjd2 = ModifiedJulianDate.fromDate(d);
|
|
573
|
+
assert.ok(Math.abs(mjd2.value - mjd.value) < 1e-6);
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it('Period built from JD difference is consistent', () => {
|
|
577
|
+
const start = new JulianDate(J2000_JD).toMjd().value;
|
|
578
|
+
const end = new JulianDate(J2000_JD + 365.25).toMjd().value;
|
|
579
|
+
const p = new Period(start, end);
|
|
580
|
+
assert.ok(Math.abs(p.durationDays() - 365.25) < 1e-6);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it('adding 36525 days to J2000 gives J2100', () => {
|
|
584
|
+
const jd2100 = new JulianDate(J2000_JD).addDays(36_525);
|
|
585
|
+
assert.ok(Math.abs(jd2100.julianCenturies().value - 1.0) < 1e-10);
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
it('Period.fromDates ↔ startMjd/endMjd agreement', () => {
|
|
589
|
+
const d1 = new Date(Date.UTC(2020, 0, 1));
|
|
590
|
+
const d2 = new Date(Date.UTC(2020, 6, 1));
|
|
591
|
+
const p = Period.fromDates(d1, d2);
|
|
592
|
+
// Reconstruct via free functions
|
|
593
|
+
const startExpected = mjdFromDate(d1);
|
|
594
|
+
const endExpected = mjdFromDate(d2);
|
|
595
|
+
assert.ok(Math.abs(p.startMjd - startExpected) < 1e-9);
|
|
596
|
+
assert.ok(Math.abs(p.endMjd - endExpected) < 1e-9);
|
|
597
|
+
});
|
|
598
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const js = require('@eslint/js');
|
|
2
|
+
const globals = require('globals');
|
|
3
|
+
|
|
4
|
+
const baseRules = {
|
|
5
|
+
...js.configs.recommended.rules,
|
|
6
|
+
'no-console': 'off',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
module.exports = [
|
|
10
|
+
{
|
|
11
|
+
ignores: ['coverage/**', 'node_modules/**', 'target/**', '*.d.ts', '*.node', 'index.js'],
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
files: ['**/*.js'],
|
|
15
|
+
languageOptions: {
|
|
16
|
+
ecmaVersion: 'latest',
|
|
17
|
+
sourceType: 'commonjs',
|
|
18
|
+
globals: globals.node,
|
|
19
|
+
},
|
|
20
|
+
rules: baseRules,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
files: ['**/*.mjs'],
|
|
24
|
+
languageOptions: {
|
|
25
|
+
ecmaVersion: 'latest',
|
|
26
|
+
sourceType: 'module',
|
|
27
|
+
globals: globals.node,
|
|
28
|
+
},
|
|
29
|
+
rules: baseRules,
|
|
30
|
+
},
|
|
31
|
+
];
|