user1028028
user1028028

Reputation: 6433

NSDate beginning of day and end of day

-(NSDate *)beginningOfDay:(NSDate *)date
{
    NSCalendar *cal = [NSCalendar currentCalendar];
    NSDateComponents *components = [cal components:( NSMonthCalendarUnit | NSYearCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit ) fromDate:date];
    
    [components setHour:0];
    [components setMinute:0];
    [components setSecond:0];
    
    return [cal dateFromComponents:components];
    
}

-(NSDate *)endOfDay:(NSDate *)date
{
    NSCalendar *cal = [NSCalendar currentCalendar];
    NSDateComponents *components = [cal components:(  NSMonthCalendarUnit | NSYearCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit ) fromDate:date];
    
    [components setHour:23];
    [components setMinute:59];
    [components setSecond:59];
    
    return [cal dateFromComponents:components];

}

When I call : [self endOfDay:[NSDate date]]; I get the first of the month ... Why is that? I use this two methods because I need an interval that is from the first second of the first date (beginningOfDay:date1) to the last second of the second date (endOfDay:Date2) ...

Upvotes: 131

Views: 76865

Answers (25)

Zelko
Zelko

Reputation: 3971

Start Of Day / End Of Day — Swift 5.7

extension Date {
    var startOfDay: Date {
        return Calendar.current.startOfDay(for: self)
    }

    var endOfDay: Date {
        var components = DateComponents()
        components.day = 1
        components.second = -1
        return Calendar.current.date(byAdding: components, to: startOfDay)!
    }
    
    var startOfWeek: Date {
        Calendar.current.dateComponents([.calendar, .yearForWeekOfYear, .weekOfYear], from: self).date!
    }
    
    var endOfWeek: Date {
        var components = DateComponents()
        components.weekOfYear = 1
        components.second = -1
        return Calendar.current.date(byAdding: components, to: startOfWeek)!
    }
    
    var startOfMonth: Date {
        let components = Calendar.current.dateComponents([.year, .month], from: startOfDay)
        return Calendar.current.date(from: components)!
    }

    var endOfMonth: Date {
        var components = DateComponents()
        components.month = 1
        components.second = -1
        return Calendar.current.date(byAdding: components, to: startOfMonth)!
    }
}


// End of day = Start of tomorrow minus 1 second
// End of week = Start of next week minus 1 second
// End of month = Start of next month minus 1 second

Upvotes: 283

Mojtaba Hosseini
Mojtaba Hosseini

Reputation: 120092

Updating @Zelko's Answer to get the calendar:

extension Date {

    func startOfDay(in calendar: Calendar = .current) -> Date {
        calendar.startOfDay(for: self)
    }

    func endOfDay(in calendar: Calendar = .current) -> Date {
        var components = DateComponents()
        components.day = 1
        components.second = -1
        return calendar.date(byAdding: components, to: startOfDay(in: calendar))!
    }

    func startOfMonth(in calendar: Calendar = .current) -> Date {
        let components = calendar.dateComponents([.year, .month], from: startOfDay(in: calendar))
        return calendar.date(from: components)!
    }

    func endOfMonth(in calendar: Calendar = .current) -> Date {
        var components = DateComponents()
        components.month = 1
        components.second = -1
        return calendar.date(byAdding: components, to: startOfMonth(in: calendar))!
    }
}

Note:

It is very important to use the correct calendar when your app supports more regions

Upvotes: 3

Bryan Bryce
Bryan Bryce

Reputation: 1379

In iOS 8+ this is really convenient; you can do:

let startOfDay: Date = Calendar.current.startOfDay(for: Date())

To get the end of day then just use the Calendar methods for 23 hours, 59 mins, 59 seconds, depending on how you define end of day.

// Swift 5.0
let components = DateComponents(hour: 23, minute: 59, second: 59)
let endOfDay = Calendar.current.date(byAdding: components, to: startOfDay)

Date Math

Apple iOS NSCalendar Documentation. (See Section: Calendrical Calculations)

NSCalendar methods discussed by NSHipster.

Upvotes: 33

Nick Kohrn
Nick Kohrn

Reputation: 6049

Since the Calendar struct contains startOfDay(for:), I find it best to encapsulate an endOfDay(for:) method within an extension on Calendar rather than within Date.

The following code can be placed within a Playground file for quick execution.

import Foundation

extension Calendar {

    func endOfDay(for date: Date) -> Date {

        // Get the start of the date argument.
        let dayStart = self.startOfDay(for: date)

        // Add one day to the start of the day 
        // in order to get the start of the following day.            
        guard let nextDayStart = self.date(byAdding: .day, value: 1, to: dayStart) else {
            preconditionFailure("Expected start of next day")
        }

        // Create date components that will subtract a single
        // second from the start of the next day. This will
        // allow you to get the last hour, last minute, and last
        // second of the previous day, which is the day for the 
        // date argument that was passed to this method.
        var components = DateComponents()
        components.second = -1

        // Add the date components to the date for the next 
        // day, which will perform the subtraction of the single
        // second. This will return the end of the day for the date
        // that was passed into this method.
        guard let dayEnd = self.date(byAdding: components, to: nextDayStart) else {
            preconditionFailure("Expected end of day")
        }

        // Simply return the date value.
        return dayEnd
    }

}

To see the formatted date, create a DateFormatter and print the output.

let formatter = DateFormatter()
formatter.dateStyle = .long
formatter.timeStyle = .long
Calendar.current.endOfDay(for: Date()) // "Dec 31, 2020 at 11:59 PM"

Upvotes: 0

David Arve
David Arve

Reputation: 894

Calendar units should be thought of as intervals. As of iOS 10 Calendar has some nice methods for this

let day = Calendar.autoupdatingCurrent.dateInterval(of: .day, for: Date())
day?.start
day?.end.addingTimeInterval(-1)

You can use the same method, to get the start/end of any calendar component (week/month/year etc)

The caveat here is that there isn't a single point in time that can be defined as the end (given that we have a start). The above will give one second before the end of the day (23:59:59).

Upvotes: -1

vadian
vadian

Reputation: 285270

Just another way using dateInterval(of:start:interval:for:) of Calendar and the dedicated DateInterval struct

On return startDate contains the start of the day and interval the number of seconds in the day.

func dateInterval(of date : Date) -> DateInterval {
    var startDate = Date()
    var interval : TimeInterval = 0.0
    Calendar.current.dateInterval(of: .day, start: &startDate, interval: &interval, for: date)
    return DateInterval(start: startDate, duration: interval-1)
}

let interval = dateInterval(of: Date())
print(interval.start, interval.end)

Upvotes: 1

AppleSauce007
AppleSauce007

Reputation: 41

In Swift 5.3, both Calendar.current.startOfDay() and Calendar.current.date(bySettingHour:, minute:, second:, of:) are timezone specific. If you want your date to normalize to the same time regardless of timezone, you should use GMT time like this.

// Normalize time of aDate to 12:00 GMT
let aDate = Date()
let twelveGMT = 12 + TimeZone.current.secondsFromGMT() / 3600
let normalizedDate = Calendar.current.date(bySettingHour: twelveGMT, minute: 0, second: 0, of: aDate)

Upvotes: 4

closetCoder
closetCoder

Reputation: 1092

I think the most succinct way to do this in Swift is as follows:

extension Date {
    func startOfDay() -> Date {
        return Calendar.current.startOfDay(for: self)
    }
    func endOfDay() -> Date {
        return Calendar.current.date(bySettingHour: 23, minute: 59, second: 59, of: self) ?? self
    }
}

Upvotes: 12

August Lin
August Lin

Reputation: 1279

Swift 5 Simple and more precise answer.

Start time: 00:00:00

End time: 23:59:59.5

let date = Date() // current date or replace with a specific date
let calendar = Calendar.current
let startTime = calendar.startOfDay(for: date)
let endTime = calendar.date(bySettingHour: 23, minute: 59, second: 59, of: date)

Extra

let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: noon)!
let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: noon)!
let specificDate = Date("2020-01-01")

extension Date {
    init(_ dateString:String) {
        let dateStringFormatter = DateFormatter()
        dateStringFormatter.dateFormat = "yyyy-MM-dd"
        dateStringFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX") as Locale
        let date = dateStringFormatter.date(from: dateString)!
        self.init(timeInterval:0, since:date)
    }
}

Upvotes: 44

Ben
Ben

Reputation: 761

Swift 5.1 - XCode 11 with Date class instead of NSDate and Calender instead of NSCalender

extension Date {

    var startOfDay : Date {
        let calendar = Calendar.current
        let unitFlags = Set<Calendar.Component>([.year, .month, .day])
        let components = calendar.dateComponents(unitFlags, from: self)
        return calendar.date(from: components)!
   }

    var endOfDay : Date {
        var components = DateComponents()
        components.day = 1
        let date = Calendar.current.date(byAdding: components, to: self.startOfDay)
        return (date?.addingTimeInterval(-1))!
    }
}

Usage:

    let myDate = Date()
    let startOfDate = myDate.startOfDay
    let endOfDate = myDate.endOfDay

Upvotes: 17

Gurjit Singh
Gurjit Singh

Reputation: 1913

In Swift 3 and above

extension Date {
    var startOfDayDate: Date {
        return Calendar.current.startOfDay(for: self)
    }

    var endOfDayDate: Date {
        let nextDayDate = Calendar.current.date(byAdding: .day, value: 1, to: self.startOfDayDate)!
        return nextDayDate.addingTimeInterval(-1)
    }
}

Usage:

var currentDayStart = Date().startOfDayDate
var currentDayEnd = Date().endOfDayDate

Upvotes: 7

Paul Peelen
Paul Peelen

Reputation: 10329

This is what I use for Swift 4.2:

    let calendar = Calendar.current
    let fromDate = calendar.startOfDay(for: Date())
    let endDate = calendar.date(bySettingHour: 23, minute: 59, second: 59, of: Date())

Works like a charm for me. You could add this to an extension for start and end dates on Date, however keep in mind that adding an extension increases compile time (unless in the same file as the class), so if you only need it at one place or in one class... don't use an extension.

Upvotes: 1

Renish Dadhaniya
Renish Dadhaniya

Reputation: 10752

Just for reference, simple way to set Start and End of the day in Swift 4,

var comp: DateComponents = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute, .second], from: Date())
comp.hour = 0
comp.minute = 0
comp.second = 0
comp.timeZone = TimeZone(abbreviation: "UTC")!


//Set Start of Day
let startDate : Date = Calendar.current.date(from: comp)!
print(“Day of Start : \(startDate)")


//Set End of Day
comp.hour = 23
comp.minute = 59
comp.second = 59

let endDate : Date = Calendar.current.date(from:comp)!
print("Day of End : \(endDate)")

Upvotes: 0

Hardik Thakkar
Hardik Thakkar

Reputation: 15991

For swift 4

    var calendar = Calendar.current
    calendar.timeZone = NSTimeZone(abbreviation: "UTC")! as TimeZone
    let dateAtMidnight = calendar.startOfDay(for: Date())

    //For End Date
    var components = DateComponents()
    components.day = 1
    components.second = -1

    let dateAtEnd = calendar.date(byAdding: components, to: dateAtMidnight)

    print("dateAtMidnight :: \(dateAtMidnight)")
    print("dateAtEnd :: \(dateAtEnd!)")

Upvotes: 2

Johnny Rockex
Johnny Rockex

Reputation: 4196

Objective-C

NSCalendar * calendar = [NSCalendar currentCalendar];
NSDate * startDate = [calendar startOfDayForDate:[NSDate date]];
NSLog(@"start date is %@", startDate);

Upvotes: 4

LombaX
LombaX

Reputation: 17374

You are missing NSDayCalendarUnit in the components.

Upvotes: 2

JaanusSiim
JaanusSiim

Reputation: 2192

You are missing NSDayCalendarUnit in

NSDateComponents *components = [cal components:( NSMonthCalendarUnit | NSYearCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit ) fromDate:date];

Upvotes: 35

wzso
wzso

Reputation: 3905

extension Date {
    func stringFrom(dateFormat: String) -> String {
        let formatter = DateFormatter()
        formatter.dateFormat = dateFormat
        return formatter.string(from: self)
    }

    func firstSecondInDay() -> Date {
        let dayStr = self.stringFrom(dateFormat: "yyyy-MM-dd")
        let firstSecondStr = "\(dayStr) 00:00:00"
        let format = DateFormatter()
        format.dateFormat = "yyyy-MM-dd HH:mm:ss"
        return format.date(from: firstSecondStr)!
    }

    func lastSecondInDay() -> Date {
        let dayStr = self.stringFrom(dateFormat: "yyyy-MM-dd")
        let laseSecondStr = "\(dayStr) 23:59:59"
        let format = DateFormatter()
        format.dateFormat = "yyyy-MM-dd HH:mm:ss"
        return format.date(from: laseSecondStr)!
    }
}

Upvotes: 0

John Doe
John Doe

Reputation: 1414

Swift3 Using *XCode8
Apple is removing the NS from the class name so that NSDate can be swapped out to Date. You may get a compiler warning if you try to cast them saying they will always fail, but they work fine when you run them in the playground.

I replaced my generated NSDate in core data model with Date and they still work.

extension Date {
  func startTime() -> Date {
    return Calendar.current.startOfDay(for: self)
  }

  func endTime() -> Date {
    var components = DateComponents()
    components.day = 1
    components.second = -1
    return Calendar.current.date(byAdding: components, to: startTime())!
  }
}

Upvotes: 4

Nat
Nat

Reputation: 12968

Since iOS 8.0+ / macOS 10.12+ / tvOS 10.0+ / watchOS 3.0+ there is a built in function in the Foundation, which you can use out of the box. No need to implement own functions.

public func startOfDay(for date: Date) -> Date

So you can use it this way:

let midnightDate = Calendar(identifier: .gregorian).startOfDay(for: Date())

It's worth to remember, that this takes upon consideration the device time zone. You can set .timeZone on calendar if you want to have eg UTC zone.

Link to the Apple reference pages: https://developer.apple.com/reference/foundation/nscalendar/1417161-startofday.

Upvotes: 1

Jaseem Abbas
Jaseem Abbas

Reputation: 5230

Swift 3

  class func today() -> NSDate {
        return NSDate()
    }

    class func dayStart() -> NSDate {
          return NSCalendar.current.startOfDay(for: NSDate() as Date) as NSDate
    }

    class func dayEnd() -> NSDate {
        let components = NSDateComponents()
        components.day = 1
        components.second = -1
        return NSCalendar.current.date(byAdding: components as DateComponents, to: self.dayStart() as Date)
    }

Upvotes: 4

Libor Zapletal
Libor Zapletal

Reputation: 14102

My Swift extensions for NSDate:

Swift 1.2

extension NSDate {

    func beginningOfDay() -> NSDate {
        var calendar = NSCalendar.currentCalendar()
        var components = calendar.components(.CalendarUnitYear | .CalendarUnitMonth | .CalendarUnitDay, fromDate: self)
        return calendar.dateFromComponents(components)!
    }

    func endOfDay() -> NSDate {
        var components = NSDateComponents()
        components.day = 1
        var date = NSCalendar.currentCalendar().dateByAddingComponents(components, toDate: self.beginningOfDay(), options: .allZeros)!
        date = date.dateByAddingTimeInterval(-1)!
        return date
    }
}

Swift 2.0

extension NSDate {

    func beginningOfDay() -> NSDate {
        let calendar = NSCalendar.currentCalendar()
        let components = calendar.components([.Year, .Month, .Day], fromDate: self)
        return calendar.dateFromComponents(components)!
    }

    func endOfDay() -> NSDate {
        let components = NSDateComponents()
        components.day = 1
        var date = NSCalendar.currentCalendar().dateByAddingComponents(components, toDate: self.beginningOfDay(), options: [])!
        date = date.dateByAddingTimeInterval(-1)
        return date
    }
}

Upvotes: 17

Vikram Rao
Vikram Rao

Reputation: 514

For me none of the answers here and else where on stackoverflow worked. To get start of today i did this.

NSCalendar * gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; 
[gregorian setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];    
NSDateComponents *components = [gregorian components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:[NSDate date]]; 
[components setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; 
NSDate *beginningOfToday = [gregorian dateFromComponents:components];

Note this [gregorian setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; and [components setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];.

When a calendar is created it gets initialised with current timezone and when date is extracted from its components, since NSDate has no timezone, the date from current timezone is considered as UTC timezone. So we need to set the timezone before extracting components and later when extracting date from these components.

Upvotes: 5

k06a
k06a

Reputation: 18825

One more way to get result:

NSDate *date = [NSDate date];

NSDateComponents *components = [[NSDateComponents alloc] init];
components.day = [[NSCalendar currentCalendar] ordinalityOfUnit:(NSCalendarUnitDay) inUnit:(NSCalendarUnitEra) forDate:date];
NSDate *dayBegin = [[NSCalendar currentCalendar] dateFromComponents:components];

components.day += 1;
NSDate *dayEnd = [[NSCalendar currentCalendar] dateFromComponents:components];

Upvotes: 2

Maxim Lavrov
Maxim Lavrov

Reputation: 336

You don't have to set up the components to zero, just ignore them:

-(NSDate *)beginningOfDay:(NSDate *)date
{
    NSCalendar *calendar = [NSCalendar currentCalendar];
    NSDateComponents *components = [calendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit fromDate:date];
    return [calendar dateFromComponents:components];
}

Upvotes: 4

Related Questions