Ubah DateTimeIndex yang peka terhadap zona waktu panda menjadi stempel waktu yang naif, tetapi di zona waktu tertentu

99

Anda dapat menggunakan fungsi ini tz_localizeuntuk membuat Timestamp atau DateTimeIndex menjadi sadar, tetapi bagaimana Anda bisa melakukan yang sebaliknya: bagaimana Anda bisa mengubah Timestamp yang sadar zona waktu menjadi yang naif, sambil mempertahankan zona waktunya?

Sebuah contoh:

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

Saya dapat menghapus zona waktu dengan mengaturnya ke Tidak Ada, tetapi kemudian hasilnya diubah ke UTC (pukul 12 menjadi 10):

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

Apakah ada cara lain untuk mengonversi DateTimeIndex menjadi zona waktu naif, tetapi sambil mempertahankan zona waktu yang telah ditetapkan?


Beberapa konteks tentang alasan saya menanyakan ini: Saya ingin bekerja dengan rangkaian waktu naif zona waktu (untuk menghindari kerumitan ekstra dengan zona waktu, dan saya tidak membutuhkannya untuk kasus yang sedang saya tangani).
Tetapi untuk beberapa alasan, saya harus berurusan dengan rangkaian waktu yang sadar zona waktu di zona waktu lokal saya (Eropa / Brussel). Karena semua data saya yang lain adalah zona waktu naif (tetapi terwakili dalam zona waktu lokal saya), saya ingin mengubah rangkaian waktu ini menjadi naif untuk digunakan lebih lanjut, tetapi juga harus diwakili dalam zona waktu lokal saya (jadi hapus saja info zona waktu tersebut, tanpa mengubah waktu yang terlihat pengguna ke UTC).

Saya tahu waktu sebenarnya disimpan secara internal sebagai UTC dan hanya dikonversi ke zona waktu lain jika Anda mewakilinya, jadi harus ada semacam konversi ketika saya ingin "mendelokalisasi" -nya. Misalnya, dengan modul datetime python Anda dapat "menghapus" zona waktu seperti ini:

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> 

Jadi, berdasarkan ini, saya dapat melakukan hal berikut, tetapi saya rasa ini tidak akan terlalu efisien saat bekerja dengan rangkaian waktu yang lebih besar:

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
joris
sumber
Zona Waktu = Tidak ada berarti UTC ... Saya tidak yakin saya mengerti apa yang Anda tanyakan di sini.
Andy Hayden
Saya menambahkan beberapa penjelasan. Saya ingin menjaga waktu yang Anda 'lihat' sebagai pengguna. Saya harap ini sedikit menjelaskannya.
joris
Ah ha, benar, saya tidak menyadari Anda bisa melakukan itu dengan replace.
Andy Hayden
@AndyHayden Jadi sebenarnya apa yang saya inginkan adalah kebalikan yang tepat dari tz_localizeapa yang replace(tzinfo=None)dilakukan untuk datetimes, tetapi memang bukan cara yang sangat jelas.
joris

Jawaban:

123

Untuk menjawab pertanyaan saya sendiri, sementara itu fungsi ini telah ditambahkan ke panda. Mulai dari panda 0.15.0 , Anda dapat menggunakan tz_localize(None)untuk menghapus zona waktu yang menghasilkan waktu lokal.
Lihat entri whatsnew: http://pandas.pydata.org/pandas-docs/stable/whatsnew.html#timezone-handling-improvements

Jadi dengan contoh saya dari atas:

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')

menggunakan tz_localize(None)menghapus informasi zona waktu yang mengakibatkan waktu lokal yang naif :

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')

Selanjutnya, Anda juga dapat menggunakan tz_convert(None)untuk menghapus informasi zona waktu tetapi mengonversi ke UTC, sehingga menghasilkan waktu UTC yang naif :

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')

Ini jauh lebih baik daripada datetime.replacesolusinya:

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
joris
sumber
1
Dalam kasus Anda bekerja dengan sesuatu yang sudah UTC dan kebutuhan untuk mengubahnya menjadi waktu setempat dan kemudian menjatuhkan zona waktu: from tzlocal import get_localzone, tz_here = get_localzone(),<datetime object>.tz_convert(tz_here).tz_localize(None)
Nathan Lloyd
3
Jika Anda tidak memiliki indeks yang berguna, Anda mungkin perlu t.dt.tz_localize(None)atau t.dt.tz_convert(None). Perhatikan .dt.
Acumenus
2
Solusi ini hanya berfungsi jika ada satu tz unik di Seri. Jika Anda memiliki beberapa tz berbeda dalam Seri yang sama, lihat (dan
beri suara positif
14

Saya pikir Anda tidak dapat mencapai apa yang Anda inginkan dengan cara yang lebih efisien daripada yang Anda usulkan.

Masalah yang mendasarinya adalah bahwa stempel waktu (seperti yang Anda ketahui) terdiri dari dua bagian. Data yang mewakili waktu UTC, dan zona waktu, tz_info. Informasi zona waktu digunakan hanya untuk tujuan tampilan saat mencetak zona waktu ke layar. Pada waktu tampilan, data diimbangi dengan tepat dan +01: 00 (atau serupa) ditambahkan ke string. Menghapus nilai tz_info (menggunakan tz_convert (tz = None)) sebenarnya tidak mengubah data yang mewakili bagian naif dari stempel waktu.

Jadi, satu-satunya cara untuk melakukan apa yang Anda inginkan adalah mengubah data yang mendasarinya (panda tidak mengizinkan ini ... DatetimeIndex tidak dapat diubah - lihat bantuan di DatetimeIndex), atau untuk membuat kumpulan baru objek stempel waktu dan menggabungkannya di DatetimeIndex baru. Solusi Anda melakukan yang terakhir:

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

Untuk referensi, berikut adalah replacemetode Timestamp(lihat tslib.pyx):

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

Anda dapat merujuk ke dokumen di datetime.datetimeuntuk melihat itu datetime.datetime.replacejuga membuat objek baru.

Jika Anda bisa, taruhan terbaik Anda untuk efisiensi adalah memodifikasi sumber data sehingga (secara tidak benar) melaporkan cap waktu tanpa zona waktunya. Anda menyebutkan:

Saya ingin bekerja dengan rangkaian waktu naif zona waktu (untuk menghindari kerumitan ekstra dengan zona waktu, dan saya tidak membutuhkannya untuk kasus yang sedang saya tangani)

Saya ingin tahu kerumitan ekstra apa yang Anda maksud. Saya sarankan sebagai aturan umum untuk semua pengembangan perangkat lunak, pertahankan 'nilai naif' stempel waktu Anda di UTC. Ada sedikit yang lebih buruk daripada melihat dua nilai int64 yang berbeda bertanya-tanya di zona waktu mana mereka berada. Jika Anda selalu, selalu, selalu menggunakan UTC untuk penyimpanan internal, maka Anda akan terhindar dari sakit kepala yang tak terhitung jumlahnya. Mantra saya adalah Zona Waktu hanya untuk I / O manusia .

DA
sumber
3
Terima kasih atas jawabannya, dan balasan terlambat: kasus saya bukanlah sebuah aplikasi, hanya analisis ilmiah untuk pekerjaan saya sendiri (jadi misalnya tidak ada berbagi dengan kolaborator di seluruh dunia). Dan dalam hal ini, akan lebih mudah untuk bekerja dengan stempel waktu yang naif, tetapi dalam waktu lokal Anda. Jadi saya tidak perlu khawatir tentang zona waktu dan hanya dapat mengartikan stempel waktu sebagai waktu lokal ('kerumitan' tambahannya dapat berupa misalnya semuanya harus berada dalam zona waktu, jika tidak, Anda mendapatkan hal-hal seperti "tidak dapat membandingkan offset- datetimes yang naif dan sadar offset "). Tetapi saya sepenuhnya setuju dengan Anda ketika berhadapan dengan aplikasi yang lebih kompleks.
joris
13

Karena saya selalu kesulitan untuk mengingat, ringkasan singkat tentang apa yang masing-masing lakukan:

>>> 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')
Juan A. Navarro
sumber
7

Menetapkan tzatribut indeks secara eksplisit tampaknya berfungsi:

ts_utc = ts.tz_convert("UTC")
ts_utc.index.tz = None
filmor
sumber
3
Komentar terlambat, tetapi saya ingin hasilnya menjadi waktu yang terwakili dalam zona waktu lokal, bukan dalam UTC. Dan seperti yang saya tunjukkan di pertanyaan, menyetel tzke Tidak Ada juga mengubahnya menjadi UTC.
joris
Lebih lanjut, rangkaian waktu sudah sadar zona waktu, jadi memanggilnya tz_convertakan menimbulkan kesalahan.
joris
4

Solusi yang diterima tidak berfungsi saat ada beberapa zona waktu berbeda dalam satu Rangkaian. Itu melemparValueError: Tz-aware datetime.datetime cannot be converted to datetime64 unless utc=True

Solusinya adalah dengan menggunakan applymetode tersebut.

Silakan lihat contoh di bawah ini:

# 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]
tozCSS
sumber
3

Berdasarkan saran DA bahwa " satu-satunya cara untuk melakukan apa yang Anda inginkan adalah mengubah data yang mendasarinya " dan menggunakan numpy untuk mengubah data pokok ...

Ini bekerja untuk saya, dan cukup cepat:

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)
Jack Kelly
sumber
Terima kasih atas jawaban anda! Namun, menurut saya ini hanya akan berfungsi jika tidak ada transisi musim panas / musim dingin dalam periode kumpulan data.
joris
@joris Ah, tangkapan bagus! Saya tidak mempertimbangkan itu! Saya akan mengubah solusi saya untuk menangani situasi ini secepatnya.
Jack Kelly
Saya yakin ini masih salah karena Anda hanya menghitung offset untuk pertama kalinya dan bukan karena kemajuannya sepanjang waktu. Ini akan menyebabkan Anda melewatkan waktu musim panas dan tidak menyesuaikannya pada tanggal yang ditentukan dan seterusnya.
Pierre-Luc Bertrand
2

Kontribusi terlambat tetapi baru saja menemukan sesuatu yang serupa di Python datetime dan panda memberikan stempel waktu yang berbeda untuk tanggal yang sama .

Jika Anda memiliki tanggal waktu sadar zona dalam pandas, secara teknis, tz_localize(None)ubah stempel waktu POSIX (yang digunakan secara internal) seolah-olah waktu lokal dari stempel waktu adalah UTC. Lokal dalam konteks ini berarti lokal dalam zona waktu yang ditentukan . Ex:

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]')

Perhatikan bahwa ini akan membuat Anda mengalami hal-hal aneh selama transisi DST , mis

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')

Sebaliknya, tz_convert(None)tidak mengubah stempel waktu internal, itu hanya menghapus tzinfo.

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

Intinya saya adalah: tetap dengan waktu yang sadar zona waktu jika Anda dapat atau hanya menggunakan t.tz_convert(None)yang tidak mengubah cap waktu POSIX yang mendasarinya. Ingatlah bahwa Anda secara praktis bekerja dengan UTC.

(Python 3.8.2 x64 di Windows 10, pandasv1.0.5.)

MrFuppes
sumber
0

Yang paling penting adalah menambahkan tzinfosaat Anda mendefinisikan objek 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())
Yuchao Jiang
sumber