Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I set an NSDate object to midnight?

I have an NSDate object and I want to set it to an arbitrary time (say, midnight) so that I can use the timeIntervalSince1970 function to retrieve data consistently without worrying about the time when the object is created.

I've tried using an NSCalendar and modifying its components by using some Objective-C methods, like this:

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) 

The problem with the above method is that you can only set one unit of time (/* a unit of time */), so you could only have one of the following be accurate:

  1. Day
  2. Month
  3. Year
  4. Hours
  5. Minutes
  6. Seconds

Is there a way to set hours, minutes, and seconds at the same time and retain the date (day/month/year)?

like image 542
AstroCB Avatar asked Oct 04 '14 04:10

AstroCB


Video Answer


2 Answers

Your statement

The problem with the above method is that you can only set one unit of time ...

is not correct. NSCalendarUnit conforms to the RawOptionSetType protocol which inherits from BitwiseOperationsType. This means that the options can be bitwise combined with & and |.

In Swift 2 (Xcode 7) this was changed again to be an OptionSetType which offers a set-like interface, see for example Error combining NSCalendarUnit with OR (pipe) in Swift 2.0.

Therefore the following compiles and works in iOS 7 and 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) 

(Note that I have omitted the type annotations for the variables, the Swift compiler infers the type automatically from the expression on the right hand side of the assignments.)

Determining the start of the given day (midnight) can also done with the rangeOfUnit() method (iOS 7 and 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) 

If your deployment target is iOS 8 then it is even simpler:

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

Update for Swift 3 (Xcode 8):

let date = Date() let cal = Calendar(identifier: .gregorian) let newDate = cal.startOfDay(for: date) 
like image 167
Martin R Avatar answered Sep 20 '22 21:09

Martin R


Yes.

You don't need to fiddle with the components of the NSCalendar at all; you can simply call the dateBySettingHour method and use the ofDate parameter with your existing date.

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

For 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)! 

Then, to get your time since 1970, you can just do

let time: NSTimeInterval = newDate.timeIntervalSince1970 

dateBySettingHour was introduced in OS X Mavericks (10.9) and gained iOS support with iOS 8.

Declaration in 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); 
like image 35
AstroCB Avatar answered Sep 24 '22 21:09

AstroCB