Repeat Notifications monthly on specific day - ios

I want to repeat notifications monthly on a specific day, but my concern is, what if the user chooses the 31? Then the Notification would only fire every 2 months and never in February?
Because the day component would not match.
Is it possible to set the day to the last day of the month, so when for example February is the next month and the user selected the 31. then it would fire on the last day of February?
I could go the non repeating way and add the notifications manually, but then i would have to face the 64 scheduled notification limit.
Thanks for your help in advance.

The Calendar module is the way to go. Handling dates manually can get very tricky.
import Calendar
let selectedDate = "31/01/2020"
// Convert string to Date
let dateF = DateFormatter()
dateF.dateFormat = "dd/MM/yyyy"
let myDate = dateF.date(from: selectedDate)!
// Advancing date by a month, to get end of next month.
Calendar.current.date(byAdding: .month, value: 1, to: myDate) // "Feb 29, 2020 at 12:00 AM"
In the example above, you can see how to advance date by a month.
If the user chooses say the 31 of a month, the calendar automatically calculates the end of the next month accurately, even if it has only 28, 29, 30 or 31 days.

One option would be to use remote notifications and handle them on a server. This would involve a bit more work and a server, but it is more flexible and allows you to change the notifications without the user needing an app update. Link to the Apple Doc for Remote Notifications.
The other option would be the use Calendar.
When you do your logic to send the notification, you can test if today is the last day of the month. If the user chooses to get the notifications on the 31st, and say today is the last day of the month and is the 30th, you can still send the notification because you know there isn't a 31st in this specific month.

Related

Swift: first day of the week in app doesn't change after I change it in Settings

In my app I need to group different items based on their date, so I need a method to get date of the beginning of the week, and also a method to find out if two dates are in the same week. However, I get unexpected problems trying to implement this.
I use this code to get the first day of the week:
return Calendar.current.date(from: Calendar.current.dateComponents([.yearForWeekOfYear, .weekOfYear], from: self))
I use Calendar.current, but it returns the same day no matter which day is chosen as the beginning of the week in iOS Settings.
To check if two dates are in the same week I use this code:
func isInSameWeek(date: Date) -> Bool { isEqual(to: date, toGranularity: .weekOfYear) }
Still, Sunday and Monday dates are always considered not in the same week no matter which day is chosen as the beginning of the week in iOS Settings.
How do I fix this? Or maybe it is a normal behavior and this code will work correctly for users in different regions?
You are changing the settings of the Calendar App not the iOS current calendar itself. You need to provide your own app settings to be changed by the user.
var calendar = Calendar.current
calendar.firstWeekday = 2
calendar.date(from: calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: Date()))?.description(with: .current) // "Monday, September 14, 2020 at 12:00:00 AM Brasilia Standard Time"

iOS : How to check if date-time selected is valid time during day light savings?

I have a special case to check where a user inputs the future date and time and I need to verify if that time is valid (what do I mean by valid is explained below) considering user might be affected by daylight saving in his timezone.
For Example:
Assume the user's timezone is Adelaide, Australia. Open the link to see how timezone affects in Adelaide OR see below.
4 Oct 2020 - Daylight Saving Time Starts
When local standard time is about to reach
Sunday, 4 October 2020, 2:00:00 am clocks are turned forward 1 hour to
Sunday, 4 October 2020, 3:00:00 am local daylight time instead.
Now based on the above information my understanding is if my user selected the date-time between
4 October 2020, 2:01:00 am - 4 October 2020, 2:59:00 am
it's not valid as the hour is forwarded to 3 am.
How can I validate that in an iOS app? (Assuming i)
Basically I need to inform the user that the time selected is affected by DST and users need to select a different time.
I've looked into Date and Timezone APIs and couldn't seem to find anything which can validate this.
Thanks in advance, any help would be appreciated.
I guess the easiest way would be to convert the string (or whatever the user enters, seems you are not using the UIDatePicker) into a date and check if this is possible.
For Mid-Europe, DST is starting on March 29th, 2020, so
let df = DateFormatter()
df.locale = Locale(identifier: "de_DE")
df.timeZone = TimeZone(identifier:"Europe/Berlin")
df.dateFormat="yyyy-MM-dd hh:mm"
if let theDate = df.date(from: "2020-03-29 02:01")
{
print(theDate)
} else {
print ("Illegal date")
}
will print Illegal date.
A little more tricky is switch back to non-DST, because (in Mid-Europe) there are two possible dates for 2020-10-25 02:01 - it could mean winter or summer time with two different UTC representations.

How calculate days to person birthday with Swift?

I have a Date variable with a person's date birthday. I would like to know how many days remains before this person next birthday. It should be calculated from today date to current year birthday date.
How can this be done with Swift? Also it will be great to consider February 29 in leap years.
To the guys who tried to close this: This is about birthday which has totally different rules from days.
Birthdays are complicated. Your birthday was the date of the moment when you were born, in the timezone where you were born. Considering that Samoa = UTC+14 and Baker Island = UTC-12, it is possible that people born at the same moment in time have birthdays that are two days apart.
So to store somebody's birthday, not the moment of birth, you either store year/month/day, or if you want to store it as a point in time, you store the beginning of that day in UTC, with the understanding that this is to specify a day, and must not be converted to local time.
Now when does your birthday repeat? If the person is born on D/M/Y and D/M is not February 29th, then the next birthday is either D/M/current year or D/M/next year. It is D/M/current year if that date is in the future, otherwise D/M/next year.
If the person is born on February 29th, then you have to determine when officially the next birthday is if the year is not a leap year - this will be February 28th or March 1st, depending on which rules apply.
We also need to clarify what "number of days" means. If my birthday is on April 1st, and now it is March 31st, one second to midnight, my birthday will be one second from now. However, I will assume that the result is supposed to be "one day from now".
So here is the algorithm:
Step 1: Find day/month/year when the person was born.
Step 2: Determine the current time, and convert it to local day/month/year. Determine the current time only once to avoid problems if this calculation is done nanoseconds before midnight.
Step 3: Determine the year when the birthday repeats: If day/month of birthday is greater than current day/month, then the year when the birthday repeats is the current year, otherwise the next year. This is also correct if the birthday was on Feb. 29th.
Step 4: Determine the day/month when the birthday repeats: This is the same as the day/month of the birthday, unless the birthday was on Feb. 29th and the year when the birthday repeats is not a leap year. In that case, the birthday repeats on Feb 28th or March 1st, depending on which rules you decide to apply.
Step 5: Convert the current day/month/year + 12 hours to UTC. Convert the date when the birthday repeats + 12 hours to UTC. Calculate the difference in seconds (which the OS should do for you). Divide by 86,400, then round to the nearest integer. The "+12 hours" and "round to nearest integer" make sure that you have no problems with daylight savings time, leap seconds etc.
Writing this code in Swift or any other language should be no problem.
It depends on what you are looking to use the days value for but here is a small function that will return a Double describing the amount of days until a given Date. Martin R gave a really good answer here and my answer is mainly based on theirs with a little bit of documentation added.
/// This function takes a `Date` parameter and returns an `Int` describing how many days away the `Date` is into the future (accounting for leap years by making 2/29 dates land on 3/1 in non-leap years).
/// - Parameter date: The `Date` object to be evaluated.
func daysUntil(birthday: Date) -> Int {
let cal = Calendar.current
let today = cal.startOfDay(for: Date())
let date = cal.startOfDay(for: birthday)
let components = cal.dateComponents([.day, .month], from: date)
let nextDate = cal.nextDate(after: today, matching: components, matchingPolicy: .nextTimePreservingSmallerComponents)
return cal.dateComponents([.day], from: today, to: nextDate ?? today).day ?? 0
}

Comparing working hours of an entity per day against the date (day of the week and hours) from the device

I would like to compare the working hours per day of an entity, fetched from JSON, against the day and hour information from the device. Related to that, I have two questions:
1) What is the best/right way of saving information of working hours per day for a particular entity in a JSON? I think, its structure should be something like this:
"working_hours": {
"<DAY_OF_THE_WEEK_1>":"<WORKING_HOURS>",
"<DAY_OF_THE_WEEK_2>":"<WORKING_HOURS>",
"<DAY_OF_THE_WEEK_3>":"<WORKING_HOURS>",
"<DAY_OF_THE_WEEK_4>":"<WORKING_HOURS>",
"<DAY_OF_THE_WEEK_5>":"<WORKING_HOURS>",
"<DAY_OF_THE_WEEK_6>":"<WORKING_HOURS>",
"<DAY_OF_THE_WEEK_7>":"<WORKING_HOURS>"
}
Here, I use days of the week as keys in a dictionary. However, I'm not sure what is the best/right way to enter "<DAY_OF_THE_WEEK_N>" and "<WORKING_HOURS>" inside a JSON.
2) After fetching this information, how to compare it against the time information from the device. Basically, I need to check the day of the week and then checking whether the current from the device is within the interval of a particular <WORKING_HOURS>.
For those who ask me whether I'm asking about comparing two numbers: I don't ask about that. I need to know the right format to save working hours in JSON also taking into account the day changes.
Currently, the problem is, when I save a time like 21:00 and transform it to a Date:
let workingHoursEnd = "21:00"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "H:mm"
dateFormatter.timeZone = TimeZone.autoupdatingCurrent
let dateFromString = dateFormatter.date(from: workingHoursEnd)
I get Jan 1, 2000 at 9:00 PM. The time format is ok, I can change it later, however, what I need is to get the current date (especially, day of the week) because I need to take into account the current day, too.
If you can answer to both questions (or even to one of them), I would appreciate your help.

How does NSMonthCalendarUnit and NSYearCalendarUnit behave exactly when used with UILocalNotification repeatInterval?

How does NSMonthCalendarUnit behave exactly related to UILocalNotification repeatInterval ?
Find same day next month ?
or
Find the date for 30 days later ?
For example:
if i create a UILocalNotification with date:27/02/2015 and repeatInterval:NSMonthCalendarUnit when will be the next notification ? Do not mind whether February is 27 or 28. Just consider it is always 28. It is not the real question.
Will repetition be on 27/03/2015 or 29/03/2015 ?
Same question also applies for NSYearCalendarUnit. Is it just a addition of 365 days or does it mean same day, same month of next year (27/03/2016) ?
I would post this as an edit, but it's a bit long.
I imagine this does is based on the unit. For instance, if you create a uilocalnotification with date Feb 2, 2015 and give it an NSMonthCalendarUnit, it will call it again at every month interval for the same date.
Broken down into components Feb 2 2015 is NSDayCalendarUnit=2, NSMonthCalendarUnit=2 NSYearCalendarUnit=2015. So, the notification will be called whenever NSDayCalendarUnit=2 and NSYearCalendarUnit=2015 for any NSMonthCalendarUnit. (so if you did a leap-year date at the end of the month, like jan 30, and it was a leap year month in february, it wouldn't get called.) So, this would have the same day and year for ever NSMonthCalendarUnit. The same goes for year, except for years.
It would not make much sense for this to be an addition of days. Imagine setting the NSMonth interval for a date on the first and having it be returned on the 31st of the same month, that wouldn't be a very practical API.

Resources