Chuyển đổi DateTimeIndex nhận biết múi giờ của gấu trúc thành dấu thời gian ngây thơ, nhưng theo múi giờ nhất định


99

Bạn có thể sử dụng chức năng tz_localizeđể nhận biết múi giờ Timestamp hoặc DateTimeIndex, nhưng làm cách nào để làm ngược lại: làm cách nào bạn có thể chuyển đổi Dấu thời gian nhận biết múi giờ thành dấu thời gian đơn giản mà vẫn giữ nguyên múi giờ của nó?

Một ví dụ:

In [82]: t = pd.date_range(start="2013-05-18 12:00:00", periods=10, freq='s', tz="Europe/Brussels")

In [83]: t
Out[83]: 
<class 'pandas.tseries.index.DatetimeIndex'>
[2013-05-18 12:00:00, ..., 2013-05-18 12:00:09]
Length: 10, Freq: S, Timezone: Europe/Brussels

Tôi có thể xóa múi giờ bằng cách đặt nó thành Không, nhưng sau đó kết quả được chuyển đổi thành UTC (12 giờ trở thành 10 giờ):

In [86]: t.tz = None

In [87]: t
Out[87]: 
<class 'pandas.tseries.index.DatetimeIndex'>
[2013-05-18 10:00:00, ..., 2013-05-18 10:00:09]
Length: 10, Freq: S, Timezone: None

Có cách nào khác để tôi có thể chuyển đổi DateTimeIndex sang múi giờ đơn thuần, nhưng vẫn giữ nguyên múi giờ mà nó đã được đặt không?


Một số bối cảnh về lý do tôi hỏi điều này: Tôi muốn làm việc với múi giờ ngây thơ về múi giờ (để tránh thêm rắc rối với múi giờ và tôi không cần chúng cho trường hợp tôi đang làm việc).
Nhưng vì lý do nào đó, tôi phải xử lý các múi giờ nhận biết múi giờ trong múi giờ địa phương của mình (Châu Âu / Brussels). Vì tất cả các dữ liệu khác của tôi đều không có múi giờ (nhưng được thể hiện ở múi giờ địa phương của tôi), nên tôi muốn chuyển đổi khoảng thời gian này thành ngây thơ để tiếp tục làm việc với nó, nhưng nó cũng phải được thể hiện theo múi giờ địa phương của tôi (vì vậy chỉ cần xóa thông tin múi giờ, mà không cần chuyển đổi thời gian hiển thị của người dùng thành UTC).

Tôi biết thời gian thực sự được lưu trữ nội bộ dưới dạng UTC và chỉ được chuyển đổi sang múi giờ khác khi bạn đại diện cho nó, vì vậy phải có một số loại chuyển đổi khi tôi muốn "phân định vị trí" nó. Ví dụ: với mô-đun ngày giờ trong python, bạn có thể "xóa" múi giờ như sau:

In [119]: d = pd.Timestamp("2013-05-18 12:00:00", tz="Europe/Brussels")

In [120]: d
Out[120]: <Timestamp: 2013-05-18 12:00:00+0200 CEST, tz=Europe/Brussels>

In [121]: d.replace(tzinfo=None)
Out[121]: <Timestamp: 2013-05-18 12:00:00> 

Vì vậy, dựa trên điều này, tôi có thể làm như sau, nhưng tôi cho rằng điều này sẽ không hiệu quả lắm khi làm việc với thời gian lớn hơn:

In [124]: t
Out[124]: 
<class 'pandas.tseries.index.DatetimeIndex'>
[2013-05-18 12:00:00, ..., 2013-05-18 12:00:09]
Length: 10, Freq: S, Timezone: Europe/Brussels

In [125]: pd.DatetimeIndex([i.replace(tzinfo=None) for i in t])
Out[125]: 
<class 'pandas.tseries.index.DatetimeIndex'>
[2013-05-18 12:00:00, ..., 2013-05-18 12:00:09]
Length: 10, Freq: None, Timezone: None

Múi giờ = Không có nghĩa là UTC ... Tôi không chắc mình hiểu bạn đang hỏi gì ở đây.
Andy Hayden

Tôi đã thêm một số giải thích. Tôi muốn giữ thời gian bạn 'xem' với tư cách là người dùng. Tôi hy vọng điều này làm rõ nó một chút.
joris

Ah ha, nó đúng, tôi không nhận ra bạn có thể làm điều đó với replace.
Andy Hayden

@AndyHayden Vì vậy, thực sự những gì tôi muốn là nghịch đảo chính xác của tz_localizenhững gìreplace(tzinfo=None) làm cho lịch hẹn ngày, nhưng nó thực sự không phải là một cách quá rõ ràng.
joris

Câu trả lời:


123

Để trả lời câu hỏi của riêng tôi, chức năng này đã được thêm vào gấu trúc trong thời gian chờ đợi. Bắt đầu từ pandas 0.15.0 , bạn có thể sử dụng tz_localize(None)để xóa múi giờ dẫn đến giờ địa phương.
Xem mục nhập mới nhất: http://pandas.pydata.org/pandas-docs/stable/whatsnew.html#timezone-handling-improvements

Vì vậy, với ví dụ của tôi ở trên:

In [4]: t = pd.date_range(start="2013-05-18 12:00:00", periods=2, freq='H',
                          tz= "Europe/Brussels")

In [5]: t
Out[5]: DatetimeIndex(['2013-05-18 12:00:00+02:00', '2013-05-18 13:00:00+02:00'],
                       dtype='datetime64[ns, Europe/Brussels]', freq='H')

sử dụng tz_localize(None)loại bỏ thông tin múi giờ dẫn đến giờ địa phương không chính xác :

In [6]: t.tz_localize(None)
Out[6]: DatetimeIndex(['2013-05-18 12:00:00', '2013-05-18 13:00:00'], 
                      dtype='datetime64[ns]', freq='H')

Hơn nữa, bạn cũng có thể sử dụng tz_convert(None) để xóa thông tin múi giờ nhưng chuyển đổi sang UTC, do đó mang lại thời gian UTC ngây thơ :

In [7]: t.tz_convert(None)
Out[7]: DatetimeIndex(['2013-05-18 10:00:00', '2013-05-18 11:00:00'], 
                      dtype='datetime64[ns]', freq='H')

Điều này hiệu quả hơn nhiều so với datetime.replacegiải pháp:

In [31]: t = pd.date_range(start="2013-05-18 12:00:00", periods=10000, freq='H',
                           tz="Europe/Brussels")

In [32]: %timeit t.tz_localize(None)
1000 loops, best of 3: 233 µs per loop

In [33]: %timeit pd.DatetimeIndex([i.replace(tzinfo=None) for i in t])
10 loops, best of 3: 99.7 ms per loop

1
Trong trường hợp bạn đang làm việc với một cái gì đó đã UTC và cần phải chuyển nó sang giờ địa phương và sau đó thả các múi giờ: from tzlocal import get_localzone, tz_here = get_localzone(),<datetime object>.tz_convert(tz_here).tz_localize(None)
Nathan Lloyd

3
Nếu bạn không có một chỉ mục hữu ích, bạn có thể cần t.dt.tz_localize(None)hoặc t.dt.tz_convert(None). Lưu ý .dt.
Acumenus

2
Giải pháp này chỉ hoạt động khi có một tz duy nhất trong Series. Nếu bạn có nhiều tz khác nhau trong cùng một Dòng thì hãy xem (và upvote) giải pháp tại đây :-): stackoverflow.com/a/59204751/1054154
tozCSS

14

Tôi nghĩ rằng bạn không thể đạt được những gì bạn muốn một cách hiệu quả hơn những gì bạn đã đề xuất.

Vấn đề cơ bản là dấu thời gian (như bạn có vẻ đã biết) được tạo thành từ hai phần. Dữ liệu đại diện cho giờ UTC và múi giờ, tz_info. Thông tin múi giờ chỉ được sử dụng cho mục đích hiển thị khi in múi giờ ra màn hình. Tại thời điểm hiển thị, dữ liệu được bù đắp thích hợp và +01: 00 (hoặc tương tự) được thêm vào chuỗi. Loại bỏ giá trị tz_info (sử dụng tz_convert (tz = None)) không thực sự thay đổi dữ liệu đại diện cho phần ngây thơ của dấu thời gian.

Vì vậy, cách duy nhất để làm những gì bạn muốn là sửa đổi dữ liệu cơ bản (gấu trúc không cho phép điều này ... DatetimeIndex là bất biến - xem trợ giúp trên DatetimeIndex) hoặc để tạo một tập hợp các đối tượng dấu thời gian mới và bọc chúng trong một DatetimeIndex mới. Giải pháp của bạn thực hiện sau:

pd.DatetimeIndex([i.replace(tzinfo=None) for i in t])

Để tham khảo, đây là replacephương pháp của Timestamp(xem tslib.pyx):

def replace(self, **kwds):
    return Timestamp(datetime.replace(self, **kwds),
                     offset=self.offset)

Bạn có thể tham khảo các tài liệu trên datetime.datetimeđể thấy rằngdatetime.datetime.replace cũng tạo một đối tượng mới.

Nếu có thể, cách tốt nhất để đạt được hiệu quả là sửa đổi nguồn dữ liệu để nó (không chính xác) báo cáo các dấu thời gian không có múi giờ của chúng. Bạn đã đề cập:

Tôi muốn làm việc với múi giờ ngây thơ về múi giờ (để tránh thêm rắc rối với múi giờ và tôi không cần chúng cho trường hợp tôi đang làm việc)

Tôi muốn biết bạn đang đề cập đến rắc rối nào thêm. Tôi khuyên bạn nên theo quy tắc chung cho tất cả việc phát triển phần mềm, hãy giữ cho dấu thời gian của bạn là 'giá trị ngây thơ' theo giờ UTC. Có một điều tồi tệ hơn là nhìn vào hai giá trị int64 khác nhau mà tự hỏi chúng thuộc về múi giờ nào. Nếu bạn luôn luôn sử dụng UTC cho bộ nhớ trong, thì bạn sẽ tránh được vô số vấn đề đau đầu. Câu thần chú của tôi là Múi giờ chỉ dành cho I / O của con người .


3
Cảm ơn vì câu trả lời, và trả lời muộn: trường hợp của tôi không phải là một ứng dụng, chỉ là một phân tích khoa học cho công việc của riêng tôi (vì vậy, chẳng hạn như không chia sẻ với cộng tác viên trên toàn thế giới). Và trong trường hợp đó, có thể dễ dàng hơn nếu chỉ làm việc với các dấu thời gian ngây thơ, nhưng theo giờ địa phương của bạn. Vì vậy, tôi không phải lo lắng về múi giờ và chỉ có thể hiểu dấu thời gian là giờ địa phương ('rắc rối' thêm có thể ví dụ: mọi thứ sau đó phải theo múi giờ, nếu không bạn sẽ gặp những thứ như "không thể so sánh bù- ngày giờ ngây thơ và nhận biết bù trừ "). Nhưng tôi hoàn toàn đồng ý với bạn khi xử lý các ứng dụng phức tạp hơn.
joris 21/12/13

12

Bởi vì tôi luôn cố gắng ghi nhớ, một bản tóm tắt nhanh về những gì mỗi thứ làm được:

>>> pd.Timestamp.now()  # naive local time
Timestamp('2019-10-07 10:30:19.428748')

>>> pd.Timestamp.utcnow()  # tz aware UTC
Timestamp('2019-10-07 08:30:19.428748+0000', tz='UTC')

>>> pd.Timestamp.now(tz='Europe/Brussels')  # tz aware local time
Timestamp('2019-10-07 10:30:19.428748+0200', tz='Europe/Brussels')

>>> pd.Timestamp.now(tz='Europe/Brussels').tz_localize(None)  # naive local time
Timestamp('2019-10-07 10:30:19.428748')

>>> pd.Timestamp.now(tz='Europe/Brussels').tz_convert(None)  # naive UTC
Timestamp('2019-10-07 08:30:19.428748')

>>> pd.Timestamp.utcnow().tz_localize(None)  # naive UTC
Timestamp('2019-10-07 08:30:19.428748')

>>> pd.Timestamp.utcnow().tz_convert(None)  # naive UTC
Timestamp('2019-10-07 08:30:19.428748')

7

Đặt tzthuộc tính của chỉ mục một cách rõ ràng dường như hoạt động:

ts_utc = ts.tz_convert("UTC")
ts_utc.index.tz = None

3
Nhận xét muộn, nhưng tôi muốn kết quả là thời gian được biểu thị theo múi giờ địa phương, không phải theo UTC. Và như tôi đã trình bày trong câu hỏi, việc đặt tzthành Không cũng chuyển nó thành UTC.
joris

Hơn nữa, thời gian đã được nhận biết múi giờ, vì vậy việc gọi tz_convertnó sẽ gây ra lỗi.
joris

4

Giải pháp được chấp nhận không hoạt động khi có nhiều múi giờ khác nhau trong một Chuỗi. Nó némValueError: Tz-aware datetime.datetime cannot be converted to datetime64 unless utc=True

Giải pháp là sử dụng applyphương pháp.

Vui lòng xem các ví dụ dưới đây:

# Let's have a series `a` with different multiple timezones. 
> a
0    2019-10-04 16:30:00+02:00
1    2019-10-07 16:00:00-04:00
2    2019-09-24 08:30:00-07:00
Name: localized, dtype: object

> a.iloc[0]
Timestamp('2019-10-04 16:30:00+0200', tz='Europe/Amsterdam')

# trying the accepted solution
> a.dt.tz_localize(None)
ValueError: Tz-aware datetime.datetime cannot be converted to datetime64 unless utc=True

# Make it tz-naive. This is the solution:
> a.apply(lambda x:x.tz_localize(None))
0   2019-10-04 16:30:00
1   2019-10-07 16:00:00
2   2019-09-24 08:30:00
Name: localized, dtype: datetime64[ns]

# a.tz_convert() also does not work with multiple timezones, but this works:
> a.apply(lambda x:x.tz_convert('America/Los_Angeles'))
0   2019-10-04 07:30:00-07:00
1   2019-10-07 13:00:00-07:00
2   2019-09-24 08:30:00-07:00
Name: localized, dtype: datetime64[ns, America/Los_Angeles]

3

Dựa trên đề xuất của DA rằng " cách duy nhất để làm những gì bạn muốn là sửa đổi dữ liệu cơ bản " và sử dụng numpy để sửa đổi dữ liệu cơ bản ...

Điều này phù hợp với tôi và khá nhanh:

def tz_to_naive(datetime_index):
    """Converts a tz-aware DatetimeIndex into a tz-naive DatetimeIndex,
    effectively baking the timezone into the internal representation.

    Parameters
    ----------
    datetime_index : pandas.DatetimeIndex, tz-aware

    Returns
    -------
    pandas.DatetimeIndex, tz-naive
    """
    # Calculate timezone offset relative to UTC
    timestamp = datetime_index[0]
    tz_offset = (timestamp.replace(tzinfo=None) - 
                 timestamp.tz_convert('UTC').replace(tzinfo=None))
    tz_offset_td64 = np.timedelta64(tz_offset)

    # Now convert to naive DatetimeIndex
    return pd.DatetimeIndex(datetime_index.values + tz_offset_td64)

Cảm ơn câu trả lời của bạn! Tuy nhiên, tôi nghĩ rằng điều này sẽ chỉ hoạt động nếu không có sự chuyển đổi mùa hè / mùa đông trong khoảng thời gian của tập dữ liệu.
joris 21/12/13

@joris Ah, bắt tốt! Tôi đã không xem xét điều đó! Tôi sẽ sửa đổi giải pháp của mình để xử lý tình huống này càng sớm càng tốt.
Jack Kelly

Tôi tin rằng điều này vẫn sai vì bạn chỉ đang tính toán phần bù của lần đầu tiên và không phải khi nó tiến triển trong suốt thời gian. Điều này sẽ khiến bạn bỏ lỡ thời gian tiết kiệm ánh sáng ban ngày và không điều chỉnh phù hợp vào ngày nhất định đó trở đi.
Pierre-Luc Bertrand

2

Đóng góp muộn nhưng chỉ gặp một cái gì đó tương tự trong datetime của Python và gấu trúc đưa ra các dấu thời gian khác nhau cho cùng một ngày .

Nếu bạn có datetime timezone-aware trong pandas, về mặt kỹ thuật, tz_localize(None)thay đổi dấu thời gian POSIX (được sử dụng trong nội bộ) như thể thời gian địa phương từ timestamp là UTC. Cục bộ trong ngữ cảnh này có nghĩa là cục bộ trong múi giờ được chỉ định . Ví dụ:

import pandas as pd

t = pd.date_range(start="2013-05-18 12:00:00", periods=2, freq='H', tz="US/Central")
# DatetimeIndex(['2013-05-18 12:00:00-05:00', '2013-05-18 13:00:00-05:00'], dtype='datetime64[ns, US/Central]', freq='H')

t_loc = t.tz_localize(None)
# DatetimeIndex(['2013-05-18 12:00:00', '2013-05-18 13:00:00'], dtype='datetime64[ns]', freq='H')

# offset in seconds according to timezone:
(t_loc.values-t.values)//1e9
# array([-18000, -18000], dtype='timedelta64[ns]')

Lưu ý rằng điều này sẽ để lại cho bạn những điều kỳ lạ trong quá trình chuyển đổi DST , ví dụ:

t = pd.date_range(start="2020-03-08 01:00:00", periods=2, freq='H', tz="US/Central")
(t.values[1]-t.values[0])//1e9
# numpy.timedelta64(3600,'ns')

t_loc = t.tz_localize(None)
(t_loc.values[1]-t_loc.values[0])//1e9
# numpy.timedelta64(7200,'ns')

Ngược lại, tz_convert(None) không sửa đổi dấu thời gian bên trong, nó chỉ loại bỏ tzinfo.

t_utc = t.tz_convert(None)
(t_utc.values-t.values)//1e9
# array([0, 0], dtype='timedelta64[ns]')

Điểm mấu chốt của tôi sẽ là: gắn bó với ngày giờ nhận biết múi giờ nếu bạn có thể hoặc chỉ sử dụng t.tz_convert(None)mà không sửa đổi dấu thời gian POSIX bên dưới. Chỉ cần lưu ý rằng bạn thực tế đang làm việc với UTC.

(Python 3.8.2 x64 trên Windows 10, pandasv1.0.5.)


0

Điều quan trọng nhất là thêm tzinfokhi bạn xác định một đối tượng datetime.

from datetime import datetime, timezone
from tzinfo_examples import HOUR, Eastern
u0 = datetime(2016, 3, 13, 5, tzinfo=timezone.utc)
for i in range(4):
     u = u0 + i*HOUR
     t = u.astimezone(Eastern)
     print(u.time(), 'UTC =', t.time(), t.tzname())
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.