Handling per-object timezone settings in Django - django

I have a Django application in which a user can create an Activity, and then for an Activity create a number of Log entries. An Activity has a created datetime, and a Log entry also has a created datetime.
The Activity should be created with the timezone of the creating user at the time at which they create it. Specifically, a given user may have two Activities in separate timezones; the timezone setting is not per-user, but per-activity. I can derive a user's current offset from UTC in JavaScript in the browser (and I'm happy to rely on that).
What I do not know is: how do I save these times to the database, and then display them correctly on rendering?
I have USE_TZ turned on. Here in the UK, everything works: when creating an Activity, I save the created datetime as django.utils.timezone.now(), and I save the user's current offset (from JavaScript) as timezoneHoursOffset in the Activity. Then, when I display the Activity and its Log entries on a page, I wrap this in {% timezone "Etc/GMT+(timezoneHoursOffset)" %} ... {% endtimezone %}.
However, this approach doesn't work if the server's not in the UK. The time is saved in the database as UTC (I've verified this by checking in the database itself, not using the ORM), that time is then "changed" by the ORM to be eight hours behind UTC (because the server's timezone is UTC-8) (and that gives the correct time!) and then the {% timezone %} tag subtracts another eight hours off the time, meaning that the displayed time is eight hours behind what it should be.
I'm not sure whether I should be saving the times differently; changing them on output; using the timezone template tag; or something else. This stuff is very confusing. How would you advise that I handle this situation?

Ref the answer, "Etc/GMT" timezones have their sign reversed. For example, I'm in +0800, it should be 'Etc/GMT-8' instead of 'Etc/GMT+8' (and I suspect that your UTC-8 actually should be "Etc/GMT+8" ):
>>> from django.template import Template, Context
>>> from django.utils.timezone import now
>>> print(Template("""{% load tz %}
localtime: {{ t }}
Etc/GMT-8: {{ t|timezone:"Etc/GMT-8" }}
Etc/GMT+8: {{ t|timezone:"Etc/GMT+8" }}
""").render(Context({'t':now()})))
localtime: Jan. 29, 2013, 11:48 p.m.
Etc/GMT-8: Jan. 29, 2013, 11:48 p.m.
Etc/GMT+8: Jan. 29, 2013, 7:48 a.m.
So, you have to reverse your timezoneHoursOffset...
For DB storing, according to the doc, it's better to store data in UTC.
Also, you need to make sure your javascript generates correct hour offset because of DST. If you can, it's better to store timezone name and use it accordingly.

from datetime import datetime
from dateutil import zoneinfo
from_zone = zoneinfo.gettz('UTC')
to_zone = zoneinfo.gettz('UK/UK')
utc = created # your datetime object from the db
# Tell the datetime object that it's in UTC time zone since
# datetime objects are 'naive' by default
utc = utc.replace(tzinfo=from_zone)
# Convert time zone
eastern_time = utc.aztimezone(to_zone)

Related

How to Convert UTC Time To User's Local Time in Django

I undrestand that this question is repeated, but unfortunately I cannot find any answer.
My impression is that Django already takes care of converting server time to local user as long as I have
TIME_ZONE = 'UTC'
USE_TZ = True
in my settings.
Even more if the db is postgresql that setting also doesn't matter and every thing will be still converted.
however I tried all the followings:
{% load tz %}
{{ obj.date }}
{{ obj.date|localtime }}
{{ obj.date | timezone:"Canada/Mountain" }}
and only last one works and the rest gives me UTC time. Last one also is not useful as the time would be only correct for users in that zone.
I was wondering if I am missing anything here.
I have a very simple test model:
class TimeObject(models.Model):
date = models.DateTimeField(auto_now=True)
My impression is that Django already takes care of converting server time to local
Unfortunately that is incorrect.
As discussed here, there's no automatic way of knowing what the user's timezone is. You need to figure that out in some other way (it could be saved as a user setting, for example). Then you need to activate() that timezone. Once you do, Django will perform the conversion.

Using date filter in Jinja2 template raises TemplateSyntaxError [duplicate]

Using Jinja2, how do I format a date field? I know in Python I can simply do this:
print(car.date_of_manufacture.strftime('%Y-%m-%d'))
But how do I format the date in Jinja2?
There are two ways to do it. The direct approach would be to simply call (and print) the strftime() method in your template, for example
{{ car.date_of_manufacture.strftime('%Y-%m-%d') }}
Another, sightly better approach would be to define your own filter, e.g.:
from flask import Flask
import babel
app = Flask(__name__)
#app.template_filter()
def format_datetime(value, format='medium'):
if format == 'full':
format="EEEE, d. MMMM y 'at' HH:mm"
elif format == 'medium':
format="EE dd.MM.y HH:mm"
return babel.dates.format_datetime(value, format)
(This filter is based on babel for reasons regarding i18n, but you can use strftime too). The advantage of the filter is, that you can write
{{ car.date_of_manufacture|format_datetime }}
{{ car.date_of_manufacture|format_datetime('full') }}
which looks nicer and is more maintainable. Another common filter is also the "timedelta" filter, which evaluates to something like "written 8 minutes ago". You can use babel.dates.format_timedelta for that, and register it as filter similar to the datetime example given here.
Here's the filter that I ended up using for strftime in Jinja2 and Flask
#app.template_filter('strftime')
def _jinja2_filter_datetime(date, fmt=None):
date = dateutil.parser.parse(date)
native = date.replace(tzinfo=None)
format='%b %d, %Y'
return native.strftime(format)
And then you use the filter like so:
{{car.date_of_manufacture|strftime}}
I think you have to write your own filter for that. It's actually the example for custom filters in the documentation.
If you are dealing with a lower level time object (I often just use integers), and don't want to write a custom filter for whatever reason, an approach I use is to pass the strftime function into the template as a variable, where it can be called where you need it.
For example:
import time
context={
'now':int(time.time()),
'strftime':time.strftime } # Note there are no brackets () after strftime
# This means we are passing in a function,
# not the result of a function.
self.response.write(jinja2.render_template('sometemplate.html', **context))
Which can then be used within sometemplate.html:
<html>
<body>
<p>The time is {{ strftime('%H:%M%:%S',now) }}, and 5 seconds ago it was {{ strftime('%H:%M%:%S',now-5) }}.
</body>
</html>
You can use it like this in template without any filters
{{ car.date_of_manufacture.strftime('%Y-%m-%d') }}
Google App Engine users : If you're moving from Django to Jinja2, and looking to replace the date filter, note that the % formatting codes are different.
The strftime % codes are here: http://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior
You can use it like this in jinja template
{{ row.session_start_date_time.strftime('%d-%m-%Y %H:%M:%S')}}
In this code the field name is row.session_start_date_time.
in flask, with babel, I like to do this :
#app.template_filter('dt')
def _jinja2_filter_datetime(date, fmt=None):
if fmt:
return date.strftime(fmt)
else:
return date.strftime(gettext('%%m/%%d/%%Y'))
used in the template with {{mydatetimeobject|dt}}
so no with babel you can specify your various format in messages.po like this for instance :
#: app/views.py:36
#, python-format
msgid "%%m/%%d/%%Y"
msgstr "%%d/%%m/%%Y"
I use this filter, it's in Spanish but you may change the names as you need.
#app.template_filter('datetime')
def date_format(value):
months = ('Enero','Febrero',"Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre")
month = months[value.month-1]
hora = str(value.hour).zfill(2)
minutos = str(value.minute).zfill(2)
return "{} de {} del {} a las {}:{}hs".format(value.day, month, value.year, hora, minutos)
There is a jinja2 extension you can use just need pip install (https://github.com/hackebrot/jinja2-time)

Getting the current date in a particular timezone django

I want to get the current date for a specific timezone in my Django app, irrespective of the server's timezone. I save the user's timezone in the database. I'll then use that in the following function:
def current_date(zone):
utc = timezone.now()
tz = pytz.timezone(zone)
return utc.astimezone(tz).date()
print(current_date('Pacific/Auckland')) #prints 2016-05-30
print(current_date('Africa/Accra')) #prints 2016-05-29
It seems to work, but working with timezones seems complex and I'm wondering if something can go wrong with this approach?
It looks fine as long as getting to the value in view itself is what you want. Since the date/time print is an aspect of presentation you probably may not want to do it in the view code rather in the template using something like below:
{% load tz %}
{{ object.datetime_field|timezone:request.user.timezone }}
assuming you are storing the user's timezone selection in the user model.

Django datetime localization

I want to output a datetime object (with timezone info set to UTC) in a Django template. I have I18N, L10N, and TZ support all on in the settings. I've also added the LocaleMiddleware and set the language settings. Settings also contains my local timezone as the default. If I simply do {{ mydatetime }} in the template than it properly gives the information in the local timezone. However {{ mydatetime|localize }} seems to always output the datetime in UTC.
Now, I know you're supposed to call django.utils.timezone.activate() to report the current user's timezone (which I'm not doing), but the docs seem to indicate that if you don't use that to report the user's timezone that it uses the default.
Also, does the localize filter get used on datetimes automatically? Is the only purpose of having an explicit call is for when you use {% localize off %} and you want a one-off localization made?

Haystack more like this ignores filters

When I chain a filter before my more_like_this call on SearchQuerySet, the filter doesn't seem to be applied at all.
from haystack.query import SearchQuerySet as sqs
from articles.models import Article
article = Article.objects.get(pk=4560) # Article instance of one of the many articles I have
sqs().filter(author='testest#testtest.com').count() # 147 - 147 documents with author=testest#testtest.com... so far so good
sqs().more_like_this(article).count() #54893
sqs().filter(author='testest#testtest.com').more_like_this(article).count() # 54893!!!
I assumed doing:
sqs().filter(author='testest#testtest.com').more_like_this(article)
would limit my MLT search within the 147 filtered documents, but it's almost as if the filter is being completely ignored.
I also tried reversing the order of the chain:
sqs().more_like_this(article).filter(author='testest#testtest.com')
but it ends up with returning the entire search index
Any ideas? Thanks in advance.
Here's my article_text.txt
{{ object.title }}
{{ object.body.excerpt|striptags|escape }}
search_index.py
class ArticleIndexes(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True, boost=1.1)
author = indexes.CharField(model_attr='author')
site_id = indexes.CharField(model_attr='site_id')
# non-indexed, stored field
stored_obj = ArticleStorageField(indexed=False)
I've run into the same problem long time ago - filtering with more like this is just not implemented in haystacks elasticsearch backend.
I've made pull request to both pyelasticsearch and haystack to make it work. Pyelasticsearch allows it now, but haystack still have no support (and I've closed my pull request).
This commit fixes it in haystack:
https://github.com/jasisz/django-haystack/commit/76473d8eebf49a0fffba025993a533b852aa8578