From dca557fb124f96d08e4ee695de2139ccc7574e5c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Bedna=C5=99=C3=ADk?= <jan.bednarik@gmail.com>
Date: Mon, 24 May 2021 21:47:40 +0200
Subject: [PATCH] Timezones and localization

---
 README.md                                  |  2 +-
 helios/fields.py                           |  6 +++++-
 helios/forms.py                            | 21 +++++++++++++++------
 helios/templates/election_not_started.html |  5 +++--
 helios/templates/election_tallied.html     |  5 +++--
 helios/templates/election_view.html        | 15 ++++++++-------
 helios/templates/email/vote_body.txt       |  5 +++--
 helios/widgets.py                          |  9 +++++----
 settings.py                                |  6 ++++--
 9 files changed, 47 insertions(+), 27 deletions(-)

diff --git a/README.md b/README.md
index 626383f..4523eef 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ Helios is an end-to-end verifiable voting system.
 
 ## Info
 
-větve v gitu:
+Větve v gitu:
 
 - `original` je kopie https://github.com/benadida/helios-server
 - `master` je z `original` odvozená Pirátská verze nasazená v produkci
diff --git a/helios/fields.py b/helios/fields.py
index 8d8e885..e8eafff 100644
--- a/helios/fields.py
+++ b/helios/fields.py
@@ -1,6 +1,8 @@
 import datetime
+import pytz
 
 from django.forms import fields
+from django.utils.timezone import make_aware, make_naive
 
 from .widgets import SplitSelectDateTimeWidget
 
@@ -25,6 +27,8 @@ class SplitDateTimeField(fields.MultiValueField):
         if data_list:
             if not (data_list[0] and data_list[1]):
                 return None
-            return datetime.datetime.combine(*data_list)
+            value = datetime.datetime.combine(*data_list)
+            # do formuláře zadáváme Europe/Prague, ale do DB se ukládá naivní UTC
+            return make_naive(make_aware(value, pytz.timezone("Europe/Prague")), pytz.UTC)
         return None
 
diff --git a/helios/forms.py b/helios/forms.py
index f7540a5..b1d6687 100644
--- a/helios/forms.py
+++ b/helios/forms.py
@@ -3,8 +3,11 @@
 Forms for Helios
 """
 
+import pytz
+
 from django import forms
 from django.conf import settings
+from django.utils.timezone import make_aware, make_naive
 
 from .fields import SplitDateTimeField
 from .models import Election
@@ -21,18 +24,24 @@ class ElectionForm(forms.Form):
   randomize_answer_order = forms.BooleanField(required=False, initial=False, help_text=u'zvolte, pokud chcete, aby se každému voliči zobrazovaly odpovědi na otázky v náhodně zvoleném pořadí', label=u"Odpovědi v náhodném pořadí")
   private_p = forms.BooleanField(required=False, initial=False, label=u"Soukromé?", help_text=u'Soukromé hlasování je viditelné jen pro registrované voliče.')
   help_email = forms.CharField(required=False, initial="", label=u"E-mail pro nápovědu", help_text=u'e-mailová adresa, na kterou se budou voliči obracet s žádostmi o pomoc.')
-  voting_starts_at = SplitDateTimeField(help_text = u'datum a čas zahájení hlasování; v UTC, takže oproti časovému pásmu ČR je menší o 1 hodinu v zimním, resp. o 2 hodiny v letním čase',
-                                   widget=SplitSelectDateTimeWidget, required=False, label=u"Hlasování začíná v")
-  voting_ends_at = SplitDateTimeField(help_text = u'datum a čas ukončení hlasování; v UTC, takže oproti časovému pásmu ČR je menší o 1 v zimním, resp. o 2 hodiny v letním čase',
-                                   widget=SplitSelectDateTimeWidget, required=False, label=u"Hlasování končí v")
+  voting_starts_at = SplitDateTimeField(help_text = u'datum a čas zahájení hlasování v lokálním čase!', widget=SplitSelectDateTimeWidget, required=False, label=u"Hlasování začíná v")
+  voting_ends_at = SplitDateTimeField(help_text = u'datum a čas ukončení hlasování v lokálním čase!', widget=SplitSelectDateTimeWidget, required=False, label=u"Hlasování končí v")
 
   if settings.ALLOW_ELECTION_INFO_URL:
     election_info_url = forms.CharField(required=False, initial="", label=u"URL pro stažení informací o hlasování", help_text=u"URL dokumentu ve formátu PDF, obsahujícího doplňkové informace k hlasování, např. životopisy a profily kandidátů")
 
-  pass
+  def __init__(self, data=None, *args, **kwargs):
+    # v DB se ukládá naivní UTC, ale do formuláře potřebujeme převést zpět na Europe/Prague
+    if data:
+      tz = pytz.timezone("Europe/Prague")
+      if "voting_starts_at" in data:
+        data["voting_starts_at"] = make_naive(make_aware(data["voting_starts_at"], pytz.UTC), tz)
+      if "voting_ends_at" in data:
+        data["voting_ends_at"] = make_naive(make_aware(data["voting_ends_at"], pytz.UTC), tz)
+    super().__init__(data, *args, **kwargs)
 
 class ElectionTimeExtensionForm(forms.Form):
-  voting_extended_until = SplitDateTimeField(help_text = u'datum a čas prodlouženého ukončení hlasování; v UTC',
+  voting_extended_until = SplitDateTimeField(help_text = u'datum a čas prodlouženého ukončení hlasování; v lokálním čase!',
                                    widget=SplitSelectDateTimeWidget, required=False, label=u"Hlasování prodlouženo do")
 
 class EmailVotersForm(forms.Form):
diff --git a/helios/templates/election_not_started.html b/helios/templates/election_not_started.html
index 3735a12..18cd5c9 100644
--- a/helios/templates/election_not_started.html
+++ b/helios/templates/election_not_started.html
@@ -1,4 +1,5 @@
 {% extends TEMPLATE_BASE %}
+{% load tz %}
 
 {% block content %}
 
@@ -9,8 +10,8 @@
   </p>
 
   <p>
-      {% if election.voting_start_at %}začátek hlasování: {{election.voting_start_at}}<br />{% endif %}
-      {% if election.voting_end_at %}konec hlasování: {{election.voting_end_at}}<br />{% endif %}
+      {% if election.voting_start_at %}začátek hlasování: {{election.voting_start_at|timezone:"Europe/Prague"}}<br />{% endif %}
+      {% if election.voting_end_at %}konec hlasování: {{election.voting_end_at|timezone:"Europe/Prague"}}<br />{% endif %}
   </p>
 
   <p>
diff --git a/helios/templates/election_tallied.html b/helios/templates/election_tallied.html
index dd729d2..b46d215 100644
--- a/helios/templates/election_tallied.html
+++ b/helios/templates/election_tallied.html
@@ -1,4 +1,5 @@
 {% extends TEMPLATE_BASE %}
+{% load tz %}
 
 {% block content %}
 
@@ -9,8 +10,8 @@
   </p>
 
   <p>
-      {% if election.voting_start_at %}začátek hlasování: {{election.voting_start_at}}<br />{% endif %}
-      {% if election.voting_end_at %}konec hlasování: {{election.voting_end_at}}<br />{% endif %}
+      {% if election.voting_start_at %}začátek hlasování: {{election.voting_start_at|timezone:"Europe/Prague"}}<br />{% endif %}
+      {% if election.voting_end_at %}konec hlasování: {{election.voting_end_at|timezone:"Europe/Prague"}}<br />{% endif %}
   </p>
 
   <p>
diff --git a/helios/templates/election_view.html b/helios/templates/election_view.html
index 0d04aa6..6617500 100644
--- a/helios/templates/election_view.html
+++ b/helios/templates/election_view.html
@@ -1,4 +1,5 @@
 {% extends TEMPLATE_BASE %}
+{% load tz %}
 {% block title %}{{election.name}}{% endblock %}
 {% block content %}
 <div style="float: left; margin-right: 50px;">
@@ -48,8 +49,8 @@ toto {{election.election_type}} <u>není</u> zobrazeno na titulní stránce.
 
 <p>
 {% if election.help_email and admin_p%}Email pro nápovědu: {{election.help_email}}<br />{% endif %}
-{% if election.voting_start_at %}Hlasování začíná: {{election.voting_start_at}}<br />{% endif %}
-{% if election.voting_end_at %}Hlasování končí: {{election.voting_end_at}}<br />{% endif %}
+{% if election.voting_start_at %}Hlasování začíná: {{election.voting_start_at|timezone:"Europe/Prague"}}<br />{% endif %}
+{% if election.voting_end_at %}Hlasování končí: {{election.voting_end_at|timezone:"Europe/Prague"}}<br />{% endif %}
 </p>
 
 {% if election.election_info_url %}
@@ -97,7 +98,7 @@ toto {{election.election_type}} <u>není</u> zobrazeno na titulní stránce.
 <br />
 {% if election.voting_starts_at %}
 jakmile to učiníte, hlasování bude připraveno k odevzdávání hlasovacích lístků a bude automaticky zahájeno<br />
-v {{election.voting_starts_at}}, podle vašeho nastavení.
+v {{election.voting_starts_at|timezone:"Europe/Prague"}}, podle vašeho nastavení.
 {% else %}
 jakmile to učiníte, hlasování bude ihned zahájeno.
 {% endif %}
@@ -158,7 +159,7 @@ Poté uvidíte výsledek pouze vy jakožto zakladatel hlasování.
 {% if show_result %}
 {% if election.result_released_at %}
 <span class="highlight-box round">
-    Toto hlasování bylo ukončeno. Výsledek byl zveřejněn {{election.result_released_at}}. Celkem bylo odevzdáno {{election.num_cast_votes}} hlasů.
+Toto hlasování bylo ukončeno. Výsledek byl zveřejněn {{election.result_released_at|timezone:"Europe/Prague"}}. Celkem bylo odevzdáno {{election.num_cast_votes}} hlasů.
 </span><br /><br /><br />
 {% endif %}
 
@@ -185,12 +186,12 @@ Poté uvidíte výsledek pouze vy jakožto zakladatel hlasování.
 <br />
 <br />
 {% if election.voting_extended_until %}
-Toto {{election.election_type}} mělo původně skončit v {{election.voting_ends_at}} (UTC),<br />
-ale bylo prodlouĹľeno do {{ election.voting_extended_until }} (UTC).
+Toto {{election.election_type}} mělo původně skončit v {{election.voting_ends_at|timezone:"Europe/Prague"}},<br />
+ale bylo prodlouĹľeno do {{ election.voting_extended_until|timezone:"Europe/Prague" }}.
 {% else %}
 {% if election.voting_ends_at %}
 <br />
-Toto {{election.election_type}} bude uzavřeno v {{election.voting_ends_at}} (UTC).
+Toto {{election.election_type}} bude uzavřeno v {{election.voting_ends_at|timezone:"Europe/Prague"}}.
 {% else %}
 Toto {{election.election_type}} bude uzavřeno podle rozhodnutí jeho zakladatele.
 {% endif %}
diff --git a/helios/templates/email/vote_body.txt b/helios/templates/email/vote_body.txt
index 8503778..6e42d08 100644
--- a/helios/templates/email/vote_body.txt
+++ b/helios/templates/email/vote_body.txt
@@ -1,11 +1,12 @@
+{% load tz %}
 Vážený {{voter.name}},
 
 {{custom_message|safe}}
 
 URL Hlasování: {{election_vote_url}}
 Otisk Hlasováné: {{voter.election.hash}}
-{% if election.voting_start_at %}Hlasování začíná {{election.voting_start_at}}
-{% endif %}{% if election.voting_end_at %}Hlasování končí {{election.voting_end_at}}
+{% if election.voting_start_at %}Hlasování začíná {{election.voting_start_at|timezone:"Europe/Prague"}}
+{% endif %}{% if election.voting_end_at %}Hlasování končí {{election.voting_end_at|timezone:"Europe/Prague"}}
 {% endif %}
 
 {% ifequal voter.voter_type "password" %}
diff --git a/helios/widgets.py b/helios/widgets.py
index ba271ce..3e8c36d 100644
--- a/helios/widgets.py
+++ b/helios/widgets.py
@@ -95,16 +95,17 @@ class SelectTimeWidget(Widget):
                                 self.meridiem_val = 'a.m.'
                         else:
                             self.meridiem_val = None
-                    
+
 
         # If we're doing a 12-hr clock, there will be a meridiem value, so make sure the
         # hours get printed correctly
         if self.twelve_hr and self.meridiem_val:
             if self.meridiem_val.lower().startswith('p') and hour_val > 12 and hour_val < 24:
                 hour_val = hour_val % 12
-        elif hour_val == 0:
-            hour_val = 12
-            
+        # BUG this breaks 24h clock on 0 hours
+        # elif hour_val == 0:
+        #     hour_val = 12
+
         output = []
         if 'id' in self.attrs:
             id_ = self.attrs['id']
diff --git a/settings.py b/settings.py
index 05fed0b..a158572 100644
--- a/settings.py
+++ b/settings.py
@@ -62,11 +62,11 @@ if get_from_env('DATABASE_URL', None):
 # although not all choices may be available on all operating systems.
 # If running in a Windows environment this must be set to the same as your
 # system time zone.
-TIME_ZONE = 'America/Los_Angeles'
+TIME_ZONE = 'UTC'
 
 # Language code for this installation. All choices can be found here:
 # http://www.i18nguy.com/unicode/language-identifiers.html
-LANGUAGE_CODE = 'en-us'
+LANGUAGE_CODE = 'cs-cz'
 
 SITE_ID = 1
 
@@ -74,6 +74,8 @@ SITE_ID = 1
 # to load the internationalization machinery.
 USE_I18N = True
 
+USE_L10N = True
+
 # Absolute path to the directory that holds media.
 # Example: "/home/media/media.lawrence.com/"
 MEDIA_ROOT = ''
-- 
GitLab