使用DateComponent构造NSDate会产生不一致的结果

时间:2015-05-28 22:07:53

标签: swift nsdate nsdatecomponents

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

我猜测某处可能存在舍入错误(可能在我的测试代码中),我已经弄乱了转换,或者有一个Swift错误,但会欣赏评论。以下单元测试的代码和输出。

代码如下:

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

1 个答案:

答案 0 :(得分: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和时区。