Você pode usar a função tz_localize
para tornar um timestamp ou DateTimeIndex ciente do fuso horário, mas como você pode fazer o oposto: como você pode converter um timestamp que reconhece o fuso horário em um ingênuo, preservando seu fuso horário?
Um exemplo:
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
Eu poderia remover o fuso horário definindo-o como Nenhum, mas o resultado é convertido para UTC (12 horas se tornaram 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
Existe outra maneira de converter um DateTimeIndex em fuso horário ingênuo, mas preservando o fuso horário em que foi definido?
Algum contexto sobre o motivo pelo qual estou perguntando isso: quero trabalhar com séries de tempo ingênuas de fuso horário (para evitar o incômodo extra com fusos horários, e não preciso deles para o caso em que estou trabalhando).
Mas, por alguma razão, tenho que lidar com uma série de tempo ciente de fuso horário no meu fuso horário local (Europa / Bruxelas). Como todos os meus outros dados são ingênuos de fuso horário (mas representados em meu fuso horário local), quero converter esta série de tempo em ingênuo para trabalhar mais com ele, mas também deve ser representado em meu fuso horário local (então, basta remover as informações de fuso horário, sem converter a hora visível ao usuário em UTC).
Eu sei que a hora é armazenada internamente como UTC e só é convertida para outro fuso horário quando você a representa, então deve haver algum tipo de conversão quando eu quiser "deslocalizá-la". Por exemplo, com o módulo Python datetime, você pode "remover" o fuso horário assim:
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>
Portanto, com base nisso, eu poderia fazer o seguinte, mas suponho que não seja muito eficiente ao trabalhar com uma série do tempo maior:
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
replace
.tz_localize
que oreplace(tzinfo=None)
faz para os datetimes, mas de fato não é uma maneira muito óbvia.Respostas:
Para responder à minha própria pergunta, esta funcionalidade foi adicionada aos pandas entretanto. A partir do pandas 0.15.0 , você pode usar
tz_localize(None)
para remover o fuso horário resultante da hora local.Veja a nova entrada: http://pandas.pydata.org/pandas-docs/stable/whatsnew.html#timezone-handling-improvements
Então, com meu exemplo acima:
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')
o uso
tz_localize(None)
remove as informações de fuso horário, resultando em horário local ingênuo :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')
Além disso, você também pode usar
tz_convert(None)
para remover as informações de fuso horário, mas convertendo para UTC, gerando um tempo UTC ingênuo :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')
Isso é muito mais eficiente do que a
datetime.replace
solução: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
fonte
from tzlocal import get_localzone
,tz_here = get_localzone()
,<datetime object>.tz_convert(tz_here).tz_localize(None)
t.dt.tz_localize(None)
out.dt.tz_convert(None)
. Observe o.dt
.Acho que você não pode conseguir o que deseja de uma maneira mais eficiente do que propôs.
O problema subjacente é que os carimbos de data / hora (como você parece saber) são compostos de duas partes. Os dados que representam a hora UTC e o fuso horário, tz_info. As informações de fuso horário são usadas apenas para fins de exibição ao imprimir o fuso horário na tela. No tempo de exibição, os dados são deslocados apropriadamente e +01: 00 (ou similar) é adicionado à string. Retirar o valor tz_info (usando tz_convert (tz = None)) não altera realmente os dados que representam a parte ingênua do carimbo de data / hora.
Portanto, a única maneira de fazer o que você deseja é modificar os dados subjacentes (o pandas não permite isso ... DatetimeIndex são imutáveis - consulte a ajuda em DatetimeIndex) ou criar um novo conjunto de objetos de carimbo de data / hora e agrupá-los em um novo DatetimeIndex. Sua solução faz o último:
pd.DatetimeIndex([i.replace(tzinfo=None) for i in t])
Para referência, aqui está o
replace
método deTimestamp
(consulte tslib.pyx):def replace(self, **kwds): return Timestamp(datetime.replace(self, **kwds), offset=self.offset)
Você pode consultar a documentação
datetime.datetime
para ver quedatetime.datetime.replace
também cria um novo objeto.Se você puder, sua melhor aposta para eficiência é modificar a fonte dos dados de forma que (incorretamente) relate os carimbos de data / hora sem seu fuso horário. Você mencionou:
Ficaria curioso em saber a que incômodo extra você está se referindo. Eu recomendo como regra geral para todo desenvolvimento de software, manter seu carimbo de data / hora 'valores ingênuos' em UTC. Não há nada pior do que olhar para dois valores int64 diferentes e imaginar a qual fuso horário eles pertencem. Se você sempre, sempre, sempre usar UTC para armazenamento interno, evitará inúmeras dores de cabeça. Meu mantra é Fusos horários são apenas para E / S humana .
fonte
Porque sempre me esforço para lembrar, um rápido resumo do que cada um deles faz:
>>> 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')
fonte
Definir o
tz
atributo do índice explicitamente parece funcionar:ts_utc = ts.tz_convert("UTC") ts_utc.index.tz = None
fonte
tz
como Nenhum também o converte em UTC.tz_convert
-la gerará um erro.Com base na sugestão do DA de que " a única maneira de fazer o que você quer é modificar os dados subjacentes " e usando o numpy para modificar os dados subjacentes ...
Isso funciona para mim e é muito rápido:
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)
fonte
A solução aceita não funciona quando há vários fusos horários diferentes em uma série. Joga
ValueError: Tz-aware datetime.datetime cannot be converted to datetime64 unless utc=True
A solução é usar o
apply
método.Veja os exemplos abaixo:
# 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]
fonte
Contribuição tardia, mas acabei de encontrar algo semelhante no datetime do Python e o pandas fornece carimbos de data / hora diferentes para a mesma data .
Se você tiver uma data e hora ciente de fuso horário em
pandas
, tecnicamente,tz_localize(None)
altera o carimbo de data / hora POSIX (que é usado internamente) como se a hora local do carimbo de data / hora fosse UTC. Local neste contexto significa local no fuso horário especificado . 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]')
Observe que isso vai deixar você com coisas estranhas durante as transições do horário de verão , por exemplo
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')
Em contraste,
tz_convert(None)
não modifica o carimbo de data / hora interno, apenas remove otzinfo
.t_utc = t.tz_convert(None) (t_utc.values-t.values)//1e9 # array([0, 0], dtype='timedelta64[ns]')
Minha linha de fundo seria: mantenha-se com a data e hora com reconhecimento de fuso horário se você puder ou apenas usar o
t.tz_convert(None)
que não modifica o carimbo de data / hora POSIX subjacente. Lembre-se de que você está praticamente trabalhando com a UTC.(Python 3.8.2 x64 no Windows 10,
pandas
v1.0.5.)fonte
O mais importante é adicionar
tzinfo
ao definir um objeto 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())
fonte