Skip to content

Commit

Permalink
Fixed a crash when user is in porlar region where sun may never rise …
Browse files Browse the repository at this point in the history
…or never set.
  • Loading branch information
venj committed Jan 24, 2018
1 parent 5e6717d commit eca031c
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 55 deletions.
Expand Up @@ -123,6 +123,7 @@
TargetAttributes = {
5B7EA00A1EA84DC900B58F3A = {
CreatedOnToolsVersion = 8.2.1;
LastSwiftMigration = 0920;
ProvisioningStyle = Automatic;
};
};
Expand Down Expand Up @@ -339,7 +340,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.emergencestudios.DaylightExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
SWIFT_VERSION = 4.0;
};
name = Debug;
};
Expand All @@ -353,7 +354,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.emergencestudios.DaylightExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
SWIFT_VERSION = 4.0;
};
name = Release;
};
Expand All @@ -376,6 +377,7 @@
5B7EA01F1EA84DC900B58F3A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
Expand Down
20 changes: 10 additions & 10 deletions Example/DaylightExample/DaylightExample/ViewController.swift
Expand Up @@ -43,11 +43,11 @@ class ViewController: UIViewController {
let date = Date()
let loc = Location(tz: timeZone, coords: location.coordinate)

let sunrise = date.timeOf(.sunrise, at: loc)
let noon = date.timeOf(.noon, at: loc)
let sunset = date.timeOf(.sunset, at: loc)
let nextSunrise = date.timeOfNext(.sunrise, at: loc)
let nextSunset = date.timeOfNext(.sunset, at: loc)
let sunrise = try? date.timeOf(.sunrise, at: loc)
let noon = try? date.timeOf(.noon, at: loc)
let sunset = try? date.timeOf(.sunset, at: loc)
let nextSunrise = try? date.timeOfNext(.sunrise, at: loc)
let nextSunset = try? date.timeOfNext(.sunset, at: loc)

let numberFormatter = NumberFormatter()
numberFormatter.maximumFractionDigits = 3
Expand All @@ -58,11 +58,11 @@ class ViewController: UIViewController {
let labelStrings = [
"Lon: \(latString)º Lat: \(lonString)º",
"Timezone: \(timeZone.identifier)",
"Sunrise: \(dateFormatter.string(from: sunrise))",
"Solar Noon: \(dateFormatter.string(from: noon))",
"Sunset: \(dateFormatter.string(from: sunset))",
"Next Sunrise: \(dateFormatter.string(from: nextSunrise))",
"Next Sunset: \(dateFormatter.string(from: nextSunset))"
"Sunrise: \(dateFormatter.string(from: sunrise!))",
"Solar Noon: \(dateFormatter.string(from: noon!))",
"Sunset: \(dateFormatter.string(from: sunset!))",
"Next Sunrise: \(dateFormatter.string(from: nextSunrise!))",
"Next Sunset: \(dateFormatter.string(from: nextSunset!))"
]

for view in stackView.subviews {
Expand Down
11 changes: 9 additions & 2 deletions Example/DaylightExample/Pods/Pods.xcodeproj/project.pbxproj

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 25 additions & 21 deletions Sources/Daylight.swift
Expand Up @@ -8,6 +8,11 @@
import Foundation
import CoreLocation

public enum SolarEventError: Error {
case neverRise
case neverSet
}

public enum SolarEvent {
case sunrise
case noon
Expand Down Expand Up @@ -59,69 +64,68 @@ public struct Day {
self.day = day
}

public func timeOf(_ solarEvent: SolarEvent, at location: Location) -> Date {
public func timeOf(_ solarEvent: SolarEvent, at location: Location) throws -> Date {
let components = DateComponents(year: year, month: month, day: day)
let calendar = Calendar.with(tz: location.tz)
let inputDate = calendar.date(from: components)!
return inputDate.timeOf(solarEvent, at: location)
return try inputDate.timeOf(solarEvent, at: location)
}
}

public extension Date {

public func timeOf(_ solarEvent: SolarEvent, at location: Location) -> Date {
public func timeOf(_ solarEvent: SolarEvent, at location: Location) throws -> Date {
switch solarEvent {
case .sunrise, .civilDawn, .nauticalDawn, .astronomicalDawn:
return calculateDawn(location: location.coords,
solarElevation: solarEvent.elevation.rawValue,
timezone: location.tz)
return try calculateDawn(location: location.coords,
solarElevation: solarEvent.elevation.rawValue,
timezone: location.tz)
case .sunset, .civilDusk, .nauticalDusk, .astronomicalDusk:
return calculateDusk(location: location.coords,
solarElevation: solarEvent.elevation.rawValue,
timezone: location.tz)
return try calculateDusk(location: location.coords,
solarElevation: solarEvent.elevation.rawValue,
timezone: location.tz)
case .noon:
return calculateNoon(location: location.coords,
return try calculateNoon(location: location.coords,
timezone: location.tz)
}
}

public func timeOfNext(_ solarEvent: SolarEvent, at location: Location) -> Date {
var time = self.timeOf(solarEvent, at: location)
public func timeOfNext(_ solarEvent: SolarEvent, at location: Location) throws -> Date {
var time = try timeOf(solarEvent, at: location)
if self >= time {
time = self.dayAfter.timeOf(solarEvent, at: location)
time = try dayAfter.timeOf(solarEvent, at: location)
}
return time
}

private func calculateDawn(location: CLLocationCoordinate2D,
solarElevation: Float64,
timezone: TimeZone) -> Date {
timezone: TimeZone) throws -> Date {

let midnight = self.atMidnight(timeZone: timezone)
let julianDate = midnight.julianTz(timezone)
let sunriseUTCMins = julianDate.sunriseUTC(location: location, solarElevation: solarElevation)
let sunriseUTCMins = try julianDate.sunriseUTC(location: location, solarElevation: solarElevation)

return self.dateFromTimeUTC(timeUTCMins: sunriseUTCMins, timezone: timezone)
}

private func calculateDusk(location: CLLocationCoordinate2D,
solarElevation: Float64,
timezone: TimeZone) -> Date {
timezone: TimeZone) throws -> Date {

let midnight = self.atMidnight(timeZone: timezone)
let julianDate = midnight.julianTz(timezone)
let duskUTCMins = julianDate.sunsetUTC(location: location, solarElevation: solarElevation)
let duskUTCMins = try julianDate.sunsetUTC(location: location, solarElevation: solarElevation)

return self.dateFromTimeUTC(timeUTCMins: duskUTCMins, timezone: timezone)
}

private func calculateNoon(location: CLLocationCoordinate2D,
timezone: TimeZone) -> Date {

let sunrise = calculateDawn(location: location,
timezone: TimeZone) throws -> Date {
let sunrise = try calculateDawn(location: location,
solarElevation: SolarEvent.sunrise.elevation.rawValue,
timezone: timezone)
let sunset = calculateDusk(location: location,
let sunset = try calculateDusk(location: location,
solarElevation: SolarEvent.sunset.elevation.rawValue,
timezone: timezone)
let dayLength = sunset.timeIntervalSince(sunrise)
Expand Down
55 changes: 35 additions & 20 deletions Sources/DaylightAstronomy.swift
Expand Up @@ -134,7 +134,7 @@ internal extension JulianDate {
private typealias HourAngleFunction = (
_ latitude: Float64,
_ solarDeclination: Float64,
_ solarElevation: Float64) -> Float64
_ solarElevation: Float64) throws -> Float64

// Purpose: calculate the Universal Coordinated Time (UTC) of the given solar position
// for the given day at the given location on earth
Expand All @@ -143,14 +143,14 @@ internal extension JulianDate {

private func calculateTimeUTCatHourAngle(location: CLLocationCoordinate2D,
solarElevation: Float64,
hourAngleFunction: @escaping HourAngleFunction ) -> Float64 {
hourAngleFunction: @escaping HourAngleFunction ) throws -> Float64 {

let longitude = location.longitude * -1

func calculateTime(from centuries: JulianCenturies) -> Float64 {
func calculateTime(from centuries: JulianCenturies) throws -> Float64 {
let eqTime = centuries.equationOfTime
let declination = centuries.declinationOfSun
let hourAngle = hourAngleFunction(location.latitude, declination, solarElevation)
let hourAngle = try hourAngleFunction(location.latitude, declination, solarElevation)
let delta = longitude - hourAngle.radToDeg
let timeDiff = delta * 4.0
return 720.0 + timeDiff - eqTime
Expand All @@ -162,23 +162,23 @@ internal extension JulianDate {

// First Pass

let timeUTC = calculateTime(from: noonTime)
let timeUTC = try calculateTime(from: noonTime)

// Second Pass

let newTime = (JulianDate.from(centuries: centuries) + (timeUTC/1440.0)).centuries
let finalTimeUTC = calculateTime(from: newTime)
let finalTimeUTC = try calculateTime(from: newTime)
return finalTimeUTC
}

func sunriseUTC(location: CLLocationCoordinate2D, solarElevation: Float64) -> Float64 {
return calculateTimeUTCatHourAngle(location: location,
solarElevation: solarElevation,
hourAngleFunction: hourAngleOfSunrise)
func sunriseUTC(location: CLLocationCoordinate2D, solarElevation: Float64) throws -> Float64 {
return try calculateTimeUTCatHourAngle(location: location,
solarElevation: solarElevation,
hourAngleFunction: hourAngleOfSunrise)
}

func sunsetUTC(location: CLLocationCoordinate2D, solarElevation: Float64) -> Float64 {
return calculateTimeUTCatHourAngle(location: location,
func sunsetUTC(location: CLLocationCoordinate2D, solarElevation: Float64) throws -> Float64 {
return try calculateTimeUTCatHourAngle(location: location,
solarElevation: solarElevation,
hourAngleFunction: hourAngleOfSunset)
}
Expand Down Expand Up @@ -218,26 +218,41 @@ private func refractiveCorrection(solarElevation: Float64) -> Float64 {
}
}

private func hourAngleOfSunrise(latitude: Float64, solarDeclination: Float64, solarElevation: Float64) -> Float64 {
private func hourAngleOfSunrise(latitude: Float64, solarDeclination: Float64, solarElevation: Float64) throws -> Float64 {

let latRad = latitude.degToRad
let sdRad = solarDeclination.degToRad
let correction = refractiveCorrection(solarElevation: solarElevation)
let cosH = cos((90+correction).degToRad)/(cos(latRad)*cos(sdRad)) - tan(latRad)*tan(sdRad)

if cosH > 1 {
throw SolarEventError.neverRise
}
if cosH < -1 {
throw SolarEventError.neverSet
}

return (acos(cos((90+correction).degToRad)/(cos(latRad)*cos(sdRad)) - tan(latRad)*tan(sdRad)))
return (acos(cosH))
}

private func hourAngleOfSunset(latitude: Float64, solarDeclination: Float64, solarElevation: Float64) -> Float64 {
private func hourAngleOfSunset(latitude: Float64, solarDeclination: Float64, solarElevation: Float64) throws -> Float64 {

let latRad = latitude.degToRad
let sdRad = solarDeclination.degToRad
let correction = refractiveCorrection(solarElevation: solarElevation)

let hourAngle = acos(
cos((90.0 + correction).degToRad) /
(cos(latRad) * cos(sdRad)) -
(tan(latRad) * tan(sdRad))
)
let cosH = cos((90.0 + correction).degToRad) /
(cos(latRad) * cos(sdRad)) -
(tan(latRad) * tan(sdRad))

if cosH > 1 {
throw SolarEventError.neverRise
}
if cosH < -1 {
throw SolarEventError.neverSet
}

let hourAngle = acos(cosH)

return -hourAngle
}

0 comments on commit eca031c

Please sign in to comment.