krz/moonrise

Night-sky companion for iPhone, Apple Watch and Vision Pro: what's up tonight, the Moon, planets, launches and missions, and a Library of the solar system. SwiftUI app with a Cloudflare Workers backend. astronomy cloudflare-workers ios night-sky space swiftui visionos watchos

Packages/MoonriseKit/Sources/SkyEngine/DailySummary.swift

04b2b1d914788057bcd20945b876847386eb8f65
moonrise/Packages/MoonriseKit/Sources/SkyEngine/DailySummary.swift history · blame · raw

60 lines · 2775 bytes

3 symbols in this file
 1import Foundation
 2
 3/// The text of the daily summary notification: tonight's Moon, planets, the best satellite pass and the day's events.
 4public struct DailySummary: Sendable, Equatable {
 5    public var title: String
 6    public var body: String
 7
 8    public static func phaseName(_ angle: Double) -> String {
 9        switch angle {
10        case ..<10, 350...: "New moon"
11        case ..<80: "Waxing crescent"
12        case ..<100: "First quarter"
13        case ..<170: "Waxing gibbous"
14        case ..<190: "Full moon"
15        case ..<260: "Waning gibbous"
16        case ..<280: "Last quarter"
17        default: "Waning crescent"
18        }
19    }
20
21    /// Summary for the night following `date`. `passes` and `events` may cover any span; only those
22    /// for this night (passes) and this calendar day or night (events) are used.
23    public static func make(at date: Date, observer: Observer, passes: [SatellitePass], events: [SkyEvent],
24                            locale: Locale = .current, timeZone: TimeZone = .current) throws -> DailySummary {
25        let tonight = try Tonight(at: date, from: observer)
26        let time = Date.FormatStyle(date: .omitted, time: .shortened, locale: locale, timeZone: timeZone)
27        var calendar = Calendar(identifier: .gregorian)
28        calendar.timeZone = timeZone
29
30        let lit = Int((tonight.moonIllumination * 100).rounded())
31        let title = "Tonight: \(phaseName(tonight.moonPhase).lowercased().replacingOccurrences(of: " moon", with: "")) Moon, \(lit)% lit"
32
33        var lines: [String] = []
34
35        let todays = events.filter { calendar.isDate($0.date, inSameDayAs: date) || ($0.date >= tonight.start && $0.date <= tonight.end) }
36        if !todays.isEmpty {
37            lines.append(todays.map(\.title).joined(separator: ". ") + ".")
38        }
39
40        let planets = tonight.planets.filter { $0.best != nil }.map { $0.body.rawValue.capitalized }
41        if planets.isEmpty {
42            lines.append("No bright planets above 10° tonight.")
43        } else {
44            lines.append("Up tonight: \(ListFormatter.localizedString(byJoining: planets)).")
45        }
46
47        if let pass = passes
48            .filter({ $0.visible && $0.featured && $0.rise >= tonight.start.addingTimeInterval(-3600) && $0.rise <= tonight.end })
49            .max(by: { $0.maxAltitude < $1.maxAltitude }) {
50            let when = pass.rise.formatted(time) + (calendar.isDate(pass.rise, inSameDayAs: date) ? "" : " tomorrow")
51            lines.append("\(pass.displayName) passes at \(when), \(Int(pass.maxAltitude.rounded()))° high.")
52        }
53
54        if let sunset = tonight.sunset {
55            lines.append("Sunset \(sunset.formatted(time)).")
56        }
57
58        return DailySummary(title: title, body: lines.joined(separator: " "))
59    }
60}