32

I want to generate a new NSDate with 0 hours, 0 minutes, and 0 seconds for time. The source date can be any random NSDate.

Is there a way to achieve this? The documentation did not help me with this.


Example

Have: 2010-10-30 10:14:13 GMT

Want: 2010-10-30 00:00:00 GMT

Evan Mulawski
  • 54,662
  • 15
  • 117
  • 144
Benjamin
  • 323
  • 1
  • 3
  • 5
  • See [my answer](http://stackoverflow.com/a/43121423/1966109) that offers up to four ways to solve your problem with Swift 3. – Imanou Petit Mar 30 '17 at 21:19

6 Answers6

64
unsigned int flags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay;
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDateComponents* components = [calendar components:flags fromDate:date];
NSDate* dateOnly = [calendar dateFromComponents:components];

date is the date you want to remove the time from.

This separates the date and time and creates a new date with the default time (00:00:00).

EDIT

To take time zone into account:

NSDate* dateOnly = [[calendar dateFromComponents:components] dateByAddingTimeInterval:[[NSTimeZone localTimeZone]secondsFromGMT]];
Bryan Norden
  • 2,397
  • 18
  • 22
Evan Mulawski
  • 54,662
  • 15
  • 117
  • 144
  • it retained its hour value :o i get 19:00:00 from 19:27:23 – Benjamin Nov 15 '10 at 18:28
  • 4
    Great answer. It is really stupid that commonly needed things like this are not included in `Foundation`. – Jonathan Sterling Nov 15 '10 at 18:28
  • This works well, but the reason I prefer using rangeOfUnit:startDate:interval:forDate: is that it's easier to generalize to find other date boundaries like year, minute, hour, etc. NSDateComponents requires that you work out the correct flags for each case. – Rob Napier Nov 15 '10 at 18:30
  • 1
    why 22:00:00 is default hour ? it depends on timezone? – Benjamin Nov 15 '10 at 18:32
  • 1
    Yes, there is a matter of the timezone. If you are GMT, it will return 00:00:00. See my edit. – Evan Mulawski Nov 15 '10 at 18:38
  • my real timezone is CET (Central Europe Time) DebugLog(@"Today: %@", [NSDate date]); said GMT (wrote in console) – Benjamin Nov 15 '10 at 18:44
  • I have tested random dates and times. All results have had the original date and a time of 00:00:00. – Evan Mulawski Nov 15 '10 at 18:49
  • yeah, okay, thank you very much, now i can do this on my own, that was very helpful, i'm grateful. – Benjamin Nov 15 '10 at 18:52
  • I'm getting 01:00:00. I'm guessing that's the same issue as Benjamin's - must need to adjust for daylight savings also. – Jonathan Moffatt Oct 22 '11 at 04:12
  • 2
    None of the answers on truncating date/time components worked for me without setting `[calendarInstance setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];` before extracting the components. I have answered it here http://stackoverflow.com/a/31278150/3056278 for a similar question as to why it is so. – Vikram Rao Jul 07 '15 at 20:05
17

Use NSCalendar's rangeOfUnit:startDate:interval:forDate:. This code will choose the day boundary based on the current time zone. If you want a particular time zone, you need to create an NSCalendar and set its time zone appropriately.

- (NSDate*)boundaryForCalendarUnit:(NSCalendarUnit)calendarUnit
{
    NSDate *boundary;
    [[NSCalendar currentCalendar] rangeOfUnit:calendarUnit startDate:&boundary interval:NULL forDate:self];
    return boundary;
}

- (NSDate*)dayBoundary
{
    return [self boundaryForCalendarUnit:NSDayCalendarUnit];
}
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • 1
    +1 I like this better than the accepted answer. It doesn't run into problems with daylight savings and doesn't require a hack to take time zone into account. – Jonathan Moffatt Oct 22 '11 at 04:25
  • +1 @rob great solution. Evan's accepted answer slowed my app down (around **1642.0ms** CPU-Time in ['Instruments'](https://developer.apple.com/library/mac/#documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40004652-CH1-SW1) 'Time Profiler' for each method call) whereas rob's solution fasted up to around **100.0ms**. – anneblue Jun 20 '13 at 11:41
  • Interesting answer, but I don't think it addresses the question completely. You can get the correct output my setting the timezone to gmt. One way is to do this: NSCalendar *calendar = [NSCalendar currentCalendar]; [calendar setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]; – SmileBot Nov 05 '13 at 03:52
  • 1
    If you use this, and pass [NSDate date] as the argument you'll get the date in the GMT timezone, NOT your local timezone. If you want just the date in the local / system timezone use the accepted answer. Also note that the debugger will show something like this: __NSTaggedDate * 2015-09-10 00:00:00 UTC which is slightly confusing because it shows "UTC", but since the time is zero anyway the UTC is irrelevant. – Mark Edington Sep 10 '15 at 21:20
  • @MarkEdington UTC = GMT and yes it is relevant :) NSDate has no custom time zone as it is always saved in UTC / GMT. While the accepted answer and this answer both support the users time zone out of the box, this one is much cleaner. – marsbear Apr 03 '16 at 22:37
5

With Swift 3, you can choose one of the four following patterns in order to solve your problem.


#1. Using Calendar startOfDay(for:)

startOfDay(for:) has the following declaration:

func startOfDay(for date: Date) -> Date

Returns the first moment of a given Date, as a Date.

The Playground code below shows how to use this method:

import Foundation

let date = Date()

// Get new date
let calendar = Calendar.current
let newDate = calendar.startOfDay(for: date)

// Format dates
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_UK")
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .long

let formattedDate = dateFormatter.string(from: date)
let formattedNewDate = dateFormatter.string(from: newDate)

// Print formatted dates
print(formattedDate) // Prints: 30/03/2017, 15:14:41 CEST
print(formattedNewDate) // Prints: 30/03/2017, 00:00:00 CEST

#2. Using Calendar date(bySettingHour:minute:second:of:matchingPolicy:repeatedTimePolicy:direction:)

date(bySettingHour:minute:second:of:matchingPolicy:repeatedTimePolicy:direction:) has the following declaration:

func date(bySettingHour hour: Int, minute: Int, second: Int, of date: Date, matchingPolicy: Calendar.MatchingPolicy = default, repeatedTimePolicy: Calendar.RepeatedTimePolicy = default, direction: Calendar.SearchDirection = default) -> Date?

Returns a new Date representing the date calculated by setting hour, minute, and second to a given time on a specified Date.

The Playground code below shows how to use this method:

import Foundation

let date = Date()

// Get new date
let calendar = Calendar.current
let newDate = calendar.date(bySettingHour: 0, minute: 0, second: 0, of: date)

// Format dates
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_UK")
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .long

let formattedDate = dateFormatter.string(from: date)
let formattedNewDate = dateFormatter.string(from: newDate!)

// Print formatted dates
print(formattedDate) // Prints: 30/03/2017, 15:14:41 CEST
print(formattedNewDate) // Prints: 30/03/2017, 00:00:00 CEST

#3. Using Calendar dateComponents(_:from:) and date(from:) methods

dateComponents(_:from:) has the following declaration:

func dateComponents(_ components: Set<Calendar.Component>, from date: Date) -> DateComponents

Returns all the date components of a date, using the calendar time zone.

date(from:) has the following declaration:

func date(from components: DateComponents) -> Date?

Returns a date created from the specified components.

The Playground code below shows how to use those methods:

import Foundation

let date = Date()

// Get new date
let calendar = Calendar.current
let components = calendar.dateComponents([.day, .month, .year], from: date)
let newDate = calendar.date(from: components)

// Format dates
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_UK")
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .long

let formattedDate = dateFormatter.string(from: date)
let formattedNewDate = dateFormatter.string(from: newDate!)

// Print formatted dates
print(formattedDate) // Prints: 30/03/2017, 15:14:41 CEST
print(formattedNewDate) // Prints: 30/03/2017, 00:00:00 CEST

#4. Using NSCalendar range(of:start:interval:for:)

range(of:start:interval:for:) has the following declaration:

func range(of unit: NSCalendar.Unit, start datep: AutoreleasingUnsafeMutablePointer<NSDate?>?, interval tip: UnsafeMutablePointer<TimeInterval>?, for date: Date) -> Bool

Returns by reference the starting time and duration of a given calendar unit that contains a given date.

The Playground code below shows how to use this method:

import Foundation

let date = Date()

// Get new date
let calendar = Calendar.current as NSCalendar
var newDate: NSDate?
calendar.range(of: .day, start: &newDate, interval: nil, for: date)

// Format dates
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_UK")
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .long

let formattedDate = dateFormatter.string(from: date)
let formattedNewDate = dateFormatter.string(from: newDate as! Date)

// Print formatted dates
print(formattedDate) // Prints: 30/03/2017, 15:14:41 CEST
print(formattedNewDate) // Prints: 30/03/2017, 00:00:00 CEST
Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
3

I know its late, but there are now better methods: why dont you just use

Swift 2

NSCalendar.currentCalendar().dateBySettingHour(0, minute: 0, second: 0, ofDate: yourDateToZeroOutTime, options: [])

Swift 5

var yourDate = Date() //Or any Date
yourDate  = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: yourDate) ?? yourDate
Krishna Raj Salim
  • 7,331
  • 5
  • 34
  • 66
smat88dd
  • 2,258
  • 2
  • 25
  • 38
1

Swift 3

extension Date {
    func trimTime() -> Date {
        var boundary = Date()
        var interval: TimeInterval = 0
        _ = Calendar.current.dateInterval(of: .day, start: &boundary, interval: &interval, for: self)

        return Date(timeInterval: TimeInterval(NSTimeZone.system.secondsFromGMT()), since: boundary)
    }
}
Chris Byatt
  • 3,689
  • 3
  • 34
  • 61
0

I would use the description method to get the given date as a string, then modify the string and create your new date with initWithString.

initWithString: Returns an NSDate object initialized with a date and time value specified by a given string in the international string representation format.

  • (id)initWithString:(NSString *)description Parameters description A string that specifies a date and time value in the international string representation format—YYYY-MM-DD HH:MM:SS ±HHMM, where ±HHMM is a time zone offset in hours and minutes from GMT (for example, “2001-03-24 10:45:32 +0600”). You must specify all fields of the format string, including the time zone offset, which must have a plus or minus sign prefix. Return Value An NSDate object initialized with a date and time value specified by aString.
jakev
  • 2,815
  • 3
  • 26
  • 39
  • 2
    nm, evan's solution is better – jakev Nov 15 '10 at 18:26
  • This is a bad idea. If you really want to modify the date component-wise there is Evan's solution for that. I don't want to pull the performance card on such a small topic but well... string processing is comparatively expensive. If you just want to get the start of the day (or the "base date" for a day) for calculations, filtering etc - use Rob's version as it is even cleaner. – marsbear Apr 03 '16 at 22:41