How to manage NSDate for different timezones

柔情痞子 提交于 2019-12-04 16:50:17

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!