11.4 时区处理

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

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

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

    1. In [113]: tz
    2. 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
    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方法处理的:

    一旦时间序列被本地化到某个特定时区,就可以用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-10 14:30:00+00:00 0.050718
    5. 2012-03-11 13:30:00+00:00 0.639869
    6. 2012-03-12 13:30:00+00:00 0.597594
    7. 2012-03-13 13:30:00+00:00 -0.797246
    8. 2012-03-14 13:30:00+00:00 0.472879
    9. Freq: D, dtype: float64
    10. In [126]: ts_eastern.tz_convert('Europe/Berlin')
    11. Out[126]:
    12. 2012-03-09 15:30:00+01:00 -0.202469
    13. 2012-03-10 15:30:00+01:00 0.050718
    14. 2012-03-11 14:30:00+01:00 0.639869
    15. 2012-03-12 14:30:00+01:00 0.597594
    16. 2012-03-13 14:30:00+01:00 -0.797246
    17. 2012-03-14 14:30:00+01:00 0.472879
    18. 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对象也能被从单纯型(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时,还可以传入一个时区信息:

    时区意识型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. Out[137]: Timestamp('2012-03-12 01:30:00-0400', tz='US/Eastern')
    4. In [138]: stamp + Hour()
    5. 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')
    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',