├── VERSION
├── example
├── __init__.py
├── requirements.txt
├── templates
│ └── index.html
├── urls.py
├── manage.py
├── README.rst
└── settings.py
├── tz_detect
├── __init__.py
├── models.py
├── templatetags
│ ├── __init__.py
│ └── tz_detect.py
├── urls.py
├── defaults.py
├── static
│ └── tz_detect
│ │ └── js
│ │ ├── tzdetect.min.js
│ │ └── tzdetect.js
├── templates
│ └── tz_detect
│ │ └── detector.html
├── views.py
├── middleware.py
├── utils.py
└── tests.py
├── requirements
├── dev.txt
├── py310-django32.txt
├── py310-django40.txt
├── py37-django22.txt
├── py37-django30.txt
├── py37-django31.txt
├── py37-django32.txt
├── py38-django22.txt
├── py38-django30.txt
├── py38-django31.txt
├── py38-django32.txt
├── py38-django40.txt
├── py39-django22.txt
├── py39-django30.txt
├── py39-django31.txt
├── py39-django32.txt
└── py39-django40.txt
├── pytest.ini
├── MANIFEST.in
├── .github
├── dependabot.yml
└── workflows
│ └── develop.yml
├── tox.ini
├── test_settings.py
├── CONTRIBUTING.md
├── .gitignore
├── LICENSE
├── setup.py
├── README.rst
└── CHANGES.txt
/VERSION:
--------------------------------------------------------------------------------
1 | 0.5.0
2 |
--------------------------------------------------------------------------------
/example/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tz_detect/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tz_detect/models.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tz_detect/templatetags/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/example/requirements.txt:
--------------------------------------------------------------------------------
1 | django>=1.10
2 | pytz
3 |
--------------------------------------------------------------------------------
/requirements/dev.txt:
--------------------------------------------------------------------------------
1 | build
2 | tox
3 | tox-py
4 | twine
5 | wheel
6 |
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | DJANGO_SETTINGS_MODULE = test_settings
3 | testpaths =
4 | tz_detect
5 |
6 |
--------------------------------------------------------------------------------
/requirements/py310-django32.txt:
--------------------------------------------------------------------------------
1 | coverage==6.3
2 | django==3.2.11
3 | pytest==6.2.5
4 | pytest-django==4.5.2
5 |
--------------------------------------------------------------------------------
/requirements/py310-django40.txt:
--------------------------------------------------------------------------------
1 | coverage==6.3
2 | django==4.0.1
3 | pytest==6.2.5
4 | pytest-django==4.5.2
5 |
--------------------------------------------------------------------------------
/requirements/py37-django22.txt:
--------------------------------------------------------------------------------
1 | coverage==6.3
2 | django==2.2.26
3 | pytest==6.2.5
4 | pytest-django==4.5.2
5 |
--------------------------------------------------------------------------------
/requirements/py37-django30.txt:
--------------------------------------------------------------------------------
1 | coverage==6.3
2 | django==3.0.14
3 | pytest==6.2.5
4 | pytest-django==4.5.2
5 |
--------------------------------------------------------------------------------
/requirements/py37-django31.txt:
--------------------------------------------------------------------------------
1 | coverage==6.3
2 | django==3.1.14
3 | pytest==6.2.5
4 | pytest-django==4.5.2
5 |
--------------------------------------------------------------------------------
/requirements/py37-django32.txt:
--------------------------------------------------------------------------------
1 | coverage==6.3
2 | django==3.2.11
3 | pytest==6.2.5
4 | pytest-django==4.5.2
5 |
--------------------------------------------------------------------------------
/requirements/py38-django22.txt:
--------------------------------------------------------------------------------
1 | coverage==6.3
2 | django==2.2.26
3 | pytest==6.2.5
4 | pytest-django==4.5.2
5 |
--------------------------------------------------------------------------------
/requirements/py38-django30.txt:
--------------------------------------------------------------------------------
1 | coverage==6.3
2 | django==3.0.14
3 | pytest==6.2.5
4 | pytest-django==4.5.2
5 |
--------------------------------------------------------------------------------
/requirements/py38-django31.txt:
--------------------------------------------------------------------------------
1 | coverage==6.3
2 | django==3.1.14
3 | pytest==6.2.5
4 | pytest-django==4.5.2
5 |
--------------------------------------------------------------------------------
/requirements/py38-django32.txt:
--------------------------------------------------------------------------------
1 | coverage==6.3
2 | django==3.2.11
3 | pytest==6.2.5
4 | pytest-django==4.5.2
5 |
--------------------------------------------------------------------------------
/requirements/py38-django40.txt:
--------------------------------------------------------------------------------
1 | coverage==6.3
2 | django==4.0.1
3 | pytest==6.2.5
4 | pytest-django==4.5.2
5 |
--------------------------------------------------------------------------------
/requirements/py39-django22.txt:
--------------------------------------------------------------------------------
1 | coverage==6.3
2 | django==2.2.26
3 | pytest==6.2.5
4 | pytest-django==4.5.2
5 |
--------------------------------------------------------------------------------
/requirements/py39-django30.txt:
--------------------------------------------------------------------------------
1 | coverage==6.3
2 | django==3.0.14
3 | pytest==6.2.5
4 | pytest-django==4.5.2
5 |
--------------------------------------------------------------------------------
/requirements/py39-django31.txt:
--------------------------------------------------------------------------------
1 | coverage==6.3
2 | django==3.1.14
3 | pytest==6.2.5
4 | pytest-django==4.5.2
5 |
--------------------------------------------------------------------------------
/requirements/py39-django32.txt:
--------------------------------------------------------------------------------
1 | coverage==6.3
2 | django==3.2.11
3 | pytest==6.2.5
4 | pytest-django==4.5.2
5 |
--------------------------------------------------------------------------------
/requirements/py39-django40.txt:
--------------------------------------------------------------------------------
1 | coverage==6.3
2 | django==4.0.1
3 | pytest==6.2.5
4 | pytest-django==4.5.2
5 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include CHANGES.txt
2 | include LICENSE
3 | include README.rst
4 | recursive-include tz_detect/static *
5 | recursive-include tz_detect/templates *
6 | include VERSION
7 |
--------------------------------------------------------------------------------
/tz_detect/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from .views import SetOffsetView
4 |
5 | urlpatterns = [
6 | path("set/", SetOffsetView.as_view(), name="tz_detect__set"),
7 | ]
8 |
--------------------------------------------------------------------------------
/example/templates/index.html:
--------------------------------------------------------------------------------
1 | {% load tz_detect %}
2 |
3 |
4 |
5 | It is {% now "jS F Y H:i" %}
6 | {% tz_detect %}
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 | from django.urls import include
3 | from django.views.generic import TemplateView
4 |
5 | urlpatterns = [
6 | path("tz_detect/", include("tz_detect.urls")),
7 | path("", TemplateView.as_view(template_name="index.html")),
8 | ]
9 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "pip"
4 | directory: "/"
5 | schedule:
6 | interval: "monthly"
7 | # The requirements files used by tox are designed to test different versions
8 | # of Django on purpose. Dependabot is noise for this package's use case.
9 | # Setting the limit of open pull requests to zero is the documented way
10 | # to disable Dependabot.
11 | open-pull-requests-limit: 0
12 |
--------------------------------------------------------------------------------
/example/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings")
7 |
8 | from django.core.management import execute_from_command_line
9 |
10 | # Allow starting the app without installing the module.
11 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
12 |
13 | execute_from_command_line(sys.argv)
14 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist =
3 | py37-django{22,30,31,32}
4 | py38-django{22,30,31,32,40}
5 | py39-django{22,30,31,32,40}
6 | py310-django{32,40}
7 |
8 | [testenv]
9 | commands =
10 | python \
11 | -W error::ResourceWarning \
12 | -W error::DeprecationWarning \
13 | -W error::PendingDeprecationWarning \
14 | -m coverage run \
15 | -m pytest {posargs:tz_detect/tests.py}
16 | deps = -r requirements/{envname}.txt
17 | setenv =
18 | PYTHONDEVMODE=1
19 |
--------------------------------------------------------------------------------
/tz_detect/defaults.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 |
3 | # These countries will be prioritised in the search
4 | # for a matching timezone. Consider putting your
5 | # app's most popular countries first.
6 | # Defaults to top Internet using countries.
7 |
8 | TZ_DETECT_COUNTRIES = getattr(settings, "TZ_DETECT_COUNTRIES", ("CN", "US", "IN", "JP", "BR", "RU", "DE", "FR", "GB"))
9 |
10 |
11 | # Session key to use to store the detected timezone
12 | TZ_SESSION_KEY = getattr(settings, "TZ_SESSION_KEY", "detected_tz")
13 |
--------------------------------------------------------------------------------
/tz_detect/templatetags/tz_detect.py:
--------------------------------------------------------------------------------
1 | from django import template
2 | from django.conf import settings
3 |
4 | from ..utils import convert_header_name
5 |
6 | register = template.Library()
7 |
8 |
9 | @register.inclusion_tag("tz_detect/detector.html", takes_context=True)
10 | def tz_detect(context, **script_attrs):
11 | return {
12 | "show": not hasattr(context.get("request"), "timezone_active"),
13 | "debug": getattr(settings, "DEBUG", False),
14 | "csrf_header_name": convert_header_name(getattr(settings, "CSRF_HEADER_NAME", "HTTP_X_CSRFTOKEN")),
15 | "script_attrs": script_attrs,
16 | }
17 |
--------------------------------------------------------------------------------
/test_settings.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | SECRET_KEY = "h_ekayhzss(0lzsacd5cat7d=pu#51sh3w&uqn#tz26vuq4"
4 |
5 | DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}}
6 |
7 | INSTALLED_APPS = [
8 | "django.contrib.sites",
9 | "django.contrib.sessions",
10 | "django.contrib.contenttypes",
11 | "tz_detect",
12 | ]
13 |
14 | MIDDLEWARE_CLASSES = [
15 | "django.contrib.sessions.middleware.SessionMiddleware",
16 | "django.middleware.common.CommonMiddleware",
17 | "tz_detect.middleware.TimezoneMiddleware",
18 | ]
19 |
20 | MIDDLEWARE = MIDDLEWARE_CLASSES
21 |
22 | SITE_ID = 1
23 |
24 | USE_TZ = True
25 |
--------------------------------------------------------------------------------
/example/README.rst:
--------------------------------------------------------------------------------
1 | Example
2 | =======
3 |
4 | To run the example application, make sure you have the required
5 | packages installed. You can do this using following commands :
6 |
7 | .. code-block:: bash
8 |
9 | mkvirtualenv example
10 | pip install -r example/requirements.txt
11 |
12 | This assumes you already have ``virtualenv`` and ``virtualenvwrapper``
13 | installed and configured.
14 |
15 | Next, you can setup the django instance using :
16 |
17 | .. code-block:: bash
18 |
19 | python example/manage.py syncdb --noinput
20 |
21 | And run it :
22 |
23 | .. code-block:: bash
24 |
25 | python example/manage.py runserver
26 |
27 | Good luck!
28 |
--------------------------------------------------------------------------------
/tz_detect/static/tz_detect/js/tzdetect.min.js:
--------------------------------------------------------------------------------
1 | !function(){if((areCookiesEnabled=function(){var e,t=navigator.cookieEnabled;if(!1===t)return!1;if(e=!1===t||!!t,!document.cookie&&!e){if(document.cookie="testcookie=1",!document.cookie)return!1;document.cookie="testcookie=; expires="+new Date(0).toUTCString()}return!0})()){var e,t=(e=null,new XMLHttpRequest);t&&(t.open("post",window.tz_set_endpoint,!0),t.setRequestHeader("Content-type","application/x-www-form-urlencoded"),t.setRequestHeader(window.csrf_header_name,window.csrf_token),t.setRequestHeader("X-Requested-With","XMLHttpRequest"),t.send("offset="+new Date().getTimezoneOffset()+"&timezone="+Intl.DateTimeFormat().resolvedOptions().timeZone))}}();
--------------------------------------------------------------------------------
/tz_detect/templates/tz_detect/detector.html:
--------------------------------------------------------------------------------
1 | {% load static %}
2 | {% if show %}
3 |
14 | {% endif %}
15 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Setup
2 |
3 | django-tz-detect uses `tox` to run tests.
4 | The following commands show what you need to get started.
5 |
6 | ```
7 | python3 -m venv venv
8 | source venv/bin/activate
9 | pip install -r requirements/dev.txt
10 | tox --py current
11 | ```
12 |
13 | # Release checklist
14 |
15 | These are notes for how to make a release.
16 | First, make sure release tools are installed.
17 |
18 | ```
19 | pip install -r requirements/dev.txt
20 | ```
21 |
22 | * Update `VERSION`
23 | * Update `CHANGES.txt`
24 | * Update `classifiers` in `setup.py`
25 | * Build package: `python -m build`
26 | * Tag release: `git tag -a -m "Version "`
27 | * Push to GitHub with new tag: `git push --follow-tags`
28 | * Release! `twine upload dist/*`
29 |
--------------------------------------------------------------------------------
/tz_detect/views.py:
--------------------------------------------------------------------------------
1 | from django.http import HttpResponse
2 | from django.views.generic import View
3 |
4 |
5 | from .defaults import TZ_SESSION_KEY
6 |
7 |
8 | class SetOffsetView(View):
9 | http_method_names = ["post"]
10 |
11 | def post(self, request, *args, **kwargs):
12 | timezone = request.POST.get("timezone", None)
13 | if timezone:
14 | request.session[TZ_SESSION_KEY] = timezone
15 | else:
16 | offset = request.POST.get("offset", None)
17 | if not offset:
18 | return HttpResponse("No 'offset' parameter provided", status=400)
19 |
20 | try:
21 | offset = int(offset)
22 | except ValueError:
23 | return HttpResponse("Invalid 'offset' value provided", status=400)
24 |
25 | request.session[TZ_SESSION_KEY] = int(offset)
26 |
27 | return HttpResponse("OK")
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | venv/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 |
47 | # Translations
48 | *.mo
49 | *.pot
50 |
51 | # Django stuff:
52 | *.log
53 |
54 | # Sphinx documentation
55 | docs/_build/
56 |
57 | # PyBuilder
58 | target/
59 |
60 | # Vim
61 | *.sw*
62 |
--------------------------------------------------------------------------------
/.github/workflows/develop.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - develop
7 | pull_request:
8 |
9 | concurrency:
10 | group: ${{ github.head_ref || github.run_id }}
11 | cancel-in-progress: true
12 |
13 | jobs:
14 | tests:
15 | name: Python ${{ matrix.python-version }}
16 | runs-on: ubuntu-20.04
17 |
18 | strategy:
19 | matrix:
20 | python-version:
21 | - 3.7
22 | - 3.8
23 | - 3.9
24 | - '3.10'
25 |
26 | steps:
27 | - uses: actions/checkout@v2
28 |
29 | - uses: actions/setup-python@v2
30 | with:
31 | python-version: ${{ matrix.python-version }}
32 | cache: pip
33 | cache-dependency-path: 'requirements/*.txt'
34 |
35 | - name: Install dependencies
36 | run: |
37 | python -m pip install --upgrade pip setuptools wheel
38 | python -m pip install --upgrade tox tox-py
39 |
40 | - name: Run tox targets for ${{ matrix.python-version }}
41 | run: tox --py current
42 |
--------------------------------------------------------------------------------
/tz_detect/middleware.py:
--------------------------------------------------------------------------------
1 | import pytz
2 | from django.utils import timezone
3 | from pytz.tzinfo import BaseTzInfo
4 |
5 | from .defaults import TZ_SESSION_KEY
6 |
7 |
8 | try:
9 | from django.utils.deprecation import MiddlewareMixin
10 | except ImportError: # Django < 1.10
11 | MiddlewareMixin = object
12 |
13 | from .utils import offset_to_timezone
14 |
15 |
16 | class TimezoneMiddleware(MiddlewareMixin):
17 | def process_request(self, request):
18 | tz = request.session.get(TZ_SESSION_KEY)
19 | if tz:
20 | # ``request.timezone_active`` is used in the template tag
21 | # to detect if the timezone has been activated
22 | request.timezone_active = True
23 | # for existing sessions storing BaseTzInfo objects
24 | if isinstance(tz, BaseTzInfo):
25 | timezone.activate(tz)
26 | elif isinstance(tz, str):
27 | timezone.activate(pytz.timezone(tz))
28 | else:
29 | timezone.activate(offset_to_timezone(tz))
30 | else:
31 | timezone.deactivate()
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2012-2015 Adam Charnock
4 | Copyright (c) 2015 Basil Shubin
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
24 |
--------------------------------------------------------------------------------
/tz_detect/utils.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from itertools import chain
3 |
4 | import pytz
5 |
6 | from .defaults import TZ_DETECT_COUNTRIES
7 |
8 |
9 | def get_prioritized_timezones():
10 | def tz_gen():
11 | for c in TZ_DETECT_COUNTRIES:
12 | yield pytz.country_timezones(c)
13 | yield pytz.common_timezones
14 |
15 | return chain.from_iterable(tz_gen())
16 |
17 |
18 | def offset_to_timezone(offset, now=None):
19 | """Convert a minutes offset (JavaScript-style) into a pytz timezone
20 |
21 | The ``now`` parameter is generally used for testing only
22 | """
23 | now = now or datetime.now()
24 |
25 | # JS offsets are flipped, so unflip.
26 | user_offset = -offset
27 |
28 | # Helper: timezone offset in minutes
29 | def get_tz_offset(tz):
30 | try:
31 | return tz.utcoffset(now).total_seconds() / 60
32 | except (pytz.NonExistentTimeError, pytz.AmbiguousTimeError):
33 | return tz.localize(now, is_dst=False).utcoffset().total_seconds() / 60
34 |
35 | # Return the timezone with the minimum difference to the user's offset.
36 | return min(
37 | (pytz.timezone(tz_name) for tz_name in get_prioritized_timezones()),
38 | key=lambda tz: abs(get_tz_offset(tz) - user_offset),
39 | )
40 |
41 |
42 | def convert_header_name(django_header):
43 | """Converts header name from django settings to real header name.
44 |
45 | For example:
46 | 'HTTP_CUSTOM_CSRF' -> 'custom-csrf'
47 | """
48 | return django_header.lower().replace("_", "-").split("http-")[-1]
49 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import codecs
4 | import os
5 | import re
6 | import subprocess
7 | import sys
8 |
9 | from setuptools import find_packages, setup
10 |
11 |
12 | def read(*parts):
13 | file_path = os.path.join(os.path.dirname(__file__), *parts)
14 | return codecs.open(file_path, encoding="utf-8").read()
15 |
16 |
17 | setup(
18 | name="django-tz-detect",
19 | version=read("VERSION").strip(),
20 | license="MIT License",
21 | install_requires=[
22 | "django>=2.2",
23 | "pytz",
24 | ],
25 | description="Automatic user timezone detection for django",
26 | long_description=read("README.rst"),
27 | author="Adam Charnock",
28 | author_email="adam@adamcharnock.com",
29 | maintainer="Basil Shubin",
30 | maintainer_email="basil.shubin@gmail.com",
31 | url="http://github.com/adamcharnock/django-tz-detect",
32 | download_url="https://github.com/adamcharnock/django-tz-detect/zipball/master",
33 | packages=find_packages(exclude=("example*", "*.tests*")),
34 | include_package_data=True,
35 | zip_safe=False,
36 | classifiers=[
37 | "Development Status :: 5 - Production/Stable",
38 | "Environment :: Web Environment",
39 | "Framework :: Django",
40 | "Intended Audience :: Developers",
41 | "License :: OSI Approved :: MIT License",
42 | "Operating System :: OS Independent",
43 | "Programming Language :: Python",
44 | "Programming Language :: Python :: 3",
45 | "Programming Language :: Python :: 3.7",
46 | "Programming Language :: Python :: 3.8",
47 | "Programming Language :: Python :: 3.9",
48 | "Programming Language :: Python :: 3.10",
49 | "Topic :: Internet :: WWW/HTTP",
50 | "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
51 | ],
52 | )
53 |
--------------------------------------------------------------------------------
/tz_detect/static/tz_detect/js/tzdetect.js:
--------------------------------------------------------------------------------
1 | (function(){
2 |
3 | areCookiesEnabled = function() {
4 | // Credit for this function goes to: http://stackoverflow.com/a/18114024/764723
5 | var cookieEnabled = navigator.cookieEnabled;
6 | var cookieEnabledSupported;
7 |
8 | // When cookieEnabled flag is present and false then cookies are disabled.
9 | if (cookieEnabled === false) {
10 | return false;
11 | }
12 |
13 | // If cookieEnabled is null or undefined then assume the browser
14 | // doesn't support this flag
15 | if (cookieEnabled !== false && !cookieEnabled) {
16 | cookieEnabledSupported = false;
17 | } else {
18 | cookieEnabledSupported = true;
19 | }
20 |
21 |
22 | // try to set a test cookie if we can't see any cookies and we're using
23 | // either a browser that doesn't support navigator.cookieEnabled
24 | // or IE (which always returns true for navigator.cookieEnabled)
25 | if (!document.cookie && (!cookieEnabledSupported || /*@cc_on!@*/false)) {
26 | document.cookie = "testcookie=1";
27 |
28 | if (!document.cookie) {
29 | return false;
30 | } else {
31 | document.cookie = "testcookie=; expires=" + new Date(0).toUTCString();
32 | }
33 | }
34 |
35 | return true;
36 | };
37 |
38 | var createXMLHttp = function() {
39 | var xmlHttp = null;
40 | // Use XMLHttpRequest where available
41 | if (typeof(XMLHttpRequest) !== undefined) {
42 | xmlHttp = new XMLHttpRequest();
43 | return xmlHttp;
44 | // IE
45 | } else if (window.ActiveXObject) {
46 | var ieXMLHttpVersions = ['MSXML2.XMLHttp.5.0', 'MSXML2.XMLHttp.4.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp', 'Microsoft.XMLHttp'];
47 | for (var i = 0; i < ieXMLHttpVersions.length; i++) {
48 | try {
49 | xmlHttp = new ActiveXObject(ieXMLHttpVersions[i]);
50 | return xmlHttp;
51 | } catch (e) {}
52 | }
53 | }
54 | };
55 |
56 | if(!areCookiesEnabled()) {
57 | // If cookies are disabled then storing the timezone in the user's
58 | // session is a hopeless task and will trigger a request for each
59 | // page load. Therefore, we shouldn't bother.
60 | return;
61 | }
62 |
63 | var xmlHttp = createXMLHttp();
64 | if(xmlHttp) {
65 | xmlHttp.open('post', window.tz_set_endpoint, true);
66 | xmlHttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
67 | xmlHttp.setRequestHeader(window.csrf_header_name, window.csrf_token);
68 | xmlHttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
69 | xmlHttp.send("offset=" + (new Date()).getTimezoneOffset() + '&timezone=' + Intl.DateTimeFormat().resolvedOptions().timeZone);
70 | }
71 |
72 | }());
--------------------------------------------------------------------------------
/example/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for app project.
3 |
4 | For more information on this file, see
5 | https://docs.djangoproject.com/en/1.8/topics/settings/
6 |
7 | For the full list of settings and their values, see
8 | https://docs.djangoproject.com/en/1.8/ref/settings/
9 | """
10 |
11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
12 | import os
13 |
14 | BASE_DIR = os.path.dirname(os.path.dirname(__file__))
15 |
16 |
17 | # Quick-start development settings - unsuitable for production
18 | # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
19 |
20 | # SECURITY WARNING: keep the secret key used in production secret!
21 | SECRET_KEY = "YOUR_SECRET_KEY"
22 |
23 | # SECURITY WARNING: don't run with debug turned on in production!
24 | DEBUG = True
25 |
26 | ALLOWED_HOSTS = []
27 |
28 |
29 | # Application definition
30 |
31 | PROJECT_APPS = [
32 | "tz_detect",
33 | ]
34 |
35 | INSTALLED_APPS = [
36 | "django.contrib.auth",
37 | "django.contrib.sites",
38 | "django.contrib.sessions",
39 | "django.contrib.staticfiles",
40 | "django.contrib.contenttypes",
41 | ] + PROJECT_APPS
42 |
43 | MIDDLEWARE = (
44 | "django.contrib.sessions.middleware.SessionMiddleware",
45 | "django.middleware.common.CommonMiddleware",
46 | "django.middleware.csrf.CsrfViewMiddleware",
47 | # 'django.contrib.auth.middleware.AuthenticationMiddleware',
48 | # 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
49 | # 'django.contrib.messages.middleware.MessageMiddleware',
50 | # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
51 | # 'django.middleware.security.SecurityMiddleware',
52 | "tz_detect.middleware.TimezoneMiddleware",
53 | )
54 |
55 | ROOT_URLCONF = "example.urls"
56 |
57 | SITE_ID = 1
58 |
59 | TEMPLATES = [
60 | {
61 | "BACKEND": "django.template.backends.django.DjangoTemplates",
62 | "DIRS": [
63 | os.path.join(os.path.dirname(__file__), "templates"),
64 | ],
65 | "APP_DIRS": True,
66 | "OPTIONS": {
67 | "context_processors": [
68 | "django.template.context_processors.debug",
69 | "django.template.context_processors.request",
70 | "django.contrib.auth.context_processors.auth",
71 | "django.contrib.messages.context_processors.messages",
72 | ],
73 | },
74 | },
75 | ]
76 |
77 |
78 | # Database
79 | # https://docs.djangoproject.com/en/1.7/ref/settings/#databases
80 |
81 | DATABASES = {
82 | "default": {
83 | "ENGINE": "django.db.backends.sqlite3",
84 | "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
85 | }
86 | }
87 |
88 |
89 | # Internationalization
90 | # https://docs.djangoproject.com/en/1.7/topics/i18n/
91 |
92 | LANGUAGE_CODE = "en-us"
93 |
94 | TIME_ZONE = "UTC"
95 |
96 | USE_I18N = True
97 |
98 | USE_L10N = True
99 |
100 | USE_TZ = True
101 |
102 |
103 | # Static files (CSS, JavaScript, Images)
104 | # https://docs.djangoproject.com/en/1.7/howto/static-files/
105 |
106 | STATIC_URL = "/static/"
107 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | django-tz-detect
2 | ================
3 |
4 | .. image:: https://img.shields.io/pypi/v/django-tz-detect.svg
5 | :target: https://pypi.python.org/pypi/django-tz-detect/
6 |
7 | .. image:: https://img.shields.io/pypi/dm/django-tz-detect.svg
8 | :target: https://pypi.python.org/pypi/django-tz-detect/
9 |
10 | .. image:: https://img.shields.io/github/license/adamcharnock/django-tz-detect.svg
11 | :target: https://pypi.python.org/pypi/django-tz-detect/
12 |
13 | .. image:: https://coveralls.io/repos/adamcharnock/django-tz-detect/badge.svg?branch=develop
14 | :target: https://coveralls.io/r/adamcharnock/django-tz-detect?branch=develop
15 |
16 | This app will auto-detect a user's timezone using JavaScript, then
17 | configure Django's timezone localization system accordingly. As a
18 | result, dates shown to users will be in their local timezones.
19 |
20 | Authored by `Adam Charnock `_ (who is available for freelance/contract work), and some great `contributors `_.
21 |
22 | How it works
23 | ------------
24 |
25 | On the first page view you should find that ``tz_detect`` places a
26 | piece of asynchronous JavaScript code into your page using the
27 | template tag you inserted. The script will obtain the user's GMT
28 | offset using ``getTimezoneOffset``, and post it back to Django. The
29 | offset is stored in the user's session and Django's timezone awareness
30 | is configured in the middleware.
31 |
32 | The JavaScript will not be displayed in future requests.
33 |
34 | Installation
35 | ------------
36 |
37 | 1. Either checkout ``tz_detect`` from GitHub, or install using pip:
38 |
39 | .. code-block:: bash
40 |
41 | pip install django-tz-detect
42 |
43 | 2. Add ``tz_detect`` to your ``INSTALLED_APPS``:
44 |
45 | .. code-block:: python
46 |
47 | INSTALLED_APPS += (
48 | 'tz_detect',
49 | )
50 |
51 | 3. Be sure you have the ``django.template.context_processors.request`` processor
52 |
53 | .. code-block:: python
54 |
55 | TEMPLATES = [
56 | {
57 | ...
58 | 'OPTIONS': {
59 | 'context_processors': [
60 | ...
61 | 'django.template.context_processors.request',
62 | ],
63 | },
64 | },
65 | ]
66 |
67 | 4. Update your ``urls.py`` file:
68 |
69 | .. code-block:: python
70 |
71 | urlpatterns += [
72 | path('tz_detect/', include('tz_detect.urls')),
73 | ]
74 |
75 | 5. Add the detection template tag to your site, ideally in your base layout just before the ``