NSDateComponents - day method returning wrong day
I can't seem to figure out why the day doesn't change when I get to the 6th of November, 2011. All I'm doing is iterating through the days. Any help would be appreciated. Here is my code:
NSCalendar* calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents* comp = [[NSDateComponents alloc] init];
[comp setDay:2];
[comp setMonth:11];
[comp setYear:2011];
NSDate* date = [calendar dateFromComponents:comp];
for (int i = 0; i < 7; i++) {
NSDate* d = [date dateByAddingTimeInterva开发者_如何学JAVAl:((3600 * 24) * i)];
NSDateComponents* dComponents = [calendar components:(NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit) fromDate:d];
int day = [dComponents day];
NSLog(@"\nDate: %@\nDay: %i", [d description], day);
}
This is my output:
Date: 2011-11-02 06:00:00 +0000
Day: 2
Date: 2011-11-03 06:00:00 +0000
Day: 3
Date: 2011-11-04 06:00:00 +0000
Day: 4
Date: 2011-11-05 06:00:00 +0000
Day: 5
Date: 2011-11-06 06:00:00 +0000
Day: 6
Date: 2011-11-07 06:00:00 +0000
Day: 6
Date: 2011-11-08 06:00:00 +0000
Day: 7
Thanks!
Welcome to the wonderful world of date calculations!
@Vladimir is correct. November 6th happens to have ~90,000 seconds in it because that's the day (in the US on the Gregorian calendar) that Daylight Savings Time takes effect. Accept his answer; it's the right one.
This one is to educate you a bit more, because this is a hard topic.
An NSDate
represents an absolute point in time. This point is immutable. It will exist regardless of whether humans and their time measurements exist.
A "day" is a relative value. It is relative to many things, such as:
- The planet earth. A martian day is not the same thing as an earth day
- The calendar of the person doing the calculation. Not all calendars measure time in the same way.
Thus, it is impossible to take a relative value and add it to an absolute value without more context. You have no idea what the relative value is relative to. You make the assumption that everyone is using the same point of reference as you, and that assumption is wrong. For example:
- The current year is 2011. But is it? "2011" is relative to the Gregorian calendar. What if you're not using the Gregorian calendar?
- The current year is 23. But that's only if you're using the Japanese calendar and specifically using the Japanese era names.
- The current year is 5771. But that's only if you're using the Hebrew calendar. And the turnover for the next year (5772) is not the same turnover as the next Gregorian year.
- The current year is (probably) 1432. If you're a Muslim.
- The current year is 4344, if you're Korean.
And so on and so on.
As a general rule, most calendars are solar based, meaning that one orbit around the sun is equivalent to one "year" in that calendar. But this makes things REALLY complicated, because the number of rotations the Earth makes (a "day") in its orbit around the Sun is not an integral (whole) number. It's something like 365.24 rotations. Enter leap days! But only in certain calendars! In other calendars, we have whole leap months! Or leap seconds. Or leap hours. Or leap-whatever-time-unit-you-wants. And don't even get me started on time zones.
So, how do you deal with this?
The simple answer: YOU DON'T. People much smarter than you or I have already written the code to handle all of this for us.
BEHOLD:
NSCalendar
NSDateComponents
An NSCalendar
encapsulates all of the rules and regulations for calculating time (on Earth) according to that system of measurement. NSDateComponents
encapsulates the relative nature of measuring time.
So you have an absolute point in time (an NSDate
), and you want to know what day of the week it corresponds to. Here's what you do:
- Get the appropriate calendar object. For almost everything you'll ever do, this is probably
[NSCalendar currentCalendar]
. - Using the calendar, you say "i want such-and-such 'date components' from this absolute point in time"
The calendar's going to churn and burn and give you back an NSDateComponents
that signify whatever information you're looking for.
Or let's say you have an absolute point in time and you want to know "what's the absolute point in time 1 week later?". Who knows how long a week is? I don't. It's usually 7 days, but it doesn't have to be. That's just what I'm familiar with. But I'm sure there are calendars out there that are based on non-7-day weeks. So you make an NSDateComponents
object, lash it up to mean "1 week", and say "calendar: I have this date and this relative amount. Add them and tell me the new date" and it does that.
<update>
However, adding date components to dates can also be problematic. For example, what if you want to find the last day of every month this year? (We'll assume the Gregorian calendar here) So you start with an NSDate
that happens to fall on Jan 31 and you add "1 month" to it. What do you get? Feb 28! So then you add one month to that. What do you get? Mar 28! That's not the end of the month anymore!
The way to fix that is to always compute the new date from the original date. So instead of adding 1 month to each successive date, you add "n" months to the original date. And that will work correctly.
</update>
You can see how this is a difficult topic. In case you haven't, here's one last hint:
THIS IS A DIFFICULT TOPIC
The sooner you learn to do things correctly, the happier you and your code will be. :)
November, 6 is a day when time changed due to the Day Saving Time so that day actually lasts 25 hours and incorrect result is probably comes from that (in general adding time intervals to a date is unreliable because of calendar irregularities and bahaviour may depend on many parameters: current calendar, time zone, locale settings etc).
The more correct way to iterate through days (per wwdc11 video "Performing Calendar Calculations"(iTunes link)) is to add appropriate number of date components to a starting date on each iteration:
...
NSDateComponents *addComponents = [[NSDateComponents alloc] init];
for (int i = 0; i < 7; i++) {
[addComponents setDay: i];
NSDate* d = [calendar dateByAddingComponents:addComponents toDate:date options:0];
NSDateComponents* dComponents = [calendar components:(NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit) fromDate:d];
int day = [dComponents day];
NSLog(@"\nDate: %@\nDay: %i", [d description], day);
}
精彩评论