Làm thế nào để phân tích cú pháp ngày với chuỗi múi giờ -0400 trong Python?


81

Tôi có một chuỗi ngày có dạng '2009/05/13 19:19:30 -0400'. Có vẻ như các phiên bản trước của Python có thể đã hỗ trợ thẻ định dạng% z trong strptime cho đặc điểm múi giờ cuối, nhưng 2.6.x dường như đã loại bỏ điều đó.

Cách phù hợp để phân tích cú pháp chuỗi này thành một đối tượng datetime là gì?

Câu trả lời:


117

Bạn có thể sử dụng hàm phân tích cú pháp từ 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))

Bằng cách này, bạn có được một đối tượng datetime mà bạn có thể sử dụng.

Như đã trả lời , dateutil2.0 được viết cho Python 3.0 và không hoạt động với Python 2.x. Đối với Python 2.x dateutil1.5 cần được sử dụng.


13
Điều này hoạt động tốt đối với tôi ( dateutil2.1) với Python 2.7.2; Python 3 không bắt buộc. Lưu ý rằng nếu bạn đang cài đặt từ pip, thì tên gói là python-dateutil.
BigglesZX

47

%z được hỗ trợ trong 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)))

Trên các phiên bản trước:

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

đâu FixedOffsetlà một lớp dựa trên ví dụ mã từ tài liệu :

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
Điều này gây ra ValueError: 'z' is a bad directive in format '%Y-%m-%d %M:%H:%S.%f %z'trong trường hợp của tôi (Python 2.7).
Jonathan H

@Sheljohn nó không được cho là hoạt động trên Python 2.7 Hãy nhìn vào phần trên cùng của câu trả lời.
jfs

kỳ lạ, bằng cách này, rằng đây không phải là ở tất cả được đề cập trên Python 2,7 docs: docs.python.org/2.7/library/...
62mkv

22

Đây là bản sửa lỗi "%z"cho Python 2.7 trở về trước

Thay vì sử dụng:

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

Sử dụng timedeltađể tính múi giờ, như sau:

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

Lưu ý rằng ngày tháng sẽ được chuyển đổi thành GMT, cho phép thực hiện số học ngày tháng mà không cần lo lắng về múi giờ.


Tôi thích điều này, mặc dù bạn cần thay đổi 'giây =' thành 'phút ='.
Dave

1
Cũng như một lưu ý, nếu bạn muốn lấy múi giờ trong một chuỗi và chuyển đổi ngày giờ thành UTC, bạn sẽ sử dụng logic ngược lại được liệt kê ở đây. Nếu múi giờ có dấu +, bạn sẽ trừ đi múi giờ và ngược lại.
Sector95

Việc chuyển đổi sang UTC đã sai, nếu có một +nhân vật timedelta nên được khấu trừ , và ngược lại. Tôi đã chỉnh sửa và sửa mã.
tomtastico

7

Vấn đề với việc sử dụng dateutil là bạn không thể có cùng một chuỗi định dạng cho cả tuần tự hóa và giải mã hóa, vì dateutil có các tùy chọn định dạng hạn chế (chỉ dayfirstyearfirst).

Trong ứng dụng của mình, tôi lưu trữ chuỗi định dạng trong tệp .INI và mỗi triển khai có thể có định dạng riêng. Vì vậy, tôi thực sự không thích cách tiếp cận dateutil.

Đây là một phương pháp thay thế sử dụng pytz để thay thế:

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

Một tấm lót cho những con Trăn già ngoài kia. Bạn có thể nhân một đồng thời gian với 1 / -1 tùy thuộc vào dấu +/-, như trong:

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

Nếu bạn đang sử dụng Linux, thì bạn có thể sử dụng datelệnh bên ngoài để 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)

Điều này tất nhiên là ít di động hơn dateutil, nhưng linh hoạt hơn một chút, vì datecũng sẽ chấp nhận các đầu vào như "hôm qua" hoặc "năm ngoái" :-)


3
Tôi không nghĩ rằng nó là tốt để gọi một chương trình bên ngoài cho điều này. Và điểm yếu tiếp theo: eval (): Nếu bây giờ máy chủ web thực thi mã này, bạn có thể thực hiện mã tùy ý trên máy chủ!
guettli

5
Tất cả phụ thuộc vào bối cảnh: nếu những gì chúng tôi sau khi chỉ là một ghi-and-ném-xa kịch bản, sau đó những điểm yếu chỉ là không thích hợp :-)
Gyom

10
Bỏ phiếu từ chối điều này vì: 1) Nó thực hiện một lệnh gọi hệ thống cho một cái gì đó tầm thường, 2) Nó đưa các chuỗi trực tiếp vào một lệnh gọi shell, 3) Nó gọi eval () và 4) Nó có một ngoại lệ tóm tắt. Về cơ bản đây là một ví dụ về cách không làm mọi thứ.
benjaoming

Trong trường hợp này, mặc dù eval là ác và không nên được sử dụng. một cuộc gọi bên ngoài dường như là cách dễ nhất và thiết thực nhất để lấy dấu thời gian unix từ chuỗi dữ liệu nhận biết múi giờ, trong đó múi giờ không phải là phần bù số.
Leliel

1
Vâng, một lần nữa, phương châm "eval is evil" này thực sự phụ thuộc vào bối cảnh của bạn (điều này không được OP nêu rõ). Khi tôi viết script để sử dụng cho riêng mình, tôi sử dụng eval một cách tự do, và điều đó thật tuyệt vời. Python là một ngôn ngữ tuyệt vời cho các tập lệnh keo! Tất nhiên, bạn có thể đưa ra các giải pháp tổng quát phức tạp được thiết kế quá mức như trong một số câu trả lời ở trên, và sau đó khẳng định nó là-duy-nhất-đúng-cách-để-làm-nó, ala Java. Nhưng đối với nhiều trường hợp sử dụng, giải pháp nhanh chóng và bẩn cũng tốt.
Gyom
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.