Автоматическое определение часового пояса пользователя

    Мужик сидит, слушает радио.
    — В Москве полночь, в Благовещенске 6 утра, во Владивостоке, Хабаровске, Южно-Сахалинске 7, в Магадане 8, в Петропавловске-Камчатском 9 часов.
    Мужик сидит, сидит, потом встаёт, и с некоторым сожалением произносит:
    — Эх, ну и бардак же у нас в стране!

    Текст перепечатан с аудиозаписи позывных «Маяка».
    Для справки: в Петропавловске-Камчатском теперь UTC+11 (летом UTC+12), поэтому теперь в полночь по московскому времени там было бы 8 часов, а не 9.


    Во многих местах на сайтах отображается время. И во многих случаях лучше всего отображать не время по Гринвичу, не время на сервере, а время в часовом поясе пользователя.

    Часто предлагается выбрать свой часовой пояс из огромного списка возможных вариантов. Конечно, возможность приятная, но удобнее, если сайт может определить часовой пояс пользователя сам. А сделать это, как можно догадаться, совсем несложно — достаточно получить локальное время и отступ от UTC с помощью JavaScript и передать этот отступ на сервер с помощью XMLHttpRequest.


    Вперёд, попробуем



    Создадим новый Django-проект. Назовём его tep — timezone experiment project.

    django-admin startproject tep

    Добавим новое приложение.

    cd tep/
    python manage.py startapp whattimeisit

    Теперь откроем settings.py и добавим в начале:
    import os
    SITE_ROOT = os.path.dirname(os.path.realpath(__file__))

    Теперь укажем путь до директории шаблонов:
    TEMPLATE_DIRS = (
        os.path.join(SITE_ROOT, 'templates'),
    )

    И добавим наше приложение в список установленных:
    INSTALLED_APPS = (
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.sites',
        'django.contrib.messages',
        # Uncomment the next line to enable the admin:
        # 'django.contrib.admin',
        'whattimeisit',
    )

    А также укажем базу данных:
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
            'NAME': 'database', # Or path to database file if using sqlite3.
        }
    }

    И, конечно же, создадим директорию шаблонов, которую мы указали в settings.py.

    mkdir templates/

    И выполним syncdb.

    python manage.py syncdb

    В директории шаблонов создадим шаблон index.html.

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Время</title>
    <script type="text/javascript" src="/static/jquery-1.4.2.min.js"></script>
    <script type="text/javascript">
     $(document).ready(function(){
      var time_zone = (new Date().getTimezoneOffset()/60)*(-1);
      var dateoffset = time_zone*60*60*1000;
      $('span.please_parse').each(function() {
        var date = new Date($(this).text());
        date.setTime(date.getTime() + dateoffset);
        $(this).text(date.toLocaleString());
      });
      $.post('/please_set_timezone', { 'offset': time_zone });
     });
    </script>
    </head>
    <body>
        <p>
        {% if local_time %}
        Сейчас <b>{{ local_time }}</b>.
        {% else %}
        Сейчас <b><span class="please_parse">{{ time }}</span></b>.
        {% endif %}
        </p>
    </body>
    </html>


    Теперь создадим директорию для статических файлов.

    mkdir media/

    urls.py в итоге вот такой:

    from django.conf.urls.defaults import *
    import os
    from settings import SITE_ROOT

    # Uncomment the next two lines to enable the admin:
    # from django.contrib import admin
    # admin.autodiscover()

    urlpatterns = patterns('',
        # Example:
        # (r'^tep/', include('tep.foo.urls')),

        # Uncomment the admin/doc line below and add 'django.contrib.admindocs'
        # to INSTALLED_APPS to enable admin documentation:
        # (r'^admin/doc/', include('django.contrib.admindocs.urls')),

        # Uncomment the next line to enable the admin:
        # (r'^admin/', include(admin.site.urls)),

        (r'^static/(?P<path>.*)$', 'django.views.static.serve', {'document_root': os.path.join(SITE_ROOT, 'media')}),

        (r'^$', 'whattimeisit.views.index'),
        (r'^please_set_timezone$', 'whattimeisit.views.please_set_timezone'),
    )


    В директорию media/ нужно загрузить jQuery. Я сохранил в эту директорию jquery-1.4.2.min.js.

    А views.py получился вот такой:

    # Create your views here.

    from django.http import HttpResponse, HttpResponseRedirect
    from django.core.urlresolvers import reverse
    from django.shortcuts import render_to_response
    from datetime import datetime
    from datetime import timedelta

    def please_set_timezone(request):
        if request.is_ajax():
            timezone_offset = request.POST.get('offset', False)
            if timezone_offset:
                request.session['timezone_offset'] = timezone_offset
            message = "OK"
            return HttpResponse(message)
        return HttpResponseRedirect(reverse('whattimeisit.views.index'))

    def index(request):
        time = datetime.utcnow()
        timezone_offset = request.session.get('timezone_offset', False)
        if timezone_offset:
            offset = timedelta(hours=int(timezone_offset))
            local_time = time+offset
            local_time_string = local_time.strftime("%d.%m.%Y %H:%M:%S")
        else:
            local_time_string = False
        time_string = time.strftime("%m/%d/%Y %H:%M:%S")
        return render_to_response("index.html", {'time': time_string, 'local_time': local_time_string,})


    Вот, собственно, и готово. Теперь можно запустить сервер и посмотреть результат.

    python manage.py runserver

    А вот сходу ещё идея. На основе автоматически определённого часового пояса сокращать список стран и городов для выбора. Разумеется, с возможностью показа полного списка — поскольку, по-первых, автоматика может ошибаться (если, например, время на компьютере пользователя установлено неправильно), во-вторых, пользователь может просто по каким-то причинам временно находиться в другой стране или в другом городе.

    Этот метод хорош тем, что даже если у пользователя не принимаются cookies, у него всё равно будет показываться время в его часовом поясе с помощью JavaScript. Если же cookies у него включены, то со второй страницы сервер сам будет выдавать ему его локальную дату.
    Поделиться публикацией

    Комментарии 35

      –11
      правильнее время определять конечно по IP, но там с мобильными операторами проблема будет.
      у многих моих знакомых стоит Московское время, хотя живем мы на 2 часа позже (потому что им так продали и они не знают как время перевести, или переводят, а оно обновляется с интернета опять на GMT+3 :)
        0
        Покрытие решения выше шире + решает проблему с пользователями, которые «живут по другому часовому поясу».
        +5
        Тогда уж надежнее получать время на машине пользователя, сравнивать с серверным, а потом уже определять часовой пояс. Проблема в том, что у многих верное время и при этом неверный пояс.
          +2
          Когда мы то же самое придумали, обходились без Ajax, одними кукисами. С отключенными кукисами сайт всё равно бы не работал (аутентификация) и специфика веб-приложения позволяла гарантировать, что при первой загрузке даты показывать не надо.

          Ещё можно обернуть все даты в специальный блок, скажем, <span class=«datetime» data-time=«1282756067»>2010-08-26 00:07:47</span>, а JS-функция их все разом сконвертит в локальное время.
            +1
            Можно использовать HTML5 тег: <time datetime=«1282756067»>2010-08-26 00:07:47</time>.
            +19
            offtopic: а почему у вас идентификаторы называются please_*?
            Давайте лучше сделаем что-нибудь вроде woul_you_be_so_kind_to_set_time_zone?
            Или sorry_to_trouble_you_but_could_you_parse_this?
              +6
              У меня у клиента и сервера очень хорошие отношения. Поэтому они обращаются друг к другу вежливо. :)
                +1
                Может, автор привык писать на INTERCAL? :)
                +1
                Смущает вот этот кусок:

                offset = timedelta(hours=int(timezone_offset))

                Насколько я помню, где-то в арабских странах смещение может дробным, типа 3,5 часа
                  0
                  вообще, конечно, правильно написал Hint #:
                  у очень многих юзеров стоит правильное время, но неправильная таймзона. из-за этого бывают баги с короткоживущими куками.
                    0
                    Чтобы не было таких проблем, RFC 2965 назначает кукам свойство Max-Age, и ни словом не говорит про свойство Expires — тяжкое наследие Нетскейпа.

                    Только мало кто ещё его пользует.
                    0
                    А в Катманду и вовсе UTC+5:45
                    • НЛО прилетело и опубликовало эту надпись здесь
                        +2
                        Какая разница сколько — лучше перебдеть, чем недобдеть.
                          0
                          Лично я каждую неделю читаю тамошних журналистов. Так что есть, есть.
                    • НЛО прилетело и опубликовало эту надпись здесь
                        0
                        Что-то решение бадяжное) не учитывает переход на летнее время… ведь не все страны переводят часы, соответственно часовых поясов становится больше… Лучшего решения, описанного Джошем Фразером — www.onlineaspect.com/2007/06/08/auto-detect-a-time-zone-with-javascript/, пока не встречал)…
                          0
                          О чём Вы?

                          Речь не идёт о запоминании времени в базе данных в, скажем, таблице пользователей. Оно сохраняется в сессии. Потом сессия заканчивается, и время считывается ещё раз.
                            +1
                            Кто вообще говорил про базу данных? Речь о Вашем определении часовой зоны с помощью функции getTimezoneOffset(). Где хотите, там и запоминайте часовой пояс — в базе, сессии, куках — ваше право.

                            Топик называется «Автоматическое определение часового пояса пользователя», которое свилось с вызову данной фукнции, которая не совсем-то точно работает ;) По моей ссылке вы сможете найти работающий кроссбраузерный алгоритм определения часовой зоны пользователя.
                              0
                              Тогда да, об этом я был не в курсе. Но тут смысл больше в общей схеме взаимодействия, а не этой конкретной строчке. Как уже писали выше, довольно эффективным может быть сравнение локального времени пользователя с временем UTC на сервере. Я об этом тоже думал, но в статью добавил более простой в реализации вариант. Предполагается, что уже готовое решение улучшать проще. :)

                              Спасибо за информацию, Павел.
                          +3
                          Я делаю еще проще.
                          Все даты у меня возвращаются пользователю, как Unixtime в UTC. После, скармливаю этот таймштамп объекту Date: new Date(), а Javascript знает, что Unixtime должен быть в UTC и сам корректирует дату с учетом текущего часового пояса пользователя. И в конце можно отформатировать дату как угодно, перед отображением пользователю. Таким образом, я никогда у пользователя не спрашиваю его часовой пояс, но даты ему отображаются корректно.
                          Особенно приятно, что этот самый timestamp легко достается из ObjectID MongoDB.
                            +2
                            new Date(timestamp)
                              +1
                              из ObjectID доставал, но оборачивать в new Data() и получать локальное время не додумывался, премного благодарен!
                                0
                                Да, все гениальное — просто. Почему этот подход не получил распространения, мне не понятно.
                                  0
                                  Получил. Просто все в Москве готовятся к предстоящему велопробегу ;-)
                              +9
                              Создадим новый Django-проект.

                              Внезапно.
                                0
                                Всё, что уходит на сервер, должно быть в GMT, а на клиенте можно показывать в его временной зоне. Как правило, это решает все проблемы.
                                  –1
                                  Для справки: в Петропавловске-Камчатском теперь UTC+11 (летом UTC+12), поэтому теперь в полночь по московскому времени там было бы 8 часов, а не 9.
                                  Спасибо нашей партии, поклон ей до земли!!!
                                  © Сектор Газа.

                                  ЗЫ Из-за этого идиотизма столько проблем блин…
                                    0
                                    и вобще — нехуй было землю круглой делать. оставили бы плоской, как раньше.
                                    +4
                                    Статью можно было урезать до html-кода и кода функций, если она не ставит своей целью научить создавать джанго-проекты.
                                      0
                                      А зачем? Те, кто знают, что делать, просто пропустят кусок, а те, кто не знают, могут научиться. :)

                                      В своём недавнем скринкасте (Programming an image hosting application with Django, час Django-видео в 1920x1080), например, проект тоже создаётся с нуля. Мне вообще кажется, что постепенное продвижение вперёд — очень уютный, медитативный процесс. :)
                                        0
                                        Конечно, толпа сейчас рванула изучать джанго дабы автоматически определять часовой пояс пользователя.

                                        >час Django-видео в 1920x1080
                                        Рекомендую озвучивать их на Django-русском или серьезно поработать над произношением Django-английского. Ради Django-добра.
                                          0
                                          ага.
                                          а кто не знает джанго — пусть разберуться в джанге, поймут логику работы, и уже потом переделают по свой фреймворк.
                                        +1
                                        А ещё есть сайты, где пользователи в своём профиле сами указывают регион/город своего нахождения (типа Хабра). И там вообще можно было бы показывать юзерам время постов/комментариев в их локальном времени, ведь привязка часовых поясов к регионам известна. Но почему-то Хабр этого не делает.
                                          0
                                          Часовой пояс != смещению от Гринвича.

                                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                          Самое читаемое