Як проаналізувати дати за допомогою рядка часового поясу -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))

Таким чином ви отримуєте об'єкт дати та часу, який потім можна використовувати.

Як відповіли , dateutil2.0 написаний для Python 3.0 і не працює з Python 2.x. Для Python 2.x потрібно використовувати dateutil1.5.


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)

1
Це викликає a ValueError: 'z' is a bad directive in format '%Y-%m-%d %M:%H:%S.%f %z'у моєму випадку (Python 2.7).
Джонатан Х

@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 має обмежені можливості форматування (лише 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)

2

Один лайнер для старих пітонів. Ви можете помножити шкалу часу на 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

5
Все залежить від контексту: якщо те, що ми шукаємо, є лише сценарієм, що пише та викидає, то ці слабкі сторони просто не мають значення :-)
Гьом,

10
Голосування вниз, оскільки: 1) Це робить системний виклик чогось тривіального, 2) Він вводить рядки безпосередньо у виклик оболонки, 3) Викликає eval () та 4) У нього є виняток catch-all. В основному це приклад того, як не робити щось.
benjaoming

У цьому випадку, хоча eval - це зло, і його не слід використовувати. зовнішній дзвінок, здається, є найпростішим і найпрактичнішим способом отримати часову мітку unix із датчика, що знає часовий пояс, де часовий пояс не є числовим зміщенням.
Леліель

1
Ну, знову ж таки, цей девіз "eval is evil" насправді залежить від вашого контексту (що не було зазначено в ОП). Коли я пишу сценарії для власного використання, я використовую eval ліберально, і це чудово. Python - чудова мова для клейових скриптів! Звичайно, ви можете випустити заплутані загальноприйняті надмірно розроблені рішення, як у деяких відповідях вище, а потім заявити, що це єдиний правильний спосіб зробити це, але Java. Але для багатьох випадків використання швидке і брудне рішення є таким же хорошим.
Gyom
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.