Testing for Anniversary Date using NSDate/NSCalendar - swift

I'm trying to figure out how to determine whether today is the anniversary of an NSCalendar item. Everything I've found compares a specific date's month, day, year to another specific month, day, and year. The granularity works against me, as the first thing it compares is the year.
Here's what I've got so far. I've read the documentation, but I'm missing something (basic). What is it?
// get the current date
let calendar = NSCalendar.currentCalendar()
var dateComponents = calendar.components([.Month, .Day], fromDate: NSDate())
let today = calendar.dateFromComponents(dateComponents)
var birthDates = [NSDate]()
let tomsBirthday = calendar.dateWithEra(1, year: 1964, month: 9, day: 3, hour: 0, minute: 0, second: 0, nanosecond: 0)
birthDates.append(tomsBirthday!)
let dicksBirthday = calendar.dateWithEra(1, year: 1952, month: 4, day: 5, hour: 0, minute: 0, second: 0, nanosecond: 0)
birthDates.append(dicksBirthday!)
let harrysBirthday = calendar.dateWithEra(1, year: 2015, month: 10, day: 27, hour: 0, minute: 0, second: 0, nanosecond: 0)
birthDates.append(harrysBirthday!)
for birthday in birthDates {
// compare the month and day to today's month and day
// if birthday month == today's month {
// if birthday day == today's day {
// do something
// }
}

To make the date comparison, while treating February 29 as March 1 in non-leap years, you need to use the month and day components of the person's birthday to construct an NSDate in the current year.
Also, don't use midnight as time when constructing an NSDate for comparing dates. Some days don't have a midnight in some time zones. Use noon instead.
struct Person {
let name: String
let birthYear: Int
let birthMonth: Int
let birthDay: Int
}
let people = [
Person(name: "Tom", birthYear: 1964, birthMonth: 9, birthDay: 3),
Person(name: "Dick", birthYear: 1952, birthMonth: 4, birthDay: 5),
Person(name: "Harry", birthYear: 2015, birthMonth: 10, birthDay: 28)
]
let calendar = NSCalendar.autoupdatingCurrentCalendar()
let todayComponents = calendar.components([.Era, .Year, .Month, .Day], fromDate: NSDate())
todayComponents.hour = 12
let todayNoon = calendar.dateFromComponents(todayComponents)
for person in people {
let components = todayComponents.copy() as! NSDateComponents
// DON'T copy person.birthYear
components.month = person.birthMonth
components.day = person.birthDay
let birthdayNoon = calendar.dateFromComponents(components)
if todayNoon == birthdayNoon {
print("Happy birthday, \(person.name)!")
}
}

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 to Omit Missing Fields from Date String (day, month, year)?

I have a DateFormatter where dateStyle is set to .long like so:
private let formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .long
return formatter
}()
In my application, I make requests to an API and can receive some info about dates in the response body. In response, it'll look like this for example:
struct StartDate {
let day: Int?
let month: Int?
let year: Int?
}
As all the fields are optional, some fields may or may not be present. For example, I may have a year and month, but not a day. Or maybe I only have the year. A demonstration of why this is a problem:
let date = Calendar.current.date(from: DateComponents(year: 2020, month: 12))!
print(formatter.string(from: date))
We have the year and month, but not the day. I may expect it to look like December 2020, December, 2020 or even December ?, 2020. However, it decides to replace the missing day with a 1. I can't use that because it's misleading to the user. How could I solve this problem?
Maybe a bit straight-forward but still. You can try something like this:
struct StartDate {
let day: Int?
let month: Int?
let year: Int?
var string: String {
get {
let daySymbols = day == nil ? "??" : String(day!)
let monthSymbols = month == nil ? "??" : DateFormatter().monthSymbols[month! - 1]
let yearSymbols = year == nil ? "??" : String(year!)
return "\(daySymbols) \(monthSymbols) \(yearSymbols)"
}
}
}
print(StartDate(day: nil, month: 10, year: 2020).string)
//prints '?? October 2020'
print(StartDate(day: 21, month: nil, year: 2020).string)
//prints '21 ?? 2020'
print(StartDate(day: 21, month: 9, year: nil).string)
//prints '21 September ??'

How to get dates for every Friday between two dates?

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

Swift - Check two arrays are they have same day without looping in a loop

I have 2 arrays [Date]
First is with myDates:[Date]
Second is with coreDataDates:[Date]
I'm going to for loop the second array with Core Data Dates
How to check in the loop is the DAY of myDates existing in coreDataDates ?
I know that, I can use the function NSCalendar isDate(_:inSameDayAs:), but is there a function that I don't need to loop myDates:[Date] in the coreDataDates:[Date] for loop?
Newbie example:
for date1 in coreDataDates{
for date2 in myDates{
if(Calendar.current.isDate(date1, inSameDayAs: date2)==true){
//have it
}else{
//don't have it
}
}
}
I need code that I don't have to use loop in the loop
If you don't care about time, Apple recommends that you set the time to noon because every day has a noon but not a midnight due to clock changes for daylight saving time.
Convert all dates in the two arrays to noon and then find the union of the two sets:
import Foundation
extension Date {
var noon: Date {
return Calendar.current.date(bySettingHour: 12, minute: 0, second: 0, of: self)!
}
}
let myDates = [
DateComponents(calendar: .current, year: 2018, month: 12, day: 1, hour: 1).date!,
DateComponents(calendar: .current, year: 2018, month: 12, day: 2, hour: 2).date!,
DateComponents(calendar: .current, year: 2018, month: 12, day: 4, hour: 3).date!
]
let coreDataDates = [
DateComponents(calendar: .current, year: 2018, month: 12, day: 1, hour: 4).date!,
DateComponents(calendar: .current, year: 2018, month: 12, day: 3, hour: 5).date!,
DateComponents(calendar: .current, year: 2018, month: 12, day: 4, hour: 6).date!
]
let myDateSet = Set(myDates.map { $0.noon })
let coreDataDateSet = Set(coreDataDates.map { $0.noon })
let commonDateSet = myDateSet.intersection(coreDataDateSet)
print(commonDateSet)
You can use the power of the Set class
let dateSet = Set(myDates)
let common = dateSet.intersection(coreDataDates)
//If you want it as an array
let commonDatesArray = Array(common)
See the documentation for the Set class for more info.
Something like:
let date1 = Date(timeIntervalSince1970: 1544019937)
let date2 = Date(timeIntervalSince1970: 1544019936)
let date3 = Date(timeIntervalSince1970: 1544019935)
let date4 = Date(timeIntervalSince1970: 1544019934)
let date5 = Date(timeIntervalSince1970: 1544019933)
let date6 = Date(timeIntervalSince1970: 1544019932)
let arr1 = [date1, date2, date3]
let arr2 = [date4, date5, date6]
print(arr2.filter { arr1.contains($0) }.count > 0 ) // false
let arr1 = [date1, date2, date3]
let arr2 = [date1, date5, date6]
print(arr2.filter { arr1.contains($0) }.count > 0 ) // true

How to find the nearest date of the stored data

For example:
stored data: date = data1(2015.12.31), data2(2016.01.05), data3(2016.01.14), data4(2016.01.15), data5(2016.01.18)
today is 2016.01.12
then nearest data is data3
What are the methods that can be called here data3?
realDate(datePicker.date), date(textField.text), content(textField.text) in Core Data.
var stores: Contact!
// Contact is CoreData Entity Name.
#IBOutlet weak var label: UILabel!
ViewController viewDidLoad :
let list = [stores]
let timeSort = list.map{_ in stores?.realDate!.timeIntervalSinceNow}.filter{$0 > 0}.sort(<)
if let firstTime = timeSort.first {
_ = NSDate(timeIntervalSinceReferenceDate: firstTime!)
if timeSort.first != nil {
label.text = stores?.content
} else { }
}
I was trying to get help for writing code. There is no error, it does not display any information on the label.
You can use NSDate property timeIntervalSinceNow to sort your dates and get the first positive value:
edit/update:
Swift 3 introduced Date which conforms to Comparable protocol so it can be easily sorted now:
extension Date {
init?(_ year: Int,_ month: Int,_ day: Int) {
guard let date = DateComponents(calendar: .current, year: year, month: month, day: day, hour: 12).date else { return nil }
self = date
}
}
Playground testing:
let dateList = [Date(2018, 2, 18)!, Date(2017, 12, 31)!, Date(2018, 1, 5)!, Date(2018, 2, 14)!, Date(2018, 2, 15)!]
if let closestDate = dateList.sorted().first(where: {$0.timeIntervalSinceNow > 0}) { // "Feb 15, 2018, 12:00 PM"
print(closestDate.description(with: .current)) // Thursday, February 15, 2018 at 12:00:00 PM Brasilia Summer Time
}