【问题标题】:Constructing NSDate using DateComponent gives inconsistent results使用 DateComponent 构造 NSDate 会产生不一致的结果
【发布时间】:2015-05-28 22:07:53
【问题描述】:

我为 NSDate 创建了一个扩展,它删除了时间组件,以允许仅基于日期对 NSDate 进行相等性检查。我通过获取原始 NSDate 对象,使用 DateComponent 类获取日、月和年,然后使用获得的信息构造一个新的 NSDate 来实现这一点。虽然获得的 NSDate 对象在打印到控制台时看起来是正确的(即时间戳为 00:00:00)并且在两个相同的日期使用 NSDate.compare 函数返回 NSComparisonResult.OrderedSame,但如果您再次使用 DateComponent 解构它们,其中一些将小时设置为 1。这似乎是一个随机事件,大约 55% 的时间出现此错误。在构造新的 NSDate 之前强制 DateComponent 的小时、分钟和秒属性为零,而不是假设它们将默认为这些值并不能纠正这种情况。确保设置时区会有所帮助,但同样不能解决问题。

我猜某处可能存在舍入错误(可能在我的测试代码中),我已经弄乱了转换,或者存在 Swift 错误,但希望 cmets。下面的单元测试的代码和输出。

代码如下:

extension NSDate {

    // creates a NSDate object with time set to 00:00:00 which allows equality checks on dates alone
    var asDateOnly: NSDate {
        get {
            let userCalendar = NSCalendar.currentCalendar()
            let dayMonthYearUnits: NSCalendarUnit = .CalendarUnitDay | .CalendarUnitMonth | .CalendarUnitYear
            var dateComponents = userCalendar.components(dayMonthYearUnits, fromDate: self)
            dateComponents.timeZone = NSTimeZone(name: "GMT")
//            dateComponents.hour = 0
//            dateComponents.minute = 0
//            dateComponents.second = 0
            let result = userCalendar.dateFromComponents(dateComponents)!
            return result
        }
    }

测试功能:

func testRemovingTimeComponentFromRandomNSDateObjectsAlwaysResultsInNSDateSetToMidnight() {
    var dates = [NSDate]()
    let dateRange = NSDate.timeIntervalSinceReferenceDate()
    for var i = 0; i < 30; i++ {
        let randomTimeInterval = Double(arc4random_uniform(UInt32(dateRange)))
        let date = NSDate(timeIntervalSinceReferenceDate: randomTimeInterval).asDateOnly
        let dateStrippedOfTime = date.asDateOnly

        // get the hour, minute and second components from the stripped date
        let userCalendar = NSCalendar.currentCalendar()
        var hourMinuteSecondUnits: NSCalendarUnit = .CalendarUnitHour | .CalendarUnitMinute | .CalendarUnitSecond
        var dateComponents = userCalendar.components(hourMinuteSecondUnits, fromDate: dateStrippedOfTime)
        dateComponents.timeZone = NSTimeZone(name: "GMT")
        XCTAssertTrue((dateComponents.hour == 0) && (dateComponents.minute == 0) && (dateComponents.second == 0), "Time components were not set to zero - \nNSDate: \(date) \nIndex: \(i) H: \(dateComponents.hour) M: \(dateComponents.minute) S: \(dateComponents.second)")
    }
}

输出:

testRemovingTimeComponentFromRandomNSDateObjectsAlwaysResultsInNSDateSetToMidnight] : XCTAssertTrue failed - Time components were not set to zero - 
NSDate: 2009-06-19 00:00:00 +0000 
Index: 29 H: 1 M: 0 S: 0

【问题讨论】:

    标签: swift nsdate nsdatecomponents


    【解决方案1】:

    我确信您随机创建的测试日期将包含 DST(夏令时)的日期,因此会有 1 小时的偏移量 - 实际上时钟会显示 0:00。


    当您覆盖它时,您的代码无论如何都过于复杂并且不知道时区。

    准备:在同一天创建日期,相隔 5 小时。

    var d1 = NSDate()
    let cal = NSCalendar.currentCalendar()
    d1 = cal.dateBySettingUnit(NSCalendarUnit.CalendarUnitHour, value: 12, ofDate: d1, options: nil)!
    d1 = cal.dateBySettingUnit(NSCalendarUnit.CalendarUnitMinute, value: 0, ofDate: d1, options: nil)!
    

    d1 今天中午在用户所在地。

    let comps = NSDateComponents()
    comps.hour = 5;
    
    var d2 = cal.dateByAddingComponents(comps, toDate: d1, options: nil)!
    

    d2 五小时后

    比较:此比较将产生equal,因为日期是同一天

    let b = cal.compareDate(d1, toDate: d2, toUnitGranularity: NSCalendarUnit.CalendarUnitDay)
    if b == .OrderedSame {
        println("equal")
    } else {
        println("not equal")
    }
    

    以下将产生non equal,因为日期不在同一小时

    let b = cal.compareDate(d1, toDate: d2, toUnitGranularity: NSCalendarUnit.CalendarUnitHour)
    if b == .OrderedSame {
        println("equal")
    } else {
        println("not equal")
    }
    

    使用日期格式化程序显示日期,因为它将考虑 DST 和时区。

    【讨论】:

    • 感谢您的建议。我想知道时区是否有问题,但它不能解释为什么 1hr 偏移随机出现。我的测试代码使用完全相同的代码创建 30 个随机日期(诚然复杂,但我目前正在学习,所以效率不高!)在“修剪”之前删除时间。首先将时区设置为 GMT,我已经访问了用于显示的时间组件。由于构建和显示的方法每次都是一致的,我不明白为什么输出在 0h0m0s 和 1h0m0s 之间以随机方式交替。有什么想法吗?
    • 我假设您的语言环境设置为英国时区。那是 DST 时区。所以有些日期是用 dat 创建的,有些不是。如果您打印一个普通的日期对象,这将在 +/- 1 小时内解决,因为 nsdate 本身不知道语言环境的内容。如果你通过 NSDateFormatter 打印它们,你只会看到 0:00。并且永远不要强制日期区。让日历和本地处理。
    • 每个可可开发者都应该观看这个视频:Performing Calendar Calculations
    • 感谢您的指点,但您为什么说永远不要“强制”时区?我认为 NSDateFormatter 的全部意义在于允许时区之间的转换?恐怕我仍然对输出的随机变化感到困惑——当然,我可能会做错这一切,但输出肯定会一直是错误的,事实并非如此。
    • WWDC 视频 URL 已更新:developer.apple.com/videos/play/wwdc2011/117
    猜你喜欢
    • 1970-01-01
    • 2017-08-18
    • 2012-01-16
    • 1970-01-01
    • 2017-06-10
    • 1970-01-01
    • 2014-03-30
    • 2018-11-20
    • 2021-09-06
    相关资源
    最近更新 更多