From 8a46f3c66a21467151d998d3ec8398946859e6e6 Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Mon, 2 Jul 2018 15:07:53 +0200 Subject: [PATCH] Add proper time zone support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Until now event timestamps were saved in the server’s time zone. Now they are saved in UTC, considering the time zone set by the creator of the event. --- calsocial/forms.py | 63 ++++++++++++++++++++++++++++- calsocial/models.py | 13 ++++++ calsocial/templates/month-view.html | 22 +++++++++- 3 files changed, 95 insertions(+), 3 deletions(-) diff --git a/calsocial/forms.py b/calsocial/forms.py index 1274948..91e10fa 100644 --- a/calsocial/forms.py +++ b/calsocial/forms.py @@ -1,6 +1,7 @@ from flask_babelex import lazy_gettext as _ from flask_wtf import FlaskForm -from wtforms import PasswordField, StringField, BooleanField +import pytz +from wtforms import BooleanField, PasswordField, SelectField, StringField from wtforms.ext.dateutil.fields import DateTimeField from wtforms.validators import DataRequired, Email, ValidationError from wtforms.widgets import TextArea @@ -17,14 +18,72 @@ class RegistrationForm(FlaskForm): raise ValidationError(_('The two passwords must match!')) +class TimezoneField(SelectField): + def __init__(self, *args, **kwargs): + kwargs.update({ + 'choices': [ + (pytz.timezone(tz), tz.replace('_', ' ')) + for tz in pytz.common_timezones + ], + }) + + SelectField.__init__(self, *args, **kwargs) + + def process_formdata(self, valuelist): + if not valuelist: + self.data = None + + return + + try: + self.data = pytz.timezone(valuelist[0]) + except pytz.exceptions.UnknownTimeZoneError: + self.data = None + + raise ValueError('Unknown time zone') + + @staticmethod + def is_pytz_instance(value): + return value is pytz.UTC or isinstance(value, pytz.tzinfo.BaseTzInfo) + + def process_data(self, value): + if value is None: + self.data = None + + return + + if is_pytz_instance(value): + self.data = value + + return + + try: + self.data = pytz.timezone(value) + except Exception as exc: + raise ValueError(f'Unknown time zone {value}') + + def iter_choices(self): + for value, label in self.choices: + yield (value, label, value == self.data) + + class EventForm(FlaskForm): title = StringField(_('Title'), validators=[DataRequired()]) - time_zone = StringField(_('Time zone'), validators=[DataRequired()]) + time_zone = TimezoneField(_('Time zone'), validators=[DataRequired()]) start_time = DateTimeField(_('Start time'), validators=[DataRequired()]) end_time = DateTimeField(_('End time'), validators=[DataRequired()]) all_day = BooleanField(_('All day')) description = StringField(_('Description'), widget=TextArea()) + def populate_obj(self, obj): + FlaskForm.populate_obj(self, obj) + + tz = self.time_zone.data + + obj.time_zone = str(tz) + obj.start_time = tz.localize(self.start_time.data).astimezone(pytz.utc) + obj.end_time = tz.localize(self.end_time.data).astimezone(pytz.utc) + def validate_end_time(self, field): if field.data < self.start_time.data: raise ValidationError(_('End time must be later than start time!')) diff --git a/calsocial/models.py b/calsocial/models.py index c3fb466..e18d628 100644 --- a/calsocial/models.py +++ b/calsocial/models.py @@ -83,5 +83,18 @@ class Event(db.Model): #: The description of the event description = db.Column(db.UnicodeText()) + def __as_tz(self, timestamp): + from pytz import timezone, utc + + return utc.localize(timestamp).astimezone(timezone(self.time_zone)) + + @property + def start_time_tz(self): + return self.__as_tz(self.start_time) + + @property + def end_time_tz(self): + return self.__as_tz(self.end_time) + def __repr__(self): return f'' diff --git a/calsocial/templates/month-view.html b/calsocial/templates/month-view.html index 8400c67..1294b77 100644 --- a/calsocial/templates/month-view.html +++ b/calsocial/templates/month-view.html @@ -6,6 +6,7 @@ table.calendar { width: 100%; border-collapse: collapse; + table-layout: fixed; } tr.month > td { @@ -46,14 +47,31 @@ background-color: #d8d8d8; } - td > div.event { + tr.week > td > div.event { border: 1px solid green; background-color: white; border-radius: 2px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + + tr.sizer > td { + width: 14.2857%; + height: 0; } + + + + + + + + +
« {{ calendar.prev_year_year }} @@ -101,6 +119,8 @@ {{ day.day }} {% for event in calendar.day_events(day, user=current_user) %}
+ {{ event.start_time_tz.strftime('%H:%M') }}–{{ event.end_time_tz.strftime('%H:%M') }} + ({{ event.time_zone }}) {{ event.title }}
{% endfor %}