| Index: third_party/google-endpoints/strict_rfc3339.py
|
| diff --git a/third_party/google-endpoints/strict_rfc3339.py b/third_party/google-endpoints/strict_rfc3339.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..4558c01b3d7da61925446069458bb5a7dc34f1d3
|
| --- /dev/null
|
| +++ b/third_party/google-endpoints/strict_rfc3339.py
|
| @@ -0,0 +1,202 @@
|
| +# Copyright 2012 (C) Daniel Richman, Adam Greig
|
| +#
|
| +# This file is part of strict_rfc3339.
|
| +#
|
| +# strict_rfc3339 is free software: you can redistribute it and/or modify
|
| +# it under the terms of the GNU General Public License as published by
|
| +# the Free Software Foundation, either version 3 of the License, or
|
| +# (at your option) any later version.
|
| +#
|
| +# strict_rfc3339 is distributed in the hope that it will be useful,
|
| +# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| +# GNU General Public License for more details.
|
| +#
|
| +# You should have received a copy of the GNU General Public License
|
| +# along with strict_rfc3339. If not, see <http://www.gnu.org/licenses/>.
|
| +
|
| +"""
|
| +Super simple lightweight RFC3339 functions
|
| +"""
|
| +
|
| +import re
|
| +import time
|
| +import calendar
|
| +
|
| +__all__ = ["validate_rfc3339",
|
| + "InvalidRFC3339Error",
|
| + "rfc3339_to_timestamp",
|
| + "timestamp_to_rfc3339_utcoffset",
|
| + "timestamp_to_rfc3339_localoffset",
|
| + "now_to_rfc3339_utcoffset",
|
| + "now_to_rfc3339_localoffset"]
|
| +
|
| +rfc3339_regex = re.compile(
|
| + r"^(\d\d\d\d)\-(\d\d)\-(\d\d)T"
|
| + r"(\d\d):(\d\d):(\d\d)(\.\d+)?(Z|([+\-])(\d\d):(\d\d))$")
|
| +
|
| +
|
| +def validate_rfc3339(datestring):
|
| + """Check an RFC3339 string is valid via a regex and some range checks"""
|
| +
|
| + m = rfc3339_regex.match(datestring)
|
| + if m is None:
|
| + return False
|
| +
|
| + groups = m.groups()
|
| +
|
| + year, month, day, hour, minute, second = [int(i) for i in groups[:6]]
|
| +
|
| + if not 1 <= year <= 9999:
|
| + # Have to reject this, unfortunately (despite it being OK by rfc3339):
|
| + # calendar.timegm/calendar.monthrange can't cope (since datetime can't)
|
| + return False
|
| +
|
| + if not 1 <= month <= 12:
|
| + return False
|
| +
|
| + (_, max_day) = calendar.monthrange(year, month)
|
| + if not 1 <= day <= max_day:
|
| + return False
|
| +
|
| + if not (0 <= hour <= 23 and 0 <= minute <= 59 and 0 <= second <= 59):
|
| + # forbid leap seconds :-(. See README
|
| + return False
|
| +
|
| + if groups[7] != "Z":
|
| + (offset_sign, offset_hours, offset_mins) = groups[8:]
|
| + if not (0 <= int(offset_hours) <= 23 and 0 <= int(offset_mins) <= 59):
|
| + return False
|
| +
|
| + # all OK
|
| + return True
|
| +
|
| +
|
| +class InvalidRFC3339Error(ValueError):
|
| + """Subclass of ValueError thrown by rfc3339_to_timestamp"""
|
| + pass
|
| +
|
| +
|
| +def rfc3339_to_timestamp(datestring):
|
| + """Convert an RFC3339 date-time string to a UTC UNIX timestamp"""
|
| +
|
| + if not validate_rfc3339(datestring):
|
| + raise InvalidRFC3339Error
|
| +
|
| + groups = rfc3339_regex.match(datestring).groups()
|
| +
|
| + time_tuple = [int(p) for p in groups[:6]]
|
| + timestamp = calendar.timegm(time_tuple)
|
| +
|
| + seconds_part = groups[6]
|
| + if seconds_part is not None:
|
| + timestamp += float("0" + seconds_part)
|
| +
|
| + if groups[7] != "Z":
|
| + (offset_sign, offset_hours, offset_mins) = groups[8:]
|
| + offset_seconds = int(offset_hours) * 3600 + int(offset_mins) * 60
|
| + if offset_sign == '-':
|
| + offset_seconds = -offset_seconds
|
| + timestamp -= offset_seconds
|
| +
|
| + return timestamp
|
| +
|
| +
|
| +def _seconds_and_microseconds(timestamp):
|
| + """
|
| + Split a floating point timestamp into an integer number of seconds since
|
| + the epoch, and an integer number of microseconds (having rounded to the
|
| + nearest microsecond).
|
| +
|
| + If `_seconds_and_microseconds(x) = (y, z)` then the following holds (up to
|
| + the error introduced by floating point operations):
|
| +
|
| + * `x = y + z / 1_000_000.`
|
| + * `0 <= z < 1_000_000.`
|
| + """
|
| +
|
| + if isinstance(timestamp, int):
|
| + return (timestamp, 0)
|
| + else:
|
| + timestamp_us = int(round(timestamp * 1e6))
|
| + return divmod(timestamp_us, 1000000)
|
| +
|
| +def _make_datestring_start(time_tuple, microseconds):
|
| + ds_format = "{0:04d}-{1:02d}-{2:02d}T{3:02d}:{4:02d}:{5:02d}"
|
| + datestring = ds_format.format(*time_tuple)
|
| +
|
| + seconds_part_str = "{0:06d}".format(microseconds)
|
| + # There used to be a bug here where it could be 1000000
|
| + assert len(seconds_part_str) == 6 and seconds_part_str[0] != '-'
|
| + seconds_part_str = seconds_part_str.rstrip("0")
|
| + if seconds_part_str != "":
|
| + datestring += "." + seconds_part_str
|
| +
|
| + return datestring
|
| +
|
| +
|
| +def timestamp_to_rfc3339_utcoffset(timestamp):
|
| + """Convert a UTC UNIX timestamp to RFC3339, with the offset as 'Z'"""
|
| +
|
| + seconds, microseconds = _seconds_and_microseconds(timestamp)
|
| +
|
| + time_tuple = time.gmtime(seconds)
|
| + datestring = _make_datestring_start(time_tuple, microseconds)
|
| + datestring += "Z"
|
| +
|
| + assert abs(rfc3339_to_timestamp(datestring) - timestamp) < 0.000001
|
| + return datestring
|
| +
|
| +
|
| +def timestamp_to_rfc3339_localoffset(timestamp):
|
| + """
|
| + Convert a UTC UNIX timestamp to RFC3339, using the local offset.
|
| +
|
| + localtime() provides the time parts. The difference between gmtime and
|
| + localtime tells us the offset.
|
| + """
|
| +
|
| + seconds, microseconds = _seconds_and_microseconds(timestamp)
|
| +
|
| + time_tuple = time.localtime(seconds)
|
| + datestring = _make_datestring_start(time_tuple, microseconds)
|
| +
|
| + gm_time_tuple = time.gmtime(seconds)
|
| + offset = calendar.timegm(time_tuple) - calendar.timegm(gm_time_tuple)
|
| +
|
| + if abs(offset) % 60 != 0:
|
| + raise ValueError("Your local offset is not a whole minute")
|
| +
|
| + offset_minutes = abs(offset) // 60
|
| + offset_hours = offset_minutes // 60
|
| + offset_minutes %= 60
|
| +
|
| + offset_string = "{0:02d}:{1:02d}".format(offset_hours, offset_minutes)
|
| +
|
| + if offset < 0:
|
| + datestring += "-"
|
| + else:
|
| + datestring += "+"
|
| +
|
| + datestring += offset_string
|
| + assert abs(rfc3339_to_timestamp(datestring) - timestamp) < 0.000001
|
| +
|
| + return datestring
|
| +
|
| +
|
| +def now_to_rfc3339_utcoffset(integer=True):
|
| + """Convert the current time to RFC3339, with the offset as 'Z'"""
|
| +
|
| + timestamp = time.time()
|
| + if integer:
|
| + timestamp = int(timestamp)
|
| + return timestamp_to_rfc3339_utcoffset(timestamp)
|
| +
|
| +
|
| +def now_to_rfc3339_localoffset(integer=True):
|
| + """Convert the current time to RFC3339, using the local offset."""
|
| +
|
| + timestamp = time.time()
|
| + if integer:
|
| + timestamp = int(timestamp)
|
| + return timestamp_to_rfc3339_localoffset(timestamp)
|
|
|