如何将NSDate对象设置为午夜?

59

我有一个NSDate对象,并且我想将其设置为任意时间(比如午夜),以便我可以使用timeIntervalSince1970函数一致地检索数据,而不用担心创建对象的具体时间。

我尝试使用NSCalendar并通过使用Objective-C方法修改其组件来实现,例如:

let date: NSDate = NSDate()
let cal: NSCalendar = NSCalendar(calendarIdentifier: NSGregorianCalendar)!

let components: NSDateComponents = cal.components(NSCalendarUnit./* a unit of time */CalendarUnit, fromDate: date)
let newDate: NSDate = cal.dateFromComponents(components)
上述方法的问题在于只能设置一个时间单位(/* 时间单位 */),因此只能有以下其中一个准确:

  1. 小时
  2. 分钟

有没有一种方法可以同时设置小时、分钟和秒,并保留日期(日/月/年)?

9个回答

96

你的陈述

上述方法的问题在于,你只能设置一种时间单位...

是不正确的。 NSCalendarUnit 符合 RawOptionSetType 协议, 该协议继承自 BitwiseOperationsType。这意味着可以使用 &| 进行按位组合。

Swift 2(Xcode 7) 中,这又被更改为 OptionSetType, 它提供了类似于集合的接口,例如请参见Error combining NSCalendarUnit with OR (pipe) in Swift 2.0.

因此,以下代码可在 iOS 7 和 iOS 8 中编译并正常工作:

let date = NSDate()
let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!

// Swift 1.2:
let components = cal.components(.CalendarUnitDay | .CalendarUnitMonth | .CalendarUnitYear, fromDate: date)
// Swift 2:
let components = cal.components([.Day , .Month, .Year ], fromDate: date)

let newDate = cal.dateFromComponents(components)
(请注意,我已省略了变量的类型注释,Swift编译器会自动从赋值语句右侧的表达式中推断出类型。)
确定给定日期(午夜)的开始时间也可以使用rangeOfUnit()方法来完成(适用于iOS 7和iOS 8):
let date = NSDate()
let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
var newDate : NSDate?

// Swift 1.2:
cal.rangeOfUnit(.CalendarUnitDay, startDate: &newDate, interval: nil, forDate: date)
// Swift 2:
cal.rangeOfUnit(.Day, startDate: &newDate, interval: nil, forDate: date)

如果您的部署目标是iOS 8,则更加简单:

let date = NSDate()
let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let newDate = cal.startOfDayForDate(date)

更新至Swift 3(Xcode 8):

let date = Date()
let cal = Calendar(identifier: .gregorian)
let newDate = cal.startOfDay(for: date)

1
很奇怪的是,startOfDayForDate:方法虽然在头文件中公开了,但没有文档记录,与许多其他未记录的新方法一起。感谢您的提醒。 - Desdenova
3
@Desdenova说:它也被列在iOS 8.0 API差异中,但你是对的,NSCalendar文档确实不是最新的。 - Martin R
1
@Desdenova 对于 dateBySettingHour 方法也是如此。 - AstroCB
1
@adnako:我非常确定startOfDayForDate在所有时区中都能正常工作。你有具体的例子吗? - Martin R
1
请注意,'2016-06-27 21:00:00 UTC' 在您的时区中是 '2016-06-28 00:00:00',因此结果是正确的。要根据您的时区打印结果,您需要使用NSDateFormatter。 - Martin R
显示剩余4条评论

38

是的。

您无需调整NSCalendar组件; 您可以简单地调用dateBySettingHour方法,并使用ofDate参数与您现有的日期一起使用。

let date: NSDate = NSDate()
let cal: NSCalendar = NSCalendar(calendarIdentifier: NSGregorianCalendar)!

let newDate: NSDate = cal.dateBySettingHour(0, minute: 0, second: 0, ofDate: date, options: NSCalendarOptions())!

针对Swift 3:

let date: Date = Date()
let cal: Calendar = Calendar(identifier: .gregorian)

let newDate: Date = cal.date(bySettingHour: 0, minute: 0, second: 0, of: date)!

然后,要获取自1970年以来的时间,您只需执行以下操作:

let time: NSTimeInterval = newDate.timeIntervalSince1970

dateBySettingHour 函数在 OS X Mavericks (10.9) 中被引入,并在 iOS 8 中获得支持。

NSCalendar.h 中声明如下:

/*
    This API returns a new NSDate object representing the date calculated by setting hour, minute, and second to a given time.
    If no such time exists, the next available time is returned (which could, for example, be in a different day than the nominal target date).
    The intent is to return a date on the same day as the original date argument.  This may result in a date which is earlier than the given date, of course.
 */
- (NSDate *)dateBySettingHour:(NSInteger)h minute:(NSInteger)m second:(NSInteger)s ofDate:(NSDate *)date options:(NSCalendarOptions)opts NS_AVAILABLE(10_9, 8_0);

我没有看到文档 https://developer.apple.com/library/ios/documentation/cocoa/reference/Foundation/Classes/NSCalendar_Class/index.html - Andrew
2
非常重要的是需要 iOS 8。 - Andrew
你不应该强制解包。如果你试图将某些日期设置为午夜,由于夏令时偏移,它可能会返回nil。 - Leo Dabus
在NSDate中,我找不到dateBySettingHour这个方法。 - jose920405
1
@jose920405 这是因为它是 NSCalendar 类的一个方法。 - user3344977
显示剩余4条评论

8
这里是一个示例,演示如何在不使用dateBySettingHour函数的情况下完成操作(以确保您的代码仍与iOS 7设备兼容):
NSDate* now = [NSDate date];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *dateComponents = [gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:now];
NSDate* midnightLastNight = [gregorian dateFromComponents:dateComponents];

呸。

我更喜欢用C#编码的原因就在这里...

有人想看一些易读的代码吗?

DateTime midnightLastNight = DateTime.Today;

;-)


我希望在Swift中处理日期和C#一样容易 :(。 - BriOnH

5

Swift iOS 8及以上版本:人们往往会忘记日历和日期格式化对象有一个时区属性。如果您没有设置所需的时区并且默认时区值不适合您,则得到的小时和分钟可能会出错。

注意:在实际应用程序中,您可以进一步优化此代码。

注意:当不关心时区时,结果在一个设备上可能会是好的,在另一个设备上可能会很糟糕,只因为不同的时区设置。

注意:确保添加一个存在的时区标识符!此代码不处理缺少或拼写错误的时区名称。

func dateTodayZeroHour() -> Date {
    var cal = Calendar.current
    cal.timeZone = TimeZone(identifier: "Europe/Paris")!
    return cal.startOfDay(for: Date())   
}

你甚至可以扩展语言。如果默认时区适合你,请勿设置它。
extension Date {
    var midnight: Date {
        var cal = Calendar.current
        cal.timeZone = TimeZone(identifier: "Europe/Paris")!
        return cal.startOfDay(for: self)
    }
    var midday: Date {
        var cal = Calendar.current
        cal.timeZone = TimeZone(identifier: "Europe/Paris")!
        return cal.date(byAdding: .hour, value: 12, to: self.midnight)!
    }
}

然后像这样使用:

let formatter = DateFormatter()
formatter.timeZone = TimeZone(identifier: "Europe/Paris")
formatter.dateFormat = "yyyy/MM/dd HH:mm:ss"

let midnight = Date().midnight
let midnightString = formatter.string(from: midnight)
let midday = Date().midday
let middayString = formatter.string(from: midday)

let wheneverMidnight = formatter.date(from: "2018/12/05 08:08:08")!.midnight
let wheneverMidnightString = formatter.string(from: wheneverMidnight)

print("dates: \(midnightString) \(middayString) \(wheneverMidnightString)")

在我们的情况下,需要进行字符串转换和使用DateFormatter格式化日期,以便进行时区调整,因为日期对象本身不保存或关心时区值。

注意!由于计算链中的时区偏移量,最终结果可能会有所不同!


1
值为12是不正确的。请使用冒号符号,就像在任何其他方法参数中一样。 - Matěj Mefjů Novák

5

Swift 5+

let date = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: Date())

1
这将返回与时区相对的零点时间。 - Jason
输出不是00:00:00。 - chitgoks

2

如果有人正在寻找这个:

使用SwiftDate,你只需要这样做:

Date().atTime(hour: 0, minute: 0, second: 0)

1
在我看来,最容易验证但可能不是最快的解决方案是使用字符串。
func set( hours: Int, minutes: Int, seconds: Int, ofDate date: Date ) -> Date {

    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "yyyy-MM-dd"

    let newDateString = "\(dateFormatter.string(from: date)) \(hours):\(minutes):\(seconds)"

    dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"

    return dateFormatter.date(from: newDateString)
}

0
func resetHourMinuteSecond(date: NSDate, hour: Int, minute: Int, second: Int) -> NSDate{
    let nsdate = NSCalendar.currentCalendar().dateBySettingHour(hour, minute: minute, second: second, ofDate: date, options: NSCalendarOptions(rawValue: 0))
    return nsdate!
}

0
使用当前日历获取当前时间的一天开始。
let today = Calendar.current.startOfDay(for: Date())

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接