encoding = "ascii"
asn1_type_name = "UTCTime"
- fmt = "%y%m%d%H%M%SZ"
-
def __init__(
self,
value=None,
if self._value is None:
self._value = default
+ def _strptime(self, value):
+ # datetime.strptime's format: %y%m%d%H%M%SZ
+ if len(value) != LEN_YYMMDDHHMMSSZ:
+ raise ValueError("invalid UTCTime length")
+ if value[-1] != "Z":
+ raise ValueError("non UTC timezone")
+ return datetime(
+ 2000 + int(value[:2]), # %y
+ int(value[2:4]), # %m
+ int(value[4:6]), # %d
+ int(value[6:8]), # %H
+ int(value[8:10]), # %M
+ int(value[10:12]), # %S
+ )
+
def _value_sanitize(self, value):
if isinstance(value, binary_type):
try:
value_decoded = value.decode("ascii")
except (UnicodeEncodeError, UnicodeDecodeError) as err:
raise DecodeError("invalid UTCTime encoding")
- if len(value_decoded) == LEN_YYMMDDHHMMSSZ:
- try:
- datetime.strptime(value_decoded, self.fmt)
- except (TypeError, ValueError):
- raise DecodeError("invalid UTCTime format")
- return value
- else:
- raise DecodeError("invalid UTCTime length")
+ try:
+ self._strptime(value_decoded)
+ except (TypeError, ValueError) as err:
+ raise DecodeError("invalid UTCTime format: %r" % err)
+ return value
if isinstance(value, self.__class__):
return value._value
if isinstance(value, datetime):
- return value.strftime(self.fmt).encode("ascii")
+ return value.strftime("%y%m%d%H%M%SZ").encode("ascii")
raise InvalidValueType((self.__class__, datetime))
def __eq__(self, their):
having < 50 years are treated as 20xx, 19xx otherwise, according
to X.509 recomendation.
"""
- value = datetime.strptime(self._value.decode("ascii"), self.fmt)
+ value = self._strptime(self._value.decode("ascii"))
year = value.year % 100
return datetime(
year=(2000 + year) if year < 50 else (1900 + year),
'20170930220750.000123Z'
>>> t = GeneralizedTime(datetime(2057, 9, 30, 22, 7, 50))
GeneralizedTime GeneralizedTime 2057-09-30T22:07:50
+
+ .. warning::
+
+ Only microsecond fractions are supported.
+ :py:exc:`pyderasn.DecodeError` will be raised during decoding of
+ higher precision values.
"""
__slots__ = ()
tag_default = tag_encode(24)
asn1_type_name = "GeneralizedTime"
- fmt = "%Y%m%d%H%M%SZ"
- fmt_ms = "%Y%m%d%H%M%S.%fZ"
+ def _strptime(self, value):
+ l = len(value)
+ if l == LEN_YYYYMMDDHHMMSSZ:
+ # datetime.strptime's format: %y%m%d%H%M%SZ
+ if value[-1] != "Z":
+ raise ValueError("non UTC timezone")
+ return datetime(
+ int(value[:4]), # %Y
+ int(value[4:6]), # %m
+ int(value[6:8]), # %d
+ int(value[8:10]), # %H
+ int(value[10:12]), # %M
+ int(value[12:14]), # %S
+ )
+ if l >= LEN_YYYYMMDDHHMMSSDMZ:
+ # datetime.strptime's format: %Y%m%d%H%M%S.%fZ
+ if value[-1] != "Z":
+ raise ValueError("non UTC timezone")
+ if value[14] != ".":
+ raise ValueError("no fractions separator")
+ us = value[15:-1]
+ if us[-1] == "0":
+ raise ValueError("trailing zero")
+ us_len = len(us)
+ if us_len > 6:
+ raise ValueError("only microsecond fractions are supported")
+ us = int(us + ("0" * (6 - us_len)))
+ decoded = datetime(
+ int(value[:4]), # %Y
+ int(value[4:6]), # %m
+ int(value[6:8]), # %d
+ int(value[8:10]), # %H
+ int(value[10:12]), # %M
+ int(value[12:14]), # %S
+ us, # %f
+ )
+ return decoded
+ raise ValueError("invalid GeneralizedTime length")
def _value_sanitize(self, value):
if isinstance(value, binary_type):
value_decoded = value.decode("ascii")
except (UnicodeEncodeError, UnicodeDecodeError) as err:
raise DecodeError("invalid GeneralizedTime encoding")
- if len(value_decoded) == LEN_YYYYMMDDHHMMSSZ:
- try:
- datetime.strptime(value_decoded, self.fmt)
- except (TypeError, ValueError):
- raise DecodeError(
- "invalid GeneralizedTime (without ms) format",
- )
- return value
- elif len(value_decoded) >= LEN_YYYYMMDDHHMMSSDMZ:
- try:
- datetime.strptime(value_decoded, self.fmt_ms)
- except (TypeError, ValueError):
- raise DecodeError(
- "invalid GeneralizedTime (with ms) format",
- )
- return value
- else:
+ try:
+ self._strptime(value_decoded)
+ except (TypeError, ValueError) as err:
raise DecodeError(
- "invalid GeneralizedTime length",
+ "invalid GeneralizedTime format: %r" % err,
klass=self.__class__,
)
+ return value
if isinstance(value, self.__class__):
return value._value
if isinstance(value, datetime):
- return value.strftime(
- self.fmt_ms if value.microsecond > 0 else self.fmt
- ).encode("ascii")
+ encoded = value.strftime("%Y%m%d%H%M%S")
+ if value.microsecond > 0:
+ encoded = encoded + (".%06d" % value.microsecond).rstrip("0")
+ return (encoded + "Z").encode("ascii")
raise InvalidValueType((self.__class__, datetime))
def todatetime(self):
- value = self._value.decode("ascii")
- if len(value) == LEN_YYYYMMDDHHMMSSZ:
- return datetime.strptime(value, self.fmt)
- return datetime.strptime(value, self.fmt_ms)
+ return self._strptime(self._value.decode("ascii"))
class GraphicString(CommonString):
pprint(obj, big_blobs=True, with_decode_path=True)
self.assertFalse(obj.expled)
obj_encoded = obj.encode()
+ self.additional_symmetric_check(value, obj_encoded)
obj_expled = obj(value, expl=tag_expl)
self.assertTrue(obj_expled.expled)
repr(obj_expled)
min_datetime = datetime(1900, 1, 1)
max_datetime = datetime(9999, 12, 31)
+ def additional_symmetric_check(self, value, obj_encoded):
+ if value.microsecond > 0:
+ self.assertFalse(obj_encoded.endswith(b"0Z"))
+
+ def test_x690_vector_valid(self):
+ for data in ((
+ b"19920521000000Z",
+ b"19920622123421Z",
+ b"19920722132100.3Z",
+ )):
+ GeneralizedTime(data)
+
+ def test_x690_vector_invalid(self):
+ for data in ((
+ b"19920520240000Z",
+ b"19920622123421.0Z",
+ b"19920722132100.30Z",
+ )):
+ with self.assertRaises(DecodeError) as err:
+ GeneralizedTime(data)
+ repr(err.exception)
+
def test_go_vectors_invalid(self):
for data in ((
b"20100102030405",
junk
)
+ def test_ns_fractions(self):
+ GeneralizedTime(b"20010101000000.000001Z")
+ with assertRaisesRegex(self, DecodeError, "only microsecond fractions"):
+ GeneralizedTime(b"20010101000000.0000001Z")
+
class TestUTCTime(TimeMixin, CommonMixin, TestCase):
base_klass = UTCTime
min_datetime = datetime(2000, 1, 1)
max_datetime = datetime(2049, 12, 31)
+ def additional_symmetric_check(self, value, obj_encoded):
+ pass
+
+ def test_x690_vector_valid(self):
+ for data in ((
+ b"920521000000Z",
+ b"920622123421Z",
+ b"920722132100Z",
+ )):
+ UTCTime(data)
+
+ def test_x690_vector_invalid(self):
+ for data in ((
+ b"920520240000Z",
+ b"9207221321Z",
+ )):
+ with self.assertRaises(DecodeError) as err:
+ UTCTime(data)
+ repr(err.exception)
+
def test_go_vectors_invalid(self):
for data in ((
b"a10506234540Z",