NSDate created from NSDateComponents incorrect? - ios

I created an NSDate by dateFromComponents, using NSCalendar with NSGregorianCalendar identifier, here's the strange part:
The date get incorrect if it's before a certain point in time before 1900/12/31
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *components = [[NSDateComponents alloc] init];
components.year = 1900; components.month = 12; components.day = 31;
NSDate *date = [calendar dateFromComponents:components];
components.year = 1901; components.month = 1; components.day = 1;
NSDate *date2 = [calendar dateFromComponents:components];
NSLog(#"%#",calendar.timeZone.description);
NSLog(#"%#",date);
NSLog(#"%#",date2);
The log will be:
2016-05-25 14:58:21.014 date[79754:2192157] Asia/Shanghai (GMT+8) offset 28800
2016-05-25 14:58:21.015 date[79754:2192157] 1900-12-30 15:54:17 +0000
2016-05-25 14:58:21.015 date[79754:2192157] 1900-12-31 16:00:00 +0000
As you can see, there is a 5 minutes gap during the day.
However, if I set the timeZone by [NSTimeZone timeZoneForSecondsFromGMT:], even with the same seconds deviation - 28800, it will be normal.
What is the cause of this?

No, the date isn't incorrect. Instead, the NSCalendar code knows things about calendars that you wouldn't dream about, like calendars changing their time offsets at some points in time in the past.
You asked for the Asia/Shanghai calendar to convert two dates, one on the day before they changed their time zone, one on the day after they changed their time zone, and both times are converted correctly. That night everyone in Shanghai had to adjust their watches.

Interesting question. What you are seeing is the effects of a time zone change from Local Mean Time to China Standard Time on 01-01-1901 when the clocks were turned back 05m43s.
More details here.

Try this!!
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setYear:1987];
[components setMonth:3];
[components setDay:17];
[components setHour:14];
[components setMinute:20];
[components setSecond:0];
NSDate *date = [calendar dateFromComponents:components];
Hope this Help:)

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

Comparing 2 dates gives me 1 day less in comparison in IOS

I am trying to compare 2 Dates but in i am getting 1 day less
here is a snippet:
NSDateComponents* dateComponents = [[NSDateComponents alloc] init];
[dateComponents setYear: 2014];
[dateComponents setMonth: 1];
[dateComponents setDay: 31];
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDate* otherDay = [calendar dateFromComponents: dateComponents];
NSDate * todaydate= [NSDate date];
if ([otherDay compare:todaydate]>= NSOrderedDescending)
{
NSLog(#"In If other Date= %# & Today = %# ", otherDay,todaydate);
}else
{
NSLog(#"I am in else other date= %# and today = %# ",otherDay, todaydate);
}
The Log i am getting is :
I am in else other date= 2014-01-30 18:30:00 +0000 and today = 2014-01-31 08:34:21 +0000
Why it's showing other date = 30th jan 2014 ?
You need to set timezone of NSDateComponents such like,
[dateComponents setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"GMT"]];
Or as trojanfoe's suggestion you can also set timezone of NSCalendar such like,
[calendar setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"GMT"]];
And To exact compare otherDay to todaydate please see answer of Martin R you need to set
NSDate * todaydate;
[calendar rangeOfUnit:NSDayCalendarUnit startDate:&todaydate interval:NULL forDate:[NSDate date]];
From Martin R's answer this is very useful.
There are two different aspects in your question. First,
NSDateComponents* dateComponents = [[NSDateComponents alloc] init];
[dateComponents setYear: 2014];
[dateComponents setMonth: 1];
[dateComponents setDay: 31];
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDate* otherDay = [calendar dateFromComponents: dateComponents];
computes otherDay as "2014-01-31 00:00" (in your time zone), and
NSDate *todaydate = [NSDate date];
computes todaydate as the current point in time, which includes the hours, minutes
and seconds, for example "2014-01-31 13:00:00" (in your time zone). Therefore
[otherDay compare:todaydate]
returns NSOrderedAscending: otherDay is earlier than todaydate!
What you probably want is to compute todaydate as the start of the current day
(today at 00:00). This can be done as
NSDate * todaydate;
[calendar rangeOfUnit:NSDayCalendarUnit startDate:&todaydate interval:NULL forDate:[NSDate date]];
And now [otherDay compare:todaydate] returns NSOrderedSame, as you expected.
The other aspect is the NSLog output. Printing a NSDate with NSLog()
prints the date according to GMT, and "2014-01-31 00:00" in your time zone is
exactly the same time as "2014-01-30 18:30:00 +0000" in GMT.
The output is correct, it just uses GMT instead of your local timezone for display.

How to check whether now date is during 9:00-18:00

When my app is launched I want to check whether the date is between 9:00-18:00.
And I can get the time of now using NSDate. How can I check the time?
So many answers and so many flaws...
You can use NSDateFormatter in order to get an user-friendly string from a date. But it is a very bad idea to use that string for date comparisons!
Please ignore any answer to your question that involves using strings...
If you want to get information about a date's year, month, day, hour, minute, etc., you should use NSCalendar and NSDateComponents.
In order to check whether a date is between 9:00 and 18:00 you can do the following:
NSDate *date = [NSDate date];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *dateComponents = [calendar components:NSHourCalendarUnit fromDate:date];
if (dateComponents.hour >= 9 && dateComponents.hour < 18) {
NSLog(#"Date is between 9:00 and 18:00.");
}
EDIT:
Whoops, using dateComponents.hour <= 18 will result in wrong results for dates like 18:01. dateComponents.hour < 18 is the way to go. ;)
Construct dates for 09:00 and 18:00 today and compare the current time with those dates:
NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *now = [NSDate date];
NSDateComponents *components = [cal components:NSEraCalendarUnit|NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit fromDate:now];
[components setHour:9];
[components setMinute:0];
[components setSecond:0];
NSDate *nineHundred = [cal dateFromComponents:components];
[components setHour:18];
NSDate *eighteenHundred = [cal dateFromComponents:components];
if ([nineHundred compare:now] != NSOrderedDescending &&
[eighteenHundred compare:now] != NSOrderedAscending)
{
NSLog(#"Date is between 09:00 and 18:00");
}

NSDate subtracting trouble

so in my app I want to subtract two dates. The first date is set date and the second one is the current date. So i tried this code:
NSString *setDateString = [NSString stringWithFormat:#"12:30"];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
NSDate *setDate = [dateFormatter dateFromString:setDateString];
NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:setDate];
But it counts the interval from 1970. But I need from the current day. Can anyone help?
I assume you want the difference between now and 1230 hrs of today. This can be done by
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components =[gregorian components:(NSDayCalendarUnit|NSYearCalendarUnit|NSMonthCalendarUnit) fromDate:today];
[components setHour:12];
[components setMinute:30];
[components setSecond:0];
NSDate *setDate=[gregorian dateFromComponents:components];
NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:setDate];
[gregorian release];
The reason it doesn't work for you is you've not given the format correctly. Alternatively, you can describe it correctly in setDateString by specifying all the components rather than just hour and minute.

Resources