UTC to local timezone in a flask/jinja template

13,551

Solution 1

This is what ended up working for me, in this particular case, and I'm still not sure if it is going to give incorrect info during DST, but we'll see.

import pytz
from pytz import timezone
import tzlocal 

def datetimefilter(value, format="%I:%M %p"):
    tz = pytz.timezone('US/Eastern') # timezone you want to convert to from UTC
    utc = pytz.timezone('UTC')  
    value = utc.localize(value, is_dst=None).astimezone(pytz.utc)
    local_dt = value.astimezone(tz)
    return local_dt.strftime(format)

flask_app.jinja_env.filters['datetimefilter'] = datetimefilter

Then my jinja2 template looks like:

{% for data in items %}
...
<td>{{data.loggedInBy}}</td>
<td>{{data.timeIn | datetimefilter }}</td>
...
{% endfor %}

If there's any way to improve on this, I'm open to suggestions, but so far, this is the only way that I've found that works.

Solution 2

Localize is not going to change the timezone, but rather add the timezone information to a naive datetime object. You say your datetime is coming from SQL and is in UTC, but is is a timezone aware datetime object? If value is a datetime object that is aware it is UTC and not a naive datetime object you should change the timezone like this

tz = pytz.timezone('US/Eastern')  # timezone you want to convert to from UTC
local_dt = value.astimezone(tz)
return local_dt.strftime(format)

If you get an error
ValueError: astimezone() cannot be applied to a naive datetime
then your value is a naive datetime object and you could make it time zone aware (as UTC) 1st and then get your localized datetime like this.

tz = pytz.timezone('US/Eastern')
utc = pytz.timezone('UTC')
tz_aware_dt = utc.localize(value)
local_dt = tz_aware_dt.astimezone(tz)
return local_dt.strftime(format)

There are a number of ways you could check to see if your datetime object is naive or not. For instance, if it is aware of it's timezone value.tzname() will return the name of the timezone, otherwise it will return none.

Timezone aware: datetime.datetime(2016, 1, 1, 4, 28, 26, 149703, tzinfo=<DstTzInfo 'US/Eastern' EST-1 day, 19:00:00 STD>)

Naive datetime object: datetime.datetime(2016, 1, 1, 9, 28, 26, 149703)

Solution 3

Flask-Moment is good to be used in this case. See the link for the detailed instruction.

Share:
13,551
Ryan Greever
Author by

Ryan Greever

Updated on June 04, 2022

Comments

  • Ryan Greever
    Ryan Greever almost 2 years

    I know this has been asked before, and I'm still pulling my hair out trying to figure this one out. I've tried pytz, dateutil, and now flask_moment. Still having problems converting a MySQL table datetime, which is UTC, to display in the jinja2 template as local time, specifically UTC - 05:00 (EST).

    My jinja2 for loop looks like this:

    {% for data in items %}
        ...
       <td>{{data.loggedInBy}}</td>
       <td>{{data.timeIn.strftime('%I:%M %p')}}</td>
        ...
    {% endfor %}
    

    I'm new to python/flask/jinja, so go easy on me, please. The documentation is pretty confusing for a noob such as myself. Can someone please walk me through getting my MySQL table to display the times in local timezone?

    I feel like this is close, but getting errors with it. In init.py, I had:

    from pytz import timezone
    
    def datetimefilter(value, format='%I:%M %p'):
        tz = timezone('US/Eastern')
        dt = value
        local_dt = tz.localize(dt)
        local_dt.replace(hour=local_dt.hour + int(local_dt.utcoffset().total_seconds() / 3600))
        return local_dt.strftime(format)
    
    flask_app.jinja_env.filters['datetimefilter'] = datetimefilter
    
    jinja template had:
    
    {% for data in items %}
    ...
    <td>{{data.loggedInBy}}</td>
    <td>{{data.timeIn | datetimefilter }}</td>
    ...
    {% endfor %}
    

    But this was giving me "ValueError: hour must be in 0..23" From line:

    local_dt.replace(hour=local_dt.hour + int(local_dt.utcoffset().total_seconds() / 3600))
    

    Thanks in advance!

  • Ryan Greever
    Ryan Greever over 8 years
    Thanks Schechter, But it seems that even after your 2nd suggestion that I'm still getting the ValueError: astimezone() cannot be applied to a naive datetime. Closer, but still not quite workin' for me.
  • Ryan Greever
    Ryan Greever over 8 years
    Yeah Schechter, looks like the line that reads tz_aware_dt = utc.localize(value) should be: tz_aware_dt = utc.localize(value).astimezone(pytz.utc) to actually make it aware
  • Schechter
    Schechter over 8 years
    Thanks for pointing that out @RyanGreever. I think I had left 'value' where it should have been tz_aware_dt. I've edited the answer to reflect the changes.
  • Jason Lee
    Jason Lee about 7 years
    This is what I was looking for. Thanks.
  • NeverCast
    NeverCast over 4 years
    As of Python 3.6, astimezone() can be called on a naive datetime and it will be assumed to be in the local timezone. Watch out for that!