如何管理不同时区的NSDate

时间:2014-05-24 13:38:03

标签: ios nsdate nsdatecomponents nstimezone

我在我的应用中创建了一个日历,使用日期对象:

    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];

当用户想要下个月去时,我会突出显示该月的第一个日期。因此,在这种情况下,日期将是1-06-2014,00:00:01。

以下是代码:

    - (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];
    }

在上述方法的倒数第二行计算m_currentMonthContext时,其值为1-06-2014,00:00:01。

createCalendar实施:

-(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];     
}

此处,我将month定为5,year为2014,但日期为1-06-2014。这种情况只发生在美国时区,在所有其他时区都能正常工作。

所以我想知道如何有效地处理时区,或者在其他意义上,如何确保即使时区发生变化也不会改变NSDate。

2 个答案:

答案 0 :(得分:2)

最近的原因是在计算日期和日期组件时,日历上的时区不一致。有时您将时区设置为UTC,有时不会,这会导致不一致,因为有时会应用本地时间的偏移,有时不会。但/ / p>

详细地说,在您的情况下,m_currentMonthContextNSDate,代表2014年6月1日午夜后一秒的UTC时间。在createCalendar方法中,您创建了一个日历这是用户的本地时间,并计算这样一个日期的组件。在美国的所有时区,它仍然是2014年6月1日午夜后的五月一秒。代码中的一个示例,可以单独运行:

    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);

此处在MDT时区中,此日志记录:

  

utc:          日历年:2014年       月:6       闰月:没有       日:1

     

本地:       日历年:2014年       月:5       闰月:没有       第31天

总结一下,您将在内存中保留一个日期,该日期已计算为以UTC时间表示某个日历日期,然后在用户当地时间计算日历日期,但您似乎对日历的日期不正确不同的时区将以相同的方式解释相同的日期。

那么,该怎么办?您的示例非常复杂,但似乎根本不需要在UTC时区中存储日期组件,有时不会 - 保持一致。现在,在我看来,如果你只想找到下个月的第一天,你的代码就会简单得多。:

    NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    NSDateComponents *comps = [cal components:(NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit) fromDate:[NSDate date]];
    [comps setMonth:[comps month] + 1];
    [comps setDay:1];

我在2014年12月15日进行了测试,并在当地时间创建了2015年1月1日。希望这是一致的行为。

总结一下 - 很可能不会在日期组件计算中使用一致的日历。有时候有UTC,有时候是本地会让你做恶梦。看起来你应该总是在当地时间计算,但我不知道你的申请的整个背景,所以不能为此做一个全面的陈述。此外,不依赖于日期和日期组件之间的不间断转换应该是安全的,而是将日期组件作为您的真实来源。也就是说,我的意思是将日期组件转换为日期总是存储在实例变量中似乎很复杂,但是每次使用它们时立即将日期转换回日期组件 - 最好只使用日期组件尽可能。

答案 1 :(得分:0)

从评论中,我希望我能正确理解你的问题。您可以尝试以下代码: -

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);

<强>输出:

  

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日期] 始终返回GMT + 0日期,无论您的时区在哪里。可能只是用这个?同时我使用 NSDateFormatter 根据我的笔记本电脑设置为我的本地日期。在模拟器上运行上述代码时,您可以尝试在Mac上更改为几个不同的时区。 [NSDate date] 可能就是您所需要的。