Skip to content

Commit

Permalink
Immutability (#50)
Browse files Browse the repository at this point in the history
Sun is now a struct: users can no longer use Sun as a reference type. However, immutability is not enforced and functions like setDate and setLocation are now defined as mutating.
Sun initializer now accepts a Date parameter (defaults to Date()).
Sun now conforms to Identifiable, Equatable, Hashable, and Sendable protocols.
Utilities such as Angle, DMS, EclipticCoordinates, EquatorialCoordinates, HMS, and HorizonCoordinates now conform to Equatable, Hashable, Sendable, and Codable protocols.
  • Loading branch information
seldon1000 authored Jan 13, 2024
1 parent 756d899 commit 9179216
Show file tree
Hide file tree
Showing 10 changed files with 77 additions and 57 deletions.
Binary file not shown.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.3
// swift-tools-version:5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand Down
31 changes: 16 additions & 15 deletions Sources/SunKit/Angle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,39 +18,40 @@

import Foundation

public struct Angle : Equatable {
public struct Angle: Equatable, Hashable, Codable, Sendable {

public static var zero: Angle = .init()

public init() { _radians = 0 }
public init() {
_radians = 0
}

public init(radians: Double) { _radians = radians }
public init(radians: Double) {
_radians = radians
}

public init(degrees: Double) { _radians = degrees * Double.pi / 180.0 }
public init(degrees: Double) {
_radians = degrees * Double.pi / 180.0
}

public var degrees: Double {
get { return _radians * 180.0 / Double.pi }
get { _radians * 180.0 / Double.pi }
set { _radians = newValue * Double.pi / 180 }
}

public var radians: Double {
get { return _radians }
get { _radians }
set { _radians = newValue }
}


public static func ==(lhs: Angle, rhs: Angle) -> Bool {
return lhs.radians == rhs.radians
}

private var _radians: Double

public static func degrees(_ value:Double) -> Angle{
return .init(degrees: value)
public static func degrees(_ value: Double) -> Angle {
.init(degrees: value)
}

public static func radians(_ value:Double) -> Angle{
return .init(radians: value)
public static func radians(_ value: Double) -> Angle {
.init(radians: value)
}

}
2 changes: 1 addition & 1 deletion Sources/SunKit/DMS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import Foundation

/// DMS format to express angles
public struct DMS: Equatable{
public struct DMS: Equatable, Hashable, Codable, Sendable {

public var degrees: Double
public var minutes: Double
Expand Down
2 changes: 1 addition & 1 deletion Sources/SunKit/EclipticCoordinates.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import Foundation


public struct EclipticCoordinates {
public struct EclipticCoordinates: Equatable, Hashable, Codable, Sendable {

public static let obliquityOfTheEcliptic: Angle = .init(degrees: 23.439292)

Expand Down
4 changes: 1 addition & 3 deletions Sources/SunKit/EquatorialCoordinates.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@

import Foundation



public struct EquatorialCoordinates{
public struct EquatorialCoordinates: Equatable, Hashable, Codable, Sendable {

public private(set) var rightAscension: Angle? // rightAscension.degrees refers to h format
public private(set) var declination: Angle //delta
Expand Down
2 changes: 1 addition & 1 deletion Sources/SunKit/HMS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import Foundation


/// Time expressed in HMS format
public struct HMS: Equatable{
public struct HMS: Equatable, Hashable, Codable, Sendable {

public var hours: Double
public var minutes: Double
Expand Down
2 changes: 1 addition & 1 deletion Sources/SunKit/HorizonCoordinates.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import Foundation

public struct HorizonCoordinates{
public struct HorizonCoordinates: Equatable, Hashable, Codable, Sendable {

public var altitude: Angle
public var azimuth: Angle
Expand Down
73 changes: 47 additions & 26 deletions Sources/SunKit/Sun.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@
import Foundation
import CoreLocation

public class Sun {
public struct Sun: Identifiable, Sendable {
public let id: UUID = UUID()

/*--------------------------------------------------------------------
Public get Variables
*-------------------------------------------------------------------*/

public private(set) var location: CLLocation
public private(set) var timeZone: TimeZone
public private(set) var date: Date = Date()
public private(set) var date: Date

/*--------------------------------------------------------------------
Sun Events during the day
Expand Down Expand Up @@ -70,22 +71,22 @@ public class Sun {

///Date at which morning Blue Hour starts. Sun at -6 degrees elevation = civil dusk
public var morningBlueHourStart: Date{
return civilDawn
civilDawn
}

///Date at which morning Blue Hour ends. Sun at -4 degrees elevation = morning golden hour start
public var morningBlueHourEnd: Date {
return morningGoldenHourStart
morningGoldenHourStart
}

///Date at which evening Blue Hour starts. Sun at -4 degrees elevation = evening golden hour end
public var eveningBlueHourStart: Date{
return eveningGoldenHourEnd
eveningGoldenHourEnd
}

///Date at which morning Blue Hour ends. Sun at -6 degrees elevation = Civil Dawn
public var eveningBlueHourEnd: Date {
return civilDusk
civilDusk
}


Expand All @@ -102,12 +103,12 @@ public class Sun {

// Sun azimuth for (Location,Date) in Self
public var azimuth: Angle {
return self.sunHorizonCoordinates.azimuth
sunHorizonCoordinates.azimuth
}

// Sun altitude for (Location,Date) in Self
public var altitude: Angle {
return self.sunHorizonCoordinates.altitude
sunHorizonCoordinates.altitude
}

public private(set) var sunEquatorialCoordinates: EquatorialCoordinates = .init(declination: .zero)
Expand All @@ -132,12 +133,12 @@ public class Sun {

/// Longitude of location
public var longitude: Angle {
return .init(degrees: self.location.coordinate.longitude)
.init(degrees: location.coordinate.longitude)
}

/// Latitude of Location
public var latitude: Angle {
return .init(degrees: self.location.coordinate.latitude)
.init(degrees: location.coordinate.latitude)
}

/// Returns daylight time in seconds
Expand All @@ -162,9 +163,9 @@ public class Sun {
/// Returns True if is night
public var isNight: Bool {
if !isCircumPolar {
return date < sunrise || date > sunset
date < sunrise || date > sunset
} else {
return isAlwaysNight
isAlwaysNight
}
}

Expand Down Expand Up @@ -219,23 +220,27 @@ public class Sun {

/// Returns true if for (Location,Date) is always daylight (e.g Tromso city in Winter)
public var isAlwaysNight: Bool {
return sunset - TWO_HOURS_IN_SECONDS < sunrise
sunset - TWO_HOURS_IN_SECONDS < sunrise
}

/*--------------------------------------------------------------------
Initializers
*-------------------------------------------------------------------*/

public init(location: CLLocation,timeZone: Double) {
let timeZoneSeconds: Int = Int(timeZone * SECONDS_IN_ONE_HOUR)
self.timeZone = TimeZone.init(secondsFromGMT: timeZoneSeconds) ?? .current
public init(location: CLLocation, timeZone: TimeZone, date: Date = Date()) {
self.timeZone = timeZone
self.location = location
self.date = date

refresh()
}

public init(location: CLLocation,timeZone: TimeZone) {
self.timeZone = timeZone
init(location: CLLocation, timeZone: Double, date: Date = Date()) {
let timeZoneSeconds: Int = Int(timeZone * SECONDS_IN_ONE_HOUR)
self.timeZone = TimeZone.init(secondsFromGMT: timeZoneSeconds) ?? .current
self.location = location
self.date = date

refresh()
}

Expand All @@ -247,7 +252,7 @@ public class Sun {
Changing date of interest
*-------------------------------------------------------------------*/

public func setDate(_ newDate: Date) {
public mutating func setDate(_ newDate: Date) {
let newDay = calendar.dateComponents([.day,.month,.year], from: newDate)
let oldDay = calendar.dateComponents([.day,.month,.year], from: date)

Expand All @@ -266,15 +271,15 @@ public class Sun {
/// - Parameters:
/// - newLocation: New location
/// - newTimeZone: New timezone for the given location. Is highly recommanded to pass a Timezone initialized via .init(identifier: ) method
public func setLocation(_ newLocation: CLLocation,_ newTimeZone: TimeZone) {
public mutating func setLocation(_ newLocation: CLLocation,_ newTimeZone: TimeZone) {
timeZone = newTimeZone
location = newLocation
refresh()
}

/// Changing only the location
/// - Parameter newLocation: New Location
public func setLocation(_ newLocation: CLLocation) {
public mutating func setLocation(_ newLocation: CLLocation) {
location = newLocation
refresh()
}
Expand All @@ -284,7 +289,7 @@ public class Sun {
/// - Parameters:
/// - newLocation: New Location
/// - newTimeZone: New Timezone express in Double. For timezones which differs of half an hour add 0.5,
public func setLocation(_ newLocation: CLLocation,_ newTimeZone: Double) {
public mutating func setLocation(_ newLocation: CLLocation,_ newTimeZone: Double) {
let timeZoneSeconds: Int = Int(newTimeZone * SECONDS_IN_ONE_HOUR)
timeZone = TimeZone(secondsFromGMT: timeZoneSeconds) ?? .current
location = newLocation
Expand All @@ -298,14 +303,14 @@ public class Sun {

/// Changing only the timezone.
/// - Parameter newTimeZone: New Timezone
public func setTimeZone(_ newTimeZone: TimeZone) {
public mutating func setTimeZone(_ newTimeZone: TimeZone) {
timeZone = newTimeZone
refresh()
}

/// Is highly recommanded to use the other method to change timezone. This will be kept only for backwards retrocompatibility.
/// - Parameter newTimeZone: New Timezone express in Double. For timezones which differs of half an hour add 0.5,
public func setTimeZone(_ newTimeZone: Double) {
public mutating func setTimeZone(_ newTimeZone: Double) {
let timeZoneSeconds: Int = Int(newTimeZone * SECONDS_IN_ONE_HOUR)
timeZone = TimeZone(secondsFromGMT: timeZoneSeconds) ?? .current
refresh()
Expand Down Expand Up @@ -431,7 +436,7 @@ public class Sun {
/// Compute civil dusk and Civil Dawn time
///
/// - Parameter needToComputeAgainSunEvents: True if Sunrise,Sunset and all the others daily sun events have to be computed.
private func refresh(needToComputeSunEvents: Bool = true) {
private mutating func refresh(needToComputeSunEvents: Bool = true) {
updateSunCoordinates()

if(needToComputeSunEvents){
Expand Down Expand Up @@ -484,7 +489,7 @@ public class Sun {


/// Updates Horizon coordinates, Ecliptic coordinates and Equatorial coordinates of the Sun
private func updateSunCoordinates() {
private mutating func updateSunCoordinates() {
//Step1:
//Convert LCT to UT, GST, and LST times and adjust the date if needed
let gstHMS = uT2GST(self.date)
Expand Down Expand Up @@ -832,3 +837,19 @@ public class Sun {
}

}

extension Sun: Equatable {
public static func == (lhs: Sun, rhs: Sun) -> Bool {
lhs.location == rhs.location &&
lhs.timeZone == rhs.timeZone &&
lhs.date == rhs.date
}
}

extension Sun: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(location)
hasher.combine(timeZone)
hasher.combine(date)
}
}
16 changes: 8 additions & 8 deletions Tests/SunKitTests/UT_Sun.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,13 @@ final class UT_Sun: XCTestCase {

var expectedSolarNoon = createDateCustomTimeZone(day: 19, month: 11, year: 2022, hour: 11, minute: 48, seconds: 21,timeZone: timeZoneUnderTest)

var expectednauticalDawn = createDateCustomTimeZone(day: 19, month: 11, year: 2022, hour: 5, minute: 52, seconds: 21,timeZone: timeZoneUnderTest)
let expectednauticalDawn = createDateCustomTimeZone(day: 19, month: 11, year: 2022, hour: 5, minute: 52, seconds: 21,timeZone: timeZoneUnderTest)

var expectednauticalDusk = createDateCustomTimeZone(day: 19, month: 11, year: 2022, hour: 17, minute: 44, seconds: 45,timeZone: timeZoneUnderTest)
let expectednauticalDusk = createDateCustomTimeZone(day: 19, month: 11, year: 2022, hour: 17, minute: 44, seconds: 45,timeZone: timeZoneUnderTest)

var expectedastronomicalDawn = createDateCustomTimeZone(day: 19, month: 11, year: 2022, hour: 5, minute: 19, seconds: 25,timeZone: timeZoneUnderTest)
let expectedastronomicalDawn = createDateCustomTimeZone(day: 19, month: 11, year: 2022, hour: 5, minute: 19, seconds: 25,timeZone: timeZoneUnderTest)

var expectedastronomicalDusk = createDateCustomTimeZone(day: 19, month: 11, year: 2022, hour: 18, minute: 17, seconds: 20,timeZone: timeZoneUnderTest)
let expectedastronomicalDusk = createDateCustomTimeZone(day: 19, month: 11, year: 2022, hour: 18, minute: 17, seconds: 20,timeZone: timeZoneUnderTest)


//Step4: Check if the output are close to the expected ones
Expand Down Expand Up @@ -349,7 +349,7 @@ final class UT_Sun: XCTestCase {

let location: CLLocation = .init(latitude: 34.052235, longitude: -118.243683)

let sun = Sun.init(location: location, timeZone: pst)
var sun = Sun.init(location: location, timeZone: pst)

sun.setDate(SunKit.createDateCustomTimeZone(day: 11, month: 3, year: 2023, hour: 22, minute: 00, seconds: 00, timeZone: pst))
XCTAssertEqual(sun.sunrise.toString(pst), "03/11, 06:08")
Expand All @@ -372,7 +372,7 @@ final class UT_Sun: XCTestCase {
//Step1: Creating sun instance in Naples and with timezone +1 (No daylight saving)
let timeZoneUnderTest: TimeZone = .init(secondsFromGMT: UT_Sun.timeZoneNaples * Int(SECONDS_IN_ONE_HOUR)) ?? .current
let timeZoneDaylightSaving: TimeZone = .init(secondsFromGMT: UT_Sun.timeZoneNaplesDaylightSaving * Int(SECONDS_IN_ONE_HOUR)) ?? .current
let sunUnderTest = Sun.init(location: UT_Sun.naplesLocation, timeZone: timeZoneUnderTest)
var sunUnderTest = Sun.init(location: UT_Sun.naplesLocation, timeZone: timeZoneUnderTest)

//Step2: Setting 19/01/22 17:31 as date. (No daylight saving)
let dateUnderTest = createDateCustomTimeZone(day: 19, month: 1, year: 2022, hour: 17, minute: 31, seconds: 00,timeZone: timeZoneUnderTest)
Expand Down Expand Up @@ -406,7 +406,7 @@ final class UT_Sun: XCTestCase {

//Step1: Creating Sun instance in Naples and with timezone +1
let timeZoneNaples: TimeZone = .init(secondsFromGMT: UT_Sun.timeZoneNaples * Int(SECONDS_IN_ONE_HOUR)) ?? .current
let sunUnderTest = Sun.init(location: UT_Sun.naplesLocation, timeZone: timeZoneNaples)
var sunUnderTest = Sun.init(location: UT_Sun.naplesLocation, timeZone: timeZoneNaples)

//Step2: Setting 19/11/22 20:00 as date. (No daylight saving)
let dateUnderTest = createDateCustomTimeZone(day: 19, month: 11, year: 2022, hour: 20, minute: 00, seconds: 00,timeZone: timeZoneNaples)
Expand All @@ -430,7 +430,7 @@ final class UT_Sun: XCTestCase {
// Performance of setDate function that will refresh all the sun variables

//Step1: Creating sun instance in Naples with timezone +1
let sunUnderTest = Sun.init(location: UT_Sun.naplesLocation, timeZone: Double(UT_Sun.timeZoneNaples))
var sunUnderTest = Sun.init(location: UT_Sun.naplesLocation, timeZone: Double(UT_Sun.timeZoneNaples))

//Step2: Setting 19/11/22 20:00 as date.
let dateUnderTest = createDateCurrentTimeZone(day: 19, month: 11, year: 2022, hour: 20, minute: 00, seconds: 00)
Expand Down

0 comments on commit 9179216

Please sign in to comment.