11.4 时区处理

时间序列处理工作中最让人不爽的就是对时区的处理。许多人都选择以协调世界时(UTC,它是格林尼治标准时间(Greenwich Mean Time)的接替者,目前已经是国际标准了)来处理时间序列。时区是以UTC偏移量的形式表示的。例如,夏令时期间,纽约比UTC慢4小时,而在全年其他时间则比UTC慢5小时。

在Python中,时区信息来自第三方库pytz,它使Python可以使用Olson数据库(汇编了世界时区信息)。这对历史数据非常重要,这是因为由于各地政府的各种突发奇想,夏令时转变日期(甚至UTC偏移量)已经发生过多次改变了。就拿美国来说,DST转变时间自1900年以来就改变过多次!

有关pytz库的更多信息,请查阅其文档。就本书而言,由于pandas包装了pytz的功能,因此你可以不用记忆其API,只要记得时区的名称即可。时区名可以在shell中看到,也可以通过文档查看:

  1. In [110]: import pytz
  2. In [111]: pytz.common_timezones[-5:]
  3. Out[111]: ['US/Eastern', 'US/Hawaii', 'US/Mountain', 'US/Pacific', 'UTC']

要从pytz中获取时区对象,使用pytz.timezone即可:

  1. In [112]: tz = pytz.timezone('America/New_York')
  2. In [113]: tz
  3. Out[113]: <DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>

pandas中的方法既可以接受时区名也可以接受这些对象。

时区本地化和转换

默认情况下,pandas中的时间序列是单纯(naive)的时区。看看下面这个时间序列:

  1. In [114]: rng = pd.date_range('3/9/2012 9:30', periods=6, freq='D')
  2. In [115]: ts = pd.Series(np.random.randn(len(rng)), index=rng)
  3. In [116]: ts
  4. Out[116]:
  5. 2012-03-09 09:30:00 -0.202469
  6. 2012-03-10 09:30:00 0.050718
  7. 2012-03-11 09:30:00 0.639869
  8. 2012-03-12 09:30:00 0.597594
  9. 2012-03-13 09:30:00 -0.797246
  10. 2012-03-14 09:30:00 0.472879
  11. Freq: D, dtype: float64

其索引的tz字段为None:

  1. In [117]: print(ts.index.tz)
  2. None

可以用时区集生成日期范围:

  1. In [118]: pd.date_range('3/9/2012 9:30', periods=10, freq='D', tz='UTC')
  2. Out[118]:
  3. DatetimeIndex(['2012-03-09 09:30:00+00:00', '2012-03-10 09:30:00+00:00',
  4. '2012-03-11 09:30:00+00:00', '2012-03-12 09:30:00+00:00',
  5. '2012-03-13 09:30:00+00:00', '2012-03-14 09:30:00+00:00',
  6. '2012-03-15 09:30:00+00:00', '2012-03-16 09:30:00+00:00',
  7. '2012-03-17 09:30:00+00:00', '2012-03-18 09:30:00+00:00'],
  8. dtype='datetime64[ns, UTC]', freq='D')

从单纯到本地化的转换是通过tz_localize方法处理的:

  1. In [119]: ts
  2. Out[119]:
  3. 2012-03-09 09:30:00 -0.202469
  4. 2012-03-10 09:30:00 0.050718
  5. 2012-03-11 09:30:00 0.639869
  6. 2012-03-12 09:30:00 0.597594
  7. 2012-03-13 09:30:00 -0.797246
  8. 2012-03-14 09:30:00 0.472879
  9. Freq: D, dtype: float64
  10. In [120]: ts_utc = ts.tz_localize('UTC')
  11. In [121]: ts_utc
  12. Out[121]:
  13. 2012-03-09 09:30:00+00:00 -0.202469
  14. 2012-03-10 09:30:00+00:00 0.050718
  15. 2012-03-11 09:30:00+00:00 0.639869
  16. 2012-03-12 09:30:00+00:00 0.597594
  17. 2012-03-13 09:30:00+00:00 -0.797246
  18. 2012-03-14 09:30:00+00:00 0.472879
  19. Freq: D, dtype: float64
  20. In [122]: ts_utc.index
  21. Out[122]:
  22. DatetimeIndex(['2012-03-09 09:30:00+00:00', '2012-03-10 09:30:00+00:00',
  23. '2012-03-11 09:30:00+00:00', '2012-03-12 09:30:00+00:00',
  24. '2012-03-13 09:30:00+00:00', '2012-03-14 09:30:00+00:00'],
  25. dtype='datetime64[ns, UTC]', freq='D')

一旦时间序列被本地化到某个特定时区,就可以用tz_convert将其转换到别的时区了:

  1. In [123]: ts_utc.tz_convert('America/New_York')
  2. Out[123]:
  3. 2012-03-09 04:30:00-05:00 -0.202469
  4. 2012-03-10 04:30:00-05:00 0.050718
  5. 2012-03-11 05:30:00-04:00 0.639869
  6. 2012-03-12 05:30:00-04:00 0.597594
  7. 2012-03-13 05:30:00-04:00 -0.797246
  8. 2012-03-14 05:30:00-04:00 0.472879
  9. Freq: D, dtype: float64

对于上面这种时间序列(它跨越了美国东部时区的夏令时转变期),我们可以将其本地化到EST,然后转换为UTC或柏林时间:

  1. In [124]: ts_eastern = ts.tz_localize('America/New_York')
  2. In [125]: ts_eastern.tz_convert('UTC')
  3. Out[125]:
  4. 2012-03-09 14:30:00+00:00 -0.202469
  5. 2012-03-10 14:30:00+00:00 0.050718
  6. 2012-03-11 13:30:00+00:00 0.639869
  7. 2012-03-12 13:30:00+00:00 0.597594
  8. 2012-03-13 13:30:00+00:00 -0.797246
  9. 2012-03-14 13:30:00+00:00 0.472879
  10. Freq: D, dtype: float64
  11. In [126]: ts_eastern.tz_convert('Europe/Berlin')
  12. Out[126]:
  13. 2012-03-09 15:30:00+01:00 -0.202469
  14. 2012-03-10 15:30:00+01:00 0.050718
  15. 2012-03-11 14:30:00+01:00 0.639869
  16. 2012-03-12 14:30:00+01:00 0.597594
  17. 2012-03-13 14:30:00+01:00 -0.797246
  18. 2012-03-14 14:30:00+01:00 0.472879
  19. Freq: D, dtype: float64

tz_localize和tz_convert也是DatetimeIndex的实例方法:

  1. In [127]: ts.index.tz_localize('Asia/Shanghai')
  2. Out[127]:
  3. DatetimeIndex(['2012-03-09 09:30:00+08:00', '2012-03-10 09:30:00+08:00',
  4. '2012-03-11 09:30:00+08:00', '2012-03-12 09:30:00+08:00',
  5. '2012-03-13 09:30:00+08:00', '2012-03-14 09:30:00+08:00'],
  6. dtype='datetime64[ns, Asia/Shanghai]', freq='D')

注意:对单纯时间戳的本地化操作还会检查夏令时转变期附近容易混淆或不存在的时间。

操作时区意识型Timestamp对象

跟时间序列和日期范围差不多,独立的Timestamp对象也能被从单纯型(naive)本地化为时区意识型(time zone-aware),并从一个时区转换到另一个时区:

  1. In [128]: stamp = pd.Timestamp('2011-03-12 04:00')
  2. In [129]: stamp_utc = stamp.tz_localize('utc')
  3. In [130]: stamp_utc.tz_convert('America/New_York')
  4. Out[130]: Timestamp('2011-03-11 23:00:00-0500', tz='America/New_York')

在创建Timestamp时,还可以传入一个时区信息:

  1. In [131]: stamp_moscow = pd.Timestamp('2011-03-12 04:00', tz='Europe/Moscow')
  2. In [132]: stamp_moscow
  3. Out[132]: Timestamp('2011-03-12 04:00:00+0300', tz='Europe/Moscow')

时区意识型Timestamp对象在内部保存了一个UTC时间戳值(自UNIX纪元(1970年1月1日)算起的纳秒数)。这个UTC值在时区转换过程中是不会发生变化的:

  1. In [133]: stamp_utc.value
  2. Out[133]: 1299902400000000000
  3. In [134]: stamp_utc.tz_convert('America/New_York').value
  4. Out[134]: 1299902400000000000

当使用pandas的DateOffset对象执行时间算术运算时,运算过程会自动关注是否存在夏令时转变期。这里,我们创建了在DST转变之前的时间戳。首先,来看夏令时转变前的30分钟:

  1. In [135]: from pandas.tseries.offsets import Hour
  2. In [136]: stamp = pd.Timestamp('2012-03-12 01:30', tz='US/Eastern')
  3. In [137]: stamp
  4. Out[137]: Timestamp('2012-03-12 01:30:00-0400', tz='US/Eastern')
  5. In [138]: stamp + Hour()
  6. Out[138]: Timestamp('2012-03-12 02:30:00-0400', tz='US/Eastern')

然后,夏令时转变前90分钟:

  1. In [139]: stamp = pd.Timestamp('2012-11-04 00:30', tz='US/Eastern')
  2. In [140]: stamp
  3. Out[140]: Timestamp('2012-11-04 00:30:00-0400', tz='US/Eastern')
  4. In [141]: stamp + 2 * Hour()
  5. Out[141]: Timestamp('2012-11-04 01:30:00-0500', tz='US/Eastern')

不同时区之间的运算

如果两个时间序列的时区不同,在将它们合并到一起时,最终结果就会是UTC。由于时间戳其实是以UTC存储的,所以这是一个很简单的运算,并不需要发生任何转换:

  1. In [142]: rng = pd.date_range('3/7/2012 9:30', periods=10, freq='B')
  2. In [143]: ts = pd.Series(np.random.randn(len(rng)), index=rng)
  3. In [144]: ts
  4. Out[144]:
  5. 2012-03-07 09:30:00 0.522356
  6. 2012-03-08 09:30:00 -0.546348
  7. 2012-03-09 09:30:00 -0.733537
  8. 2012-03-12 09:30:00 1.302736
  9. 2012-03-13 09:30:00 0.022199
  10. 2012-03-14 09:30:00 0.364287
  11. 2012-03-15 09:30:00 -0.922839
  12. 2012-03-16 09:30:00 0.312656
  13. 2012-03-19 09:30:00 -1.128497
  14. 2012-03-20 09:30:00 -0.333488
  15. Freq: B, dtype: float64
  16. In [145]: ts1 = ts[:7].tz_localize('Europe/London')
  17. In [146]: ts2 = ts1[2:].tz_convert('Europe/Moscow')
  18. In [147]: result = ts1 + ts2
  19. In [148]: result.index
  20. Out[148]:
  21. DatetimeIndex(['2012-03-07 09:30:00+00:00', '2012-03-08 09:30:00+00:00',
  22. '2012-03-09 09:30:00+00:00', '2012-03-12 09:30:00+00:00',
  23. '2012-03-13 09:30:00+00:00', '2012-03-14 09:30:00+00:00',
  24. '2012-03-15 09:30:00+00:00'],
  25. dtype='datetime64[ns, UTC]', freq='B')