如何在Python中使用-0400时区字符串解析日期?


80

我有一个日期字符串,其格式为'2009/05/13 19:19:30 -0400'。似乎Python的早期版本可能在strptime中为尾随时区规范支持了%z格式标签,但2.6.x似乎已将其删除。

将这个字符串解析为datetime对象的正确方法是什么?

Answers:


116

您可以使用dateutil中的parse函数:

>>> from dateutil.parser import parse
>>> d = parse('2009/05/13 19:19:30 -0400')
>>> d
datetime.datetime(2009, 5, 13, 19, 19, 30, tzinfo=tzoffset(None, -14400))

这样,您便可以获取可以使用的datetime对象。

作为回答,dateutil2.0就是Python 3.0编写,并且不使用Python 2.x协同工作。对于Python 2.x,需要使用dateutil1.5。


13
这对于dateutil使用Python的我(2.1)很好2.7.2;不需要Python 3。请注意,如果您是从pip安装的,则软件包名称为python-dateutil
BigglesZX

44

%z 在Python 3.2+中受支持:

>>> from datetime import datetime
>>> datetime.strptime('2009/05/13 19:19:30 -0400', '%Y/%m/%d %H:%M:%S %z')
datetime.datetime(2009, 5, 13, 19, 19, 30,
                  tzinfo=datetime.timezone(datetime.timedelta(-1, 72000)))

在早期版本上:

from datetime import datetime

date_str = '2009/05/13 19:19:30 -0400'
naive_date_str, _, offset_str = date_str.rpartition(' ')
naive_dt = datetime.strptime(naive_date_str, '%Y/%m/%d %H:%M:%S')
offset = int(offset_str[-4:-2])*60 + int(offset_str[-2:])
if offset_str[0] == "-":
   offset = -offset
dt = naive_dt.replace(tzinfo=FixedOffset(offset))
print(repr(dt))
# -> datetime.datetime(2009, 5, 13, 19, 19, 30, tzinfo=FixedOffset(-240))
print(dt)
# -> 2009-05-13 19:19:30-04:00

FixedOffset一个基于docs代码示例的类在哪里:

from datetime import timedelta, tzinfo

class FixedOffset(tzinfo):
    """Fixed offset in minutes: `time = utc_time + utc_offset`."""
    def __init__(self, offset):
        self.__offset = timedelta(minutes=offset)
        hours, minutes = divmod(offset, 60)
        #NOTE: the last part is to remind about deprecated POSIX GMT+h timezones
        #  that have the opposite sign in the name;
        #  the corresponding numeric value is not used e.g., no minutes
        self.__name = '<%+03d%02d>%+d' % (hours, minutes, -hours)
    def utcoffset(self, dt=None):
        return self.__offset
    def tzname(self, dt=None):
        return self.__name
    def dst(self, dt=None):
        return timedelta(0)
    def __repr__(self):
        return 'FixedOffset(%d)' % (self.utcoffset().total_seconds() / 60)

1
ValueError: 'z' is a bad directive in format '%Y-%m-%d %M:%H:%S.%f %z'在我的情况下,这会导致(Python 2.7)。
乔纳森·H

@Sheljohn它不应该在Python 2.7上运行请看答案的最上方。
jfs

顺便说一句,很奇怪,Python 2.7 docs上根本没有提到:docs.python.org/2.7/library/…–
62mkv

22

这是"%z"针对Python 2.7和更早版本的问题的修复

而不是使用:

datetime.strptime(t,'%Y-%m-%dT%H:%M %z')

使用timedelta来表示时区,如下所示:

from datetime import datetime,timedelta
def dt_parse(t):
    ret = datetime.strptime(t[0:16],'%Y-%m-%dT%H:%M')
    if t[18]=='+':
        ret-=timedelta(hours=int(t[19:22]),minutes=int(t[23:]))
    elif t[18]=='-':
        ret+=timedelta(hours=int(t[19:22]),minutes=int(t[23:]))
    return ret

请注意,日期将转换为GMT,这将允许进行日期算术而无需担心时区。


我喜欢这样,尽管您需要将“ seconds =”更改为“ minutes =”。
戴夫

1
请注意,如果要在字符串中采用时区并将日期时间转换为UTC,则可以使用此处列出的相反逻辑。如果时区为+,则减去时差,反之亦然。
Sector95

转换为UTC是错误的,如果有+字符,则应减去timedelta ,反之亦然。我已经编辑并更正了代码。
tomtastico

7

使用dateutil的问题在于,序列化和反序列化都不能使用相同的格式字符串,因为dateutil的格式化选项有限(仅dayfirstyearfirst)。

在我的应用程序中,我将格式字符串存储在.INI文件中,每个部署都可以具有自己的格式。因此,我真的不喜欢dateutil方法。

这是使用pytz的替代方法:

from datetime import datetime, timedelta

from pytz import timezone, utc
from pytz.tzinfo import StaticTzInfo

class OffsetTime(StaticTzInfo):
    def __init__(self, offset):
        """A dumb timezone based on offset such as +0530, -0600, etc.
        """
        hours = int(offset[:3])
        minutes = int(offset[0] + offset[3:])
        self._utcoffset = timedelta(hours=hours, minutes=minutes)

def load_datetime(value, format):
    if format.endswith('%z'):
        format = format[:-2]
        offset = value[-5:]
        value = value[:-5]
        return OffsetTime(offset).localize(datetime.strptime(value, format))

    return datetime.strptime(value, format)

def dump_datetime(value, format):
    return value.strftime(format)

value = '2009/05/13 19:19:30 -0400'
format = '%Y/%m/%d %H:%M:%S %z'

assert dump_datetime(load_datetime(value, format), format) == value
assert datetime(2009, 5, 13, 23, 19, 30, tzinfo=utc) \
    .astimezone(timezone('US/Eastern')) == load_datetime(value, format)

2

一种用于旧Python的衬板。您可以根据+/-符号将时间增量乘以1 / -1,如下所示:

datetime.strptime(s[:19], '%Y-%m-%dT%H:%M:%S') + timedelta(hours=int(s[20:22]), minutes=int(s[23:])) * (-1 if s[19] == '+' else 1)

-10

如果您使用的是Linux,则可以使用外部date命令进行dwim:

import commands, datetime

def parsedate(text):
  output=commands.getoutput('date -d "%s" +%%s' % text )
  try:
      stamp=eval(output)
  except:
      print output
      raise
  return datetime.datetime.frometimestamp(stamp)

当然,它的可移植性不如dateutil,但灵活性更高,因为它date还将接受“昨天”或“去年”之类的输入:-)


3
我认为为此调用外部程序不是很好。下一个弱点是:eval():如果您现在由网络服务器执行此代码,则可以在服务器上执行任意代码!
guettli 2011年

5
这完全取决于上下文:如果我们所追求的只是一个写后扔脚本,那么这些弱点就无关紧要了:-)
Gyom

10
拒绝投票的原因是:1)它使系统调用变得无关紧要; 2)将字符串直接注入到shell调用中; 3)调用eval(); 4)它具有异常捕获所有功能。基本上,这是如何的例子做事。
benjaoming

在这种情况下,尽管eval是邪恶的,不应该使用。外部调用似乎是从时区感知日期字符串中获取unix时间戳的最简单,最实用的方法,其中时区不是数字偏移量。
Leliel

1
好吧,再次,这个“评估是邪恶的”座右铭确实取决于您的上下文(OP并未声明)。当我编写供自己使用的脚本时,我会自由地使用eval,它很棒。Python是胶水脚本的绝佳语言!当然,您可以像上面的某些答案一样推出复杂的,过分设计的通用解决方案,然后声称这是唯一可行的方法,ala Java。但是对于许多用例来说,快速而又肮脏的解决方案也是一样。
Gyom '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.