ISO格式中的时区字段

27

我有一个时间戳,应该是以东部标准时间(EST)为基准:

2014-10-06T18:06:40-04:56

我理解这部分:2014-10-06T18:06:40,但不理解-04:56是什么意思。

这里的-04:56表示什么?

以下是我获得该时间戳的方法:

import datetime
start_time = datetime.datetime(year  = 2014, 
                               month = 10, 
                               day   = 6, 
                               hour  = 18, 
                               tzinfo = pytz.timezone('US/Eastern'))
end_time   = start_time + datetime.timedelta(seconds=400)

然后:

end_time.isoformat()

返回值:

2014-10-06T18:06:40-04:56

首先,EST是UTC-5,而不是UTC-4。 - abarnert
其次,当我将一个datetime对象赋予东部标准时间时区(EST),无论是来自于pytztzlocal还是其他任何我能找到的内容,我都会得到预期的“-05:00”。当然,如果我想这样做,我总可以费心构造一个位于“-04:56”的时区并将其命名为EST,而Python或我所拥有的任何模块都不会注意到我在费力让自己困惑,但是...这并不容易。 - abarnert
Josh,它意味着你认为的那样。它描述了一个比UTC落后四个小时五十六分钟的时区。该时间戳等同于 2014-10-06T23:02:40Z。你从哪里得到这个时间戳?或者,是什么软件生成了该时间戳? - Robᵩ
@Robᵩ 这个例子缺少什么?你不能得到相同的字符串吗? - Josh
1
@Josh:作为以后的参考,通常最好包括一个可以运行的完整脚本,或者如果没有,包括导入和所有内容的完整交互式提示转录。在这种情况下它并不模糊或复杂,但是尤其是在编辑最初缺少信息的问题时,充分详细说明永远不会有坏处。 - abarnert
显示剩余4条评论
3个回答

40

问题在于pytz

...... pytz与文档中Python API for tzinfo实现不同;如果您想要创建本地的挂钟时间,则需要使用本文档中记录的localize()方法......

更往下,它说:

不幸的是,对于许多时区,使用标准datetime构造函数的tzinfo参数“无法使用”pytz。

>>> datetime(2002, 10, 27, 12, 0, 0, tzinfo=amsterdam).strftime(fmt)
'2002-10-27 12:00:00 LMT+0020'

因此,您需要按照文档建议使用 normalize,构造UTC时间并使用 astimezone。具体使用哪个取决于您要完成的任务。例如:

>>> from datetime import datetime
>>> from pytz import timezone
>>> utc = timezone('UTC')
>>> eastern = timezone('US/Eastern')
>>> datetime(2014, 10, 6, 18, tzinfo=eastern).isoformat()
'2014-10-06T18:00:00-04:56'
>>> eastern.normalize(datetime(2014, 10, 6, 18, tzinfo=eastern)).isoformat()
'2014-10-06T18:56:00-04:00'
>>> datetime(2014, 10, 6, 18, tzinfo=utc).astimezone(eastern).isoformat()
'2014-10-06T14:00:00-04:00'
>>> eastern.localize(datetime(2014, 10, 6, 18)).isoformat()
'2014-10-06T18:00:00-04:00'
我认为这是你想要的最后结果。正如localize的文档所说:
将本地时间转换为本地时间。
应该使用此方法来构造本地时间,而不是将tzinfo参数传递给datetime构造函数。
我认为在这里构造本地时间正是你想要的。
如果你想知道为什么……嗯,如果你查看Olson数据库中的数据,或者只是打印出eastern._utcoffset,你会看到-1天,+68640分钟。那是19.0166小时,而不是19小时。为什么?因为每个时区都是根据其起始偏移量定义的,并从那里进行调整。东部基于1883年11月18日12:03:58的纽约时区,当时它距GMT的偏移量为-04:56:02。从1920年开始有一项日期调整,减去了额外的00:03:58。当然,每年还要来回调整一个小时的夏令时。因此,就目前而言,东部是-04:00,但没有任何关于它应该代表的日期的想法,它是-04:56。而且,因为datetime只是向时区询问其偏移量,而不是特定时间点的偏移量,所以就得到了这个结果。
最后一件事:EST是东部标准时间,为-05:00。这不是2014年10月6日美国任何地方的时区,因为在2014年,美国的夏令时要到11月2日。 (印第安纳州曾经有一些县在夏季使用EST,但现在不再存在。)你要找的是EDT,也就是东部夏令时,为-04:00。或者当然,ET,在夏季为EDT,在冬季为EST,这是通过查找'US/Eastern''America/New_York'得到的。

3
哇,这伤我挺深的。 - Josh
timezone 方法来自哪里? - Josh
@Josh:抱歉,让我添加导入。它来自于pytz - abarnert
1
谢谢!一个快速的提示:最后一个命令没有返回你发布的字符串(至少在我的机器上)。我得到的是:'2014-10-06T14:00:00-04:00'。所以我猜那个路线不起作用(你必须预先计算时间差异..) - Amelio Vazquez-Reina
1
@Josh:首先,正如我之前提到的,2014年10月6日没有东部夏令时(EST),只有东部日光节约时间(EDT);美国的夏令时一直持续到11月2日。但无论如何,在这里获得你真正想要的最简单的方法是构建一个naive datetime,然后进行本地化;请参见我的新第四个示例。 - abarnert
显示剩余6条评论

7
这里的“-04:56”是什么意思?
这意味着生成输入时间戳的代码存在问题,表明缺乏对pytz时区工作原理的理解。如果您认为只有UTC偏移量(-04:56)是错误的,但日期本身是东部时区的正确时间,则应进行解析时间操作:
#!/usr/bin/env python
from datetime import datetime, timedelta
import pytz

tz = pytz.timezone('America/New_York')

naive = datetime.strptime("2014-10-06T18:06:40-04:56"[:-6],
                          "%Y-%m-%dT%H:%M:%S")
start_time = tz.localize(naive, is_dst=None)
end_time = tz.normalize(start_time + timedelta(seconds=400))
print(start_time.isoformat())
print(end_time.isoformat())
  • 你应该使用 tz.localize() 而不是直接赋值 tzinfo 属性
  • is_dst=None 断言输入的时间存在且是明确的
  • 如果日期跨越夏令时边界,tz.normalize() 是必要的
2014-10-06T18:06:40-04:00
2014-10-06T18:13:20-04:00

为什么需要使用localize()normalize(),可以在pytz文档中查看相关内容(关于localize()/normalize()的部分是文档中的第一个注释)

为什么在东部时区2014年的-04:56 UTC偏移值是错误的

由于夏令时转换或其他原因(如战争或某些政治家认为这是个好主意),同一地点的UTC偏移在不同时间可能会有所不同。例如,下面是美国东部可能出现的值:

>>> import pytz
>>> pytz.timezone('US/Eastern')
{(datetime.timedelta(-1, 72000),
  datetime.timedelta(0, 3600),
  'EWT'): <DstTzInfo 'US/Eastern' EWT-1 day, 20:00:00 DST>,
 (datetime.timedelta(-1, 68640),
  datetime.timedelta(0),
  'LMT'): <DstTzInfo 'US/Eastern' LMT-1 day, 19:04:00 STD>,
 (datetime.timedelta(-1, 72000),
  datetime.timedelta(0, 3600),
  'EDT'): <DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST>,
 (datetime.timedelta(-1, 68400),
  datetime.timedelta(0),
  'EST'): <DstTzInfo 'US/Eastern' EST-1 day, 19:00:00 STD>,
 (datetime.timedelta(-1, 72000),
  datetime.timedelta(0, 3600),
  'EPT'): <DstTzInfo 'US/Eastern' EPT-1 day, 20:00:00 DST>}

注意LMT tzinfo的UTC偏移量: timedelta(-1, 68640) 等于 '-4:56:00'。获取它的方法是使用错误的代码:

#XXX BROKEN, DO NOT DO IT
>>> dt = datetime(2014, 10, 6, tzinfo=pytz.timezone('US/Eastern'))
>>> dt
datetime.datetime(2014, 10, 6, 0, 0, tzinfo=<DstTzInfo 'US/Eastern' LMT-1 day, 19:04:00 STD>)
>>> dt.isoformat()
2014-10-06T00:00:00-04:56

直接分配tzinfo不允许pytz为给定的时间选择正确的tzinfo,而是使用可用的随机tzinfo对象之一。您应该始终使用tz.localize()来附加正确的时区信息。

我认为它不会使用可用对象中的任意对象;它使用时区的定义偏移量,如在Olson数据库中指定的那样,这几乎总是时区首次建立时的偏移量。东部时区是以1920年之前某个位置的当地平均时间作为定义的,所以这是Olson使用的定义;-05:00被列为从1920年开始的4分钟变化。 - abarnert
它是随机的,你不能依赖它。只要文档中没有保证,实现选择时区的方式并不重要。 - jfs

1
我建议查看arrow。上面的回答虽然有效,但会让代码变得难以理解。Arrow可以让您轻松地使用以下方法:
In [82]: start_time = arrow.get(2014, 10, 6, 18).to('US/Eastern')
In [83]: end_time = start_time.shift(seconds=400)
In [84]: start_time
Out[84]: <Arrow [2014-10-06T14:00:00-04:00]>
In [85]: end_time
Out[85]: <Arrow [2014-10-06T14:06:40-04:00]>

您可以通过使用以下代码检索datetime对象:
In [86]: start_time.datetime
Out[86]: datetime.datetime(2014, 10, 6, 14, 0, tzinfo=tzfile('/usr/share/zoneinfo/US/Eastern'))

这很棒!而且简单。 - alEx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接