Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Saving a local datetime offset the time by 4 minutes

I'm trying to modify a datetime based on a timezone on save and on load the following way:

An input datetime, along with a input timezone are sent to the server and the server should update the datetime to reflect the timezone. So when it saves in the database (PostregSQL), the UTC time is saved (after the offset caused by the timezone, of course).

To reflect this here's a simpler example that fails in the same way:

Some imports:

>>> import datetime
>>> import pytz
>>> from apps.myapp.models import Project

Creating the two inputs:

>>> input_date = timezone.now()
>>> input_date
datetime.datetime(2017, 2, 7, 16, 7, 14, 377429, tzinfo=<UTC>)
>>> current_tz = pytz.timezone('America/New_York')
>>> current_tz
<DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>

As you can see, the timezone is not 5h (24 - 19 = 5), but 4h56. At this stage I'm thinking that's OK, it may be related to the Daylight Saving Time.

Now I'm replacing the timezone on the input date:

>>> input_date = input_date.replace(tzinfo=current_tz)
>>> input_date
datetime.datetime(2017, 2, 7, 16, 7, 14, 377429, tzinfo=<DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>)

As expected, the time hasn't changed, but the timezone has, which is fine.

I'll assign this value to a project (the launch_date is a DateTimeField without any specific option):

>>> project = Project.objects.get(pk=1)
>>> project.launch_date
datetime.datetime(2017, 1, 14, 8, 53, 57, 241718, tzinfo=<UTC>)
>>> project.launch_date = input_date
>>> project.launch_date
datetime.datetime(2017, 2, 7, 16, 7, 14, 377429, tzinfo=<DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>)

Now I'll save this into (and refresh from) the database, leaving Django/PostgreSQL do the maths:

>>> project.save()
>>> project.refresh_from_db()
>>> project.launch_date
datetime.datetime(2017, 2, 7, 21, 3, 14, 377429, tzinfo=<UTC>)

As expected the date is now 4h56 ahead of the previous date. I'm trying now to get back the local time:

>>> project.launch_date.astimezone(current_tz)
datetime.datetime(2017, 2, 7, 16, 3, 14, 377429, tzinfo=<DstTzInfo 'America/New_York' EST-1 day, 19:00:00 STD>)
>>> input_date
datetime.datetime(2017, 2, 7, 16, 7, 14, 377429, tzinfo=<DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>)

This time, the offset is perfectly 5h. And I'm missing 4 minutes.

3 questions here:

  • Where is this 4 minutes coming from?
  • Why is astimezone not using the 4 minutes as well?
  • How can a datetime be converted to UTC, saved, loaded and converted back to local?
like image 740
nobe4 Avatar asked Feb 07 '17 16:02

nobe4


1 Answers

pytz time zones are a little weird, as you can see by multiple questions on StackOverflow. They often won't show the correct offset or timezone name unless they are allowed to adjust themselves to the datetime they are being paired with. Here is what the documentation has to say:

This library only supports two ways of building a localized time. The first is to use the localize() method provided by the pytz library. This is used to localize a naive datetime (datetime with no timezone information):

The second way of building a localized time is by converting an existing localized time using the standard astimezone() method:

Unfortunately using the tzinfo argument of the standard datetime constructors “does not work” with pytz for many timezones.

It does not say so explicitly, but using replace has the same problem as using the datetime constructor.

To accomplish what your code was doing without the 4-minute discrepancy, you can use localize():

>>> input_date
datetime.datetime(2017, 2, 7, 16, 7, 14, 377429, tzinfo=<UTC>)
>>> current_tz.localize(input_date.replace(tzinfo=None))
datetime.datetime(2017, 2, 7, 16, 7, 14, 377429, tzinfo=<DstTzInfo 'America/New_York' EST-1 day, 19:00:00 STD>)

I suspect that's a bug though, and you really want to do a timezone conversion from UTC:

>>> input_date.astimezone(current_tz)
datetime.datetime(2017, 2, 7, 11, 7, 14, 377429, tzinfo=<DstTzInfo 'America/New_York' EST-1 day, 19:00:00 STD>)
like image 200
Mark Ransom Avatar answered Oct 12 '22 08:10

Mark Ransom