Change timezone of timezone-aware object in django - django

I have a timezone aware object:
date = timezone.now()
and then I am getting the user's timezone using some middleware, so I have it as a string, like:
tz = "America/New_York"
This is probably a really simple question but I have not been able to find the answer anywhere: How do I change the timezone of the date object to the user's timezone? I tried date.localize(tz) but that gave me an error. What am I doing wrong.

You need to convert the string to a timezone object, then use astimezone()
import pytz
timezone = pytz.timezone(tz)
your_date_with_usertimezone = date.astimezone(timezone)

Related

Django ORM executes query with wrong time?

I am trying to fetch data based on DateTime Field! I wrote the following script for the same:
import datetime as dt
from dasboard.models import Visionsystem
from django.utils.timezone import make_aware
min_dt = dt.datetime.combine(dt.date.today(), dt.time(7, 15))
max_dt = dt.datetime.combine(dt.date.today(), dt.time(15, 44))
# Changing format for Django
min_dt_aware = make_aware(min_dt)
max_dt_aware = make_aware(max_dt)
# Fetching all the data between 7:15:00 to 15:44:00 for current day
l1 = Visionsystem.objects.filter(start_datetime__range=(min_dt_aware, max_dt_aware))
print(l1) yields empty list and Checking str(l1.query) gives:
'SELECT `visionsystem`.`id`, `visionsystem`.`Start_Datetime` ... FROM `visionsystem` WHERE
`visionsystem`.`Start_Datetime` BETWEEN 2019-10-16 01:45:00 AND 2019-10-16 10:14:00'
Desired query would be:
'SELECT `visionsystem`.`id`, `visionsystem`.`Start_Datetime` ... FROM `visionsystem` WHERE
`visionsystem`.`Start_Datetime` BETWEEN 2019-10-16 07:15:00 AND 2019-10-16 15:44:00'
I don't understand why Django-ORM is querying with a different time then what's specified ?
My timezone settings ( in settings.py ):
TIME_ZONE = 'Asia/Kolkata'
USE_I18N = True
USE_L10N = True
USE_TZ = True
How do I resolve this, to fetch the required data from 7:15:00 to 15:44:00, for current day ? NOTE: I am using MySQL Database!!
models.py file:
from django.db import models
class Visionsystem(models.Model):
start_datetime = models.DateTimeField(db_column='Start_Datetime', blank=True, null=True)
class Meta:
managed = False
db_table = 'visionsystem'
MySql doesn't supports timezones, It stores datetime in UTC, while your django
application timezone is Asia/Kolkata (UTC +5:30).
Here django automatically converts your application timezone to UTC before querying to MySQL database. This makes sense as when you save data using django application, it converts datetime to UTC time, so 07:15:00 Kolkata time would be stored as 01:45:00 in UTC.
Solution:
You can store MySql data in UTC timezone, If existing data is in Kolkata timezone, run update query by subtracting -5:30 and save all new data in UTC timezone.
OR
If your application doesn't need to support multiple timezone, you can change application timezone to UTC. This is quick solution but not a good idea as timezone information is truncated.
In short
Django saves store UTC in database.
If you don't mention a timezone, the timezone you mentioned in settings.py is taken (in your case, Asia/Kolkata is taken, so make_aware function assumes, you are entering a datetime with timezone Asia/Kolkata. But as mentioned in step 1., it is saved as UTC. Both time points to same time - that is, time described by both UTC and Asia/Kolkata is same).
In addition to both 1 and 2, django convert your timezone_aware datetime (ie. min_dt_aware and max_dt_aware) to UTC, to make the correctness of your query during query execution.
Here is the detailed explanation.
Time zones overview
When support for time zones is enabled, Django stores date-time information in UTC in the database, uses time-zone-aware date-time objects internally, and translates them to the end user’s time zone in templates and forms.
Value is shown as UTC, which is printed in SQL query:
import pytz
utc = pytz.UTC
# Print the utc value of min_dt_aware and max_dt_aware
print(min_dt_aware.astimezone(utc), max_dt_aware.astimezone(utc))
# So, as you can see, these utc values are which you see in SQL queries.
You can use timezone.now(), it worked for me. For this first you need to import timezone,from django.utils import timezone https://docs.djangoproject.com/en/3.1/topics/i18n/timezones/

How can I convert the DateTimeField in Django from UTC to enduser's timezone (usually PST) when querying a Django model?

I'm querying a Django model connected to a table in my Postgres database that contains a datetime stored in UTC.
My query code looks something like this:
query_set = table_object.objects.values()
One of the columns in the query set is the datetime value in UTC.
The model looks like this:
class ops_inbox_view(models.Model):
requested_date = models.DateTimeField()
other_item = models.CharField(max_length=20)
other_item2 = models.CharField(max_length=40)
other_item3 = models.CharField(primary_key=True, max_length=10)
other_item4 = models.CharField(max_length=50)
other_item5 = models.CharField(max_length=50)
other_item6 = models.CharField(max_length=50)
I want to convert this into PST or robustly in the enduser's local time zone. My current solution is to use pandas with dt.tz_localize and dt.tz_convert after loading the query set into a dataframe but I'm trying to find a solution that is easily manageable in one location of the project file structure of the app. In my settings.py, I have TIME_ZONE set to 'US/Pacific' but because I'm using Pandas, the conversion to PST is not automatically done and will have to change many lines of code in my views.py to make the conversion with pandas.
Is there a way to not use Pandas and instead either make the field timezone aware or make the explicit conversion in the query code? Also looking for any other best practices in timezone management and display. Serving this datetimefield into an html file...
You can use F() with annotate
from datetime import timedelta
from django.db.models import DateTimeField, ExpressionWrapper, F
table_object.objects.annotate(modified_date=ExpressionWrapper(F('requested_date')+timedelta(minutes=960), output_field=DateTimeField())).filter(modified_date='2019-11-30')
Here you need to +/- minutes to convert utc datetime to your(PST) timezone and it will store converted dates into modofied_date, after that we can filter it.
Note:
You don't need to use pandas to convert timezones. Django good built-in timezone management tools.
Their timezone docs are quite good as well, so I'd suggest reading it all the way through.
The problem you're having seems very similar to one described in their docs

Freezegun always causes RuntimeWarning of receiving naive datetime

I'm working with a testcase where an organization can be suspended.
Currently I'm using freezegun to freeze a firm time, which is a datetime.datetime object with tzinfo=pytz.UTC.
In the test below you'll see a print of self.fake_datetime which returns a tzaware datetime: 2000-01-01 00:00:00+00:00.
When the test runs, I keep getting the famous RuntimeWarning:
/usr/local/lib/python2.7/dist-packages/django/db/models/fields/init.py:1447: RuntimeWarning: DateTimeField Organization.suspended received a naive datetime (2000-01-01 00:00:00) while time zone support is active.
RuntimeWarning)
import datetime
import pytz
from freezegun import freeze_time
# crop
class OrganizationSuspendTestCase(TestCase):
def setUp(self):
self.organization = OrganizationFactory()
self.fake_datetime = datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=pytz.UTC)
print self.fake_datetime
def test_suspend_organization(self):
"""
Case: An organization is being suspended with the suspend service.
Expected: The organization is updated with the suspending datetime and the reason.
"""
with freeze_time(self.fake_datetime):
mc_organization.services.organization.suspend(organization=self.organization, reason="Did not pay the bill")
self.assertEqual(self.organization.suspended, datetime.datetime(2000, 1, 1, 0, 0, 0))
I've been playing around with the freezegun timezone examples without any success to remove the runtime warning.
Any suggestions on how this should be resolved properly? I'd like to keep using Freezegun for this without a RuntimeWarning. Suppressing is an option, but I'd prefer not to.
update -- Solution based on the answer of xyres
Service was saving the datetime timezone unaware. Old situation commented and new situation is the actual code. I was thinking to much about mocking and assumed that the datetime saved in the service would be mocked with the timezone aware datetime object from the testcase by freezegun - which is not the case.
def suspend(organization, reason):
"""
Suspend an organization with the given reason.
:param mc_organization.models.Organization organization: The organization to suspend.
:param string reason: The reason of the suspending.
:return: None
"""
# organization.suspended = datetime.datetime.now() (Old sitation)
organization.suspended = timezone.now() # timezone aware situation.
organization.suspended_reason = reason
organization.save()
Seems like you're trying to save an object with a timezone-naive datetime. To get rid of this warning, just use timezone-aware datetime everywhere in your application.
Instead of managing timezone yourself manually using pytz, you can use Django's timezone module found at django.utils.timezone . It has some shortcut methods that you can use to convert naive datetime to aware datetime.
An advantage of using this is that if you ever change the timezone settings in your settings file, it will automatically pick the new timezone, whereas with pytz you'll have to manually update the new timezone everywhere.
from django.utils import timezone
fake_datetime = timezone.make_aware(timezone.datetime(2000, 1, 1, 0, 0, 0))

Retrieve timezone aware DateTimeField in Django

In my models, I have a DateTimeField say
class ReturnEvent(models.Model):
book_title = models.CharField()
return_time = models.DateTimeField()
When I retrieve the return_time to be printed, for example:
return_event = ReturnEvent.objects.get(id=1)
print(return_event.return_time.strftime("%H:%M"))
I am seeing the datetime unaware time like:
2016-06-18 08:18:00+00:00
However, I like to see the local time using strftime.
2016-06-18 10:18:00+02:00
Is there a quick solution for this?
If you have USE_TZ = True in your settings, Django stores all the datetime objects in the database as UTC and convert them to your TIME_ZONE=XYZ from settings.py on the fly when rendering in the templates.
That is why, when retrieved from the database, datetime object is timezone aware but it has UTC as its timezone (hence +00:00 in 2016-06-18 08:18:00+00:00). As, you are converting the time to str yourself (and not rendering in the template) so Django does not convert it to your TIME_ZONE setting. You need to convert it yourself to your desired TimeZone.
If you want to convert it to the TimeZone from your TIME_ZONE setting, you can do
from django.utils import timezone
to_tz = timezone.get_default_timezone()
print return_event.return_time.astimezone(to_tz).strftime("%H:%M")

Does Django DateTimeField supporting time with time zone information?

When I post a time with timezone information eg:2013-02-27T14:00:00-05:00 to a Django datetime field throws a form error "Enter a valid date/time.".
My form field is defined as below
time = forms.DateTimeField()
I also tried passing in date formats to the form filed. eg:
DATE_FORMATS = [
'%Y-%m-%dT%H:%M:%S-%z',
]
time = forms.DateTimeField(input_formats=DATE_FORMATS)
Does Django DateTimeField not support time with timezone information?
Yes! Django DateTimeField supports timezones, but if you take a look at the documentation, you have to specify it separately from the actual DateTime in your form. Specifically, the documentation recommends that you activate a timezone on a per user basis, otherwise you will always end up entering the DateTime in the default timezone.
https://docs.djangoproject.com/en/dev/topics/i18n/timezones/#time-zone-aware-input-in-forms
This is the documentation I used for my project, and it worked out well.
In addition, if you need to specify a specific timezone in a form field, you can always create a drop down field with all of the timezones in it, and then save the DateTime in your view with the correct timezone.