Как анализировать даты с помощью строки часового пояса -0400 в Python?

81

У меня есть строка даты вида «2009/05/13 19:19:30 -0400». Кажется, что предыдущие версии Python могли поддерживать тег формата% z в strptime для конечной спецификации часового пояса, но 2.6.x, похоже, удалил это.

Как правильно преобразовать эту строку в объект datetime?

поля
источник

Ответы:

117

Вы можете использовать функцию синтаксического анализа из dateutil:

>>> 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.

txwikinger
источник
13
У меня это отлично работает ( dateutil2.1) с Python 2.7.2; Python 3 не требуется. Обратите внимание, что если вы устанавливаете из pip, имя пакета - python-dateutil.
BigglesZX
47

%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- класс, основанный на примере кода из документации :

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)
jfs
источник
1
В ValueError: 'z' is a bad directive in format '%Y-%m-%d %M:%H:%S.%f %z'моем случае это вызывает ошибку (Python 2.7).
Jonathan H
@Sheljohn не должен работать на Python 2.7. Посмотрите на самый верхний ответ.
jfs
кстати, странно, что это ВООБЩЕ НЕ упоминается в документах Python 2.7 : 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, что позволит выполнять арифметические операции с датами, не беспокоясь о часовых поясах.

Ури Горен
источник
Мне это нравится, хотя вам нужно изменить "секунды =" на "минуты =".
Дэйв
1
В качестве примечания: если вы хотите взять часовой пояс в строке и преобразовать дату и время в UTC, вы должны использовать противоположную логику, указанную здесь. Если часовой пояс имеет +, вы вычитаете timedelta, и наоборот.
Sector95
Переход к UTC был неправильным, если есть +характер timedelta должен быть вычтен , и наоборот. Я отредактировал и исправил код.
tomtastico
7

Проблема с использованием dateutil заключается в том, что вы не можете использовать одну и ту же строку формата как для сериализации, так и для десериализации, поскольку dateutil имеет ограниченные параметры форматирования (только dayfirstи yearfirst).

В своем приложении я храню строку формата в файле .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)
Sayap
источник
2

Один лайнер для старых питонов. Вы можете умножить timedelta на 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что также принимает ввод, например, «вчера» или «в прошлом году» :-)

Gyom
источник
3
Не думаю, что для этого стоит вызывать внешнюю программу. И следующее слабое место: eval (): если вы теперь, когда веб-сервер выполняет этот код, вы можете выполнять произвольное выполнение кода на сервере!
Guettli
5
Все зависит от контекста: если то, что нам нужно, - это только сценарий записи и выброса, тогда эти слабые места просто не имеют значения :-)
Gyom
10
Голосование против этого, потому что: 1) он выполняет системный вызов для чего-то тривиального, 2) он вставляет строки непосредственно в вызов оболочки, 3) он вызывает eval () и 4) он имеет все исключения. По сути, это пример того, как чего-то не делать.
benjaoming
В этом случае, хотя eval является злом и его не следует использовать. внешний вызов кажется самым простым и наиболее практичным способом получить временную метку unix из строки даты с учетом часового пояса, где часовой пояс не является числовым смещением.
Leliel
1
Что ж, опять же, этот девиз «eval - это зло» действительно зависит от вашего контекста (который не был заявлен OP). Когда я пишу скрипты для себя, я использую eval без ограничений, и это круто. Python - отличный язык для склейки скриптов! Конечно, вы можете развернуть запутанные универсальные чрезмерно спроектированные решения, как в некоторых ответах выше, а затем заявить, что это единственный правильный способ сделать это, ala Java. Но для многих случаев использования быстрое и грязное решение так же хорошо.
Gyom