【问题标题】:How to manage NSDate for different timezones如何管理不同时区的 NSDate
【发布时间】:2014-07-13 18:46:48
【问题描述】:

我在我的应用程序中创建了一个日历,以这种方式使用日期对象:

    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 即使时区改变也不会改变。

【问题讨论】:

  • 如何管理不同时区的 NSDate 是什么意思?列出一些案例研究怎么样?例如:UserA 从 timeZoneA 移动到 timeZoneB。预计日期是多少?等等。
  • 嗨,瑞奇,我希望我的 NSDate 对象无论我在哪个时区都不会改变
  • 您的意思是,如果我来自东南亚并且我飞往美国,我仍然看到基于东南亚的日期?那么,无论用户身在何处,您都希望您的应用只使用一个日期?
  • 是的,你是对的
  • @Ranjit 我试图通过从似乎是问题主体本身的一部分的代码块中移动一些关键细节来稍微澄清你的问题,如果我在这里犯了错误,请更正它,谢谢。

标签: ios nsdate nsdatecomponents nstimezone


【解决方案1】:

直接原因是在计算日期和日期组件时,日历上的时区设置不一致。有时您将时区设置为 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: 历年: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,有时有本地时间会让你做噩梦。似乎您应该始终以当地时间计算,但我不知道您的应用程序的整个上下文,因此无法对此做出笼统的陈述。此外,不依赖日期和日期组件之间的不断转换应该是安全的,而是让日期组件成为您的事实来源。也就是说,我的意思是,将日期组件转换为日期总是存储在实例变量中似乎很复杂,但是每次使用它们时立即将日期转换回日期组件 - 最好只使用日期组件尽可能。

【讨论】:

  • 您好@Carl,感谢您提供宝贵的信息来源,您说我在UTC 和当地时间的某个地方使用过一些地方,我没听懂,我在哪里做过? 2)你说的日期和日期组件之间的转换不一致是什么意思,你能指出来吗? 3)我可以使用 defaultTimeZone 代替 UTC 吗?
  • @Ranjit 1) 一个示例:您分配给m_currentMonthContext-showNextMonth 倒数第二行的日期源自您设置为UTC 时区的日历。然后在-createCalendar 中,您实例化一个新日历并且不明确设置时区,因此默认为UTC 时间。您是否看到您正在这样做,并且为什么不应该这样做有意义吗? 2)我说“不断”,暗示很多 - 所有实例变量都存储为NSDate,但似乎您几乎只使用它们来计算日期分量。编辑澄清。 3) 在哪里?
  • 嘿,谢谢,所以您的意思是说我应该在创建日历中明确使用 UTC?我正在考虑在我使用 UTC 的地方使用 defaulttimeZone,例如在我初始化上面的日期函数时。希望你明白了。
  • 这可能只是掩盖错误,但我不知道您的广泛用例究竟是什么,所以它可能会在您的应用程序的上下文中工作。但是,如果您要根据对用户日历有意义的内容来显示所有内容,为什么要在任何地方使用 UTC
  • 我想使用 UTC,因为它是通用的。有什么问题吗?
【解决方案2】:

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

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

【讨论】:

  • [NSDate date] 总是返回 GMT 并不完全正确,因为NSDate 完全没有时区的概念。
  • 真的吗?可能是我缺乏这方面的知识。到目前为止,我已经在我的 mac 上测试了不同的时区,它仍然显示为 +0000,我相信它是 GMT + 0。除非我们为 NSDate 分配时区,否则默认的 [NSDate date]总是 GMT + 0 对吗?我可能错了。
  • 这就是它在您记录它时打印的内容,实际上是实例化一个具有特定时区和语言环境的NSDateFormatter 来打印它——但NSDate 基本上只是一个时间戳。跨度>
猜你喜欢
  • 2011-09-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-17
相关资源
最近更新 更多