ios - 如何管理不同时区的 NSDate

标签 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];     
}

这里我得到的为5,为2014年,但日期是1-06-2014。这只发生在美国时区,在所有其他时区都工作正常。

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

最佳答案

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

具体来说,在您的情况下,m_currentMonthContext是一个NSDate,它表示2014年6月1日午夜后一秒的UTC时间。在您的createCalendar 方法,您创建一个用户本地时间的日历,并计算该日期的组成部分。在美国所有时区,UTC 时间 2014 年 6 月 1 日午夜过后一秒仍然是 5 月。可以单独运行的代码示例:

    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 : Calendar Year: 2014 Month: 6 Leap month: no Day: 1

local: Calendar Year: 2014 Month: 5 Leap month: no Day: 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,有时使用本地会导致你做噩梦。似乎您应该始终在本地时间进行计算,但我不知道您的应用程序的整个上下文,因此无法对此做出一揽子声明。此外,不要依赖日期和日期组件之间的不断转换,而是让日期组件作为事实来源,这应该是安全的。也就是说,我的意思是,将日期组件转换为始终存储在实例变量中的日期似乎很复杂,但随后每次使用时立即将日期转换回日期组件 - 最好尽可能多地使用日期组件尽可能。

关于ios - 如何管理不同时区的 NSDate,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23845647/

相关文章:

ios - 如何修改 MFMailComposeViewController 对象的取消按钮的操作表样式

ios - ITMS-90809 : Deprecated API Usage — Apple will stop accepting submissions of apps that use UIWebView APIs When upload my react native app

swift - 如何自定义 DatePickerDialog 以在文本中显示自定义日期? - swift

ios - NSDateComponents 返回意外结果

swift - 如何防止 DateComponents 更新时区

ios - 相机图像选择器控件 - iOS7 中的自动旋转

IOS NSDate 格式不正确

objective-c - 日期格式帮助

swift - 两个 Date 对象之间的所有日期 (Swift)

objective-c - 如何以编程方式触发方法 "tabBarController:didSelectViewController:"?