ios - 关于 NSTimeZone.secondsFromGMT 的困惑

标签 ios nsdate core-location nscalendar nstimezone

我正在开发一款应用程序,该应用程序具有在夜间自动进入黑暗/夜间模式的功能。该应用程序询问用户位置并使用 this algorithm 确定日出/日落时间(世界标准时间) .

唯一不明确的步骤是从 UT 转换为本地时间,因为算法中没有解释这一点。假设我的日出时间为 8.5(UT 早上 8:30)。我怎样才能将它转换为用户的本地时间以检查它是白天还是晚上?或者等价地,我如何将用户的本地时间转换为 UT 以便能够比较它们?

到目前为止,我已经尝试使用 NSCalendar得到NSDateComponents当前日期 ( NSDate() )。这些组件之一是 NSTimeZone?我可以从中得到 secondsFromGMT .像这样:

let dateComponents = NSCalendar.currentCalendar().components([.TimeZone], fromDate: NSDate())
let localOffset = Double(dateComponents.timeZone?.secondsFromGMT ?? 0)/3600

哪里localOffset应该是从 UT(即 GMT,如果我是对的)到本地时间的时差(以小时为单位),默认为 0 如果 dateComponents.timeZone == nil (我不知道在什么情况下会发生这种情况)。问题是我得到了相同的 localOffset现在与 future 6 个月相比(届时夏令时将与我所在的西类牙现在不同)。这是否意味着我需要使用属性 daylightSavingTime和/或 daylightSavingTimeOffset连同 secondsFromGMT ?没有 secondsFromGMT自己对此负责吗?

当我阅读算法的结果时,事情变得更加困惑。太阳落山时间(本地时间)与谷歌给出的时间完全一致,但日出时间比谷歌所说的时间提前一小时(我的位置和日期)。我与您分享该算法的整个 Swift 实现,希望它可以帮助其他人发现我做错了什么。

import Foundation
import CoreLocation

enum SunriseSunsetZenith: Double {
    case Official       =  90.83
    case Civil          =  96
    case Nautical       = 102
    case Astronomical   = 108
}

func sunriseSunsetHoursForLocation(coordinate: CLLocationCoordinate2D, atDate date: NSDate = NSDate(), zenith: SunriseSunsetZenith = .Civil) -> (sunrise: Double, sunset: Double) {
    // Initial values (will be changed later)
    var sunriseTime = 7.5
    var sunsetTime = 19.5

    // Get the longitude and latitude
    let latitude = coordinate.latitude
    let longitude = coordinate.longitude

    // Get the day, month, year and local offset
    let dateComponents = NSCalendar.currentCalendar().components([.Day, .Month, .Year, .TimeZone], fromDate: date)
    let day = Double(dateComponents.day)
    let month = Double(dateComponents.month)
    let year = Double(dateComponents.year)
    let localOffset = Double(dateComponents.timeZone?.daylightSavingTimeOffset ?? 0)/3600

    // Calculate the day of the year
    let N1 = floor(275*month/9)
    let N2 = floor((month + 9)/12)
    let N3 = 1 + floor((year - 4*floor(year/4) + 2)/3)
    let dayOfYear = N1 - N2*N3 + day - 30

    for i in 0...1 {
        // Convert the longitude to hour value and calculate an approximate time
        let longitudeHour = longitude/15
        let t = dayOfYear + ((i == 0 ? 6.0 : 18.0) - longitudeHour)/24

        // Calculate the Sun's mean anomaly
        let M = 0.9856*t - 3.289

        // Calculate the Sun's true longitude
        var L = M + 1.916*sind(M) + 0.020*sind(2*M) + 282.634
        L %= 360

        // Calculate the Sun's right ascension
        var RA = atand(0.91764 * tand(L))
        RA %= 360
        let Lquadrant = (floor(L/90))*90
        let RAquadrant = (floor(RA/90))*90
        RA += Lquadrant - RAquadrant
        RA /= 15

        // Calculate the Sun's declination
        let sinDec = 0.39782*sind(L)
        let cosDec = cosd(asind(sinDec))

        // Calculate the Sun's local hour angle
        let cosH = (cosd(zenith.rawValue) - sinDec*sind(latitude))/(cosDec*cosd(latitude))
        if cosH > 1 { // The sun never rises on this location (on the specified date)
            sunriseTime = Double.infinity
            sunsetTime = -Double.infinity
        } else if cosH < -1 { // The sun never sets on this location (on the specified date)
            sunriseTime = -Double.infinity
            sunsetTime = Double.infinity
        } else {
            // Finish calculating H and convert into hours
            var H = ( i == 0 ? 360.0 : 0.0 ) + ( i == 0 ? -1.0 : 1.0 )*acosd(cosH)
            H /= 15

            // Calculate local mean time of rising/setting
            let T = H + RA - 0.06571*t - 6.622

            // Adjust back to UTC
            let UT = T - longitudeHour

            // Convert UT value to local time zone of latitude/longitude
            let localT = UT + localOffset

            if i == 0 { // Add 24 and modulo 24 to be sure that the results is between 0..<24
                sunriseTime = (localT + 24)%24
            } else {
                sunsetTime = (localT + 24)%24
            }
        }
    }
    return (sunriseTime, sunsetTime)
}


func sind(valueInDegrees: Double) -> Double {
    return sin(valueInDegrees*M_PI/180)
}

func cosd(valueInDegrees: Double) -> Double {
    return cos(valueInDegrees*M_PI/180)
}

func tand(valueInDegrees: Double) -> Double {
    return tan(valueInDegrees*M_PI/180)
}

func asind(valueInRadians: Double) -> Double {
    return asin(valueInRadians)*180/M_PI
}

func acosd(valueInRadians: Double) -> Double {
    return acos(valueInRadians)*180/M_PI
}

func atand(valueInRadians: Double) -> Double {
    return atan(valueInRadians)*180/M_PI
}

这是我使用该函数确定是否是晚上的方式:

let latitude = ...
let longitude = ...
let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
let (sunriseHour, sunsetHour) = sunriseSunsetHoursForLocation(coordinate)
let componetns = NSCalendar.currentCalendar().components([.Hour, .Minute], fromDate: NSDate())
let currentHour = Double(componetns.hour) + Double(componetns.minute)/60
let isNight = currentHour < sunriseHour || currentHour > sunsetHour

最佳答案

我不确定为什么您获取偏移量的代码不起作用(我得到了相同的结果)。但是有一个更简单的解决方案确实有效。只需使用 secondsFromGMTForDate 询问本地时区。日期相隔六个月,我得到不同的结果:

let now = NSDate()
let future = NSCalendar.currentCalendar().dateByAddingUnit(NSCalendarUnit.Month, value: 6, toDate: now, options: NSCalendarOptions(rawValue: 0))!

let nowOffset = NSTimeZone.localTimeZone().secondsFromGMTForDate(now)/3600
let futureOffset = NSTimeZone.localTimeZone().secondsFromGMTForDate(future)/3600

关于ios - 关于 NSTimeZone.secondsFromGMT 的困惑,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34212925/

相关文章:

ios - 如何识别调用了哪个点击手势识别器?

iphone - UI 在 iOS 5 和 iOS 4.3 上看起来不同

objective-c - NSDateFormatter 从字符串中返回不正确的日期

ios - iOS 10 中的地理围栏

iphone - 如何在 iPhone 上查找车速?

ios - 带有顶角、边框和阴影的聊天项目 View

ios - ios通过JSON循环?

swift - 组件的 NSTimeInterval

ios - 使用NSDate和NSDateFormatter存储星期几的最佳方法

ios - 如何判断 iOS 设备是否具有 GPS?