-
Notifications
You must be signed in to change notification settings - Fork 445
Refactor datetime #518
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
Open
c00kiemon5ter
wants to merge
23
commits into
IdentityPython:master
Choose a base branch
from
c00kiemon5ter:refactor-datetime
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Refactor datetime #518
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
d3346ed
Add saml2.datetime module
c00kiemon5ter b84fc0f
Update assertion to use the datetime module
c00kiemon5ter eb35f6b
Update authn to use the datetime module
c00kiemon5ter cbfbb1c
Update cache to use the datetime module
c00kiemon5ter c22811a
Update cert to use the datetime module
c00kiemon5ter 4a69ad8
Update client to use the datetime module
c00kiemon5ter 44e9912
Update client_base to use the datetime module
c00kiemon5ter 45c5141
Update entity to use the datetime module
c00kiemon5ter 8477c4c
Update httputil and httpbase to use the datetime module
c00kiemon5ter 9dd3200
Update mcache to use the datetime module
c00kiemon5ter c903afc
Update mdbcache to use the datetime module
c00kiemon5ter b3f2f6d
Update mdstore to use the datetime module
c00kiemon5ter e113510
Update metadata to use the datetime module
c00kiemon5ter 6159d9a
Update population to use the datetime module
c00kiemon5ter 9e5f374
Update validate to use the datetime module
c00kiemon5ter 79321d0
Make issue_instant_ok part of validate module
c00kiemon5ter 7f9eea6
Update request to use the datetime module
c00kiemon5ter e46ad9f
Update response to use the datetime module
c00kiemon5ter 922d21f
Update s_utils to use the datetime module
c00kiemon5ter 4a0cee1
Update sigver to use the datetime module
c00kiemon5ter ce66189
Update tests
c00kiemon5ter 9b9240e
Remove time_util module
c00kiemon5ter e6fb4a3
Update documentation
c00kiemon5ter File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
"""Functions to hide compatibility issues between python2 and python3. | ||
|
||
This module encapsulates all compatibility issues between python2 and python3. | ||
Many of the compatibility issues are solved by the python-future module. Any | ||
other workarounds will be implemented under this module. | ||
""" | ||
|
||
import datetime as _datetime | ||
import time as _time | ||
|
||
from aniso8601.timezone import UTCOffset as _Timezone | ||
|
||
import future.utils as _future_utils | ||
|
||
|
||
def timestamp(date_time): | ||
"""Return the POSIX timestamp from the datetime object. | ||
|
||
Python3 provides the `.timestamp()` method call on datetime.datetime | ||
objects, but python2 does not. For python2 we must compute the timestamp | ||
ourselves. The formula has been backported from python3. | ||
|
||
The parameter `date_time` is expected to be of type: datetime.timedelta | ||
|
||
The object returned is of type: float | ||
""" | ||
if hasattr(date_time, 'timestamp'): | ||
timestamp = date_time.timestamp() | ||
else: | ||
timestamp = _time.mktime(date_time.timetuple()) | ||
timestamp += date_time.microsecond / 1e6 | ||
return timestamp | ||
|
||
|
||
def _utc_timezone(): | ||
"""Return a UTC-timezone tzinfo instance. | ||
|
||
Python3 provides a UTC-timezone tzinfo instance through the | ||
datetime.timezone module. Python2 does not define any timezone instance; it | ||
only provides the tzinfo abstract base class. For python2 the instance is | ||
generated with the _Timezone class. | ||
""" | ||
try: | ||
utc_timezone = _datetime.timezone.utc | ||
except AttributeError as e: | ||
utc_timezone = _Timezone(name='UTC', minutes=0) | ||
finally: | ||
return utc_timezone | ||
|
||
|
||
UTC_TIMEZONE = _utc_timezone() | ||
raise_from = _future_utils.raise_from |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
"""Datetime structures and operations. | ||
|
||
This module encapsulates the structures that define datetime, time unit, | ||
duration and timezone objects, their relation and the operations that can be | ||
done upon them. Should these structures change all affected components should | ||
be under this module. | ||
|
||
There are three layers of specifications that define the structure and | ||
behaviour of time constructs for SAML2: | ||
|
||
- The SAML2-core specification - section 1.3.3 Time Values. | ||
Reference: http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf | ||
|
||
- The W3C XML Schema Datatypes - section 3.2.7 dateTime. | ||
Reference: https://www.w3.org/TR/xmlschema-2/#dateTime | ||
|
||
[notable] W3C Date and Time Formats defines a profile of ISO 8601. | ||
Reference: https://www.w3.org/TR/NOTE-datetime | ||
|
||
- The ISO 8601 standard upon which the dateTime datatype is based. | ||
Reference: https://en.wikipedia.org/wiki/ISO_8601 | ||
|
||
[notable] Most systems implement rfc3339; a profile of ISO 8601. | ||
Reference: https://tools.ietf.org/html/rfc3339 | ||
|
||
Finally, further clarification was requested and in the following thread an | ||
answer was given by a member of the SAML Technical Committee: | ||
https://lists.oasis-open.org/archives/saml-dev/201310/msg00001.html | ||
|
||
To comply with the specifications, the existing implementations and the | ||
"unofficial errata" in the thread above, the following have been decided: | ||
|
||
- all ISO 8601 formats that can be parsed are accepted and converted to UTC. | ||
|
||
- if no timezone information is present, it is assumed that the other party is | ||
following the current wording of the SAML2-core specification - the time is | ||
assumed to be in UTC already, but "with no timezone component." | ||
|
||
- the datetime object produced are always in UTC timezone, that can be | ||
represented as a string of ISO 8601 combined date and time format with | ||
extended notation, where the timezone component is always present and | ||
represented by the military timezone symbol 'Z'. | ||
""" | ||
|
||
import enum as _enum | ||
from datetime import datetime as _datetime | ||
|
||
from aniso8601 import parse_datetime as _datetime_parser | ||
|
||
import saml2.compat | ||
from saml2.datetime import duration | ||
from saml2.datetime import errors | ||
from saml2.datetime import timezone | ||
|
||
|
||
def parse(data): | ||
"""Return a datetime object in UTC timezone from the given data. | ||
|
||
If timezone information is available the datetime object will be converted | ||
to UTC timezone. If no timezone information is available, it will be | ||
assumed to be in UTC timezone and that information will be added. | ||
|
||
The parameter `data` is expected to be of type: | ||
- datetime.datetime: a datetime.datetime object | ||
- str: a string in ISO 8601 combined date and time format with extended | ||
notation | ||
- int: a number representing a POSIX timestamp | ||
- float: a number representing a POSIX timestamp | ||
|
||
The object returned is of type: datetime.datetime | ||
""" | ||
try: | ||
parse = _parsers[type(data)] | ||
except KeyError as e: | ||
saml2.compat.raise_from(errors.DatetimeFactoryError(data), e) | ||
|
||
try: | ||
value = parse(data) | ||
except (ValueError, TypeError, NotImplementedError) as e: | ||
saml2.compat.raise_from(errors.DatetimeParseError(data), e) | ||
|
||
utc_timezone = timezone.UTC_TIMEZONE | ||
if value.tzinfo is None: | ||
value = value.replace(tzinfo=utc_timezone) | ||
if value.tzinfo is not utc_timezone: | ||
value = value.astimezone(utc_timezone) | ||
|
||
return value | ||
|
||
|
||
def fromtimestamp(timestamp): | ||
"""Return a datetime object in UTC timezone from the given POSIX timestamp. | ||
|
||
The parameter `timestamp` is expected to be of type: int|float | ||
|
||
The object returned is of type: datetime.datetime | ||
""" | ||
return _datetime.fromtimestamp(timestamp, timezone.UTC_TIMEZONE) | ||
|
||
|
||
def to_string(date_time_obj): | ||
"""Return an ISO 8601 string representation of the datetime object. | ||
|
||
Return the given datetime object -as returned by the `parse` function- | ||
represented as a string of ISO 8601 combined date and time format with | ||
extended notation, where the timezone component is always present and | ||
represented by the military timezone symbol 'Z'. | ||
|
||
The parameter `date_time_obj` is expected to be of type: datetime.datetime | ||
|
||
The object returned is of type: str | ||
""" | ||
return date_time_obj.isoformat().replace( | ||
timezone.UTC_OFFSET_SYMBOL, | ||
timezone.UTC_MILITARY_TIMEZONE_SYMBOL) | ||
|
||
|
||
class unit(_enum.Enum): | ||
"""Time unit representations and constructors. | ||
|
||
Available units are: | ||
- days | ||
- seconds | ||
- microseconds | ||
- milliseconds | ||
- minutes | ||
- hours | ||
- weeks | ||
|
||
Both plural and singular forms are available. Time units can be used to | ||
create objects that describe a period of time or signify the type of unit | ||
of a given amount. | ||
|
||
Usage example: | ||
|
||
* The difference between two datetime objects is a period of time: | ||
|
||
``` | ||
import saml2.datetime | ||
|
||
dt1 = saml2.datetime.parse('2018-01-25T08:45:00Z') | ||
dt2 = saml2.datetime.parse('2018-01-25T08:40:00Z') | ||
delta = dt1 - dt2 | ||
period = saml2.datetime.unit.minute(5) | ||
|
||
assert period == delta | ||
``` | ||
|
||
* Signify the type of unit for an amount: | ||
|
||
``` | ||
import saml2.datetime | ||
from saml2.datetime import duration | ||
|
||
period = saml2.datetime.duration.parse({ | ||
saml2.datetime.unit.seconds: 5 | ||
}) | ||
|
||
assert saml2.datetime.unit.seconds(5) == period | ||
``` | ||
|
||
The object returned is of type: datetime.timedelta | ||
""" | ||
|
||
day = 'days' | ||
days = 'days' | ||
second = 'seconds' | ||
seconds = 'seconds' | ||
microsecond = 'microseconds' | ||
microseconds = 'microseconds' | ||
millisecond = 'milliseconds' | ||
milliseconds = 'milliseconds' | ||
minute = 'minutes' | ||
minutes = 'minutes' | ||
hour = 'hours' | ||
hours = 'hours' | ||
week = 'weeks' | ||
weeks = 'weeks' | ||
|
||
def __str__(self): | ||
"""Return the string representation time unit types. | ||
|
||
The object returned is of type: str | ||
""" | ||
return self.value | ||
|
||
def __call__(self, amount): | ||
"""Return a period object of the specified time unit and amount. | ||
|
||
The parameter `amount` is expected to be of type: int|float | ||
|
||
The object returned is of type: datetime.timedelta | ||
""" | ||
return duration.parse({self.value: amount}) | ||
|
||
|
||
_parsers = { | ||
_datetime: lambda x: x, | ||
str: _datetime_parser, | ||
int: fromtimestamp, | ||
float: fromtimestamp, | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
"""This module provides a parser for datetime strings in ANS.1 UTCTime format. | ||
|
||
The parser produces datetime objects that can be used with the other datetime | ||
modules. | ||
""" | ||
|
||
from datetime import datetime as _datetime | ||
|
||
import saml2.datetime | ||
|
||
|
||
_ASN1_UTCTime_FORMAT = '%Y%m%d%H%M%SZ' | ||
|
||
|
||
def parse(data): | ||
"""Return a datetime object from the given ASN.1 UTCTime formatted string. | ||
|
||
The datetime object will be in UTC timezone. | ||
|
||
The parameter `data` is expected to be of type: str | ||
|
||
The object returned is of type: datetime.datetime | ||
""" | ||
value = _datetime.strptime(data, _ASN1_UTCTime_FORMAT) | ||
value = saml2.datetime.parse(value) | ||
return value |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think your current comments about arguments and types reads better but would you (also?) consider type hints for IDEs?
Something like:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like Python3 type hints, as they are inline and they refer to actual types - in contrast to types written as strings (wether in docstrings or comments).
I was thinking about experimenting with function annotations which should work with Python2 and still allow us to signal actual types rather than strings. It does have the drawback that we must type the parameter names, but I think it is still better.
In the meantime, I can do the conversion to the format that the rest of the code follows.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would also prefer the Python 3 annotations and I haven't seen the function annotations that you link to. I wasn't thrilled about the syntax of those either but if you find it workable in your experiment I would be for them.