RelativeDateTimeFormatter returns one day less than expected [duplicate] - swift

I'm wondering if there is some new and awesome possibility to get the amount of days between two NSDates in Swift / the "new" Cocoa?
E.g. like in Ruby I would do:
(end_date - start_date).to_i

You have to consider the time difference as well. For example if you compare the dates 2015-01-01 10:00 and 2015-01-02 09:00, days between those dates will return as 0 (zero) since the difference between those dates is less than 24 hours (it's 23 hours).
If your purpose is to get the exact day number between two dates, you can work around this issue like this:
// Assuming that firstDate and secondDate are defined
// ...
let calendar = NSCalendar.currentCalendar()
// Replace the hour (time) of both dates with 00:00
let date1 = calendar.startOfDayForDate(firstDate)
let date2 = calendar.startOfDayForDate(secondDate)
let flags = NSCalendarUnit.Day
let components = calendar.components(flags, fromDate: date1, toDate: date2, options: [])
components.day // This will return the number of day(s) between dates
Swift 3 and Swift 4 Version
let calendar = Calendar.current
// Replace the hour (time) of both dates with 00:00
let date1 = calendar.startOfDay(for: firstDate)
let date2 = calendar.startOfDay(for: secondDate)
let components = calendar.dateComponents([.day], from: date1, to: date2)

Here is my answer for Swift 2:
func daysBetweenDates(startDate: NSDate, endDate: NSDate) -> Int
{
let calendar = NSCalendar.currentCalendar()
let components = calendar.components([.Day], fromDate: startDate, toDate: endDate, options: [])
return components.day
}

I see a couple Swift3 answers so I'll add my own:
public static func daysBetween(start: Date, end: Date) -> Int {
Calendar.current.dateComponents([.day], from: start, to: end).day!
}
The naming feels more Swifty, it's one line, and using the latest dateComponents() method.

Here is very nice, Date extension to get difference between dates in years, months, days, hours, minutes, seconds
extension Date {
func years(sinceDate: Date) -> Int? {
return Calendar.current.dateComponents([.year], from: sinceDate, to: self).year
}
func months(sinceDate: Date) -> Int? {
return Calendar.current.dateComponents([.month], from: sinceDate, to: self).month
}
func days(sinceDate: Date) -> Int? {
return Calendar.current.dateComponents([.day], from: sinceDate, to: self).day
}
func hours(sinceDate: Date) -> Int? {
return Calendar.current.dateComponents([.hour], from: sinceDate, to: self).hour
}
func minutes(sinceDate: Date) -> Int? {
return Calendar.current.dateComponents([.minute], from: sinceDate, to: self).minute
}
func seconds(sinceDate: Date) -> Int? {
return Calendar.current.dateComponents([.second], from: sinceDate, to: self).second
}
}

I translated my Objective-C answer
let start = "2010-09-01"
let end = "2010-09-05"
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let startDate:NSDate = dateFormatter.dateFromString(start)
let endDate:NSDate = dateFormatter.dateFromString(end)
let cal = NSCalendar.currentCalendar()
let unit:NSCalendarUnit = .Day
let components = cal.components(unit, fromDate: startDate, toDate: endDate, options: nil)
println(components)
result
<NSDateComponents: 0x10280a8a0>
Day: 4
The hardest part was that the autocompletion insists fromDate and toDate would be NSDate?, but indeed they must be NSDate! as shown in the reference.
I don't see how a good solution with an operator would look like, as you want to specify the unit differently in each case. You could return the time interval, but than won't you gain much.

Update for Swift 3 iOS 10 Beta 4
func daysBetweenDates(startDate: Date, endDate: Date) -> Int {
let calendar = Calendar.current
let components = calendar.dateComponents([Calendar.Component.day], from: startDate, to: endDate)
return components.day!
}

Swift 5. Thanks to Emin Buğra Saral above for the startOfDay suggestion.
extension Date {
func daysBetween(date: Date) -> Int {
return Date.daysBetween(start: self, end: date)
}
static func daysBetween(start: Date, end: Date) -> Int {
let calendar = Calendar.current
// Replace the hour (time) of both dates with 00:00
let date1 = calendar.startOfDay(for: start)
let date2 = calendar.startOfDay(for: end)
let a = calendar.dateComponents([.day], from: date1, to: date2)
return a.value(for: .day)!
}
}
Usage:
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let start = dateFormatter.date(from: "2017-01-01")!
let end = dateFormatter.date(from: "2018-01-01")!
let diff = Date.daysBetween(start: start, end: end) // 365
// or
let diff = start.daysBetween(date: end) // 365

Here is the answer for Swift 3 (tested for IOS 10 Beta)
func daysBetweenDates(startDate: Date, endDate: Date) -> Int
{
let calendar = Calendar.current
let components = calendar.components([.day], from: startDate, to: endDate, options: [])
return components.day!
}
Then you can call it like this
let pickedDate: Date = sender.date
let NumOfDays: Int = daysBetweenDates(startDate: pickedDate, endDate: Date())
print("Num of Days: \(NumOfDays)")

Swift 5
Working, you need to set the time to be the same for both days, if you are off by seconds it will be wrong
func daysBetween(start: Date, end: Date) -> Int {
let start = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: start)!
let end = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: end)!
return Calendar.current.dateComponents([.day], from: start, to: end).day ?? 0
}

The things built into swift are still very basic. As they should be at this early stage. But you can add your own stuff with the risk that comes with overloading operators and global domain functions. They will be local to your module though.
let now = NSDate()
let seventies = NSDate(timeIntervalSince1970: 0)
// Standard solution still works
let days = NSCalendar.currentCalendar().components(.CalendarUnitDay,
fromDate: seventies, toDate: now, options: nil).day
// Flashy swift... maybe...
func -(lhs:NSDate, rhs:NSDate) -> DateRange {
return DateRange(startDate: rhs, endDate: lhs)
}
class DateRange {
let startDate:NSDate
let endDate:NSDate
var calendar = NSCalendar.currentCalendar()
var days: Int {
return calendar.components(.CalendarUnitDay,
fromDate: startDate, toDate: endDate, options: nil).day
}
var months: Int {
return calendar.components(.CalendarUnitMonth,
fromDate: startDate, toDate: endDate, options: nil).month
}
init(startDate:NSDate, endDate:NSDate) {
self.startDate = startDate
self.endDate = endDate
}
}
// Now you can do this...
(now - seventies).months
(now - seventies).days

This is an updated version of Emin's answer for Swift 5 that incorporates the suggestion to use noon instead of midnight as the definitive time for comparing days. It also handles the potential failure of various date functions by returning an optional.
///
/// This is an approximation; it does not account for time differences. It will set the time to 1200 (noon) and provide the absolute number
/// of days between now and the given date. If the result is negative, it should be read as "days ago" instead of "days from today."
/// Returns nil if something goes wrong initializing or adjusting dates.
///
func daysFromToday() -> Int?
{
let calendar = NSCalendar.current
// Replace the hour (time) of both dates with noon. (Noon is less likely to be affected by DST changes, timezones, etc. than midnight.)
guard let date1 = calendar.date(bySettingHour: 12, minute: 00, second: 00, of: calendar.startOfDay(for: Date())),
let date2 = calendar.date(bySettingHour: 12, minute: 00, second: 00, of: calendar.startOfDay(for: self)) else
{
return nil
}
return calendar.dateComponents([.day], from: date1, to: date2).day
}

Here is my answer for Swift 3:
func daysBetweenDates(startDate: NSDate, endDate: NSDate, inTimeZone timeZone: TimeZone? = nil) -> Int {
var calendar = Calendar.current
if let timeZone = timeZone {
calendar.timeZone = timeZone
}
let dateComponents = calendar.dateComponents([.day], from: startDate.startOfDay, to: endDate.startOfDay)
return dateComponents.day!
}

You could use the following extension:
public extension Date {
func daysTo(_ date: Date) -> Int? {
let calendar = Calendar.current
// Replace the hour (time) of both dates with 00:00
let date1 = calendar.startOfDay(for: self)
let date2 = calendar.startOfDay(for: date)
let components = calendar.dateComponents([.day], from: date1, to: date2)
return components.day // This will return the number of day(s) between dates
}
}
Then, you can call it like this:
startDate.daysTo(endDate)

There's hardly any Swift-specific standard library yet; just the lean basic numeric, string, and collection types.
It's perfectly possible to define such shorthands using extensions, but as far as the actual out-of-the-box APIs goes, there is no "new" Cocoa; Swift just maps directly to the same old verbose Cocoa APIs as they already exist.

I'm going to add my version even though this thread is a year old. My code looks like this:
var name = txtName.stringValue // Get the users name
// Get the date components from the window controls
var dateComponents = NSDateComponents()
dateComponents.day = txtDOBDay.integerValue
dateComponents.month = txtDOBMonth.integerValue
dateComponents.year = txtDOBYear.integerValue
// Make a Gregorian calendar
let calendar = NSCalendar(identifier: NSCalendarIdentifierGregorian)
// Get the two dates we need
var birthdate = calendar?.dateFromComponents(dateComponents)
let currentDate = NSDate()
var durationDateComponents = calendar?.components(NSCalendarUnit.CalendarUnitDay, fromDate: birthdate!, toDate: currentDate, options: nil)
let numberOfDaysAlive = durationDateComponents?.day
println("\(numberOfDaysAlive!)")
txtGreeting.stringValue = "Hello \(name), You have been alive for \(numberOfDaysAlive!) days."
I hope it helps someone.
Cheers,

Erin's method updated to Swift 3, This shows days from today (disregarding time of day)
func daysBetweenDates( endDate: Date) -> Int
let calendar: Calendar = Calendar.current
let date1 = calendar.startOfDay(for: Date())
let date2 = calendar.startOfDay(for: secondDate)
return calendar.dateComponents([.day], from: date1, to: date2).day!
}

This returns an absolute difference in days between some Date and today:
extension Date {
func daysFromToday() -> Int {
return abs(Calendar.current.dateComponents([.day], from: self, to: Date()).day!)
}
}
and then use it:
if someDate.daysFromToday() >= 7 {
// at least a week from today
}

easier option would be to create a extension on Date
public extension Date {
public var currentCalendar: Calendar {
return Calendar.autoupdatingCurrent
}
public func daysBetween(_ date: Date) -> Int {
let components = currentCalendar.dateComponents([.day], from: self, to: date)
return components.day!
}
}

Swift 3.2
extension DateComponentsFormatter {
func difference(from fromDate: Date, to toDate: Date) -> String? {
self.allowedUnits = [.year,.month,.weekOfMonth,.day]
self.maximumUnitCount = 1
self.unitsStyle = .full
return self.string(from: fromDate, to: toDate)
}
}

All answer is good. But for Localizations we need calculates a number of decimal days in between two dates. so we can provide the sustainable decimal format.
// This method returns the fractional number of days between to dates
func getFractionalDaysBetweenDates(date1: Date, date2: Date) -> Double {
let components = Calendar.current.dateComponents([.day, .hour], from: date1, to: date2)
var decimalDays = Double(components.day!)
decimalDays += Double(components.hour!) / 24.0
return decimalDays
}

Nice handy one liner :
extension Date {
var daysFromNow: Int {
return Calendar.current.dateComponents([.day], from: Date(), to: self).day!
}
}

Swift 3 - Days from today until date
func daysUntilDate(endDateComponents: DateComponents) -> Int
{
let cal = Calendar.current
var components = cal.dateComponents([.era, .year, .month, .day], from: NSDate() as Date)
let today = cal.date(from: components)
let otherDate = cal.date(from: endDateComponents)
components = cal.dateComponents([Calendar.Component.day], from: (today! as Date), to: otherDate!)
return components.day!
}
Call function like this
// Days from today until date
var examnDate = DateComponents()
examnDate.year = 2016
examnDate.month = 12
examnDate.day = 15
let daysCount = daysUntilDate(endDateComponents: examnDate)

extension Date {
func daysFromToday() -> Int {
return Calendar.current.dateComponents([.day], from: self, to: Date()).day!
}
}
Then use it like
func dayCount(dateString: String) -> String{
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM dd,yyyy hh:mm a"
let fetchedDate = dateFormatter.date(from: dateString)
let day = fetchedDate?.daysFromToday()
if day! > -1{
return "\(day!) days passed."
}else{
return "\(day! * -1) days left."
}
}

extension Date {
static func - (recent: Date, previous: Date) -> DateComponents {
var dateComponents = DateComponents()
dateComponents.year = Calendar.current.dateComponents([.day], from: previous, to: recent).year
dateComponents.month = Calendar.current.dateComponents([.month], from: previous, to: recent).month
dateComponents.day = Calendar.current.dateComponents([.day], from: previous, to: recent).day
dateComponents.hour = Calendar.current.dateComponents([.hour], from: previous, to: recent).hour
dateComponents.minute = Calendar.current.dateComponents([.minute], from: previous, to: recent).minute
dateComponents.second = Calendar.current.dateComponents([.second], from: previous, to: recent).second
return dateComponents
}
}

func completeOffset(from date:Date) -> String? {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .brief
return formatter.string(from: Calendar.current.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date, to: self))
}
if you need year month days and hours as string use this
var tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: Date())!
let dc = tomorrow.completeOffset(from: Date())

2017 version, copy and paste
func simpleIndex(ofDate: Date) -> Int {
// index here just means today 0, yesterday -1, tomorrow 1 etc.
let c = Calendar.current
let todayRightNow = Date()
let d = c.date(bySetting: .hour, value: 13, of: ofDate)
let t = c.date(bySetting: .hour, value: 13, of: todayRightNow)
if d == nil || today == nil {
print("weird problem simpleIndex#ofDate")
return 0
}
let r = c.dateComponents([.day], from: today!, to: d!)
// yesterday is negative one, tomorrow is one
if let o = r.value(for: .day) {
return o
}
else {
print("another weird problem simpleIndex#ofDate")
return 0
}
}

let calendar = NSCalendar.currentCalendar();
let component1 = calendar.component(.Day, fromDate: fromDate)
let component2 = calendar.component(.Day, fromDate: toDate)
let difference = component1 - component2

Swift 5.2.4 solution:
import UIKit
let calendar = Calendar.current
let start = "2010-09-01"
let end = "2010-09-05"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let firstDate = dateFormatter.date(from: start)!
let secondDate = dateFormatter.date(from: end)!
// Replace the hour (time) of both dates with 00:00
let date1 = calendar.startOfDay(for: firstDate)
let date2 = calendar.startOfDay(for: secondDate)
let components = calendar.dateComponents([Calendar.Component.day], from: date1, to: date2)
components.day // This will return the number of day(s) between dates

Related

How to round up date to midnight in Swift? [duplicate]

I'm trying to get the first and last day of the month in swift.
So far I have the following:
let dateFormatter = NSDateFormatter()
let date = NSDate()
dateFormatter.dateFormat = "yyyy-MM-dd"
let calendar = NSCalendar.currentCalendar()
let components = calendar.components([.Year, .Month, .Day, .Hour, .Minute, .Second], fromDate: date)
let month = components.month
let year = components.year
let startOfMonth = ("\(year)-\(month)-01")
But I'm not sure how to get the last date. Is there a built in method I'm missing? Obviously it has to take into account leap years etc.
Swift 3 and 4 drop-in extensions
This actually gets a lot easier with Swift 3+:
You can do it without guard (you could if you wanted to, but because DateComponents is a non-optional type now, it's no longer necessary).
Using iOS 8's startOfDayForDate (now startOfDay), you don't need to manually set the time to 12pm unless you're doing some really crazy calendar calculations across time zones.
It's worth mentioning that some of the other answers claim you can shortcut this by using Calendar.current.date(byAdding: .month, value: 0, to: Date())!, but where this fails, is that it doesn't actually zero out the day, or account for differences in timezones.
Here you go:
extension Date {
func startOfMonth() -> Date {
return Calendar.current.date(from: Calendar.current.dateComponents([.year, .month], from: Calendar.current.startOfDay(for: self)))!
}
func endOfMonth() -> Date {
return Calendar.current.date(byAdding: DateComponents(month: 1, day: -1), to: self.startOfMonth())!
}
}
print(Date().startOfMonth()) // "2018-02-01 08:00:00 +0000\n"
print(Date().endOfMonth()) // "2018-02-28 08:00:00 +0000\n"
You get the first day of the month simply with
let components = calendar.components([.Year, .Month], fromDate: date)
let startOfMonth = calendar.dateFromComponents(components)!
print(dateFormatter.stringFromDate(startOfMonth)) // 2015-11-01
To get the last day of the month, add one month and subtract one day:
let comps2 = NSDateComponents()
comps2.month = 1
comps2.day = -1
let endOfMonth = calendar.dateByAddingComponents(comps2, toDate: startOfMonth, options: [])!
print(dateFormatter.stringFromDate(endOfMonth)) // 2015-11-30
Alternatively, use the rangeOfUnit method which gives you
the start and the length of the month:
var startOfMonth : NSDate?
var lengthOfMonth : NSTimeInterval = 0
calendar.rangeOfUnit(.Month, startDate: &startOfMonth, interval: &lengthOfMonth, forDate: date)
For a date on the last day of month, add the length of the month minus one second:
let endOfMonth = startOfMonth!.dateByAddingTimeInterval(lengthOfMonth - 1)
Updated for Swift5:
extension Date {
var startOfDay: Date {
return Calendar.current.startOfDay(for: self)
}
var startOfMonth: Date {
let calendar = Calendar(identifier: .gregorian)
let components = calendar.dateComponents([.year, .month], from: self)
return calendar.date(from: components)!
}
var endOfDay: Date {
var components = DateComponents()
components.day = 1
components.second = -1
return Calendar.current.date(byAdding: components, to: startOfDay)!
}
var endOfMonth: Date {
var components = DateComponents()
components.month = 1
components.second = -1
return Calendar(identifier: .gregorian).date(byAdding: components, to: startOfMonth)!
}
func isMonday() -> Bool {
let calendar = Calendar(identifier: .gregorian)
let components = calendar.dateComponents([.weekday], from: self)
return components.weekday == 2
}
}
With Swift 3 & iOS 10 the easiest way I found to do this is Calendar's dateInterval(of:for:):
guard let interval = calendar.dateInterval(of: .month, for: Date()) else { return }
You can then use interval.start and interval.end to get the dates you need.
Swift 3
Many date example for :
Last 6 month,
last 3 month,
yesterday, last 7 day, last 30 day, previous month,
current month start & end, last month start & end date
let startDate = dateFormatter.string(from: Date().getThisMonthStart()!)
let endDate = dateFormatter.string(from: Date().getThisMonthEnd()!)
extension Date {
func getLast6Month() -> Date? {
return Calendar.current.date(byAdding: .month, value: -6, to: self)
}
func getLast3Month() -> Date? {
return Calendar.current.date(byAdding: .month, value: -3, to: self)
}
func getYesterday() -> Date? {
return Calendar.current.date(byAdding: .day, value: -1, to: self)
}
func getLast7Day() -> Date? {
return Calendar.current.date(byAdding: .day, value: -7, to: self)
}
func getLast30Day() -> Date? {
return Calendar.current.date(byAdding: .day, value: -30, to: self)
}
func getPreviousMonth() -> Date? {
return Calendar.current.date(byAdding: .month, value: -1, to: self)
}
// This Month Start
func getThisMonthStart() -> Date? {
let components = Calendar.current.dateComponents([.year, .month], from: self)
return Calendar.current.date(from: components)!
}
func getThisMonthEnd() -> Date? {
let components:NSDateComponents = Calendar.current.dateComponents([.year, .month], from: self) as NSDateComponents
components.month += 1
components.day = 1
components.day -= 1
return Calendar.current.date(from: components as DateComponents)!
}
//Last Month Start
func getLastMonthStart() -> Date? {
let components:NSDateComponents = Calendar.current.dateComponents([.year, .month], from: self) as NSDateComponents
components.month -= 1
return Calendar.current.date(from: components as DateComponents)!
}
//Last Month End
func getLastMonthEnd() -> Date? {
let components:NSDateComponents = Calendar.current.dateComponents([.year, .month], from: self) as NSDateComponents
components.day = 1
components.day -= 1
return Calendar.current.date(from: components as DateComponents)!
}
}
Swift 4
If you only need the ordinal day:
func lastDay(ofMonth m: Int, year y: Int) -> Int {
let cal = Calendar.current
var comps = DateComponents(calendar: cal, year: y, month: m)
comps.setValue(m + 1, for: .month)
comps.setValue(0, for: .day)
let date = cal.date(from: comps)!
return cal.component(.day, from: date)
}
lastDay(ofMonth: 2, year: 2018) // 28
lastDay(ofMonth: 2, year: 2020) // 29
This is the simplest way that I found (Swift 5+):
extension Date {
func getStart(of component: Calendar.Component, calendar: Calendar = Calendar.current) -> Date? {
return calendar.dateInterval(of: component, for: self)?.start
}
func getEnd(of component: Calendar.Component, calendar: Calendar = Calendar.current) -> Date? {
return calendar.dateInterval(of: component, for: self)?.end
}
}
Here is easiest solution:
extension Date {
func startOfMonth() -> Date {
let interval = Calendar.current.dateInterval(of: .month, for: self)
return (interval?.start.toLocalTime())! // Without toLocalTime it give last months last date
}
func endOfMonth() -> Date {
let interval = Calendar.current.dateInterval(of: .month, for: self)
return interval!.end
}
// Convert UTC (or GMT) to local time
func toLocalTime() -> Date {
let timezone = TimeZone.current
let seconds = TimeInterval(timezone.secondsFromGMT(for: self))
return Date(timeInterval: seconds, since: self)
}}
and then call these with your date instance:
print(Date().startOfMonth())
print(Date().endOfMonth())
2017...
First, get the month you need:
let cal = Calendar.current
let d = Calendar.current.date(byAdding: .month, value: 0, to: Date())!
// for "last month" just use -1, for "next month" just use 1, etc
To get the day-of-the-week for the first day of the month:
let c = cal.dateComponents([.year, .month], from: d)
let FDOM = cal.date(from: c)!
let dowFDOM = cal.component(.weekday, from: FDOM)
print("the day-of-week on the 1st is ... \(dowFDOM)")
// so, that's 1=Sunday, 2=Monday, etc.
To get the number of days in the month:
let r = cal.range(of: .day, in: .month, for: d)!
let kDays = r.count
print("the number of days is ... \(kDays)")
With Swift 3, you can choose one of the two following patters in order to retrieve the first and last days of a month.
#1. Using Calendar dateComponents(_:from:), date(from:) and date(byAdding:to:wrappingComponents:) methods
With this pattern, you first get the date of the first day of a month then add a month and remove a day from it in order to get the date of the last day of the month. The Playground code below shows how to set it:
import Foundation
// Set calendar and date
let calendar = Calendar.current
let date = calendar.date(byAdding: DateComponents(day: -10), to: Date())!
// Get first day of month
let firstDayComponents = calendar.dateComponents([.year, .month], from: date)
let firstDay = calendar.date(from: firstDayComponents)!
// Get last day of month
let lastDayComponents = DateComponents(month: 1, day: -1)
let lastDay = calendar.date(byAdding: lastDayComponents, to: firstDay)!
// Set date formatter
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_UK")
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .long
// Print results
print(dateFormatter.string(from: date)) // Prints: 22 March 2017 at 18:07:15 CET
print(dateFormatter.string(from: firstDay)) // Prints: 1 March 2017 at 00:00:00 CET
print(dateFormatter.string(from: lastDay)) // Prints: 31 March 2017 at 00:00:00 CEST
#2. Using Calendar range(of:in:for:), dateComponents(_:from:) and date(from:) and methods
With this pattern, you get a range of absolute day values in a month and then retrieve the dates of the first day and last day of the month from it. The Playground code below shows how to set it:
import Foundation
// Set calendar and date
let calendar = Calendar.current
let date = calendar.date(byAdding: DateComponents(day: -10), to: Date())!
// Get range of days in month
let range = calendar.range(of: .day, in: .month, for: date)! // Range(1..<32)
// Get first day of month
var firstDayComponents = calendar.dateComponents([.year, .month], from: date)
firstDayComponents.day = range.lowerBound
let firstDay = calendar.date(from: firstDayComponents)!
// Get last day of month
var lastDayComponents = calendar.dateComponents([.year, .month], from: date)
lastDayComponents.day = range.upperBound - 1
//lastDayComponents.day = range.count // also works
let lastDay = calendar.date(from: lastDayComponents)!
// Set date formatter
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_UK")
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .long
// Print results
print(dateFormatter.string(from: date)) // prints: 22 March 2017 at 18:07:15 CET
print(dateFormatter.string(from: firstDay)) // prints: 1 March 2017 at 00:00:00 CET
print(dateFormatter.string(from: lastDay)) // prints: 31 March 2017 at 00:00:00 CEST
In swift 3, if you put 0 to day component you can get the last day of the month. There's an example code:
public func isMoreDays(date: Date, asc: Bool)->Bool{
//components
var dayComponents = self.getDateComponents(date: date)
//asc is true if ascendant or false if descendant
dayComponents.day = asc ? 0 : 1
//plus 1 to month 'cos if you set up day to 0 you are going to the previous month
dayComponents.month = asc ? dayComponents.month! + 1 : dayComponents.month
//instantiate calendar and get the date
let calendar : Calendar = NSCalendar.current
let day = calendar.date(from: dayComponents)
//date comparison
if(day?.compare(date) == .orderedSame){
return false
}
return true
}
You can use the following extensions here :
let today = Date()
let startOfMonth = today.beginning(of: .month)
let endOfMonth = today.end(of: .month)

How to have reoccurring countdown each year to a date in Swift

I am trying to incorporate a countdown to a holiday within my app in Swift and having trouble with the timer going into negative numbers once the date has passed. The countdown should reset after the date passing. What am I doing wrong?
var holidayDate: Date {
let currentYear = Date()
let userCalendar = Calendar.current
var components = DateComponents()
components.year = userCalendar.component(.year, from: currentYear)
components.day = 02
components.month = 11
return userCalendar.date(from: components)!
}
var today: Date {
let now = Date()
let userCalendar = Calendar.current
var components = DateComponents()
components.year = userCalendar.component(.year, from: now)
components.day = userCalendar.component(.day, from: now)
components.month = userCalendar.component(.month, from: now)
return userCalendar.date(from: components)!
}
func daysBetweenDates(startDate: Date, endDate: Date) -> Int {
let calendar = Calendar.current
let components = calendar.dateComponents([.day], from: startDate, to: endDate)
return components.day!
}
You can simply use calendar nextDate method to get the next date matching components and pass the month and day components to it:
func daysUntilNextDate(matching components: DateComponents) -> Int {
let date = Date()
guard let calendar = components.calendar,
let nextDate = calendar.nextDate(after: date, matching: components, matchingPolicy: .strict) else { return .zero }
return calendar.dateComponents([.day], from: date, to: nextDate).day!
}
let holiday: DateComponents = .init(calendar: .current, month: 11, day: 2)
let christmas: DateComponents = .init(calendar: .current, month: 12, day: 25)
daysUntilNextDate(matching: holiday) // 363
daysUntilNextDate(matching: christmas) // 51

How to get all days in current week in swift

I am making an application where I need to get the day of the year for all days in the current week.
To achieve this, I am looking for a result similar to below (Today is Thursday the 23rd of March)
Monday = 79
Tuesday = 80
Wednesday = 81
Thursday = 82
Friday = 83
Saturday and Sunday can be included, however my application only needs weekdays rather then weekends. Today is day 82
To get the weekdays of the week, it is:
let calendar = Calendar.current
let today = calendar.startOfDay(for: Date())
let dayOfWeek = calendar.component(.weekday, from: today)
let dates = calendar.range(of: .weekday, in: .weekOfYear, for: today)!
.compactMap { calendar.date(byAdding: .day, value: $0 - dayOfWeek, to: today) }
.filter { !calendar.isDateInWeekend($0) }
To display that as “Thursday = 82”, it is:
let formatter = DateFormatter()
formatter.dateFormat = "eeee' = 'D"
for date in dates {
print(formatter.string(from: date))
}
Or
let strings = dates.map { formatter.string(from: $0) }
If you want it to consider firstWeekday (generally only a concern if you are no longer filtering weekends out of the results, hence no filter on isDateInWeekend, below):
let calendar = Calendar.current
let today = calendar.startOfDay(for: Date())
let todaysDayOfWeek = calendar.component(.weekday, from: today)
guard
let weekdaysRange = calendar.range(of: .weekday, in: .weekOfYear, for: today),
let index = weekdaysRange.firstIndex(of: calendar.firstWeekday)
else { return }
let weekdays = weekdaysRange[index...] + weekdaysRange[..<index].map { $0 + weekdaysRange.count }
let dates = weekdays.compactMap { calendar.date(byAdding: .day, value: $0 - todaysDayOfWeek, to: today) }
I had problems with adapting Rob's solution for when the week starts on Monday. This solution adapts to your calendar.firstWeekday setting.
var calendar = Calendar.autoupdatingCurrent
calendar.firstWeekday = 2 // Start on Monday (or 1 for Sunday)
let today = calendar.startOfDay(for: Date())
var week = [Date]()
if let weekInterval = calendar.dateInterval(of: .weekOfYear, for: today) {
for i in 0...6 {
if let day = calendar.date(byAdding: .day, value: i, to: weekInterval.start) {
week += [day]
}
}
}
return week
I made a small extension on Calendar, which might be helpful for anyone else looking for a more functional solution to this.
import Foundation
extension Calendar {
func intervalOfWeek(for date: Date) -> DateInterval? {
dateInterval(of: .weekOfYear, for: date)
}
func startOfWeek(for date: Date) -> Date? {
intervalOfWeek(for: date)?.start
}
func daysWithSameWeekOfYear(as date: Date) -> [Date] {
guard let startOfWeek = startOfWeek(for: date) else {
return []
}
return (0 ... 6).reduce(into: []) { result, daysToAdd in
result.append(Calendar.current.date(byAdding: .day, value: daysToAdd, to: startOfWeek))
}
.compactMap { $0 }
}
}
You could also use enumerateDates like this:
func getAllDaysOfTheCurrentWeek() -> [Date] {
var dates: [Date] = []
guard let dateInterval = Calendar.current.dateInterval(of: .weekOfYear,
for: Date()) else {
return dates
}
Calendar.current.enumerateDates(startingAfter: dateInterval.start,
matching: DateComponents(hour:0),
matchingPolicy: .nextTime) { date, _, stop in
guard let date = date else {
return
}
if date <= dateInterval.end {
dates.append(date)
} else {
stop = true
}
}
return dates
}

How can I figure out the day difference in the following example

I am calculating the day difference between two dates, but I figured out the the following code is actually giving me the difference for 24 hours rather than the difference in date. I have the following code:
func daysBetweenDate(startDate: NSDate, endDate: NSDate) -> Int
{
let calendar = NSCalendar.currentCalendar()
let components = calendar.components([.Day], fromDate:startDate, toDate: endDate, options: [])
return components.day
}
So, for the following example I get this result:
lastLaunch:2016-06-10 01:39:07 +0000
toady: 2016-06-11 00:41:41 +0000
dayDiff:0
I would have expected the day difference to be one, since last launch was on the 10th and today is the 11th. How can I change the code to give me the actual difference in date for days?
You can use Calendar method date BySettingHour to get the noon time for your startDate and endDate, to make your calendar calculations not time sensitive:
Xcode 8.2.1 • Swift 3.0.2
extension Date {
var noon: Date? {
return Calendar.autoupdatingCurrent.date(bySettingHour: 12, minute: 0, second: 0, of: self)
}
func daysBetween(_ date: Date) -> Int? {
guard let noon = noon, let date = date.noon else { return nil }
return Calendar.autoupdatingCurrent.dateComponents([.day], from: noon, to: date).day
}
}
let startDateString = "2016-06-10 01:39:07 +0000"
let todayString = "2016-06-11 00:41:41 +0000"
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "ex_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss xxxx"
if let startDate = formatter.date(from: startDateString),
let endDate = formatter.date(from: todayString),
let days = startDate.daysBetween(endDate) {
print(startDate) // "2016-06-10 01:39:07 +0000\n"
print(endDate) // "2016-06-11 00:41:41 +0000\n"
print(days ?? "nil") // 1
}
Swift 2.x
extension NSDate {
var noon: NSDate {
return NSCalendar.currentCalendar().dateBySettingHour(12, minute: 0, second: 0, ofDate: self, options: [])!
}
}
func daysBetweenDate(startDate: NSDate, endDate: NSDate) -> Int {
return NSCalendar.currentCalendar().components([.Day],
fromDate: startDate.noon,
toDate: endDate.noon,
options: []).day
}

Working out the start and end of a day. Swift

I have a function to work out the start and end of a week which works as expected. I want to implement another function which works out the start and end of a single day. I have the code below however I get the following error:
Type of expression is ambiguous without more context.
public class Date {
let dateFormatter = NSDateFormatter()
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
func calcStartAndEndDateForWeek(durationOccurance: Double) {
print("Calculating start and end for week")
let componentsWeek = calendar.components([.YearForWeekOfYear, .WeekOfYear], fromDate: date)
let startOfWeek = calendar.dateFromComponents(componentsWeek)!
print("start of Week = \(dateFormatter.stringFromDate(startOfWeek))")
let componentsWeekEnds = NSDateComponents()
componentsWeekEnds.weekOfYear = 1
let endOfWeek = calendar.dateByAddingComponents(componentsWeekEnds, toDate: startOfWeek, options: [])!
print("End of the week = \(dateFormatter.stringFromDate(endOfWeek))")
}
func calcStartAndEndDateForDay(durationOccurance: Double) {
print("Calculating start and end for day")
let componentsWeek = calendar.components([.Minutes, .Seconds], fromDate: date)
let startOfDay = calendar.dateFromComponents(componentsWeek)!
print("start day = \(dateFormatter.stringFromDate(startOfDay))")
}
init(){
dateFormatter.dateFormat = "dd-MM-yyyy"
}
}
We can create a more generic function using the methods on NSCalendar:
func rangeOfPeriod(period: NSCalendarUnit, date: NSDate) -> (NSDate, NSDate) {
let calendar = NSCalendar.currentCalendar()
var startDate: NSDate? = nil
// let's ask calendar for the start of the period
calendar.rangeOfUnit(period, startDate: &startDate, interval: nil, forDate: date)
// end of this period is the start of the next period
let endDate = calendar.dateByAddingUnit(period, value: 1, toDate: startDate!, options: [])
// you can subtract 1 second if you want to make "Feb 1 00:00:00" into "Jan 31 23:59:59"
// let endDate2 = calendar.dateByAddingUnit(.Second, value: -1, toDate: endDate!, options: [])
return (startDate!, endDate!)
}
Called as
print("\(rangeOfPeriod(.WeekOfYear, date: NSDate()))")
print("\(rangeOfPeriod(.Day, date: NSDate()))")
Putting it into your code:
public class Date {
let dateFormatter = NSDateFormatter()
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
func rangeOfPeriod(period: NSCalendarUnit) -> (NSDate, NSDate) {
var startDate: NSDate? = nil
calendar.rangeOfUnit(period, startDate: &startDate, interval: nil, forDate: date)
let endDate = calendar.dateByAddingUnit(period, value: 1, toDate: startDate!, options: [])
return (startDate!, endDate!)
}
func calcStartAndEndDateForWeek() {
let (startOfWeek, endOfWeek) = rangeOfPeriod(.WeekOfYear)
print("Start of week = \(dateFormatter.stringFromDate(startOfWeek))")
print("End of the week = \(dateFormatter.stringFromDate(endOfWeek))")
}
func calcStartAndEndDateForDay() {
let (startOfDay, endOfDay) = rangeOfPeriod(.Day)
print("Start of day = \(dateFormatter.stringFromDate(startOfDay))")
print("End of the day = \(dateFormatter.stringFromDate(endOfDay))")
}
init() {
dateFormatter.dateFormat = "dd-MM-yyyy"
}
}
let myDate = Date()
myDate.calcStartAndEndDateForWeek()
myDate.calcStartAndEndDateForDay()
I was implementing something similar and went the following route:
extension Date {
static var startOfToday: Date? {
let date = Date()
guard !date.isStartOfDay else { return date }
return date
.zero(out: .second)?
.zero(out: .minute)?
.zero(out: .hour)?
.addingTimeInterval(-24 * 60 * 60)
}
private func zero(out: Calendar.Component) -> Date? {
return Calendar.current
.date(bySetting: out, value: 0, of: self)
}
private var isStartOfDay: Bool {
let cal = Calendar.current
let hours = cal.component(.hour, from: self)
let minutes = cal.component(.minute, from: self)
let seconds = cal.component(.second, from: self)
return hours == 0 && minutes == 0 && seconds == 0
}
}
Setting a component to zero will increment the next bigger component. So just setting the hour to zero will push the date to the next day at 00:00, unless of course the hour is already at zero. So to make it work for any date we have to zero out the seconds, minutes and hours (in that order). And to make sure we don't end up at the beginning of yesterday we first check if all values aren't already at zero.
I realize this is kinda hacky and probably not the best way to go about this, but it seems to work well enough for my use-case at least.
Getting the end of the day can be built on top of this by just adding another day.