Datetimes, fusos horários e DST, meu Deus!

Este é um exemplo de como gerar ocorrências a partir do
módulo rrule de python-dateutil e convertê-las adequadamente para UTC antes de salvar no
banco de dados, principalmente se sua entrada estiver localizada em um fuso horário que
observe o horário de verão.

Nota: o horário de verão nos EUA em 2014 começa em 9 de março

import pytz
from dateutil.parser import *
from dateutil.rrule import rrule, WEEKLY, MO


tz
= pytz.timezone('America/Chicago')
start
= parse('Feb 15 2014, 11am')
end = parse('March 24 2014')

_dates
= rrule(WEEKLY, dtstart=start, until=end, byweekday=(MO,))
dates
= list(_dates)

print dates

Fora:

[datetime.datetime(2014, 2, 17, 11, 0), 
datetime
.datetime(2014, 2, 24, 11, 0),
datetime
.datetime(2014, 3, 3, 11, 0),
datetime
.datetime(2014, 3, 10, 11, 0),
datetime
.datetime(2014, 3, 17, 11, 0)]

Esses são horários ingênuos, o que é bom, porque queremos que sejam constantemente
às 11h, independentemente de estarmos no horário de verão ou não. Agora podemos
localizar cada um para o US Central e todos ficarão às 11h.

localized = [tz.localize(dt) for dt in dates]
for dt in localized:
print 'Central: {}; UTC: {}'.format(dt, pytz.utc.normalize(dt))

Fora:

Central: 2014-02-17 11:00:00-06:00; UTC: 2014-02-17 17:00:00+00:00
Central: 2014-02-24 11:00:00-06:00; UTC: 2014-02-24 17:00:00+00:00
Central: 2014-03-03 11:00:00-06:00; UTC: 2014-03-03 17:00:00+00:00
Central: 2014-03-10 11:00:00-05:00; UTC: 2014-03-10 16:00:00+00:00
Central: 2014-03-17 11:00:00-05:00; UTC: 2014-03-17 16:00:00+00:00

A esquerda é o que deve ser exibido para o usuário, mas a direita é o que deve
ser salvo no banco de dados. No entanto, você não pode converter diretamente para UTC, porque
isto é o que aconteceria:

utcified = [pytz.utc.localize(dt) for dt in dates]
for dt in utcified:
print 'Central: {}; UTC: {}'.format(tz.normalize(dt), dt)

Fora:

Central: 2014-02-17 05:00:00-06:00; UTC: 2014-02-17 11:00:00+00:00
Central: 2014-02-24 05:00:00-06:00; UTC: 2014-02-24 11:00:00+00:00
Central: 2014-03-03 05:00:00-06:00; UTC: 2014-03-03 11:00:00+00:00
Central: 2014-03-10 06:00:00-05:00; UTC: 2014-03-10 11:00:00+00:00
Central: 2014-03-17 06:00:00-05:00; UTC: 2014-03-17 11:00:00+00:00

O problema óbvio é que nosso evento local das 11h agora está agendado para as 17h. No entanto,
supondo por um momento que o que realmente queríamos era um evento que ocorreria às 17h no
centro dos EUA todas as segundas-feiras, você pode ver que agora, quando o horário de verão entrar em vigor, ele será
alterado para 18h, o que não é o que queremos.

Etapas para gerar ocorrências de eventos persistentes de banco de dados a partir de uma regra recorrente que respeita o horário de verão

  1. A solicitação de entrada do usuário inclui uma preferência de fuso horário (ou para eventos locais, certifique-se de ter uma constante que armazena o fuso horário local)
  2. Localize as datas de início / término ingênuas e as datas geradas por regras ingênuas para o fuso horário configurado. É possível que suas datas de início / término tenham tzinfo (se vier de um formulário Django, por exemplo), e você pode precisar removê-lo.
  3. Use o normalizemétodo de pytz para converter as datas em UTC e salve no banco de dados

Etapas para exibir ocorrências recuperadas do banco de dados no fuso horário apropriado

  1. Crie um objeto de fuso horário pytz para o fuso horário configurado do usuário ou site
  2. Chame o normalizemétodo do objeto fuso horário , passando cada data UTC, para obter a representação local