Pergunta

I've got a black-magic question about timezones.

I have an application where I need timestamped event. I store everything in UTC. However, I want to display (client-side) the date, neither in the user locale nor in UTC, but in a given timezone (namely, the Paris timezone).

Since there is no javascript way to do it easily (we would have to know the exact daylight saving time events using something like timezone.js which seems overkill), I wanted to do the following trick:

  • get the UTC date dutc=datetime(…,<UTC>)
  • convert it to paris date dparis=datetime(…,<Paris>)
  • force the tzinfo back to UTC to get an UTC timestamp corresponding to the date & time in Paris (eg. the same numerical date & time, but with UTC rather than Paris, without adding or removing hours)
  • storing this UTC date as a timestamp so the client could parse it as a "fake UTC " timestamp, and display the "fake time "

However, I don't get back my original timestamp

import datetime
import time
import pytz
utc = pytz.UTC
paris = pytz.timezone('Europe/Paris')
t = 1372982409 # timestamp in UTC

dutc = datetime.datetime.fromtimestamp(t, utc) # date in UTC
# datetime.datetime(2013, 7, 5, 0, 0, 9, tzinfo=<UTC>)

dparis = datetime.datetime.fromtimestamp(t, paris) # date in Paris time
# datetime.datetime(2013, 7, 5, 2, 0, 9, tzinfo=<DstTzInfo 'Europe/Paris' CEST+2:0
0:00 DST>)

dparisasutc = dparis.replace(tzinfo=utc) # force the date in UTC time
# datetime.datetime(2013, 7, 5, 2, 0, 9, tzinfo=<UTC>)

t0 = time.mktime(dparisasutc.timetuple())
# 1372986009.0

datetime.datetime.fromtimestamp(t0)
# datetime.datetime(2013, 7, 5, 3, 0, 9) # why not 02:00:09?
Foi útil?

Solução

You wanted to:

  • get the UTC date dutc=datetime(…,)
  • convert it to paris date dparis=datetime(…,)

Ok, I'm with you so far, but then...

  • force the tzinfo back to UTC to get an UTC timestamp corresponding to the date & time in Paris (eg. the same numerical date & time, but with UTC rather than Paris, without adding or removing hours)
  • storing this UTC date as a timestamp so the client could parse it as a "fake UTC " timestamp, and display the "fake time "

This might be a bad idea. The general convention is that at timestamp represented as an integer is always based on UTC. This holds true almost everywhere (with certain Microsoft .Net types being the exception).

Since you're client is in JavaScript, they would expect any integer to be the number of milliseconds since 1/1/1970 UTC. Changing that will have some strange consequences. The most interesting one is that you will get the user's own local time zone's rules about daylight saving time to be applied erroneously. These can be excruciatingly difficult to find in testing.

The simplest answer to to not use the Date type in JavaScript at all. Do the conversion to Paris time completely on the server, and pass a well-formatted string back to the browser.

A more interesting solution would be to pass the UTC time, either in an integer or as an ISO8601 string like 2013-08-21T17:29:00Z. On the client, use a library that can recognize the UTC time and convert it to the Europe/Paris zone. You mentioned not wanting to do this using timezone.js, but there are several other libraries to choose from. Some are very small, and let you include just the date for the zones you care about.

For example, try moment.js with the moment-timezone addon. You can specifically build a moment-timezone-data.js file containing only the Europe/Paris zone data. (They have a nice interactive data builder on that site for this.)

Outras dicas

Use the timezone's .localize() method to apply a timezone other than UTC to a timestamp:

>>> import datetime
>>> import time
>>> import pytz
>>> utc = pytz.UTC
>>> paris = pytz.timezone('Europe/Paris')
>>> t = 1372982409 # timestamp in UTC
>>> dparis = paris.localize(datetime.datetime.fromtimestamp(t))
>>> dparis
datetime.datetime(2013, 7, 5, 1, 0, 9, tzinfo=<DstTzInfo 'Europe/Paris' CEST+2:00:00 DST>)

Note the one hour time difference now displayed, as the correct current and not historical timezone transitions are applied.

Next, you need to interpret the resulting timestamp as UTC again lest Python apply the local timezone to the timestamp again:

>>> dparisasutc = dparis.replace(tzinfo=utc) # force the date in UTC time
>>> dparisasutc
datetime.datetime(2013, 7, 5, 1, 0, 9, tzinfo=<UTC>)
>>> t0 = time.mktime(dparisasutc.timetuple())
>>> t0
1372986009.0
>>> datetime.datetime.fromtimestamp(t0, utc)
datetime.datetime(2013, 7, 5, 1, 0, 9, tzinfo=<UTC>)

Because time.mktime returns struct_time in local time. You should read the docs and use calendar.timegm() instead which returns struct_time in UTC.

This is an old thing that remained from ancient times unfortunately. And never got changed because it could brake people code :(

import datetime
import time
import pytz
import calendar
utc = pytz.UTC
paris = pytz.timezone('Europe/Paris')
t = 1372982409 # timestamp in UTC

dutc = datetime.datetime.fromtimestamp(t, utc) # date in UTC
# datetime.datetime(2013, 7, 5, 0, 0, 9, tzinfo=<UTC>)

dparis = datetime.datetime.fromtimestamp(t, paris) # date in Paris time
# datetime.datetime(2013, 7, 5, 2, 0, 9, tzinfo=<DstTzInfo 'Europe/Paris' CEST+2:00:00 DST>)

dparisasutc = dparis.replace(tzinfo=utc) # force the date in UTC time
# datetime.datetime(2013, 7, 5, 2, 0, 9, tzinfo=<UTC>)    

t0 = calendar.timegm(dparis.timetuple())
print t0
# 1372989609.0

datetime.datetime.fromtimestamp(t0, utc)
# datetime.datetime(2013, 7, 5, 2, 0, 9, tzinfo=<UTC>) # now it is 02:00:09
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top