How to get dates for every Friday between two dates? - swift

I currently use the following code to return an array of dates for every single day between two dates, including today's date and the last date itself. This works great.
However, how would I go about modifying what I'm already doing in
order to do the same exact thing otherwise, but instead return an
array of the date of every Friday between the dates? For example, if the function was called on Wed Oct 23rd 2019 to return every Friday until November 10th, the first date would be Fri the 25th, Nov 1st, and then Nov 8th.
How would I do the same thing as above but for the 1st of every month? If I called the function on Wed Oct 23rd 2019 to return the first of every month until December 16th. The array should have Nov 1st and Dec 1st in it.
func dates(for date: String) -> [String] {
// first get the endDate
guard var endDate = Formatter.date.date(from: date) else { return [] }
// for calendrical calculations you should use noon time
endDate = Calendar.current.date(bySettingHour: 12, minute: 0, second: 0, of: endDate)!
// lets get todays noon time to start
var date = Calendar.current.date(bySettingHour: 12, minute: 0, second: 0, of: Date())!
var dates: [String] = []
// while date less than or equal to end date
while date <= endDate {
// add the formatted date to the array
dates.append( Formatter.date.string(from: date))
// increment the date by one day
date = Calendar.current.date(byAdding: .day, value: 1, to: date)!
}
return dates
}

You just need to add a weekday parameter to your method and check if the weekday of the date inside the loop before adding it to your array:
extension Formatter {
static let date = DateFormatter()
}
func dates(for date: String, weekday: Int? = nil) -> [String] {
Formatter.date.locale = Locale(identifier: "en_US_POSIX")
Formatter.date.dateFormat = "yyyy-MM-dd"
// first get the endDate
guard var endDate = Formatter.date.date(from: date) else { return [] }
// for calendrical calculations you should use noon time
endDate = Calendar.current.date(bySettingHour: 12, minute: 0, second: 0, of: endDate)!
// lets get todays noon time to start
var date = Calendar.current.date(bySettingHour: 12, minute: 0, second: 0, of: Date())!
var dates: [String] = []
// while date less than or equal to end date
while date <= endDate {
if weekday == nil {
dates.append(Formatter.date.string(from: date))
date = Calendar.current.date(byAdding: .day, value: 1, to: date)!
} else if let weekday = weekday, Calendar.current.component(.weekday, from: date) == weekday {
// add the formatted date to the array
dates.append(Formatter.date.string(from: date))
date = Calendar.current.date(byAdding: .weekOfYear, value: 1, to: date)!
} else {
date = Calendar.current.date(byAdding: .day, value: 1, to: date)!
}
}
return dates
}
dates(for: "2019-12-25") // ["2019-10-23", "2019-10-24", "2019-10-25", "2019-10-26", "2019-10-27", "2019-10-28", "2019-10-29", "2019-10-30", "2019-10-31", "2019-11-01", "2019-11-02", "2019-11-03", "2019-11-04", "2019-11-05", "2019-11-06", "2019-11-07", "2019-11-08", "2019-11-09", "2019-11-10", "2019-11-11", "2019-11-12", "2019-11-13", "2019-11-14", "2019-11-15", "2019-11-16", "2019-11-17", "2019-11-18", "2019-11-19", "2019-11-20", "2019-11-21", "2019-11-22", "2019-11-23", "2019-11-24", "2019-11-25", "2019-11-26", "2019-11-27", "2019-11-28", "2019-11-29", "2019-11-30", "2019-12-01", "2019-12-02", "2019-12-03", "2019-12-04", "2019-12-05", "2019-12-06", "2019-12-07", "2019-12-08", "2019-12-09", "2019-12-10", "2019-12-11", "2019-12-12", "2019-12-13", "2019-12-14", "2019-12-15", "2019-12-16", "2019-12-17", "2019-12-18", "2019-12-19", "2019-12-20", "2019-12-21", "2019-12-22", "2019-12-23", "2019-12-24", "2019-12-25"]
dates(for: "2019-12-25", weekday: 6) // ["2019-10-25", "2019-11-01", "2019-11-08", "2019-11-15", "2019-11-22", "2019-11-29", "2019-12-06", "2019-12-13", "2019-12-20"]
func firstDayOfTheMonth(until date: String) -> [String] {
Formatter.date.locale = Locale(identifier: "en_US_POSIX")
Formatter.date.dateFormat = "yyyy-MM-dd"
guard let endDate = Formatter.date.date(from: date) else { return [] }
var date = Date()
var dates: [String] = []
// while date less than or equal to end date
while let firstDayOfTheMonth = Calendar.current.nextDate(after: date, matching: .init(day: 1), matchingPolicy: .nextTime), firstDayOfTheMonth <= endDate {
dates.append(Formatter.date.string(from: firstDayOfTheMonth))
date = firstDayOfTheMonth
}
return dates
}
firstDayOfTheMonth(until: "2019-12-25") // ["2019-11-01", "2019-12-01"]

You can get the weekday for any day of the week with:
let weekDayIndex = Calendar.current.component(.weekday, from: Date())
Friday happens to be day 5. You can get the name of any day with:
print(Calendar.current.weekdaySymbols[weekDayIndex])
So just loop over all of your dates and filter out anything where the weekday is not 5 and you have your answer:
func fridays(in dates: [Date]) {
dates.filter { Calendar.current.component(.weekday, from: $0) == 5 }
}

Related

What's wrong with my usage of `enumerateDates`?

I'm trying to use enumerateDates(startingAfter:matching:matchingPolicy:repeatedTimePolicy:direction:using:) to list all first days of months between two dates. However, I find it didn't return the last date as what I expected.
Here's the code snippet. I set the endDate as a random time on 2023/1/1. I thought it should also include 2023/1/1 in its output, but seems it ignored it. Is it an time zone issue because I'm in UTC+8?
let calendar = Calendar.current
let startDate = calendar.date(from: DateComponents(year: 2022, month: 10, day: 2))!
let endDate = calendar.date(from: DateComponents(year: 2023, month: 1, day: 1, hour: 12))!
let firstDayOfMonth = DateComponents(day: 1)
calendar.enumerateDates(startingAfter: endDate, matching: firstDayOfMonth, matchingPolicy: .strict, direction: .backward) { result, exactMatch, stop in
if let date = result {
if date < startDate {
stop = true
} else {
print(date)
}
}
}
The output:
// I expect a `2022-12-31 16:00:00 +0000` here!
2022-11-30 16:00:00 +0000
2022-10-31 16:00:00 +0000

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
}
}
}

How to change the hours and minutes in an existing Date object in Swift?

I need to compare two Date object to get the day difference between them, for example: 10/10 compares with today 10/7 will be 3, but the Date object returned to me from server is not aligned with the current time. There will be a few minutes difference which results in 10/10 being 2 days ahead of 10/7 because of the delay
I found a line of code that can give me a Date object of the current time, but I want to convert an existing Date object from somewhere else, how do I do it?
let today = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: Date())!
e.g. 2020-10-08 16:08:57.259580+0000 I want it to be 2020-10-08 00:00:00 +0000 something like this
Don’t use midnight. Just parse your date string first. For calendrical calculations you should always use noon. First create a custom date format to parse your date string properly.
extension Formatter {
static let iso8601: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = .init(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSSSSxx"
formatter.timeZone = TimeZone(secondsFromGMT: 0)
return formatter
}()
}
Then create a helper to convert your date to noon time and another one to calculate the days between two dates and set them to noon:
extension Date {
var noon: Date {
Calendar.current.date(bySettingHour: 12, minute: 0, second: 0, of: self)!
}
func days(from date: Date) -> Int {
Calendar.current.dateComponents([.day], from: date.noon, to: noon).day!
}
var daysFromToday: Int { days(from: Date()) }
}
Playground testing:
let dateString = "2020-10-08 16:08:57.259580+0000"
let now = Date() // "Oct 8, 2020 at 5:56 AM"
print(Formatter.iso8601.string(from: now)) // "2020-10-08 08:56:46.179000+0000\n"
if let date = Formatter.iso8601.date(from: dateString) { // "Oct 8, 2020 at 1:08 PM"
let days = Date().days(from: date) // 0
}
let dateString = "2020-10-10 16:08:57.259580+0000"
if let date = Formatter.iso8601.date(from: dateString) {
let days = date.daysFromToday // 2
}

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 array of dates between today and given date in Swift?

I have a date in string format, "yyyy-MM-dd" and would like to return an array of the difference in dates in that same format from today.
For example, the given date is "2019-06-29", and today's date is 2019-06-25. The returned array would contain: ["2019-06-25", "2019-06-26", "2019-06-27", "2019-06-28", "2019-06-29"].
The method I am trying to write needs to also work cross-months/years. Is something like this possible in Swift?
What I have tried: calculating the difference in dates numerically (difference of days) and adding a day to the given date until it reaches today's date. This is what brought on the issue of exceeding 30/31 days and not moving to the next months/exceeding 2019-12-31 and not moving to 2020. Surely there is a simpler concise way to achieve this result without having to write that date logic manually?
extension Formatter {
static let date: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.calendar = Calendar(identifier: .iso8601)
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd"
return dateFormatter
}()
}
extension Date {
var noon: Date {
return Calendar.current.date(bySettingHour: 12, minute: 0, second: 0, of: self)!
}
}
func dates(for date: String) -> [String] {
// For calendrical calculations you should use noon time
// So lets get endDate's noon time
guard let endDate = Formatter.date.date(from: date)?.noon else { return [] }
// then lets get today's noon time
var date = Date().noon
var dates: [String] = []
// while date is less than or equal to endDate
while date <= endDate {
// add the formatted date to the array
dates.append( Formatter.date.string(from: date))
// increment the date by one day
date = Calendar.current.date(byAdding: .day, value: 1, to: date)!
}
return dates
}
dates(for: "2019-06-29") // ["2019-06-25", "2019-06-26", "2019-06-27", "2019-06-28", "2019-06-29"]