Skip to content

Instantly share code, notes, and snippets.

@theoknock
Last active November 26, 2024 02:39
Show Gist options
  • Save theoknock/9ca9fd9df909e1e5f74711c7ebd467ae to your computer and use it in GitHub Desktop.
Save theoknock/9ca9fd9df909e1e5f74711c7ebd467ae to your computer and use it in GitHub Desktop.
Sunrise Sunset Calculator
import SwiftUI
import CoreLocation
struct ContentView: View {
@StateObject private var locationManager = LocationManager()
@State private var sunrise: Date = Date().addingTimeInterval(TimeInterval(TimeZone.current.secondsFromGMT(for: Date())))
@State private var sunset: Date = Date().addingTimeInterval(TimeInterval(TimeZone.current.secondsFromGMT(for: Date())))
@State private var nextDaySunrise: Date = Date().addingTimeInterval(TimeInterval(TimeZone.current.secondsFromGMT(for: Date())))
@State private var hours: [PlanetaryHourSegmenter.PlanetaryHourSegment] = []
@State private var longitudes: [LongitudeSegmenter.Segment] = []
@State private var date = Date().addingTimeInterval(TimeInterval(TimeZone.current.secondsFromGMT(for: Date())))
@State private var showResults = false // Add this state variable
let calculator = SunriseSunsetCalculator()
let planetaryHourSegmenter = PlanetaryHourSegmenter()
var body: some View {
VStack(spacing: 20) {
Text("Sunrise and Sunset Calculator")
.font(.title)
.padding()
DatePicker("Select Date", selection: $date, displayedComponents: .date)
.datePickerStyle(GraphicalDatePickerStyle())
.padding()
if let location = locationManager.location {
Text("Latitude: \(location.coordinate.latitude)")
Text("Longitude: \(location.coordinate.longitude)")
Button("Calculate") {
// let calculator = SunriseSunsetCalculator()
sunrise = calculator.calculateSunrise(for: date, latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)!
print(sunrise.addingTimeInterval(TimeInterval(TimeZone.current.secondsFromGMT(for: sunrise))))
sunset = calculator.calculateSunset(for: date, latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)!
let nextDay = Calendar.current.date(byAdding: .day, value: 1, to: sunrise)!
nextDaySunrise = calculator.calculateSunrise(for: nextDay, latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)!
// let segmenter = DayNightSegmenter()
hours = planetaryHourSegmenter.calculatePlanetaryHourSegments(sunrise: sunrise, sunset: sunset, nextDaySunrise: nextDaySunrise)
showResults = true // Set to true when calculate button is tapped
}
.padding()
} else {
Text("Getting location...")
}
if showResults { // Conditionally show sunrise and sunset
TextField("\(sunrise.formatted(.dateTime))", text: Binding<String>(
get: { sunrise.formatted(.dateTime) },
set: { newString in
if let newDate = DateFormatter().date(from: newString) {
sunrise = newDate
}
}
))
TextField("\(sunset.formatted(.dateTime))", text: Binding<String>(
get: { sunset.formatted(.dateTime) },
set: { newString in
if let newDate = DateFormatter().date(from: newString) {
sunset = newDate
}
}
))
TextField("\(nextDaySunrise.formatted(.dateTime))", text: Binding<String>(
get: { nextDaySunrise.formatted(.dateTime) },
set: { newString in
if let newDate = DateFormatter().date(from: newString) {
nextDaySunrise = newDate
}
}
))
}
}
.padding()
.onAppear {
locationManager.requestLocation()
}
Spacer()
VStack {
List(0..<hours.count, id: \.self) { index in
let segment = hours[index]
Text("Hour \(segment.label)\n\(segment.start.formatted(.dateTime))\n\(segment.end.formatted(.dateTime))")
.padding() // Optional: Add padding for better spacing
.background(index < 12 ? Color.yellow.opacity(0.3) : Color.blue.opacity(0.3)) // Conditional background
.cornerRadius(8) // Optional: Rounded corners
}
}
}
}
// MARK: - Location Manager
class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
@Published var location: CLLocation?
private let manager = CLLocationManager()
override init() {
super.init()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
}
func requestLocation() {
manager.requestWhenInUseAuthorization()
manager.requestLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.first {
self.location = location
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Failed to get location: \(error.localizedDescription)")
}
}
// MARK: - Sunrise/Sunset Calculator
import Foundation
class SunriseSunsetCalculator {
func calculateSunrise(for date: Date, latitude: Double, longitude: Double) -> Date? {
return calculate(for: date, latitude: latitude, longitude: longitude, isSunrise: true)
}
func calculateSunset(for date: Date, latitude: Double, longitude: Double) -> Date? {
return calculate(for: date, latitude: latitude, longitude: longitude, isSunrise: false)
}
private func calculate(for date: Date, latitude: Double, longitude: Double, isSunrise: Bool) -> Date? {
let calendar = Calendar.current
let day = calendar.component(.day, from: date)
let month = calendar.component(.month, from: date)
let year = calendar.component(.year, from: date)
// Step 1: Calculate day of the year
let N1 = floor(275 * Double(month) / 9)
let N2 = floor((Double(month) + 9) / 12)
let N3 = (1 + floor((Double(year) - 4 * floor(Double(year) / 4) + 2) / 3))
let N = N1 - (N2 * N3) + Double(day) - 30
// Step 2: Convert longitude to hour value and calculate approximate time
let lngHour = longitude / 15
let t = isSunrise ? N + ((6 - lngHour) / 24) : N + ((18 - lngHour) / 24)
// Step 3: Calculate Sun's mean anomaly
let M = (0.9856 * t) - 3.289
// Step 4: Calculate Sun's true longitude
var L = M + (1.916 * sin(M * .pi / 180)) + (0.020 * sin(2 * M * .pi / 180)) + 282.634
L = fmod(L, 360)
// Step 5: Calculate Sun's right ascension
var RA = atan(0.91764 * tan(L * .pi / 180)) * 180 / .pi
RA = fmod(RA, 360)
// Step 5b: Adjust RA to the same quadrant as L
let Lquadrant = floor(L / 90) * 90
let RAquadrant = floor(RA / 90) * 90
RA = RA + (Lquadrant - RAquadrant)
// Step 5c: Convert RA to hours
RA = RA / 15
// Step 6: Calculate Sun's declination
let sinDec = 0.39782 * sin(L * .pi / 180)
let cosDec = cos(asin(sinDec))
// Step 7a: Calculate Sun's local hour angle
let cosH = (cos(90.833 * .pi / 180) - (sinDec * sin(latitude * .pi / 180))) / (cosDec * cos(latitude * .pi / 180))
guard cosH <= 1, cosH >= -1 else {
return nil // Return nil if no sunrise/sunset occurs
}
// Step 7b: Calculate H
var H = isSunrise ? 360 - acos(cosH) * 180 / .pi : acos(cosH) * 180 / .pi
H = H / 15
// Step 8: Calculate local mean time of rising/setting
let T = H + RA - (0.06571 * t) - 6.622
// Step 9: Adjust to UTC
var UT = T - lngHour
UT = UT < 0 ? UT + 24 : UT // Ensure UT is within 0-24 range
UT = UT >= 24 ? UT - 24 : UT
// Step 10: Convert to local time
let localTime = UT + Double(TimeZone.current.secondsFromGMT()) / 3600
let localTimeAdjusted = localTime < 0 ? localTime + 24 : localTime // Handle negative times
let hour = Int(localTimeAdjusted)
let minute = Int((localTimeAdjusted - Double(hour)) * 60)
let second = Int((((localTimeAdjusted - Double(hour)) * 60) - Double(minute)) * 60)
// Create a Date object for the result
var components = calendar.dateComponents([.year, .month, .day], from: date)
components.hour = hour % 24
components.minute = minute % 60
components.second = second % 60 // Add the seconds component
let utcDate = calendar.date(from: components)
// let localTimeZone = TimeZone.current
// let secondsFromUTC = localTimeZone.secondsFromGMT(for: utcDate ?? date)
// let localDate = utcDate?.addingTimeInterval(TimeInterval(secondsFromUTC))
//
return utcDate
}
}
import Foundation
struct PlanetaryHourSegmenter {
struct PlanetaryHourSegment {
let label: Int
let start: Date
let end: Date
}
private let calendar = Calendar.current
private let dateFormatter: DateFormatter
init() {
dateFormatter = DateFormatter()
dateFormatter.timeStyle = .short
}
func calculatePlanetaryHourSegments(sunrise: Date, sunset: Date, nextDaySunrise: Date) -> [PlanetaryHourSegment] {
var segments: [PlanetaryHourSegment] = []
// Calculate day and night durations
guard let dayDuration = calendar.dateComponents([.second], from: sunrise, to: sunset).second,
let nightDuration = calendar.dateComponents([.second], from: sunset, to: nextDaySunrise).second else {
return segments
}
// let day: (Double, Double) = (sunrise.timeIntervalSince1970, sunset.timeIntervalSince1970)
// let night: (Double, Double) = (sunset.timeIntervalSince1970, nextDaySunrise.timeIntervalSince1970)
// let day_range: Double = abs(sunrise.timeIntervalSince1970 - sunset.timeIntervalSince1970)
// let night_range: Double = abs(sunset.timeIntervalSince1970 - nextDaySunrise.timeIntervalSince1970)
//// var planetaryHours: [PlanetaryHourSegment] = []
let elements = 12
// for index in 0..<elements {
// segments.append(PlanetaryHourSegment(label: index,
// start: Date(timeIntervalSince1970: (sunrise.timeIntervalSince1970 + (Double(index) day_range / Double((~(-(elements))))))),
// end: Date(timeIntervalSince1970: (sunrise.timeIntervalSince1970 + (Double(index) * day_range / Double(elements))))))))
// }
// TODO: gdfgg
let daySegmentDuration: Double = sunset.timeIntervalSince1970 - sunrise.timeIntervalSince1970
let nightSegmentDuration: Double = nextDaySunrise.timeIntervalSince1970 - sunset.timeIntervalSince1970
// Day segments (0 to 11)
for i in 0..<12 {
let start = Date(timeIntervalSince1970: TimeInterval(normalize(index: Double(i), rangeMin: Double(sunrise.timeIntervalSince1970), rangeMax: Double(sunset.timeIntervalSince1970), firstIndex: Double.zero, lastIndex: 12)))
let end = Date(timeIntervalSince1970: TimeInterval(normalize(index: Double(i + 1), rangeMin: Double(sunrise.timeIntervalSince1970), rangeMax: Double(sunset.timeIntervalSince1970), firstIndex: Double.zero, lastIndex: 12)))
segments.append(PlanetaryHourSegment(label: i, start: start, end: end))
}
for i in 0..<12 {
let start = Date(timeIntervalSince1970: TimeInterval(normalize(index: Double(i), rangeMin: Double(sunset.timeIntervalSince1970), rangeMax: Double(nextDaySunrise.timeIntervalSince1970), firstIndex: Double.zero, lastIndex: 12)))
let end = Date(timeIntervalSince1970: TimeInterval(normalize(index: Double(i + 1), rangeMin: Double(sunset.timeIntervalSince1970), rangeMax: Double(nextDaySunrise.timeIntervalSince1970), firstIndex: Double.zero, lastIndex: 12)))
segments.append(PlanetaryHourSegment(label: i, start: start, end: end))
}
// Night segments (12 to 23)
// nextDaySunrise.formatted(.dateTime) i in 0..<12 {
// if let start = calendar.date(byAdding: .second, value: i * nightSegmentDuration, to: sunset),
// let end = calendar.date(byAdding: .second, value: nightSegmentDuration, to: start) {
// segments.append(PlanetaryHourSegment(label: i + 12, start: start, end: end))
// }
// }
return segments
}
func formattedTime(for date: Date) -> String {
return dateFormatter.string(from: date)
}
func normalize(index: Double, rangeMin: Double, rangeMax: Double, firstIndex: Double, lastIndex: Double) -> Double {
return (rangeMax - rangeMin) * (index - firstIndex) / (lastIndex - firstIndex) + rangeMin;
}
}
@theoknock
Copy link
Author

IMG_0022

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment