flask: convert utc time to user's local time

Question:

I’m trying to convert a UTC time to the appropriate local time in a flask application. I’m wondering if I can detect the user’s timezone and dynamically set it.

This is my code so far which works for everyone in the US/Pacific timezone only (datetimefilter pulled from this SO question)

from flask import Flask, render_template_string
import datetime
import pytz

app = Flask(__name__)

@app.template_filter('datetimefilter')
def datetimefilter(value, format='%b %d %I:%M %p'):
    tz = pytz.timezone('US/Pacific')  # timezone you want to convert to from UTC (America/Los_Angeles)
    utc = pytz.timezone('UTC')
    value = utc.localize(value, is_dst=None).astimezone(pytz.utc)
    local_dt = value.astimezone(tz)
    return local_dt.strftime(format)

@app.route('/')
def index():
    dt = datetime.datetime.utcnow()
    return render_template_string(
        '''
        <h4>UTC --> Local Time</h4>
        Datetime right now is:
        <p>{{ dt | datetimefilter }} (utc --> local)</p>
        ''',
        dt=dt, now=now)


if __name__ == '__main__':
    app.run(debug=True)

I imagine this can’t be handled server-side because the timezone will always be set to wherever the server is located? Is there a way to handle this by changing datetimefilter?

Asked By: Johnny Metz

||

Answers:

If you want to dynamically detect user’s local time on a website, it’s easiest to get browser’s datetime via JavaScript Date() object. This way, you can display the correct local time without having to detect anything in the literal sense. Your snippet might look like this:

@app.route('/')
def index():
    dt = datetime.datetime.utcnow()
    return render_template_string(
        '''
        <html><body>
        <h4>UTC --> Local Time</h4>
        Datetime right now is:
        <p>{{ dt | datetimefilter }} (utc)</p>
        <p><span id="timeNow"></span> (local)</p>
        <script>
        var elem = document.getElementById("timeNow")
        var now = new Date();
        var options = { month: 'short', day: '2-digit',
                        hour: 'numeric', minute: '2-digit' };
        elem.innerHTML = now.toLocaleString('en-us', options);
        </script>
        </body></html>
        ''',
        dt=dt)

And it will render following result, depending on the user’s local time:

UTC --> Local Time
Datetime right now is:

Dec 02 06:20 PM (utc)

Dec 03, 4:20 PM (local)

Then, if you need to save or process some data depending on the user’s local time, you can add another @app.route (with POST-method) to receive that Date() value via AJAX from the website’s front-end.

Upon receiving on the back-end side, you probably want to parse javascript Date() object (note, that in this case it’s better to use plain getTime() method, instead of toLocaleString() formatting) into Python’s datetime, validate it via e.g. webargs/marshmallow, and keep it in, say, user’s session.

Please note that similar question has already been answered earlier.

Answered By: user3576508

Here’s a relatively compact solution using modern browser APIs. It uses Javascript to get the browser’s timezone, sends it to the back-end to be stored in the session object, and uses the stored timezone in a Jinja filter that can be applied to any UTC datetime to shift it to the user’s timezone.

import pytz

from flask import request, session


@app.route('/set_timezone', methods=['POST'])
def set_timezone():
    '''Get timezone from the browser and store it in the session object.'''
    timezone = request.data.decode('utf-8')
    session['timezone'] = timezone
    return ""

@app.template_filter('localtime')
def localtime_filter(value):
    '''Use timezone from the session object, if available, to localize datetimes from UTC.'''
    if 'timezone' not in session:
        return value

    # https://stackoverflow.com/a/34832184
    utc_dt = pytz.utc.localize(value)
    local_tz = pytz.timezone(session['timezone'])
    local_dt = utc_dt.astimezone(local_tz)
    return local_dt

And in the <head> element of your base template, add this to have the browser send its timezone to the /set_timezone route on the first pageload:

{% if 'timezone' not in session %}
<script type="text/javascript">
  // Send browser's timezone to server for displaying dates/times in local timezone.
  const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
  fetch("/set_timezone", {method: 'POST', body: timezone});
</script> 
{% endif %}
Answered By: Mark L
Categories: questions Tags: , , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.