converting time in django - django

hi i'm new in working with datetime objects in django
all I know now is that instead of python's datetime.datetime.now() we should use django's timezone.now(), i've also set TIMEZONE and USE_TZ=True in settings.py
but my problem is now for converting these types of time. as far as I know, even if we use timezone.now() for saving in database, django uses UTC time to store in DB. so I need a simple syntax for converting UTC time into my local time which is set in settings.py and vice versa to get local time from human and return local time.
i've also seen that django has some template tags to do that, but since i am doing this mostly for a REST API with django-rest for an android app, i need to be able to do this in python syntax.
thanks everyone, I hope I could be clear at what I mean :)

In templates, Django will automatically convert your model dates (stored as UTC) to the current time zone. The current time zone is set by settings.TIMEZONE unless you explicitly change it somewhere else. You don't even need to use special template tags. This will convert fine:
{{ MyModel.my_date }}
Outside of templates, there is a tool called localtime that you can use to do the same conversion.
from django.utils.timezone import localtime
...
local_date = localtime(MyModel.my_date)
print( str(MyModel.my_date) ) # UTF time
print( str(local_date) ) # local time
The datetime returned by localtime is time zone aware. If you ever need a time zone naive datetime, you can convert it like this:
my_date = localtime(MyModel.my_date).replace(tzinfo=None)

If, in settings.py we have the following:
from pytz import timezone
LOCAL_TZ = pytz.timezone('CST6CDT') # asume that local timezone is central, but you can use whatever is accurate for your local
Now, you can use this to convert from utc time to local
import pytz
from django.conf import settings
def to_local_dttm(utc_dttm):
return utc_dttm.astimezone(settings.LOCAL_TZ)
def to_utc_dttm(local_dttm):
return local_dttm.astimezone(pytz.utc)

Related

Django: Trying to add 'SHORT_TIME_FORMAT' somehow changes time zone to Chicargo

I required some extra date/time formats in addition to ones Django ships with, sI created these new formats:
SHORT_TIME_FORMAT
FILE_DATETIME_FORMAT
ISO8601_DATETIME_FORMAT.
So that I could use them in templates like this:
{{ value|date:"SHORT_DATE_FORMAT" }}
And In code like this:
from django.utils import formats
formats.date_format(value, `SHORT_TIME_FORMAT`)
I created the following code in utils/time/formats.py:
from collections import ChainMap
from django.utils import formats
DJANGO_DATETIME_FORMATS = {
'DATETIME_FORMAT': 'd M Y, H:i',
'DATE_FORMAT': 'j M Y',
'TIME_FORMAT': 'H:i:s',
'SHORT_DATETIME_FORMAT': 'Y-m-d H:i:s',
'SHORT_DATE_FORMAT': 'Y-m-d',
'MONTH_DAY_FORMAT': 'd M',
'YEAR_MONTH_FORMAT': 'Y M',
}
EXTRA_DATETIME_FORMATS = {
'SHORT_TIME_FORMAT': 'H:i',
'FILE_DATETIME_FORMAT': 'Y-m-d H.i.s',
'ISO8601_DATETIME_FORMAT': 'c', # Compatible with Javascript passing
}
def set_formats(globals_dict):
"""Returns a set of datetime formats for use across the whole project
Contains a union of the default formats along with the additional ones.
To add a new format, one has to perform the following two actions:
1. Add the name of the format to formats.FORMAT_SETTINGS
2. Add a variable with the same name as the variable to the settings.py
file with the value assigned to the required format.
globals_dict is the globals from the settings.py file
"""
formats.FORMAT_SETTINGS = frozenset(formats.FORMAT_SETTINGS | EXTRA_DATETIME_FORMATS.keys())
all_formats = ChainMap(DJANGO_DATETIME_FORMATS, EXTRA_DATETIME_FORMATS)
for setting, value in all_formats.items():
globals_dict[setting] = value
However when I add it to my settings.py file as so:
# settings.py
from utils.time.formats import set_formats
set_formats(globals())
My timezone changes to Chicargo, even though in settings.py I have:
TIME_ZONE = 'Africa/Johannesburg'
After research I found out that Django's default timezone is Chicargo, which is 7 Hrs behind me (I am UTC+02).
I can test it like this:
$ python manage.py shell
> from django.utils import timezone
> from django.conf import settings
> print(f"The settings.py TIME_ZONE value is: {settings.TIME_ZONE=}")
The settings.py TIME_ZONE value is: settings.TIME_ZONE='Africa/Johannesburg'
> print("Using Django local time, now is:")
Using Django local time, now is:
> print(timezone.localtime())
2022-07-27 20:54:35.293958+02:00
If I don't use set_formats, the datetime is correctly in my local time, if I add my set_formats function, then datetime is 7 Hrs off, and in the admin interface and other date/times on my website are all 7 hours off, even though settings.TIME_ZONE reports Africa/Johannesburg.
this code used to work, but at some point recently stopped working. I did change from pytz to zoneinfo, so that might be the reason, but I was a little bit sure it worked after the change.
Why is the timezone changing? How can I add a date/time format so it can be used in templates and in code with the django.utils.formats.date_format function?

DJANGO datetime wrong on template even after timezone setup

I have a DJANGO application, and I am completely lost about times.
I am located in Budapest, Hungary, my current time for these test is: 09:26
I have my timezone correctly set in settings.py
...
TIME_ZONE = 'Europe/Budapest'
USE_I18N = True
USE_L10N = True
USE_TZ = True
...
Lets say I store a datetime object in my SQLite database, the admin page will show the correct time:
If I query that data in a view the date is suddenly wrong.
2020-10-06 07:26:41.040463+00:00
I have read solutions that I need to activate timezone in my view, but it does not work:
tzname = pytz.timezone("Europe/Budapest")
timezone.activate(tzname)
for i in MyObject.objects.all():
print(i.date)
returns
2020-10-06 07:26:41.040463+00:00
I usually fill my templates with Ajax JS calls, so I was not able to try template filters like this:
{{ value|timezone:"Europe/Budapest" }}
How can I change the time so that my JsonResponse sends the correct time to my templates?
Consider carefully the first sentence in the timezone documentation:
When support for time zones is enabled, Django stores datetime information in UTC in the database, uses time-zone-aware datetime objects internally, and translates them to the end user’s time zone in templates and forms.
Note that translation to local time zones only happens in templates and forms—not database queries, views, or other functions. There are many good reasons for that, one of which is that converting to a different timezone can lose information. Let's say that the datetime you got back from the database was 2:30am on October 25, 2019, Budapest time. What moment in time does that represent? You can't know, because that time occurred twice due to daylight savings time.
So the behavior you're seeing is entirely correct. If you want to convert a datetime to the local time in code use localtime():
from django.utils.timezone import localtime
local = localtime(myobject.timestamp)
When you create your models, you could set the datetime to your local timezone.
from django.utils import timezone
date = models.DateTimeField(
default=timezone.localtime(timezone.now()),
blank=True
)
timezone.localtime(timezone.now()) will give you the time based on the TIME_ZONE given in the settings.
Moment JS:
https://momentjs.com/timezone/docs/#/using-timezones/converting-to-zone/

flask how to keep database queries references up to date

I am creating a flask app with two panels one for the admin and the other is for users. In the app scheme I have a utilities file where I keep most of the redundant variables besides other functions, (by redundant i mean i use it in many different parts of the application)
utilities.py
# ...
opening_hour = db_session.query(Table.column).one()[0] # 10:00 AM
# ...
The Table.column or let's say the opening_hour variable's value above is entered to the database by the admin though his/her web panel. This value limits the users from accessing certain functionalities of the application before the specified hour.
The problem is:
If the admin changes that value through his/her web panel, let's say to 11:00 AM. the changes is not being shown directly in the users panel."even though it was entered to the database!".
If I want the new opening_hour's value to take control. I have to manually shutdown the app and restart it "sometimes even this doesn't work"
I have tried adding gc.collect()...did nothing. There must be a way around this other than shutting and restarting the app manually. first, I doubt the admin will be able to do that. second, even if he/she can, that would be really frustrating.
If someone can relate to this please explain why is this occurring and how to get around it. Thanks in advance :)
You are trying to add advanced logic to a simple variable: You want to query the DB only once, and periodically force the variable to update by re-loading the module. That's not how modules and the import mechanism is supposed to be used.
If you want to access a possibly changing value from the database, you have to read it over and over again.
The solution is to, instead of a variable, define a function opening_hours that executes the DB query every time you check the value
def opening_hours():
return (
db_session.query(Table.column).one()[0], # 10:00 AM
db_session.query(Table.column).one()[1] # 5:00 PM
)
Now you may not want to have to query the Database every time you check the value, but maybe cache it for a few minutes. The easiest would be to use cachetools for that:
import cachetools
cache = cachetools.TTLCache(maxsize=10, ttl=60) # Cache for 60 seconds
#cachetools.cached(cache)
def opening_hours():
return (
db_session.query(Table.column).one()[0], # 10:00 AM
db_session.query(Table.column).one()[1] # 5:00 PM
)
Also, since you are using Flask, you can create a route decorator that controls access to your views depending on the view of the day
from datetime import datetime, time
from functools import wraps
from flask import g, request, render_template
def only_within_office_hours(f):
#wraps(f)
def decorated_function(*args, **kwargs):
start_time, stop_time = opening_hour()
if start_time <= datetime.now().time() <= stop_time:
return render_template('office_hours_error.html')
return f(*args, **kwargs)
return decorated_function
that you can use like
#app.route('/secret_page')
#login_required
#only_within_office_hours
def secret_page():
pass

Django File-based session doesn't expire

I just realized that my session doesn't expire when I use file-based session engine. Looking at Django code for file-based session, Django doesn't store any expiration information for a session, thus it's never expire unless the session file gets deleted manually.
This looks like a bug to me, as the database-backed session works fine, and I believe regardless of what session back-end developer chooses, they all should behave similarly.
Switching to database-backed session is not an option for me, as I need to store user's session in files.
Can anyone shed some lights?
Is this really a bug?
If yes, how do you suggest me to work around it?
Thanks!
So it looks like you're right. At least in django 1.4, using django.contrib.sessions.backends.file totally ignores SESSION_COOKIE_AGE. I'm not sure whether that's really a bug, or just undocumented.
If you really need this functionality, you can create your own session engine based on the file backend in contrib, but extend it with expiry functionality.
Open django/contrib/sessions/backends/file.py and add the following imports:
import datetime
from django.utils import timezone
Then, add two lines to the load method, so that it appears as below:
def load(self):
session_data = {}
try:
session_file = open(self._key_to_file(), "rb")
if (timezone.now() - datetime.datetime.fromtimestamp(os.path.getmtime(self._key_to_file()))).total_seconds() > settings.SESSION_COOKIE_AGE:
raise IOError
try:
file_data = session_file.read()
# Don't fail if there is no data in the session file.
....
This will actually compare the last modified date on the session file to expire it.
Save this file in your project somewhere and use it as your SESSION_ENGINE instead of 'django.contrib.sessions.backends.file'
You'll also need to enable SESSION_SAVE_EVERY_REQUEST in your settings if you want the session to timeout based on inactivity.
An option would be to use tmpwatch in the directory where you store the sessions
I hit similar issue on Django 3.1. In my case, my program calls the function set_expiry(value) with an integer argument (int data type) before checking session expiry.
Accoring to Django documentation, the data type of argument value to set_expiry() can be int , datetime or timedelta. However for file-based session, expiry check inside load() doesn't work properly only if int argument is passed to set_expiry() beforehand, and such problem doesn't happen to datetime and timedelta argument of set_expiry().
The simple solution (workaround?) is to avoid int argument to set_expiry(value), you can do so by subclassing django.contrib.sessions.backends.file.SessionStore and overriding set_expiry(value) (code sample below), and change parameter SESSION_ENGINE accordingly in settings.py
from datetime import timedelta
from django.contrib.sessions.backends.file import SessionStore as FileSessionStore
class SessionStore(FileSessionStore):
def set_expiry(self, value):
""" force to convert to timedelta format """
if value and isinstance(value, int):
value = timedelta(seconds=value)
super().set_expiry(value=value)
Note:
It's also OK to pass timedelta or datetime to set_expiry(value) , but you will need to handle serialization issue on datetime object.

Google App Engine, parsedatetime and TimeZones

I'm working on a Google App Engine / Django app and I encountered the following problem:
In my html I have an input for time. The input is free text - the user types "in 1 hour" or "tomorrow at 11am". The text is then sent to the server in AJAX, which parses it using this python library: http://code.google.com/p/parsedatetime/. Once parsed, the server returns an epoch timestamp of the time.
Here is the problem - Google App Engine always runs on UTC. Therefore, lets say that the local time is now 11am and the UTC time is 2am. When I send "now" to the server it will return "2am", which is good because I want the date to be received in UTC time. When I send "in 1 hour" the server will return "3am" which is good, again. However, when I send "at noon" the server will return "12pm" because it thinks that I'm talking about noon UTC - but really I need it to return 3am, which is noon for the request sender.. I can pass on the TZ of the browser that sends the request, but that wont really help me - the parsedatetime library wont take a timezone argument (correct me if I'm wrong). Is there a walk around this? Maybe setting the environments TZ somehow?
Thanks!
What you could do is add the difference using a timedelta object (http://docs.python.org/library/datetime.html)
The offset
here's some (very rough) code to give you the idea:
import parsedatetime
import datetime
my_users_timezone = whatever #replace this with a string that will make sense in the offsets dictionary
utc_timezone_offsets_in_hours = {
'utc' : 0,
'pacific' : -8,
# etc
}
parsed_time = parsedatetime.whatever(input_string)
offset_hours = utc_utc_timezone_offsets_in_hours[my_users_timezone]
final_time = parsed_time + datetime.timedelta(hours=offset_hours)
return final_time
parsedatetime's parse routine expects a timetuple() as the sourceTime parameter and should carry over any timezone information you include in it as I don't recall writing any code that overrode it. If it doesn't then it's a bug and let me know.
You can use code like how the answer above suggested for now to add the TZ offset after the parse() routine returns what it has determined the datetime to be:
import parsedatetime as pdt
cal = pdt.Calendar()
start = datetime.datetime.now().timetuple()
parsed, flag = cal.parse('in 1 hr', start)
then you can take the timetuple value of parsed and use timedelta to add your offset hours