vkaba...@redhat.com writes: > From: Veronika Kabatova <vkaba...@redhat.com> > > Patchwork saves patches, comments etc with UTC timezone and reports > this time when opening the patch details. However, internally generated > processes such as events are reported with the instance's local time. > There's nothing wrong with that and making PW timezone-aware would add > useless complexity, but in a world-wide collaboration a lot of confusion > may arise as the timezone is not reported at all. Instance's local time > might be very different from the local time of CI integrating with PW, > which is different from the local time of person dealing with it etc. > > For submission views, just add 'UTC' strings. For API responses, change > the serializers to still use ISO 8601 format but with timezone > information suffix [Z+-HHMM] - UTC for submissions and PW's local > timezone for internal events.
Thanks for your patch! It looks very comprehensive - thanks especially for including documentation!! Would you prefer if we made internal events UTC? I think that might be preferable, but I haven't thought about it in great detail just yet. > > Responses are returned as JSON. Blank fields are returned as ``null``, rather > -than being omitted. Timestamps use the ISO 8601 format:: > +than being omitted. Returned timestamps use the ISO 8601 format including the > +timezone information:: > > - YYYY-MM-DDTHH:MM:SSZ > + YYYY-MM-DDTHH:MM:SS[Z+-HH:MM] > + > +.. note:: > + > + Timezone suffix is only provided in responses to avoid users' confusion > and > + to allow proper coordination with CI tooling. Underlying data are > + timezone-naive and can't be filtered based on timezones, therefore > requests > + made to the API should not use the timezone suffix. > In particular, I wonder if using UTC everywhere would make things like this simpler? As I said I haven't put an enormous deal of thought into it, and I haven't done a lot of work with TZ in python before so I don't know if it would be crazy to implement. I just wanted your thoughts on it. Regards, Daniel > Requests should use either query parameters or form-data, depending on the > method. Further information is provided `below <rest_parameters>`__. > diff --git a/docs/deployment/installation.rst > b/docs/deployment/installation.rst > index a570dd8..38e12d1 100644 > --- a/docs/deployment/installation.rst > +++ b/docs/deployment/installation.rst > @@ -173,7 +173,8 @@ this also: > .. code-block:: shell > > $ sudo apt-get install -y python3-django python3-psycopg2 \ > - python3-djangorestframework python3-django-filters > + python3-djangorestframework python3-django-filters \ > + python3-tz > > .. tip:: > > diff --git a/patchwork/api/check.py b/patchwork/api/check.py > index b37d6e0..4854a82 100644 > --- a/patchwork/api/check.py > +++ b/patchwork/api/check.py > @@ -17,6 +17,7 @@ > # along with Patchwork; if not, write to the Free Software > # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > > +from django.utils import timezone > from rest_framework.exceptions import PermissionDenied > from rest_framework.generics import ListCreateAPIView > from rest_framework.generics import RetrieveAPIView > @@ -63,6 +64,7 @@ class CheckSerializer(HyperlinkedModelSerializer): > return super(CheckSerializer, self).run_validation(data) > > def to_representation(self, instance): > + instance.date = timezone.make_aware(instance.date) > data = super(CheckSerializer, self).to_representation(instance) > data['state'] = instance.get_state_display() > return data > diff --git a/patchwork/api/cover.py b/patchwork/api/cover.py > index 1064504..2de4ea2 100644 > --- a/patchwork/api/cover.py > +++ b/patchwork/api/cover.py > @@ -19,6 +19,7 @@ > > import email.parser > > +from pytz import UTC > from rest_framework.generics import ListAPIView > from rest_framework.generics import RetrieveAPIView > from rest_framework.serializers import HyperlinkedModelSerializer > @@ -37,6 +38,10 @@ class > CoverLetterListSerializer(HyperlinkedModelSerializer): > submitter = PersonSerializer(read_only=True) > mbox = SerializerMethodField() > series = SeriesSerializer(many=True, read_only=True) > + date = SerializerMethodField() > + > + def get_date(self, instance): > + return instance.date.replace(tzinfo=UTC) > > def get_mbox(self, instance): > request = self.context.get('request') > diff --git a/patchwork/api/embedded.py b/patchwork/api/embedded.py > index 7b5090a..bc915fe 100644 > --- a/patchwork/api/embedded.py > +++ b/patchwork/api/embedded.py > @@ -23,6 +23,8 @@ A collection of serializers. None of the serializers here > should reference > nested fields. > """ > > +from django.utils import timezone > +from pytz import UTC > from rest_framework.serializers import CharField > from rest_framework.serializers import HyperlinkedModelSerializer > from rest_framework.serializers import SerializerMethodField > @@ -61,6 +63,7 @@ class CheckSerializer(HyperlinkedModelSerializer): > url = CheckHyperlinkedIdentityField('api-check-detail') > > def to_representation(self, instance): > + instance.date = timezone.make_aware(instance.date) > data = super(CheckSerializer, self).to_representation(instance) > data['state'] = instance.get_state_display() > return data > @@ -77,6 +80,10 @@ class CheckSerializer(HyperlinkedModelSerializer): > > class CoverLetterSerializer(MboxMixin, HyperlinkedModelSerializer): > > + def to_representation(self, instance): > + instance.date = instance.date.replace(tzinfo=UTC) > + return super(CoverLetterSerializer, self).to_representation(instance) > + > class Meta: > model = models.CoverLetter > fields = ('id', 'url', 'msgid', 'date', 'name', 'mbox') > @@ -88,6 +95,10 @@ class CoverLetterSerializer(MboxMixin, > HyperlinkedModelSerializer): > > class PatchSerializer(MboxMixin, HyperlinkedModelSerializer): > > + def to_representation(self, instance): > + instance.date = instance.date.replace(tzinfo=UTC) > + return super(PatchSerializer, self).to_representation(instance) > + > class Meta: > model = models.Patch > fields = ('id', 'url', 'msgid', 'date', 'name', 'mbox') > @@ -126,6 +137,10 @@ class ProjectSerializer(HyperlinkedModelSerializer): > > class SeriesSerializer(MboxMixin, HyperlinkedModelSerializer): > > + def to_representation(self, instance): > + instance.date = instance.date.replace(tzinfo=UTC) > + return super(SeriesSerializer, self).to_representation(instance) > + > class Meta: > model = models.Series > fields = ('id', 'url', 'date', 'name', 'version', 'mbox') > diff --git a/patchwork/api/event.py b/patchwork/api/event.py > index 0d97af2..22c747e 100644 > --- a/patchwork/api/event.py > +++ b/patchwork/api/event.py > @@ -19,6 +19,7 @@ > > from collections import OrderedDict > > +from django.utils import timezone > from rest_framework.generics import ListAPIView > from rest_framework.serializers import ModelSerializer > from rest_framework.serializers import SerializerMethodField > @@ -61,6 +62,7 @@ class EventSerializer(ModelSerializer): > } > > def to_representation(self, instance): > + instance.date = timezone.make_aware(instance.date) > data = super(EventSerializer, self).to_representation(instance) > payload = OrderedDict() > kept_fields = self._category_map[instance.category] + [ > diff --git a/patchwork/api/patch.py b/patchwork/api/patch.py > index 115feff..57aa97d 100644 > --- a/patchwork/api/patch.py > +++ b/patchwork/api/patch.py > @@ -20,6 +20,7 @@ > import email.parser > > from django.utils.translation import ugettext_lazy as _ > +from pytz import UTC > from rest_framework.generics import ListAPIView > from rest_framework.generics import RetrieveUpdateAPIView > from rest_framework.relations import RelatedField > @@ -86,6 +87,10 @@ class PatchListSerializer(HyperlinkedModelSerializer): > check = SerializerMethodField() > checks = SerializerMethodField() > tags = SerializerMethodField() > + date = SerializerMethodField() > + > + def get_date(self, instance): > + return instance.date.replace(tzinfo=UTC) > > def get_mbox(self, instance): > request = self.context.get('request') > diff --git a/patchwork/api/series.py b/patchwork/api/series.py > index b5f4450..562bf45 100644 > --- a/patchwork/api/series.py > +++ b/patchwork/api/series.py > @@ -17,6 +17,8 @@ > # along with Patchwork; if not, write to the Free Software > # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > > + > +from pytz import UTC > from rest_framework.generics import ListAPIView > from rest_framework.generics import RetrieveAPIView > from rest_framework.serializers import HyperlinkedModelSerializer > @@ -38,6 +40,10 @@ class SeriesSerializer(HyperlinkedModelSerializer): > mbox = SerializerMethodField() > cover_letter = CoverLetterSerializer(read_only=True) > patches = PatchSerializer(read_only=True, many=True) > + date = SerializerMethodField() > + > + def get_date(self, instance): > + return instance.date.replace(tzinfo=UTC) > > def get_mbox(self, instance): > request = self.context.get('request') > diff --git a/patchwork/templates/patchwork/submission.html > b/patchwork/templates/patchwork/submission.html > index 6ed20c3..e817713 100644 > --- a/patchwork/templates/patchwork/submission.html > +++ b/patchwork/templates/patchwork/submission.html > @@ -255,7 +255,7 @@ function toggle_div(link_id, headers_id) > <div class="comment"> > <div class="meta"> > <span>{{ submission.submitter|personify:project }}</span> > - <span class="pull-right">{{ submission.date }}</span> > + <span class="pull-right">{{ submission.date }} UTC</span> > </div> > <pre class="content"> > {{ submission|commentsyntax }} > @@ -271,7 +271,7 @@ function toggle_div(link_id, headers_id) > <div class="comment"> > <div class="meta"> > <span>{{ item.submitter|personify:project }}</span> > - <span class="pull-right">{{ item.date }} | <a href="{% url > 'comment-redirect' comment_id=item.id %}" > + <span class="pull-right">{{ item.date }} UTC | <a href="{% url > 'comment-redirect' comment_id=item.id %}" > >#{{ forloop.counter }}</a></span> > </div> > <pre class="content"> > diff --git a/patchwork/views/xmlrpc.py b/patchwork/views/xmlrpc.py > index 93f6035..d59779d 100644 > --- a/patchwork/views/xmlrpc.py > +++ b/patchwork/views/xmlrpc.py > @@ -38,6 +38,7 @@ from django.views.decorators.csrf import csrf_exempt > from django.utils import six > from django.utils.six.moves import xmlrpc_client > from django.utils.six.moves.xmlrpc_server import SimpleXMLRPCDispatcher > +from pytz import UTC > > from patchwork.compat import reverse > from patchwork.models import Check > @@ -248,7 +249,7 @@ def patch_to_dict(obj): > > { > 'id': 1 > - 'date': '2000-12-31 00:11:22', > + 'date': '2000-12-31 00:11:22+00:00', > 'filename': 'Fix-all-the-bugs.patch', > 'msgid': '<blu438-smtp36690bbdd2ce71a7138b0825...@phx.gbl>', > 'name': "Fix all the bugs", > @@ -273,7 +274,7 @@ def patch_to_dict(obj): > """ > return { > 'id': obj.id, > - 'date': six.text_type(obj.date).encode('utf-8'), > + 'date': six.text_type(obj.date.replace(tzinfo=UTC)).encode('utf-8'), > 'filename': obj.filename, > 'msgid': obj.msgid, > 'name': obj.name, > @@ -319,7 +320,7 @@ def check_to_dict(obj): > object which is OK to send to the client.""" > return { > 'id': obj.id, > - 'date': six.text_type(obj.date).encode('utf-8'), > + 'date': six.text_type(obj.date.replace(tzinfo=UTC)).encode('utf-8'), > 'patch': six.text_type(obj.patch).encode('utf-8'), > 'patch_id': obj.patch_id, > 'user': six.text_type(obj.user).encode('utf-8'), > diff --git a/releasenotes/notes/report-timezones-47e2095efcb37a84.yaml > b/releasenotes/notes/report-timezones-47e2095efcb37a84.yaml > new file mode 100644 > index 0000000..ad1dae4 > --- /dev/null > +++ b/releasenotes/notes/report-timezones-47e2095efcb37a84.yaml > @@ -0,0 +1,4 @@ > +--- > +other: > + - | > + Report timezones in web UI and APIs. > diff --git a/requirements-prod.txt b/requirements-prod.txt > index dd2040d..1e5207e 100644 > --- a/requirements-prod.txt > +++ b/requirements-prod.txt > @@ -3,3 +3,4 @@ djangorestframework>=3.4,<3.7 > django-filter>=1.0,<1.1 > psycopg2>=2.7,<2.8 > sqlparse==0.2.3 > +pytz>=2016.10 > diff --git a/requirements-test.txt b/requirements-test.txt > index 141cf66..632eb4c 100644 > --- a/requirements-test.txt > +++ b/requirements-test.txt > @@ -3,3 +3,4 @@ psycopg2>=2.7,<2.8 > django-debug-toolbar==1.8 > python-dateutil>2.0,<3.0 > selenium>=3.0,<4.0 > +pytz>=2016.10 > -- > 2.13.6 > > _______________________________________________ > Patchwork mailing list > Patchwork@lists.ozlabs.org > https://lists.ozlabs.org/listinfo/patchwork _______________________________________________ Patchwork mailing list Patchwork@lists.ozlabs.org https://lists.ozlabs.org/listinfo/patchwork