How do you compare just the time of a Date in Swift? - swift

I have two Date Objects:
2017-01-13 11:40:17 +0000
2016-03-15 10:22:14 +0000
I need to compare just the time of these values and ignore the date
example: 12:00am and 12:01am, 12:01 is later so (12:01am > 12:00am) == true

This is the route I took in the end, which makes it easy to compare just the time of a Date in swift
New Object Time:
class Time: Comparable, Equatable {
init(_ date: Date) {
//get the current calender
let calendar = Calendar.current
//get just the minute and the hour of the day passed to it
let dateComponents = calendar.dateComponents([.hour, .minute], from: date)
//calculate the seconds since the beggining of the day for comparisions
let dateSeconds = dateComponents.hour! * 3600 + dateComponents.minute! * 60
//set the varibles
secondsSinceBeginningOfDay = dateSeconds
hour = dateComponents.hour!
minute = dateComponents.minute!
}
init(_ hour: Int, _ minute: Int) {
//calculate the seconds since the beggining of the day for comparisions
let dateSeconds = hour * 3600 + minute * 60
//set the varibles
secondsSinceBeginningOfDay = dateSeconds
self.hour = hour
self.minute = minute
}
var hour : Int
var minute: Int
var date: Date {
//get the current calender
let calendar = Calendar.current
//create a new date components.
var dateComponents = DateComponents()
dateComponents.hour = hour
dateComponents.minute = minute
return calendar.date(byAdding: dateComponents, to: Date())!
}
/// the number or seconds since the beggining of the day, this is used for comparisions
private let secondsSinceBeginningOfDay: Int
//comparisions so you can compare times
static func == (lhs: Time, rhs: Time) -> Bool {
return lhs.secondsSinceBeginningOfDay == rhs.secondsSinceBeginningOfDay
}
static func < (lhs: Time, rhs: Time) -> Bool {
return lhs.secondsSinceBeginningOfDay < rhs.secondsSinceBeginningOfDay
}
static func <= (lhs: Time, rhs: Time) -> Bool {
return lhs.secondsSinceBeginningOfDay <= rhs.secondsSinceBeginningOfDay
}
static func >= (lhs: Time, rhs: Time) -> Bool {
return lhs.secondsSinceBeginningOfDay >= rhs.secondsSinceBeginningOfDay
}
static func > (lhs: Time, rhs: Time) -> Bool {
return lhs.secondsSinceBeginningOfDay > rhs.secondsSinceBeginningOfDay
}
}
Date Extension for easy access:
//Adds ability to just get the time from a date:
extension Date {
var time: Time {
return Time(self)
}
}
Example:
let firstDate = Date()
let secondDate = firstDate
//Will return true
let timeEqual = firstDate.time == secondDate.time

Much simpler than accepted answer:
SWIFT 4
// date1 and date2 are the dates you want to compare
let calendar = Calendar.current
var newDate = Date(TimeIntervalSinceReferenceDate: 0) // Initiates date at 2001-01-01 00:00:00 +0000
var newDate1 = Date(TimeIntervalSinceReferenceDate: 0) // Same as above
// Recieving the components from the dates you want to compare
let newDateComponents = calendar.dateComponents([.hour, .minute], from: date1)!
let newDate1Components = calendar.dateComponents([.hour, .minute], from: date2)!
// Adding those components
newDate = calendar.date(byAdding: newDateComponents, to: newDate)
newDate1 = calendar.date(byAdding: newDate1Components, to: newDate1)

My approach would be to use Calendar to make them Date objects with the same day and then comparing them using for example timeIntervalSinceReferenceDate.
Another, cleaner (but most likely with more lines of resulting code) would be to create extension for Date called secondsFromBeginningOfTheDay() -> TimeInterval and then comparing the resulting double values.
Example based on the second approach:
// Creating Date from String
let textDate1 = "2017-01-13T12:21:00-0800"
let textDate2 = "2016-03-06T20:12:05-0900"
let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZ"
formatter.timeZone = TimeZone.current
return formatter
} ()
// Dates used for the comparison
let date1 = dateFormatter.date(from: textDate1)
let date2 = dateFormatter.date(from: textDate2)
// Date extensions
extension Date {
func secondsFromBeginningOfTheDay() -> TimeInterval {
let calendar = Calendar.current
// omitting fractions of seconds for simplicity
let dateComponents = calendar.dateComponents([.hour, .minute, .second], from: self)
let dateSeconds = dateComponents.hour! * 3600 + dateComponents.minute! * 60 + dateComponents.second!
return TimeInterval(dateSeconds)
}
// Interval between two times of the day in seconds
func timeOfDayInterval(toDate date: Date) -> TimeInterval {
let date1Seconds = self.secondsFromBeginningOfTheDay()
let date2Seconds = date.secondsFromBeginningOfTheDay()
return date2Seconds - date1Seconds
}
}
if let date1 = date1, let date2 = date2 {
let diff = date1.timeOfDayInterval(toDate: date2)
// as text
if diff > 0 {
print("Time of the day in the second date is greater")
} else if diff < 0 {
print("Time of the day in the first date is greater")
} else {
print("Times of the day in both dates are equal")
}
// show interval as as H M S
let timeIntervalFormatter = DateComponentsFormatter()
timeIntervalFormatter.unitsStyle = .abbreviated
timeIntervalFormatter.allowedUnits = [.hour, .minute, .second]
print("Difference between times since midnight is", timeIntervalFormatter.string(from: diff) ?? "n/a")
}
// Output:
// Time of the day in the second date is greater
// Difference between times since midnight is 8h 51m 5s

My solution for comparing two times of day while ignoring the date:
let date1 = some time as a date
let date2 = some other time as a date
let time1 = 60*Calendar.current.component(.hour, from: date1!) + Calendar.current.component(.minute, from: date1!)
let time2 = 60*Calendar.current.component(.hour, from: date2!) + Calendar.current.component(.minute, from: date2!)
Now you can compare the integers time1 and time2 without regard to the day. You could add the seconds/60 if you need more precision.

This code works, check it easily in playground
let s1 = "22:31"
let s2 = "14:31"
let f = DateFormatter()
f.dateFormat = "HH:mm"
f.date(from: s1)! //"Jan 1, 2000 at 10:31 PM"
f.date(from: s2)! //"Jan 1, 2000 at 2:31 PM"
f.date(from: s1)! > f.date(from: s2)! // true

There's no standard type for a time-of-day. A reasonable type to start with is just a tuple:
typealias TimeOfDay = (hour: Int, minute: Int, second: Int)
To create these TimeOfDay values, you'll need a Calendar. By default, a Calendar uses the device's system-wide time zone. If you don't want that, set the Calendar's time zone explicitly. Example:
var calendar = Calendar.autoupdatingCurrent
calendar.timeZone = TimeZone(abbreviation: "UTC")!
Now you can use a DateFormatter to convert strings to Dates (if necessary), and then use calendar to extract the time-of-day components from the Dates:
let strings: [String] = ["2017-01-13 11:40:17 +0000", "2016-03-15 10:22:14 +0000"]
let parser = DateFormatter()
parser.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
let timesOfDay: [TimeOfDay] = strings.map({ (string) -> TimeOfDay in
let components = calendar.dateComponents([.hour, .minute, .second], from: parser.date(from: string)!)
return (hour: components.hour!, minute: components.minute!, second: components.second!)
})
Swift.print(timesOfDay)
// Output: [(11, 40, 17), (10, 22, 14)]
Finally, you can compare these TimeOfDay values. Swift comes with standard comparison operators for tuples whose elements are Comparable, so this TimeOfDay type qualifies. You can just say this:
if timesOfDay[0] < timesOfDay[1] {
Swift.print("date[0] comes first")
} else if timesOfDay[0] == timesOfDay[1] {
Swift.print("times are equal")
} else {
Swift.print("date[1] comes first")
}

Let say we got two dates in string format:
// "2017-01-13 11:40:17 +0000"
// "2016-03-15 10:22:14 +0000"
We need to convert this strings to Date format, we create DateFormatter() and set the format ("yyyy-MM-dd' 'HH:mm:ssZ") it gonna convert
//date formatter converts string to date in our case
let firstDateFormatter = DateFormatter()
firstDateFormatter.dateFormat = "yyyy-MM-dd' 'HH:mm:ssZ"
Now we can get our date from string to Date format
//convert string to dates
if let date1 = firstDateFormatter.date(from: "2017-01-13 09:40:17 +0000"),
let date2 = firstDateFormatter.date(from: "2016-03-15 10:22:14 +0000") {
What we want is to compare only Hours and Minutes. So change dateformat to "HH:mm"
//we ve got the dates, now switch dateformat for other job
firstDateFormatter.dateFormat = "HH:mm"
Now get the string value from our date, that only contain "HH:mm"
// convert date to string ( part of string we want to compare )
let HHmmDate1 = firstDateFormatter.string(from: date1) //"17:40"
let HHmmDate2 = firstDateFormatter.string(from: date2) //"18:22"
Final step is to get date from our "HH:mm" values, let say we ask DateFormatter to give us a date, based on time only, in our case "17:40" and "18:22". DateFormatter will put some values for dates, so we get Jan 1, 2000 automatically for both dates, but it will get the time we provide.
//produce "default" dates with desired HH:mm
//default means same date, but time is different
let HH1 = firstDateFormatter.date(from: HHmmDate1) //"Jan 1, 2000 at 5:40 PM"
let HH2 = firstDateFormatter.date(from: HHmmDate2) //"Jan 1, 2000 at 6:22 PM"
Now we could easily compare dates
//compare
HH1! > HH2!
}
There are many options to compare dates with Calendar also

This is very simple in Swift if you use Swifter Swift
date1.day = 1
date1.month = 1
date1.year = 2000
date2.day = 1
date2.month = 1
date2.year = 2000
now you can use >,<,== operators on date1 and date2 to compare just the time components.
edit - you could do this your self by extending the date class, for example swifter-swift does the bellow for the day component.
public var day: Int {
get {
return Calendar.current.component(.day, from: self)
}
set {
let allowedRange = Calendar.current.range(of: .day, in: .month, for: self)!
guard allowedRange.contains(newValue) else { return }
let currentDay = Calendar.current.component(.day, from: self)
let daysToAdd = newValue - currentDay
if let date = Calendar.current.date(byAdding: .day, value: daysToAdd, to: self) {
self = date
}
}
}

Related

How do I compare two Date objects by hours, minutes and seconds; ignoring the dates? [duplicate]

I have two Date Objects:
2017-01-13 11:40:17 +0000
2016-03-15 10:22:14 +0000
I need to compare just the time of these values and ignore the date
example: 12:00am and 12:01am, 12:01 is later so (12:01am > 12:00am) == true
This is the route I took in the end, which makes it easy to compare just the time of a Date in swift
New Object Time:
class Time: Comparable, Equatable {
init(_ date: Date) {
//get the current calender
let calendar = Calendar.current
//get just the minute and the hour of the day passed to it
let dateComponents = calendar.dateComponents([.hour, .minute], from: date)
//calculate the seconds since the beggining of the day for comparisions
let dateSeconds = dateComponents.hour! * 3600 + dateComponents.minute! * 60
//set the varibles
secondsSinceBeginningOfDay = dateSeconds
hour = dateComponents.hour!
minute = dateComponents.minute!
}
init(_ hour: Int, _ minute: Int) {
//calculate the seconds since the beggining of the day for comparisions
let dateSeconds = hour * 3600 + minute * 60
//set the varibles
secondsSinceBeginningOfDay = dateSeconds
self.hour = hour
self.minute = minute
}
var hour : Int
var minute: Int
var date: Date {
//get the current calender
let calendar = Calendar.current
//create a new date components.
var dateComponents = DateComponents()
dateComponents.hour = hour
dateComponents.minute = minute
return calendar.date(byAdding: dateComponents, to: Date())!
}
/// the number or seconds since the beggining of the day, this is used for comparisions
private let secondsSinceBeginningOfDay: Int
//comparisions so you can compare times
static func == (lhs: Time, rhs: Time) -> Bool {
return lhs.secondsSinceBeginningOfDay == rhs.secondsSinceBeginningOfDay
}
static func < (lhs: Time, rhs: Time) -> Bool {
return lhs.secondsSinceBeginningOfDay < rhs.secondsSinceBeginningOfDay
}
static func <= (lhs: Time, rhs: Time) -> Bool {
return lhs.secondsSinceBeginningOfDay <= rhs.secondsSinceBeginningOfDay
}
static func >= (lhs: Time, rhs: Time) -> Bool {
return lhs.secondsSinceBeginningOfDay >= rhs.secondsSinceBeginningOfDay
}
static func > (lhs: Time, rhs: Time) -> Bool {
return lhs.secondsSinceBeginningOfDay > rhs.secondsSinceBeginningOfDay
}
}
Date Extension for easy access:
//Adds ability to just get the time from a date:
extension Date {
var time: Time {
return Time(self)
}
}
Example:
let firstDate = Date()
let secondDate = firstDate
//Will return true
let timeEqual = firstDate.time == secondDate.time
Much simpler than accepted answer:
SWIFT 4
// date1 and date2 are the dates you want to compare
let calendar = Calendar.current
var newDate = Date(TimeIntervalSinceReferenceDate: 0) // Initiates date at 2001-01-01 00:00:00 +0000
var newDate1 = Date(TimeIntervalSinceReferenceDate: 0) // Same as above
// Recieving the components from the dates you want to compare
let newDateComponents = calendar.dateComponents([.hour, .minute], from: date1)!
let newDate1Components = calendar.dateComponents([.hour, .minute], from: date2)!
// Adding those components
newDate = calendar.date(byAdding: newDateComponents, to: newDate)
newDate1 = calendar.date(byAdding: newDate1Components, to: newDate1)
My approach would be to use Calendar to make them Date objects with the same day and then comparing them using for example timeIntervalSinceReferenceDate.
Another, cleaner (but most likely with more lines of resulting code) would be to create extension for Date called secondsFromBeginningOfTheDay() -> TimeInterval and then comparing the resulting double values.
Example based on the second approach:
// Creating Date from String
let textDate1 = "2017-01-13T12:21:00-0800"
let textDate2 = "2016-03-06T20:12:05-0900"
let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZ"
formatter.timeZone = TimeZone.current
return formatter
} ()
// Dates used for the comparison
let date1 = dateFormatter.date(from: textDate1)
let date2 = dateFormatter.date(from: textDate2)
// Date extensions
extension Date {
func secondsFromBeginningOfTheDay() -> TimeInterval {
let calendar = Calendar.current
// omitting fractions of seconds for simplicity
let dateComponents = calendar.dateComponents([.hour, .minute, .second], from: self)
let dateSeconds = dateComponents.hour! * 3600 + dateComponents.minute! * 60 + dateComponents.second!
return TimeInterval(dateSeconds)
}
// Interval between two times of the day in seconds
func timeOfDayInterval(toDate date: Date) -> TimeInterval {
let date1Seconds = self.secondsFromBeginningOfTheDay()
let date2Seconds = date.secondsFromBeginningOfTheDay()
return date2Seconds - date1Seconds
}
}
if let date1 = date1, let date2 = date2 {
let diff = date1.timeOfDayInterval(toDate: date2)
// as text
if diff > 0 {
print("Time of the day in the second date is greater")
} else if diff < 0 {
print("Time of the day in the first date is greater")
} else {
print("Times of the day in both dates are equal")
}
// show interval as as H M S
let timeIntervalFormatter = DateComponentsFormatter()
timeIntervalFormatter.unitsStyle = .abbreviated
timeIntervalFormatter.allowedUnits = [.hour, .minute, .second]
print("Difference between times since midnight is", timeIntervalFormatter.string(from: diff) ?? "n/a")
}
// Output:
// Time of the day in the second date is greater
// Difference between times since midnight is 8h 51m 5s
My solution for comparing two times of day while ignoring the date:
let date1 = some time as a date
let date2 = some other time as a date
let time1 = 60*Calendar.current.component(.hour, from: date1!) + Calendar.current.component(.minute, from: date1!)
let time2 = 60*Calendar.current.component(.hour, from: date2!) + Calendar.current.component(.minute, from: date2!)
Now you can compare the integers time1 and time2 without regard to the day. You could add the seconds/60 if you need more precision.
This code works, check it easily in playground
let s1 = "22:31"
let s2 = "14:31"
let f = DateFormatter()
f.dateFormat = "HH:mm"
f.date(from: s1)! //"Jan 1, 2000 at 10:31 PM"
f.date(from: s2)! //"Jan 1, 2000 at 2:31 PM"
f.date(from: s1)! > f.date(from: s2)! // true
There's no standard type for a time-of-day. A reasonable type to start with is just a tuple:
typealias TimeOfDay = (hour: Int, minute: Int, second: Int)
To create these TimeOfDay values, you'll need a Calendar. By default, a Calendar uses the device's system-wide time zone. If you don't want that, set the Calendar's time zone explicitly. Example:
var calendar = Calendar.autoupdatingCurrent
calendar.timeZone = TimeZone(abbreviation: "UTC")!
Now you can use a DateFormatter to convert strings to Dates (if necessary), and then use calendar to extract the time-of-day components from the Dates:
let strings: [String] = ["2017-01-13 11:40:17 +0000", "2016-03-15 10:22:14 +0000"]
let parser = DateFormatter()
parser.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
let timesOfDay: [TimeOfDay] = strings.map({ (string) -> TimeOfDay in
let components = calendar.dateComponents([.hour, .minute, .second], from: parser.date(from: string)!)
return (hour: components.hour!, minute: components.minute!, second: components.second!)
})
Swift.print(timesOfDay)
// Output: [(11, 40, 17), (10, 22, 14)]
Finally, you can compare these TimeOfDay values. Swift comes with standard comparison operators for tuples whose elements are Comparable, so this TimeOfDay type qualifies. You can just say this:
if timesOfDay[0] < timesOfDay[1] {
Swift.print("date[0] comes first")
} else if timesOfDay[0] == timesOfDay[1] {
Swift.print("times are equal")
} else {
Swift.print("date[1] comes first")
}
Let say we got two dates in string format:
// "2017-01-13 11:40:17 +0000"
// "2016-03-15 10:22:14 +0000"
We need to convert this strings to Date format, we create DateFormatter() and set the format ("yyyy-MM-dd' 'HH:mm:ssZ") it gonna convert
//date formatter converts string to date in our case
let firstDateFormatter = DateFormatter()
firstDateFormatter.dateFormat = "yyyy-MM-dd' 'HH:mm:ssZ"
Now we can get our date from string to Date format
//convert string to dates
if let date1 = firstDateFormatter.date(from: "2017-01-13 09:40:17 +0000"),
let date2 = firstDateFormatter.date(from: "2016-03-15 10:22:14 +0000") {
What we want is to compare only Hours and Minutes. So change dateformat to "HH:mm"
//we ve got the dates, now switch dateformat for other job
firstDateFormatter.dateFormat = "HH:mm"
Now get the string value from our date, that only contain "HH:mm"
// convert date to string ( part of string we want to compare )
let HHmmDate1 = firstDateFormatter.string(from: date1) //"17:40"
let HHmmDate2 = firstDateFormatter.string(from: date2) //"18:22"
Final step is to get date from our "HH:mm" values, let say we ask DateFormatter to give us a date, based on time only, in our case "17:40" and "18:22". DateFormatter will put some values for dates, so we get Jan 1, 2000 automatically for both dates, but it will get the time we provide.
//produce "default" dates with desired HH:mm
//default means same date, but time is different
let HH1 = firstDateFormatter.date(from: HHmmDate1) //"Jan 1, 2000 at 5:40 PM"
let HH2 = firstDateFormatter.date(from: HHmmDate2) //"Jan 1, 2000 at 6:22 PM"
Now we could easily compare dates
//compare
HH1! > HH2!
}
There are many options to compare dates with Calendar also
This is very simple in Swift if you use Swifter Swift
date1.day = 1
date1.month = 1
date1.year = 2000
date2.day = 1
date2.month = 1
date2.year = 2000
now you can use >,<,== operators on date1 and date2 to compare just the time components.
edit - you could do this your self by extending the date class, for example swifter-swift does the bellow for the day component.
public var day: Int {
get {
return Calendar.current.component(.day, from: self)
}
set {
let allowedRange = Calendar.current.range(of: .day, in: .month, for: self)!
guard allowedRange.contains(newValue) else { return }
let currentDay = Calendar.current.component(.day, from: self)
let daysToAdd = newValue - currentDay
if let date = Calendar.current.date(byAdding: .day, value: daysToAdd, to: self) {
self = date
}
}
}

Check if specific date isToday (or passed) Swift

via dateformatter, how can I write a function to know if a specific date has passed or is today?
example: March 8, 2020
Date()
if Date() >= 28March2020 {
return true
} else {
return false
}
thanks
You can do:
if Date() >= Calendar.current.dateWith(year: 2020, month: 3, day: 28) ?? Date.distantFuture {
return true
} else {
return false
}
where dateWith(year:month:day:) is defined as:
extension Calendar {
func dateWith(year: Int, month: Int, day: Int) -> Date? {
var dateComponents = DateComponents()
dateComponents.year = year
dateComponents.month = month
dateComponents.day = day
return date(from: dateComponents)
}
}
This method basically returns the Date with the specified year, month, and day, with the hour, minute, and second components all being 0, that is, start of the specified day. In other words, I am checking whether the instant now is after the start of the day 2020-03-28.
The easiest way and most flexible is to convert it to seconds and then compare.
let formatter = DateFormatter()
formatter.dateFormat = "YYYY-MM-dd"
let today = Date().timeIntervalSince1970
let date1 = formatter.date(from: "2022-10-01")!.timeIntervalSince1970
let date2 = formatter.date(from: "2022-10-02")!.timeIntervalSince1970
if today >= date1 && today <= date2 {
}

Get All Hours of The Day (in date format) from 9am tp 11pm and Save within an array in Swift

I need to get all the remaining hours of the day starting from whatever the current hour is until 11pm and save it in an array in a date format. Here is what I have written so far:
var sortedTime: [Date] = {
let today = Date()
var sortedTime: [Date] = []
(0..<14).forEach {
sortedTime.append(Calendar.current.date(byAdding: .hour, value: $0, to: today)!)
}
return sortedTime
}()
In the code I have: (0..<14).forEach { This is giving me the next 14 hours but thats not what I need; I need to make sure the hours I get are between 9am and 11pm. How would I be able to set this limit?
When you access .hour for any calender its in a 24h format. so you need to something like this:
let today = Date()
var sortedTime: [Date] = []
var calender = Calendar(identifier: .iso8601)
calender.locale = Locale(identifier: "en_US_POSIX")
let currentHour = calender.component(.hour, from: today)
(0...(24-currentHour)).forEach {
guard let newDate = calender.date(byAdding: .hour, value: $0, to: today) && calender.component(.hour, from: newDate) != 0,
calender.component(.hour, from: newDate) <= 23 else {
return
}
//convert date into desired format
sortedTime.append(newDate)
}

not getting milliseconds difference between two dates

In my app I need some kind of timer like countdown. Like for Next match there is 1 year 2 month 6 days 23 minutes 24 seconds and 5 milliseconds remaining.
For this I am using this code:
extension DateComponentsFormatter {
func difference(from fromDate: Date, to toDate: Date) -> String? {
self.allowedUnits = [.year,.month,.day,.hour, .minute, .second ,.nanosecond]
self.maximumUnitCount = 8
self.unitsStyle = .full
return self.string(from: fromDate, to: toDate)
}
}
But I am not getting milliseconds.
Here is what I am getting:
["24 years", " 8 months", " 20 days", " 12 hours", " 2 minutes", " 48 seconds"]
I need milliseconds.
You can't use a DateComponentsFormatter to format milliseconds. According to the documentation, only these calendar units are allowed:
year
month
weekOfMonth
day
hour
minute
second
You will have to format this yourself, by getting the TimeInterval between the dates, get the fractional part of the time interval, and format that.
Here is an idea of what this would look like.
extension DateComponentsFormatter {
func difference(from fromDate: Date, to toDate: Date) -> String? {
self.allowedUnits = [NSCalendar.Unit.second]
allowsFractionalUnits = true
self.maximumUnitCount = 8
self.unitsStyle = .full
guard let firstPart = self.string(from: fromDate, to: toDate) else { return nil }
let milliseconds = abs(toDate.timeIntervalSince(fromDate)).remainder(dividingBy: 1) * 1000
let numberFormatter = NumberFormatter()
numberFormatter.maximumFractionDigits = 0
guard let secondPart = numberFormatter.string(from: milliseconds as NSNumber) else { return nil }
return "\(firstPart) \(secondPart) milliseconds"
}
}
let formatter = DateComponentsFormatter()
formatter.difference(from: Date(), to: Date().addingTimeInterval(0.5))

How do I find the beginning of the week from an NSDate?

I'm implementing a calendar view, and I'd like it to start at the beginning of the week containing a particular date. Eg. If the target date is Monday, Feb 29, 2016, and the current calendar is set to start on Sunday, I'd like my view to start with Sunday, February 28.
This seems like it should be straightforward:
let calendar = NSCalendar.currentCalendar()
let firstDate = calendar.nextDateAfterDate(targetDate,
matchingUnit: .Weekday,
value: calendar.firstWeekday,
options: .SearchBackwards)
But this fails with:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Exactly one option from the set {NSCalendarMatchPreviousTimePreservingSmallerUnits, NSCalendarMatchNextTimePreservingSmallerUnits, NSCalendarMatchNextTime} must be specified.'
I can get basically what I want with:
let firstDate = calendar.nextDateAfterDate(firstDate,
matchingUnit: .Weekday,
value: calendar.firstWeekday,
options: .MatchPreviousTimePreservingSmallerUnits)?
.dateByAddingTimeInterval(-7 * 84600)
But it seems like a bad practice, since sometimes the number of seconds in a day isn't 86400.
Is there a better way?
you can use Calendar method date(from: DateComponents) passing [.yearForWeekOfYear, .weekOfYear] components from any date it will return the first day of the week from the calendar used. So if you would like to get Sunday just use Gregorian calendar. If you would like to get the Monday as the first day of the week you can use Calendar .iso8601 as you can see in this answer
Xcode 12 • Swift 5.3 or later (works with previous Swift versions as well)
extension Calendar {
static let gregorian = Calendar(identifier: .gregorian)
}
extension Date {
func startOfWeek(using calendar: Calendar = .gregorian) -> Date {
calendar.dateComponents([.calendar, .yearForWeekOfYear, .weekOfYear], from: self).date!
}
}
usage:
Date().startOfWeek() // "Sep 20, 2020 at 12:00 AM"
If you would like to get the beginning of week at a particular timezone you just need to use a custom calendar:
var gregorianUTC = Calendar.gregorian
gregorianUTC.timeZone = TimeZone(identifier: "UTC")!
print(Date().startOfWeek(using: gregorianUTC)) // "2020-09-20 00:00:00 +0000\n"
Swift 4 Solution
I have figured out according to my requirement, where I have find out dates for following.
1. Today
2. Tomorrow
3. This Week
4. This Weekend
5. Next Week
6. Next Weekend
So, I have created Date Extension to get Dates of Current Week and Next Week.
CODE
extension Date {
func getWeekDates() -> (thisWeek:[Date],nextWeek:[Date]) {
var tuple: (thisWeek:[Date],nextWeek:[Date])
var arrThisWeek: [Date] = []
for i in 0..<7 {
arrThisWeek.append(Calendar.current.date(byAdding: .day, value: i, to: startOfWeek)!)
}
var arrNextWeek: [Date] = []
for i in 1...7 {
arrNextWeek.append(Calendar.current.date(byAdding: .day, value: i, to: arrThisWeek.last!)!)
}
tuple = (thisWeek: arrThisWeek,nextWeek: arrNextWeek)
return tuple
}
var tomorrow: Date {
return Calendar.current.date(byAdding: .day, value: 1, to: noon)!
}
var noon: Date {
return Calendar.current.date(bySettingHour: 12, minute: 0, second: 0, of: self)!
}
var startOfWeek: Date {
let gregorian = Calendar(identifier: .gregorian)
let sunday = gregorian.date(from: gregorian.dateComponents([.yearForWeekOfYear, .weekOfYear], from: self))
return gregorian.date(byAdding: .day, value: 1, to: sunday!)!
}
func toDate(format: String) -> String {
let formatter = DateFormatter()
formatter.dateFormat = format
return formatter.string(from: self)
}
}
USAGE:
let arrWeekDates = Date().getWeekDates() // Get dates of Current and Next week.
let dateFormat = "MMM dd" // Date format
let thisMon = arrWeekDates.thisWeek.first!.toDate(format: dateFormat)
let thisSat = arrWeekDates.thisWeek[arrWeekDates.thisWeek.count - 2].toDate(format: dateFormat)
let thisSun = arrWeekDates.thisWeek[arrWeekDates.thisWeek.count - 1].toDate(format: dateFormat)
let nextMon = arrWeekDates.nextWeek.first!.toDate(format: dateFormat)
let nextSat = arrWeekDates.nextWeek[arrWeekDates.nextWeek.count - 2].toDate(format: dateFormat)
let nextSun = arrWeekDates.nextWeek[arrWeekDates.nextWeek.count - 1].toDate(format: dateFormat)
print("Today: \(Date().toDate(format: dateFormat))") // Sep 26
print("Tomorrow: \(Date().tomorrow.toDate(format: dateFormat))") // Sep 27
print("This Week: \(thisMon) - \(thisSun)") // Sep 24 - Sep 30
print("This Weekend: \(thisSat) - \(thisSun)") // Sep 29 - Sep 30
print("Next Week: \(nextMon) - \(nextSun)") // Oct 01 - Oct 07
print("Next Weekend: \(nextSat) - \(nextSun)") // Oct 06 - Oct 07
You can modify Extension according to your need.
Thanks!
You can implement this as Date class extension or something. It should returns something like 2020-01-06 00:00:00 +0000
Xcode 11.3 Swift 5
func firstDayOfWeek() -> Date {
var c = Calendar(identifier: .iso8601)
c.timeZone = TimeZone(secondsFromGMT: 0)!
print(
c.date(from: c.dateComponents([.weekOfYear, .yearForWeekOfYear], from: Date()))!
)
}
The Calendar has a mechanism for finding date at the start of a given time interval (say week of year, or month) that contains a given date:
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let date = dateFormatter.date(from: "2017-01-07")
if let date = date {
let calendar = Calendar(identifier: .gregorian)
var startDate : Date = Date()
var interval : TimeInterval = 0
if calendar.dateInterval(of: .weekOfYear, start: &startDate, interval: &interval, for: date) {
print("Start of week is \(startDate)")
// prints "Start of week is 2017-01-01 06:00:00 +0000"
}
}
In order to get the user's locale settings respected correctly, you should use the user's Calendar firstWeekday property in the DateComponents. This is what I usually use:
// MARK: first day of week
extension Date {
/**
Finds the first day of the week the subject date falls into.
- Parameter calendar: The calendar to use. Defaults to the user's current calendar.
- Returns: The `Date` of the first day of the week into which the subject date falls.
`startOfWeek()` respects the user's locale settings, i.e. will automatically use Sunday/Monday/etc. as first
weekday based on the user's region and locale settings.
*/
func startOfWeek(using calendar: Calendar = .current) -> Date? {
var components = calendar.dateComponents([.weekday, .year, .month, .weekOfYear], from: self)
components.weekday = calendar.firstWeekday
return calendar.date(from: components)
}
}
Basically use
NSCalender
and
dateByAddingComponents
. For solving of you're problem try to use this code sample:
let cal = NSCalendar.currentCalendar()
let components = NSDateComponents()
components.weekOfYear -= 1
if let date = cal.dateByAddingComponents(components, toDate: NSDate(), options: NSCalendarOptions(0)) {
var beginningOfWeek: NSDate?
var weekDuration = NSTimeInterval()
if cal.rangeOfUnit(.CalendarUnitWeekOfYear, startDate: &beginningOfWeek, interval: &weekDuration, forDate: date) {
print(beginningOfWeek)
}
}
I had problems with all previous solutions, since they do not take into account user's calendar setting. Next code will be taking into account that.
extension Date {
var startOfWeek: Date? {
let calendar = Calendar.current
var components: DateComponents? = calendar.dateComponents([.weekday, .year, .month, .day], from: self)
var modifiedComponent = components
modifiedComponent?.day = (components?.day ?? 0) - ((components?.weekday ?? 0) - 1)
return calendar.date(from: modifiedComponent!)
}
var endOfWeek: Date? {
let calendar = Calendar.current
var components: DateComponents? = calendar.dateComponents([.weekday, .year, .month, .day], from: self)
var modifiedComponent = components
modifiedComponent?.day = (components?.day ?? 0) + (7 - (components?.weekday ?? 0))
modifiedComponent?.hour = 23
modifiedComponent?.minute = 59
modifiedComponent?.second = 59
return calendar.date(from: modifiedComponent!)
}
}