Packages/MoonriseKit/Tests/SkyEngineTests/DarknessTests.swift
91 lines · 4704 bytes
1import Foundation
2import Testing
3@testable import SkyEngine
4
5private let central = TimeZone(identifier: "America/Chicago")!
6private let hm = Date.FormatStyle(date: .omitted, time: .shortened, locale: Locale(identifier: "en_GB"), timeZone: central)
7
8@Test func astronomicalDuskAndDawnInChicago() throws {
9 // NOAA solar position algorithm: Sun at −18° at 00:38:30 and 10:33:40 UT on 18 October 2026.
10 let t = try Tonight(at: ut(18, 0), from: chicago)
11 let dark = try #require(t.dark)
12 #expect(abs(dark.start.timeIntervalSince(ut(24, 38) + 30)) < 60)
13 #expect(abs(dark.end.timeIntervalSince(ut(34, 33) + 40)) < 60)
14}
15
16@Test func alreadyDarkStartsNow() throws {
17 // 21:00 CDT; NOAA has the Sun back at −18° at 10:32:30 UT.
18 let t = try Tonight(at: observed, from: chicago)
19 let dark = try #require(t.dark)
20 #expect(dark.start == observed)
21 #expect(abs(dark.end.timeIntervalSince(ut(10, 32) + 30)) < 60)
22}
23
24@Test func noDarknessInTromsoInJune() throws {
25 let tromso = Observer(latitude: 69.65, longitude: 18.96)
26 #expect(try SkyEngine.darkness(from: utc("2026-06-21T12:00:00Z"), to: utc("2026-06-22T12:00:00Z"), observer: tromso) == nil)
27}
28
29@Test func waxingMoonSetsInTheEvening() throws {
30 // 35% lit and up at 21:00 CDT on 16 October 2026; JPL Horizons has it setting at 02:49 UT.
31 let t = try Tonight(at: observed, from: chicago)
32 #expect(t.moonUp.count == 1)
33 #expect(t.moonUp[0].start == t.start)
34 #expect(t.moonrise == nil)
35 let set = try #require(t.moonset)
36 #expect(abs(set.timeIntervalSince(ut(2, 49))) < 90)
37 #expect(t.moonAndDarkness(time: hm) == "Dark 21:00–5:32. Moon 35% lit, sets 21:48.")
38}
39
40@Test func moonUpAlternatesWithRiseAndSet() throws {
41 let start = ut(18, 0)
42 let up = try SkyEngine.moonUp(from: start, to: start + 3 * 86_400, observer: chicago)
43 #expect(up.count >= 3)
44 for (a, b) in zip(up, up.dropFirst()) {
45 #expect(b.start > a.end)
46 #expect(try SkyEngine.horizontal(.moon, at: a.end + b.start.timeIntervalSince(a.end) / 2, from: chicago).altitude < 0)
47 }
48 for i in up {
49 #expect(try SkyEngine.horizontal(.moon, at: i.start + i.duration / 2, from: chicago).altitude > 0)
50 }
51}
52
53@Test func moonAndDarknessText() {
54 let base = ut(24, 0)
55 func at(_ h: Double) -> Date { base + h * 3600 }
56 let night = DateInterval(start: at(-1), end: at(11))
57 let dark = DateInterval(start: at(0.5), end: at(10.5))
58 func text(_ up: [DateInterval], dark: DateInterval? = dark) -> String {
59 SkyEngine.moonAndDarkness(night: night, dark: dark, moonUp: up, moonIllumination: 0.624, time: hm)
60 }
61 #expect(text([]) == "Dark 19:30–5:30. Moon down throughout.")
62 #expect(text([DateInterval(start: at(-1), end: at(0.2))]) == "Dark 19:30–5:30. Moon down throughout.")
63 #expect(text([night]) == "Dark 19:30–5:30. Moon 62% lit, up throughout.")
64 #expect(text([DateInterval(start: at(-1), end: at(4))]) == "Dark 19:30–5:30. Moon 62% lit, sets 23:00.")
65 #expect(text([DateInterval(start: at(8), end: at(11))]) == "Dark 19:30–5:30. Moon 62% lit, rises 3:00.")
66 #expect(text([DateInterval(start: at(-1), end: at(2)), DateInterval(start: at(9), end: at(11))])
67 == "Dark 19:30–5:30. Moon 62% lit, sets 21:00, rises 4:00.")
68 #expect(text([DateInterval(start: at(-1), end: at(0))], dark: nil) == "The sky does not get fully dark. Moon 62% lit, sets 19:00.")
69}
70
71@Test func moonNoteOnFaintPasses() {
72 func pass(_ name: String, peak: Date, visible: Bool = true) -> SatellitePass {
73 SatellitePass(name: name, rise: peak - 180, set: peak + 180, peak: peak, maxAltitude: 60, riseAzimuth: 240, setAzimuth: 60, visible: visible)
74 }
75 // Moon 35% lit and up at 02:00 UT on 17 October 2026, set by 04:00.
76 #expect(pass("HST", peak: observed).moonNote(from: chicago) == "Moon up, 35% lit")
77 #expect(pass("SL-8 R/B", peak: observed).moonNote(from: chicago) == "Moon up, 35% lit")
78 #expect(pass("HST", peak: ut(4, 0)).moonNote(from: chicago) == nil)
79 #expect(pass("HST", peak: observed, visible: false).moonNote(from: chicago) == nil)
80 #expect(pass("ISS (ZARYA)", peak: observed).moonNote(from: chicago) == nil)
81 #expect(pass("CSS (TIANHE)", peak: observed).moonNote(from: chicago) == nil)
82 // 2026-10-12 23:30 UT: 6% lit crescent, 3° up after sunset.
83 #expect(pass("HST", peak: utc("2026-10-12T23:30:00Z")).moonNote(from: chicago) == nil)
84}
85
86@Test func moonlitWhenUpAndLitDuringDarkness() throws {
87 // 35% lit and up until 21:48 CDT.
88 #expect(try Tonight(at: observed, from: chicago).moonlit)
89 // New moon on 12 August 2026.
90 #expect(try !Tonight(at: utc("2026-08-12T18:00:00Z"), from: chicago).moonlit)
91}