Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

convert date/times to UTC when exporting, use python-datetime for parsing date strings #493

Merged
merged 4 commits into from Apr 17, 2018

Conversation

thet
Copy link
Member

@thet thet commented Feb 11, 2018

Most of the test failures for Plone 5.2 were related to missing initialization of ZServer, which is fixed but not yet merged here: plone/plone.testing#45

Other Plone 5.2 failures were related to date/timezone related offsets which did not match (I do not yet understand why this failures only were apparent for Plone 5.2).

Investigating this, I found out that the date/times were not consistently converted to UTC when exporting. I recall some discussions about this including some contradictory statements including from myself in #253

However, I think since ISO8601 lacks of a concept of timezone names which are necessary to do date math when daylight saving time boundaries are included (timezone offsets do not allow to conclude to DST dates), the most practical thing would be to convert all date/times to UTC when exporting.

This PR addresses this:

  • Convert all datetime, DateTime and time instances to UTC before serializing.
  • Use python-dateutil instead of DateTime to parse date strings when de-serializing.

please comment, if this approach is going in the right direction!

@coveralls
Copy link

Coverage Status

Coverage increased (+0.02%) to 96.776% when pulling 238280f on thet-plone52 into 2429162 on master.

@coveralls
Copy link

coveralls commented Feb 11, 2018

Coverage Status

Coverage increased (+0.02%) to 96.532% when pulling 724b294 on thet-plone52 into c92802a on master.

@thet
Copy link
Member Author

thet commented Feb 19, 2018

ping @tisto

@tisto
Copy link
Sponsor Member

tisto commented Feb 19, 2018

@sneridagh can you please have a look. I won't be able to look into it this week...

@tisto
Copy link
Sponsor Member

tisto commented Mar 3, 2018

@thet I don't see any changes in the docs. Does that mean that we only changed internal and the API stays the same? I am trying to understand if this is an internal bugfix or a breaking change.

@tisto
Copy link
Sponsor Member

tisto commented Mar 3, 2018

@buchi would you mind having a quick look here? I would like to hear your opinion as well since you also worked on this...

@tisto
Copy link
Sponsor Member

tisto commented Mar 7, 2018

@thet ping :)

@thet
Copy link
Member Author

thet commented Mar 7, 2018

@tisto IMO this is a breaking change, because as the following test shows, all date-times are converted to UTC before serializing:
https://github.com/plone/plone.restapi/pull/493/files#diff-42b8e56bfe820a154bfca3683e40731fR146

Is this change important? Yes, if date arithmetics should be done. E.g. for recurring events or calculating the duration of two date/times and ONLY if a DST change is in between (which we have to assume that there is).

Will a conversion to UTC fix DST change issues? Actually no. It just delegates the issue to the consumer of the serialized value. In case of Plone there are some hints how to handle it, because we (partly) support timezones since 5.0.

I'm not 100% confident about the relevance of this PR again.
It's a no-brainer in Python but a problem due to lack of timezone support in ISO8601 in JavaScript.
I feel the need to consult others about that. @tisto, let's talk in Berlin about this again.
Does @regebro has a opinion on that?
I'll try to also contact https://stackoverflow.com/users/634824/matt-johnson - he seems to be very deep into this topic.

Some more resources about this topic:

https://codeofmatt.com/content/images/best-dates-ever.pdf
https://codeofmatt.com/2015/02/07/what-is-a-time-zone/
https://codeofmatt.com/2015/06/17/javascript-date-parsing-changes-in-es6/
http://www.cl.cam.ac.uk/~mgk25/iso-time.html

@tisto tisto added this to the 2.0.0 milestone Mar 19, 2018
@tisto
Copy link
Sponsor Member

tisto commented Mar 20, 2018

@thet @sneridagh @buchi I would like to release plone.restapi 2.0.0 soon. Any chance we can sort this out? As said, this goes over my head since I haven't worked with date/times lately. The pull request looks ok to me. I'd like to have a second or third opinion and approval on this before merging and releasing.

@buchi
Copy link
Member

buchi commented Mar 21, 2018

This is definitely a breaking change!

First, let's recapitulate what the ideal solution for handling of dates and times would be:

  • Always store in UTC (timezone naive)
  • Always do calculations in UTC
  • Return (GET) in UTC
  • Accept (POST/PATCH) any TZ
  • Display in local TZ

Unfortunately Plone does not store all dates and times in UTC. plone.app.event and Archetypes store them timezone aware. This is something that can't be fixed in plone.restapi.

So returning dates and times in UTC seems to be the way to go. But there are a few caveats because of not storing them in UTC:

  • If you do a GET and then a PATCH with the data from the GET, you potentially modify the dates, which is not what one would expect.
  • How do you get the timezone of an event (p.a.event)? The best approach would be to store the timezone in a separate field. But that's not the case now.

Btw, whats the reason for switching to python-dateutil for parsing of ISO8601 datetime strings? Anything that Zope's DateTime doesn't right?

@thet
Copy link
Member Author

thet commented Mar 21, 2018

@buchi I don't agree with the ideal solution:

  • IMO storing date/times timezone aware is the right thing, as this a a integral information of any timezone aware date/time and also what humans expect. We had long discussion preceding plone.app.event 2.0, where we switched from UTC to timezoned date/times - it allowed for a simpler API when working with datetime attributes and it was a prerequisite to get rid of the data_postprocessor in form handling for those who remember that.
  • IMO the lack of the concept of timezones in ISO8601 which only knows about timezone offsets is the real problem which we have to work around.

When de/serializing from/to JSON it would probably be good to store the IANA/Olson timezone identifier for each UTC-converted date.

The switch to python-dateuti (which is a dependency of plone.app.event) l for parsing date strings is mostly to get rid of DateTime in the long run. It's not needed anymore in Python and it gets the timezones wrong as it only handles timezone offsets (doesn't apply anymore).

@buchi
Copy link
Member

buchi commented Mar 21, 2018

@thet Storing dates/times in UTC has the following benefits:

  • No ambiguity (e.g. 2017-10-29 02:30 in TZ Europe/Zurich is ambiguous, could be 2017-10-29 01:30 or 2017-10-29 00:30 in UTC)
  • No invalid dates (e.g. 2018-03-25 2:30 in TZ Europe/Zurich is not a valid date)
  • Calculating durations is simple
  • Sorting is simple

IMHO timezones or local time is something that's needed only for input and output (display).
In most cases you want to display an event in your local timezone, not in a timezone defined by the event.

Also have a look at the documentation of pytz regarding local time:
"The preferred way of dealing with times is to always work in UTC, converting to localtime only when generating output to be read by humans."
https://pypi.python.org/pypi/pytz/2018.3/#problems-with-localtime

But let's get back to your PR. I'm fine if we merge it. But there are still the two issues I mentioned earlier:

  • Plone stores the timezone for the start date of an event, but there's no way to get it using the REST API. I would suggest to add it to the serialization, e.g. { "start.timezone": "Europe/Zurich" }. On the other side I would not use the timezone information anyway as we display dates in local time. Thus we can keep it as-is.
  • There's a discrepancy between GET and POST/PATCH. That's something we have to live with and for me it's not a problem. @tisto ?

Regarding Zope's DateTime I don't believe that we will get rid of it that soon. But if python-dateutil is a dependency of Plone anyway, I don't mind.

@tisto
Copy link
Sponsor Member

tisto commented Mar 22, 2018

"If you do a GET and then a PATCH with the data from the GET, you potentially modify the dates, which is not what one would expect." That is indeed a problem because it breaks the REST assumptions, caching, etc.

Do we really modify content, when reading a content object in Plone? That sounds really wrong...

@buchi
Copy link
Member

buchi commented Mar 22, 2018

@tisto I think you misunderstood that. The GET will not modify anything but the PATCH could despite I'm providing the same value as I got with GET. But I was wrong I guess. Due to #415 TZ info should be readded during deserialization, if the previous date had a TZ.

@tisto
Copy link
Sponsor Member

tisto commented Mar 23, 2018

@buchi ok, that's a relief. :)

@sneridagh
Copy link
Member

Please, also allow me to weigh in, IMO, I'm also +1 with @buchi ideal solution...

Handling UTC is always desirable. Libraries also expect somehow that, then convert it to local TZ (e.g momentjs).

What about the differences of implementation on p.a.e. for Plone 4/5? When I tried to fix this, I always got hit by this wall...

@buchi which detail of your solution is then out with the current PR?

@tisto
Copy link
Sponsor Member

tisto commented Apr 3, 2018

@sneridagh @buchi @thet I would like to make a 2.0.0 release in the next days. This is the only issue that holds me back from merging all the open PRs with breaking changes and do a release. It would be really cool if we could figure that out in the next days.

thet added 4 commits April 4, 2018 23:26
Convert all datetime, DateTime and time instances to UTC before serializing.
Use python-dateutil instead of DateTime to parse date strings when de-serializing.
except (SyntaxError, DateTimeError):
# TODO: should really a timezone naive time be returned?
# using ``timetz()`` would be timezone aware.
value = dateutil.parser.parse(value).time()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@buchi @sneridagh @tisto I'm not sure, if time should always converted to timezone-naive. Pythons datetime.time and thus zope.schema Time field supports the tzinfo objects.

value = value.replace(microsecond=0)
iso = value.isoformat()
# if value.tzinfo:
# # Use "Z" instead of a timezone offset of "+00:00" to indicate UTC.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@buchi @sneridagh @tisto I'm favoring the "Z" notation of indicating the UTC, but that's maybe a personal flavor and I don't have a strong preference here. I'll remove this code block, agreed?

@thet
Copy link
Member Author

thet commented Apr 4, 2018

I might begin to agree that dates stored in UTC are more practical. While discussing the issue with @Rotonen he brought up an argument that date calculations with two different versions of pytz can produce different results. Which is bad. Although the timezone rules are part of the tzinfo objects and thus not necessarily prone to errors with different versions of pytz ... UTC dates have advantages.

However the UTC vs timezoned isn't actually an issue as @buchi mentioned above:
timezone conversion to the field's target zone is handled here: https://github.com/plone/plone.restapi/blob/master/src/plone/restapi/deserializer/dxfields.py#L77

Do we need to hande Archetypes content also?
I'd like to have a second opinion on the two inline code comments in this PR.
Then this PR is ready to merge from my point of view.

@tisto
Copy link
Sponsor Member

tisto commented Apr 17, 2018

@thet would you mind adding a few lines to the upgrade guide regarding this? http://plonerestapi.readthedocs.io/en/latest/upgrade-guide.html#

@tisto tisto merged commit bdfd96b into master Apr 17, 2018
@tisto tisto deleted the thet-plone52 branch April 17, 2018 20:42
phgross added a commit to 4teamwork/opengever.core that referenced this pull request Aug 9, 2018
in docs and tests.

With the latest plone.restapi version all datetimes are now converted
to UTC datetimes (see plone/plone.restapi#493
for more information)
phgross added a commit to 4teamwork/opengever.core that referenced this pull request Aug 9, 2018
in docs and tests.

With the latest plone.restapi version all datetimes are now converted
to UTC datetimes (see plone/plone.restapi#493
for more information)
phgross added a commit to 4teamwork/opengever.core that referenced this pull request Aug 9, 2018
in docs and tests.

With the latest plone.restapi version all datetimes are now converted
to UTC datetimes (see plone/plone.restapi#493
for more information)
phgross added a commit to 4teamwork/opengever.core that referenced this pull request Aug 9, 2018
in docs and tests.

With the latest plone.restapi version all datetimes are now converted
to UTC datetimes (see plone/plone.restapi#493
for more information)
@tisto tisto mentioned this pull request Feb 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants