Elapsed days between two dates always giving one days difference - ios

I'm trying to work out the number of days between two dates. Here is how I am doing it:
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
unsigned int calendarFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit;
NSDate *dateToCheck = (self.subscriptionEnd ?: self.trialEnd);
NSLog(#"dateToCheck: %#", dateToCheck);
NSLog(#"current date: %#", [self systemTimeZoneDate]);
NSDateComponents *components = [gregorian components:calendarFlags
fromDate:[self systemTimeZoneDate]
toDate:dateToCheck
options:0];
return [components day] >= 0 ?: 0;
At the time of this writing, NSLog outputted the following:
2014-09-05 22:56:20.054 tweepy[9635:60b] dateToCheck: 2014-10-05 08:02:51 PM +0000
2014-09-05 22:56:20.057 tweepy[9635:60b] current date: 2014-09-05 10:56:20 PM +0000
It is returning a difference of one day because iOS thinks that the day is in YDM format.
Should I be indicating the date format somewhere?
Here is the code of how self.subscriptionEnd is setup:
NSCalendar *gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
dateComponents.day = 30;
self.subscriptionEnd = [gregorianCalendar dateByAddingComponents:dateComponents toDate:[self systemTimeZoneDate] options:0];
Here is the code of how self.trialEnd is setup:
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
dateComponents.day = 2;
self.trialEnd = [gregorian dateByAddingComponents:dateComponents toDate:[self systemTimeZoneDate] options:0];

The date format doesn't have anything to do with it. What is happening is the NSDateComponents are giving you what you are asking for, the total difference in years, months, and days. If you want just the days, you need to only provide NSDayCalendarUnit. The docs make this clear:
Some operations can be ambiguous, and the behavior of the computation is calendar-specific, but generally larger components will be computed before smaller components; for example, in the Gregorian calendar a result might be 1 month and 5 days instead of, for example, 0 months and 35 days.

Related

How to find Next Sunday in different Timezone (EST & IST)

I have to find next Sunday date (NSDate) from device's current date.
I have used below logic:
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *components = [calendar components:NSCalendarUnitWeekday | NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay fromDate:self];
NSUInteger weekdayToday = [components weekday];
NSInteger daysToMonday = (8 - weekdayToday) % 7;
NSDate *weekEndPrev = [self dateByAddingTimeInterval:60*60*24*daysToMonday];
Here, in EST if time is near to 11 PM, I'm getting Monday as Weekend.
Let me know feasible solution. I have tried many options. Nothings works with 2 different timezones.
Thanks in Advance.
You need just to get the number of days you need and then just add them to your current date:
NSCalendar * calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDate * newDate = [calendar dateByAddingUnit:NSCalendarUnitDay value:value toDate:date options:0];
where value - is the number of days to add
Find current week Sunday Date first then add 7 days using NSDateComponents.
NSDate *today = [[NSDate alloc] init];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *weekdayComponents = [gregorian components:NSCalendarUnitWeekday fromDate:today];
NSDateComponents *componentsToSubtract = [[NSDateComponents alloc] init];
[componentsToSubtract setDay:(0 - ([weekdayComponents weekday] - 1))];
NSDate *sudayCurWeek = [gregorian dateByAddingComponents:componentsToSubtract toDate:today options:0];
NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
[offsetComponents setDay:7];
NSDate *nextSunday = [gregorian dateByAddingComponents:offsetComponents toDate:sudayCurWeek options:0];
NSLog(#"%#",nextSunday);
You can do this without any math, which is best left to NSCalendar as it handles daylight savings etc. First you need a calendar with the correct time zone. For example for IST:
NSCalendar *gc = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
gc.timeZone = [NSTimeZone timeZoneWithName:#"IST"];
Then you can use nextDateAfterDate:matchingUnit:value:options: to find the start of the next Sunday after a given time. For example:
NSDate *start = ... // the time you wish to start from
NSDate *nextSunday =
[gc nextDateAfterDate:start
matching:UnitNSCalendarUnitWeekday // match based on weekday number
value:1 // Sunday = weekday 1
options:NSCalendarMatchNextTime // read the docs!
];
The returned date, nextSunday, will be the next Sunday at 00:00 in the timezone of the calendar, gc.
HTH
Finally, I got solution..
NSDateComponents *components = [[NSCalendar currentCalendar] componentsInTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"] fromDate:[NSDate date]]; // Pass date for which you want to find next Sunday
NSUInteger weekdayToday = [components weekday];
NSInteger daysToMonday = (8 - weekdayToday) % 7;
NSDate *weekEndPrev = [[NSDate date] dateByAddingTimeInterval:60*60*24*daysToMonday];

How to show time which is less than a day?

This code will return number of days between two days.
How to determine when time will pass and day become hours like (20 hours) or (40 minutes), then how do I check that it's a day or less than a day.
-(NSInteger)numberOfDaysUntilDay:(NSDate *)aDate
{
NSCalendar *calendar = [[NSCalendar alloc]
initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *components = [calendar components:NSCalendarUnitDay
fromDate:self
toDate:aDate options:kNilOptions];
return [components day];
}
Use NSCalendarUnitSecond instead.
NSDateComponents *components = [calendar components:NSCalendarUnitSecond
fromDate:date
toDate:aDate options:kNilOptions];
Later you can convert seconds to any unit (hour, day, year).

How to manage NSDate for different timezones

I have created a calendar in my app, using the date object this way:
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *weekdayComponents = [gregorian components:(NSDayCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit | NSMinuteCalendarUnit)fromDate:[NSDate date]];
NSInteger day = [weekdayComponents day];
NSInteger month = [weekdayComponents month];
NSInteger year = [weekdayComponents year];
m_dateFormatter.dateFormat = #"dd/MM/yyyy";
[gregorian setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"]];
NSDateComponents *timeZoneComps=[[NSDateComponents alloc] init];
[timeZoneComps setDay:day];
[timeZoneComps setMonth:month];
[timeZoneComps setYear:year];
[timeZoneComps setHour:00];
[timeZoneComps setMinute:00];
[timeZoneComps setSecond:01];
m_currentDate = [gregorian dateFromComponents:timeZoneComps];
When the user wants to go next month, I highlight the first date of that month. So, in this case, the date will be 1-06-2014,00:00:01.
Here is the code:
- (void)showNextMonth
{
// Move the date context to the next month
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *dateComps = [[NSDateComponents alloc] init];
[dateComps setMonth:1];
m_currentMonthContext =[gregorian dateByAddingComponents:dateComps toDate:m_currentMonthContext options:0];
NSDateComponents *weekdayComponents1 = [gregorian components:(NSDayCalendarUnit | NSWeekdayCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit) fromDate:m_currentMonthContext];
NSInteger nextMonth = [weekdayComponents1 month];
NSInteger nextyear = [weekdayComponents1 year];
NSDateComponents *weekdayComponents2 = [gregorian components:(NSDayCalendarUnit | NSWeekdayCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit) fromDate:m_currentDate];
NSInteger currentDay = [weekdayComponents2 day];
NSInteger currentMonth = [weekdayComponents2 month];
NSInteger currentYear = [weekdayComponents2 year];
NSInteger selectedDay = 1;
if(nextMonth == currentMonth && nextyear == currentYear)
{
selectedDay = currentDay;
}
NSInteger month = nextMonth;
[gregorian setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"]];
NSDateComponents *timeZoneComps=[[NSDateComponents alloc] init];
[timeZoneComps setDay:selectedDay];
[timeZoneComps setMonth:month];
[timeZoneComps setYear:nextyear];
[timeZoneComps setHour:00];
[timeZoneComps setMinute:00];
[timeZoneComps setSecond:01];
m_currentMonthContext =[gregorian dateFromComponents:timeZoneComps];
[self createCalendar];
}
When m_currentMonthContext is calculated on the second to last line of the above method, its value is 1-06-2014,00:00:01.
createCalendar implementation:
-(void)createCalendar
{
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *weekdayComponents = [gregorian components:(NSDayCalendarUnit | NSWeekdayCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit)fromDate:m_currentMonthContext];
NSInteger month = [weekdayComponents month];
NSInteger year = [weekdayComponents year];
}
Here I get month as 5 and year as 2014, but the date is 1-06-2014. This happens only in US time zone, in all other time zones it is working fine.
So I want to know how to handle timezones effectively, or in other sense, how to make sure that NSDate does not change even if time zone changes.
The proximate cause is that the time zone is not consistently set on the calendar when calculating dates and date components. Sometimes you set the time zone to UTC, and sometimes not, which is going to cause inconsistencies, as sometimes offsets for local time will be applied, and sometimes not.
In detail, in your situation, m_currentMonthContext is an NSDate which represents the UTC time one second after midnight on June 1st, 2014. In your createCalendar method, you create a calendar that is the local time of the user, and calculate the components for such a date. In all time zones in the US, it is still the month of May one second after midnight on June 1st, 2014 UTC. An example in code, that can be run in isolation:
NSCalendar *utcCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[utcCalendar setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"]];
NSCalendar *localCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *june = [NSDate dateWithTimeIntervalSince1970:1401580801];
NSDateComponents *utcComponents = [utcCalendar components:(NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit) fromDate:june];
NSDateComponents *localComponents = [localCalendar components:(NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit) fromDate:june];
NSLog(#"utc : %#", utcComponents);
NSLog(#"local: %#", localComponents);
Here in MDT time zone, this logs:
utc :
Calendar Year: 2014
Month: 6
Leap month: no
Day: 1
local:
Calendar Year: 2014
Month: 5
Leap month: no
Day: 31
To recap, you're keeping a date in memory that's been calculated to represent a certain calendar date in UTC time, and then calculating the calendar date in the user's local time, but it seems you have an incorrect expectation that calendars for different time zones will interpret the same date the same way.
So, what to do? Your example is pretty complex, but it seems there's no need at all to store date components sometimes in UTC time zone and sometimes not - be consistent. Now, it also seems to me that you can be much much simpler in your code if you just want to find the first day of the next month.:
NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *comps = [cal components:(NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit) fromDate:[NSDate date]];
[comps setMonth:[comps month] + 1];
[comps setDay:1];
I tested this with December 15th, 2014, and it worked to create January 1st, 2015 in my local time. Hopefully that is consistent behavior.
To sum up - it's very likely a bug to not use a consistent calendar for your date component calculations. Sometimes having UTC and sometimes local is going to cause you nightmares. It seems like you should always calculate in local time, but I don't know the whole context of your application so can't make a blanket statement for that. Also, it should be safe to not rely on incessant conversions between dates and date components, and instead have the date component be your source of truth. That is, I mean it seems convoluted to convert date components to dates always to store in instance variables, but then to immediately convert the dates back into date components every time they're used - it seems better to just work with date components as much as possible.
From the comment, I hope I understand your question correctly. You can try this code:-
NSDate * nowDate = [NSDate date];
NSLog(#"nowDate: %#",nowDate);
NSDateFormatter *df = [NSDateFormatter new];
[df setDateFormat:#"dd/MM/yyyy HH:mm"];
df.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:[NSTimeZone localTimeZone].secondsFromGMT];
NSString *localDate = [df stringFromDate:nowDate];
NSLog(#"localDate: %#", localDate);
Output:
2014-05-24 23:03:06.205 TestTimeZone[10214:60b] nowDate: 2014-05-24
15:03:06 +0000
2014-05-24 23:03:06.209 TestTimeZone[10214:60b] localDate: 24/05/2014
23:03
[NSDate date] always return GMT+0 date, no matter where is your timezone. May be just use this? At the same time I used NSDateFormatter to set to my local date based on my laptop. You can try to change to a few different timezones on your mac while running the above code on simulator. [NSDate date] might be just what you need.

UINotification for specific days

I am writing an alarm app. In the app user can select days for setting alarm on that day with time. But I don't know to set UILocalNotification for specific day in a week.
How I know about repeat interval. I know that I must to set
How to calculate how many days will come Friday?
How to Get an NSDate for a Specific Day of Week and Time from an Existing NSDate - this article will help you with this problem. You can get NSDate from current Friday with:
NSDate *today = [NSDate date];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[gregorian setLocale:[NSLocale currentLocale]];
 
NSDateComponents *nowComponents = [gregorian components:NSYearCalendarUnit | NSWeekCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit fromDate:today];
 
[nowComponents setWeekday:6];
[nowComponents setWeek: [nowComponents week]]; // Current week
[nowComponents setHour:8]; // 8a.m.
[nowComponents setMinute:0];
[nowComponents setSecond:0];
 
NSDate *thisWeekFriday = [gregorian dateFromComponents:nowComponents];
Then, you may want to check if thisWeekFriday is less than [NSDate date], and get the next Friday if it is. You can move date by one week with:
NSDateComponents *components = [[NSDateComponents alloc] init];
components.week = 1;
NSDate *nextWeekFriday = [gregorian dateByAddingComponents:components toDate:thisWeekFriday options:0];
You can calculate the number of days between two dates like this:
NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSDayCalendarUnit
fromDate:today
toDate:futureDate
options:0];
NSInteger daysBetweenDates = dateComponents.day;

NSDateFormatter format the week of the date

I've been looking for a way to format the following NSDate object:
19 Feb 2013
Like that
18-25 Feb 2013
The 19 occurs within the week between the 18th and the 25th of Feb.
I couldn't an easy method to do so, is there build in functionality in to the NSDateFormater? should I implement it myself?
I don't think there is built in functionality in NSDateFormatter to do this. However, Apple has an example of how to get the NSDate values for the first and last days of a the week given a date. Here's their example on getting the Sunday of the current week:
NSDate *today = [[NSDate alloc] init];
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
// Get the weekday component of the current date
NSDateComponents *weekdayComponents = [gregorian components:NSWeekdayCalendarUnit
fromDate:today];
/*
Create a date components to represent the number of days to subtract from the current date.
The weekday value for Sunday in the Gregorian calendar is 1, so subtract 1 from the number of days to subtract from the date in question. (If today is Sunday, subtract 0 days.)
*/
NSDateComponents *componentsToSubtract = [[NSDateComponents alloc] init];
[componentsToSubtract setDay: 0 - ([weekdayComponents weekday] - 1)];
NSDate *beginningOfWeek = [gregorian dateByAddingComponents:componentsToSubtract
toDate:today options:0];
/*
Optional step:
beginningOfWeek now has the same hour, minute, and second as the original date (today).
To normalize to midnight, extract the year, month, and day components and create a new date from those components.
*/
NSDateComponents *components =
[gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit |
NSDayCalendarUnit) fromDate: beginningOfWeek];
beginningOfWeek = [gregorian dateFromComponents:components];
Since Sunday is not the beginning of the week in all locales, they also show how to get the beginning of the week as defined by the calendar's locale:
NSDate *today = [[NSDate alloc] init];
NSDate *beginningOfWeek = nil;
BOOL ok = [gregorian rangeOfUnit:NSWeekCalendarUnit startDate:&beginningOfWeek
interval:NULL forDate: today];

Resources