1

While I read just about every post related to timezone conversions I'm still having some issues and my converted time is incorrect

settings.py

TIME_ZONE = 'UTC'
USE_TZ = True

views.py

utc = datetime.utcnow()
instance_time_zone = pytz.timezone(instance.timezone) # 'US/Pacific'
start_date = instance_time_zone.localize(datetime.utcnow(), is_dst=None)

template.html

utc: Oct. 2, 2015, 5:32 p.m. #correct time
start_date: Oct. 3, 2015, 1:32 a.m. #incorrect time

For some reason, the converted time is wrong and 15 hours ahead of the Pacific Time and 8 hours ahead of the UTC time.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
WayBehind
  • 1,607
  • 3
  • 39
  • 58

2 Answers2

3

timezone.localize() should be used for naive datetime objects (objects with no timezone of their own). The timezone is attached to that datetime as if the date and time are correct for that timezone. So in your case you 'localised' UTC as if it is your local time without DST, shifting it 8 hours in the wrong direction.

You used a UTC timestamp however, so you need to attach the UTC timezone to that, then move the timestamp to the desired timezone:

utc = pytz.utc.localize(datetime.utcnow())
instance_time_zone = pytz.timezone(instance.timezone) # 'US/Pacific'
start_date = utc.astimezone(instance_time_zone)

Note that the utc value is now a datetime object with timezone, so you can then use the datetime.astimezone() method to produce a value in the desired target timezone from it.

Demo:

>>> from datetime import datetime
>>> utc = pytz.utc.localize(datetime.utcnow())
>>> utc
datetime.datetime(2015, 10, 2, 17, 58, 10, 168575, tzinfo=<UTC>)
>>> instance_time_zone = pytz.timezone('US/Pacific')
>>> utc.astimezone(instance_time_zone)
datetime.datetime(2015, 10, 2, 10, 58, 10, 168575, tzinfo=<DstTzInfo 'US/Pacific' PDT-1 day, 17:00:00 DST>)

Now the produced datetime is properly 5 hours removed from UTC.

If you are outputting these values into a Django template, however, note that Django will also transform the timezone. See the Django timezone documentation, specifically the section on using aware datetime objects in templates:

When you enable time zone support, Django converts aware datetime objects to the current time zone when they’re rendered in templates. This behaves very much like format localization.

and from the current time zone section:

You should set the current time zone to the end user’s actual time zone with activate(). Otherwise, the default time zone is used.

It then doesn't matter what timezone you moved the datetime object to; it'll use whatever is the current timezone to display the value. You generally want to use aware datetime objects in the UTC timezone, then use activate() to switch what timezone everything is displayed in.

So in Django, just use timezone.now() everywhere, and let the templating system worry about converting that to a given timezone.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Thank you for the feedback! For some reason when I run your code I do get Oct. 2, 2015, 6:03 p.m.. Any suggestions? – WayBehind Oct 02 '15 at 18:03
  • @WayBehind: is your PC timezone correctly configured? – Martijn Pieters Oct 02 '15 at 18:04
  • @WayBehind: Your `datetime.utcnow` output certainly looked correct though, so I'm not sure what is going on on your system, to be honest. Does the `utc` output match mine (or at least a few minutes later)? If you get Oct. 2, 2015, 6:03 p.m., is that the UTC output or the `start_date` output? It *is* 6:10 p.m. UTC now, after all. – Martijn Pieters Oct 02 '15 at 18:10
  • Yes, thats what I get. 6:03 pm Even my server is showing wrong time. Any ideas? – WayBehind Oct 02 '15 at 18:11
  • That's the UTC time. How certain are you that the `instance.timezone` is indeed `US/Pacific`, and that you are not accidentally using the `utc` value and not the return value of `utc.astimezone()`? – Martijn Pieters Oct 02 '15 at 18:12
  • Actually, even if I change the `instance.timezone` to different timezone value the output is always the UTC time – WayBehind Oct 02 '15 at 18:14
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/91200/discussion-between-martijn-pieters-and-waybehind). – Martijn Pieters Oct 02 '15 at 18:14
  • Thank you again for the help! – WayBehind Oct 02 '15 at 18:30
  • it is unnecessary complicated. To get the current time, use [`timezone.now()`](http://stackoverflow.com/a/32918384/4279) – jfs Oct 03 '15 at 02:43
  • I agree with @jfs this is convoluted. Simple is better than complex. – eric Oct 04 '17 at 03:00
  • @neuronet: the point of the answer was to show that the OP was making things too complicated and that they should just use the UTC timestamp. I've added one more paragraph to emphasise that point some more. – Martijn Pieters Oct 04 '17 at 06:56
3

To get the current time in django, use timezone.now():

from django.utils import timezone

start_date = timezone.now()

If instance.timezone refers to the same timezone as timezone.get_current_timezone() (default is TIME_ZONE) then it is all you need (timezone.now() returns an aware datetime object in UTC (if USE_TZ=True) that is converted during rendering to the current time zone).

Otherwise, you could call timezone.activate(instance.timezone) to set the current time zone.

If you want (you don't need to) you can convert the timezones explicitly:

import pytz
from django.utils import timezone

now = timezone.localtime(timezone.now(), pytz.timezone(instance.timezone))

Outside django code, you could get the current time in a given timezone by passing the tzinfo explicitly:

from datetime import datetime
import pytz

start_date = datetime.now(pytz.timezone('America/Los_Angeles'))

It works even during ambiguous local times.

To convert an existing naive datetime object that represents time in a given pytz timezone:

start_date = instance_time_zone.localize(datetime_in_instance_time_zone,
                                         is_dst=None)

This code raises an exception for ambiguous/non-existing times in instance time zone (e.g. during DST transitions). If it is ok to return an imprecise result in some cases instead of an exception then don't pass is_dst=None:

tz = instance_time_zone
start_date = tz.normalize(tz.localize(datetime_in_instance_time_zone))

For more details about is_dst, see "Can I just always set is_dst=True?" section.

Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670