onekiloparsec / SwiftAA

The most comprehensive collection of accurate astronomical algorithms in (C++, Objective-C and) Swift.
http://www.onekiloparsec.dev/
MIT License
172 stars 31 forks source link

Calculate RiseTransitSet based on timezone #56

Closed andris-zalitis closed 7 years ago

andris-zalitis commented 7 years ago

When calculating RiseTransitSet we need to have a base date. Resulting rise, transit, set values are from a time range of (base date ..< base date + 24h).

Currently the base date is calculated like this:

    private lazy var riseTransiteSetTimesDetails: RiseTransitSetTimesDetails = {
        [unowned self] in
        let midnight = self.celestialBody.julianDay.midnight

Let's say we'd like to print moonrise/moonset for year 2017 in Sydney. Even if we pass correct dates (midnights by Sydney timezone) to let moon = Moon(julianDay: JulianDay(date)) we will still get wrong result because we discard local midnight in our code and so we'll return rise/set for 24h window based on GMT.

This code:

    func testPrintMoonriseMoonset() {
        let sydney = GeographicCoordinates(positivelyWestwardLongitude: Degree(.minus, 151, 12, 0), latitude: Degree(.minus, 33, 52, 0), altitude: Meter(0))

        var gregorianCalendar = Calendar(identifier: .gregorian)
        let timeZone = TimeZone(identifier: "Australia/Sydney")!
        gregorianCalendar.timeZone = timeZone
        let dateComponents = DateComponents(timeZone: timeZone, year: 2017, month: 1, day: 1)

        var date = gregorianCalendar.date(from: dateComponents)!

        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = DateFormatter.dateFormat(fromTemplate: "d MMM, HH mm ss, z", options: 0, locale: nil)
        dateFormatter.timeZone = timeZone

        for _ in 1...365 {

            let moon = Moon(julianDay: JulianDay(date))

            let riseTransitSetTimes = RiseTransitSetTimes(celestialBody: moon, geographicCoordinates: sydney, riseSetAltitude: 0.125)

            let riseString = riseTransitSetTimes.riseTime != nil ? dateFormatter.string(from: riseTransitSetTimes.riseTime!.date) : "----"
            let setString = riseTransitSetTimes.setTime != nil ? dateFormatter.string(from: riseTransitSetTimes.setTime!.date) : "----"
            print("rise: \(riseString) | set: \(setString)")

            date = gregorianCalendar.date(byAdding: .day, value: 1, to: date)!
        }
    }

Prints:

rise: Jan 1 08:18:48 GMT+11 | set: Dec 31 21:25:11 GMT+11
rise: Jan 2 09:16:49 GMT+11 | set: Jan 1 22:07:07 GMT+11
rise: Jan 3 10:16:11 GMT+11 | set: Jan 2 22:46:33 GMT+11
rise: ---- | set: Jan 3 23:24:09 GMT+11
rise: Jan 4 11:16:39 GMT+11 | set: Jan 5 00:00:51 GMT+11
rise: Jan 5 12:18:14 GMT+11 | set: Jan 6 00:37:43 GMT+11
rise: Jan 6 13:21:14 GMT+11 | set: Jan 7 01:15:56 GMT+11
rise: Jan 7 14:25:51 GMT+11 | set: Jan 8 01:56:48 GMT+11
rise: Jan 8 15:32:00 GMT+11 | set: Jan 9 02:41:35 GMT+11

Notice how rise and set are usually in different dates by Sydney time.

This is how it should look instead.

andris-zalitis commented 7 years ago

One option would be to remove: let midnight = self.celestialBody.julianDay.midnight and just use JulianDate itself, but then we would loose the ability of running code that asks for moonrise/moonset for a local time with hours and minutes, without caring to convert it to local midnight.

Therefore I have opted to use localMidnight(timeZone:) to solve this. Note that although we already have localMidnight(longitude:) it does not fully solve the problem because there are differences between midnight calculated based on longitude vs midnight calculated based on time zone.

andris-zalitis commented 7 years ago

BTW I've used moonrise/moonset in the example because those change more rapidly from day to day than sunrise/sunset or dawn/dusk.

onekiloparsec commented 7 years ago

This is great again, thanks for checking this in details! Again, once we settle the discussion about midnight in #54, I'll happy to merge that work.

onekiloparsec commented 7 years ago

Quick note: this isn't settled yet, but I'm still on it. I have tracked down (and down and down...) the problem of rise transit and set times. And it seems, somehow, I don't get right the equatorial coordinates for the date. I'll be in vacations for some time, but I'll be back!

onekiloparsec commented 7 years ago

I found the root cause of our problems. I was computing equatorial coordinates of solar system objects based on ecliptic coordinates. But not "normal" (geocentric) ecliptic coordinates, rather the heliocentric ones! I didn't know those latter coords existed anyway.

I had to change a bit the CelestialBody protocol, and I managed to make all tests pass. However, some of them with an accuracy that is not satisfactory. I can't do more for now, as I am leaving for a few vacations. But I am relieved this important issue is solved.