Skip to content

Commit 9179216

Browse files
authored
Immutability (#50)
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.
1 parent 756d899 commit 9179216

File tree

10 files changed

+77
-57
lines changed

10 files changed

+77
-57
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version:5.3
1+
// swift-tools-version:5.9
22
// The swift-tools-version declares the minimum version of Swift required to build this package.
33

44
import PackageDescription

Sources/SunKit/Angle.swift

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,39 +18,40 @@
1818

1919
import Foundation
2020

21-
public struct Angle : Equatable {
21+
public struct Angle: Equatable, Hashable, Codable, Sendable {
2222

2323
public static var zero: Angle = .init()
2424

25-
public init() { _radians = 0 }
25+
public init() {
26+
_radians = 0
27+
}
2628

27-
public init(radians: Double) { _radians = radians }
29+
public init(radians: Double) {
30+
_radians = radians
31+
}
2832

29-
public init(degrees: Double) { _radians = degrees * Double.pi / 180.0 }
33+
public init(degrees: Double) {
34+
_radians = degrees * Double.pi / 180.0
35+
}
3036

3137
public var degrees: Double {
32-
get { return _radians * 180.0 / Double.pi }
38+
get { _radians * 180.0 / Double.pi }
3339
set { _radians = newValue * Double.pi / 180 }
3440
}
3541

3642
public var radians: Double {
37-
get { return _radians }
43+
get { _radians }
3844
set { _radians = newValue }
3945
}
4046

41-
42-
public static func ==(lhs: Angle, rhs: Angle) -> Bool {
43-
return lhs.radians == rhs.radians
44-
}
45-
4647
private var _radians: Double
4748

48-
public static func degrees(_ value:Double) -> Angle{
49-
return .init(degrees: value)
49+
public static func degrees(_ value: Double) -> Angle {
50+
.init(degrees: value)
5051
}
5152

52-
public static func radians(_ value:Double) -> Angle{
53-
return .init(radians: value)
53+
public static func radians(_ value: Double) -> Angle {
54+
.init(radians: value)
5455
}
5556

5657
}

Sources/SunKit/DMS.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import Foundation
2020

2121
/// DMS format to express angles
22-
public struct DMS: Equatable{
22+
public struct DMS: Equatable, Hashable, Codable, Sendable {
2323

2424
public var degrees: Double
2525
public var minutes: Double

Sources/SunKit/EclipticCoordinates.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import Foundation
2020

2121

22-
public struct EclipticCoordinates {
22+
public struct EclipticCoordinates: Equatable, Hashable, Codable, Sendable {
2323

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

Sources/SunKit/EquatorialCoordinates.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@
1818

1919
import Foundation
2020

21-
22-
23-
public struct EquatorialCoordinates{
21+
public struct EquatorialCoordinates: Equatable, Hashable, Codable, Sendable {
2422

2523
public private(set) var rightAscension: Angle? // rightAscension.degrees refers to h format
2624
public private(set) var declination: Angle //delta

Sources/SunKit/HMS.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import Foundation
2020

2121

2222
/// Time expressed in HMS format
23-
public struct HMS: Equatable{
23+
public struct HMS: Equatable, Hashable, Codable, Sendable {
2424

2525
public var hours: Double
2626
public var minutes: Double

Sources/SunKit/HorizonCoordinates.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import Foundation
2020

21-
public struct HorizonCoordinates{
21+
public struct HorizonCoordinates: Equatable, Hashable, Codable, Sendable {
2222

2323
public var altitude: Angle
2424
public var azimuth: Angle

Sources/SunKit/Sun.swift

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,16 @@
1919
import Foundation
2020
import CoreLocation
2121

22-
public class Sun {
22+
public struct Sun: Identifiable, Sendable {
23+
public let id: UUID = UUID()
2324

2425
/*--------------------------------------------------------------------
2526
Public get Variables
2627
*-------------------------------------------------------------------*/
2728

2829
public private(set) var location: CLLocation
2930
public private(set) var timeZone: TimeZone
30-
public private(set) var date: Date = Date()
31+
public private(set) var date: Date
3132

3233
/*--------------------------------------------------------------------
3334
Sun Events during the day
@@ -70,22 +71,22 @@ public class Sun {
7071

7172
///Date at which morning Blue Hour starts. Sun at -6 degrees elevation = civil dusk
7273
public var morningBlueHourStart: Date{
73-
return civilDawn
74+
civilDawn
7475
}
7576

7677
///Date at which morning Blue Hour ends. Sun at -4 degrees elevation = morning golden hour start
7778
public var morningBlueHourEnd: Date {
78-
return morningGoldenHourStart
79+
morningGoldenHourStart
7980
}
8081

8182
///Date at which evening Blue Hour starts. Sun at -4 degrees elevation = evening golden hour end
8283
public var eveningBlueHourStart: Date{
83-
return eveningGoldenHourEnd
84+
eveningGoldenHourEnd
8485
}
8586

8687
///Date at which morning Blue Hour ends. Sun at -6 degrees elevation = Civil Dawn
8788
public var eveningBlueHourEnd: Date {
88-
return civilDusk
89+
civilDusk
8990
}
9091

9192

@@ -102,12 +103,12 @@ public class Sun {
102103

103104
// Sun azimuth for (Location,Date) in Self
104105
public var azimuth: Angle {
105-
return self.sunHorizonCoordinates.azimuth
106+
sunHorizonCoordinates.azimuth
106107
}
107108

108109
// Sun altitude for (Location,Date) in Self
109110
public var altitude: Angle {
110-
return self.sunHorizonCoordinates.altitude
111+
sunHorizonCoordinates.altitude
111112
}
112113

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

133134
/// Longitude of location
134135
public var longitude: Angle {
135-
return .init(degrees: self.location.coordinate.longitude)
136+
.init(degrees: location.coordinate.longitude)
136137
}
137138

138139
/// Latitude of Location
139140
public var latitude: Angle {
140-
return .init(degrees: self.location.coordinate.latitude)
141+
.init(degrees: location.coordinate.latitude)
141142
}
142143

143144
/// Returns daylight time in seconds
@@ -162,9 +163,9 @@ public class Sun {
162163
/// Returns True if is night
163164
public var isNight: Bool {
164165
if !isCircumPolar {
165-
return date < sunrise || date > sunset
166+
date < sunrise || date > sunset
166167
} else {
167-
return isAlwaysNight
168+
isAlwaysNight
168169
}
169170
}
170171

@@ -219,23 +220,27 @@ public class Sun {
219220

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

225226
/*--------------------------------------------------------------------
226227
Initializers
227228
*-------------------------------------------------------------------*/
228229

229-
public init(location: CLLocation,timeZone: Double) {
230-
let timeZoneSeconds: Int = Int(timeZone * SECONDS_IN_ONE_HOUR)
231-
self.timeZone = TimeZone.init(secondsFromGMT: timeZoneSeconds) ?? .current
230+
public init(location: CLLocation, timeZone: TimeZone, date: Date = Date()) {
231+
self.timeZone = timeZone
232232
self.location = location
233+
self.date = date
234+
233235
refresh()
234236
}
235237

236-
public init(location: CLLocation,timeZone: TimeZone) {
237-
self.timeZone = timeZone
238+
init(location: CLLocation, timeZone: Double, date: Date = Date()) {
239+
let timeZoneSeconds: Int = Int(timeZone * SECONDS_IN_ONE_HOUR)
240+
self.timeZone = TimeZone.init(secondsFromGMT: timeZoneSeconds) ?? .current
238241
self.location = location
242+
self.date = date
243+
239244
refresh()
240245
}
241246

@@ -247,7 +252,7 @@ public class Sun {
247252
Changing date of interest
248253
*-------------------------------------------------------------------*/
249254

250-
public func setDate(_ newDate: Date) {
255+
public mutating func setDate(_ newDate: Date) {
251256
let newDay = calendar.dateComponents([.day,.month,.year], from: newDate)
252257
let oldDay = calendar.dateComponents([.day,.month,.year], from: date)
253258

@@ -266,15 +271,15 @@ public class Sun {
266271
/// - Parameters:
267272
/// - newLocation: New location
268273
/// - newTimeZone: New timezone for the given location. Is highly recommanded to pass a Timezone initialized via .init(identifier: ) method
269-
public func setLocation(_ newLocation: CLLocation,_ newTimeZone: TimeZone) {
274+
public mutating func setLocation(_ newLocation: CLLocation,_ newTimeZone: TimeZone) {
270275
timeZone = newTimeZone
271276
location = newLocation
272277
refresh()
273278
}
274279

275280
/// Changing only the location
276281
/// - Parameter newLocation: New Location
277-
public func setLocation(_ newLocation: CLLocation) {
282+
public mutating func setLocation(_ newLocation: CLLocation) {
278283
location = newLocation
279284
refresh()
280285
}
@@ -284,7 +289,7 @@ public class Sun {
284289
/// - Parameters:
285290
/// - newLocation: New Location
286291
/// - newTimeZone: New Timezone express in Double. For timezones which differs of half an hour add 0.5,
287-
public func setLocation(_ newLocation: CLLocation,_ newTimeZone: Double) {
292+
public mutating func setLocation(_ newLocation: CLLocation,_ newTimeZone: Double) {
288293
let timeZoneSeconds: Int = Int(newTimeZone * SECONDS_IN_ONE_HOUR)
289294
timeZone = TimeZone(secondsFromGMT: timeZoneSeconds) ?? .current
290295
location = newLocation
@@ -298,14 +303,14 @@ public class Sun {
298303

299304
/// Changing only the timezone.
300305
/// - Parameter newTimeZone: New Timezone
301-
public func setTimeZone(_ newTimeZone: TimeZone) {
306+
public mutating func setTimeZone(_ newTimeZone: TimeZone) {
302307
timeZone = newTimeZone
303308
refresh()
304309
}
305310

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

437442
if(needToComputeSunEvents){
@@ -484,7 +489,7 @@ public class Sun {
484489

485490

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

834839
}
840+
841+
extension Sun: Equatable {
842+
public static func == (lhs: Sun, rhs: Sun) -> Bool {
843+
lhs.location == rhs.location &&
844+
lhs.timeZone == rhs.timeZone &&
845+
lhs.date == rhs.date
846+
}
847+
}
848+
849+
extension Sun: Hashable {
850+
public func hash(into hasher: inout Hasher) {
851+
hasher.combine(location)
852+
hasher.combine(timeZone)
853+
hasher.combine(date)
854+
}
855+
}

Tests/SunKitTests/UT_Sun.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,13 @@ final class UT_Sun: XCTestCase {
9898

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

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

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

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

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

109109

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

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

352-
let sun = Sun.init(location: location, timeZone: pst)
352+
var sun = Sun.init(location: location, timeZone: pst)
353353

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

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

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

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

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

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

0 commit comments

Comments
 (0)