Skip to content

Commit 5494ba4

Browse files
committed
Fix asDateTime incorrect fractional seconds parsing (#102)
Fixes: #81
1 parent 71f486e commit 5494ba4

2 files changed

Lines changed: 38 additions & 9 deletions

File tree

‎pyasn1/type/useful.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ def asDateTime(self):
102102
text, _, ms = text.partition(',')
103103

104104
try:
105-
ms = int(ms) * 1000
105+
# Normalize variable-length fraction to microseconds
106+
ms = int(ms.ljust(6, '0')[:6])
106107

107108
except ValueError:
108109
raise error.PyAsn1Error('bad sub-second time specification %s' % self)
@@ -139,8 +140,8 @@ def fromDateTime(cls, dt):
139140
new instance of |ASN.1| value
140141
"""
141142
text = dt.strftime(cls._yearsDigits == 4 and '%Y%m%d%H%M%S' or '%y%m%d%H%M%S')
142-
if cls._hasSubsecond:
143-
text += '.%d' % (dt.microsecond // 1000)
143+
if cls._hasSubsecond and dt.microsecond:
144+
text += ('.%06d' % dt.microsecond).rstrip('0')
144145

145146
if dt.utcoffset():
146147
seconds = dt.utcoffset().seconds

‎tests/type/test_useful.py‎

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,13 @@ class ObjectDescriptorTestCase(BaseTestCase):
4141
class GeneralizedTimeTestCase(BaseTestCase):
4242

4343
def testFromDateTime(self):
44-
assert useful.GeneralizedTime.fromDateTime(datetime.datetime(2017, 7, 11, 0, 1, 2, 3000, tzinfo=UTC)) == '20170711000102.3Z'
44+
assert useful.GeneralizedTime.fromDateTime(datetime.datetime(2017, 7, 11, 0, 1, 2, 3000, tzinfo=UTC)) == '20170711000102.003Z'
45+
46+
def testFromDateTimeNoMicrosecond(self):
47+
assert useful.GeneralizedTime.fromDateTime(datetime.datetime(2017, 7, 11, 0, 1, 2, 0, tzinfo=UTC)) == '20170711000102Z'
48+
49+
def testFromDateTimeFullMicrosecond(self):
50+
assert useful.GeneralizedTime.fromDateTime(datetime.datetime(2017, 7, 11, 0, 1, 2, 123456, tzinfo=UTC)) == '20170711000102.123456Z'
4551

4652
def testToDateTime0(self):
4753
assert datetime.datetime(2017, 7, 11, 0, 1, 2) == useful.GeneralizedTime('20170711000102').asDateTime
@@ -50,26 +56,48 @@ def testToDateTime1(self):
5056
assert datetime.datetime(2017, 7, 11, 0, 1, 2, tzinfo=UTC) == useful.GeneralizedTime('20170711000102Z').asDateTime
5157

5258
def testToDateTime2(self):
53-
assert datetime.datetime(2017, 7, 11, 0, 1, 2, 3000, tzinfo=UTC) == useful.GeneralizedTime('20170711000102.3Z').asDateTime
59+
assert datetime.datetime(2017, 7, 11, 0, 1, 2, 300000, tzinfo=UTC) == useful.GeneralizedTime('20170711000102.3Z').asDateTime
5460

5561
def testToDateTime3(self):
56-
assert datetime.datetime(2017, 7, 11, 0, 1, 2, 3000, tzinfo=UTC) == useful.GeneralizedTime('20170711000102,3Z').asDateTime
62+
assert datetime.datetime(2017, 7, 11, 0, 1, 2, 300000, tzinfo=UTC) == useful.GeneralizedTime('20170711000102,3Z').asDateTime
5763

5864
def testToDateTime4(self):
59-
assert datetime.datetime(2017, 7, 11, 0, 1, 2, 3000, tzinfo=UTC) == useful.GeneralizedTime('20170711000102.3+0000').asDateTime
65+
assert datetime.datetime(2017, 7, 11, 0, 1, 2, 300000, tzinfo=UTC) == useful.GeneralizedTime('20170711000102.3+0000').asDateTime
6066

6167
def testToDateTime5(self):
62-
assert datetime.datetime(2017, 7, 11, 0, 1, 2, 3000, tzinfo=UTC2) == useful.GeneralizedTime('20170711000102.3+0200').asDateTime
68+
assert datetime.datetime(2017, 7, 11, 0, 1, 2, 300000, tzinfo=UTC2) == useful.GeneralizedTime('20170711000102.3+0200').asDateTime
6369

6470
def testToDateTime6(self):
65-
assert datetime.datetime(2017, 7, 11, 0, 1, 2, 3000, tzinfo=UTC2) == useful.GeneralizedTime('20170711000102.3+02').asDateTime
71+
assert datetime.datetime(2017, 7, 11, 0, 1, 2, 300000, tzinfo=UTC2) == useful.GeneralizedTime('20170711000102.3+02').asDateTime
6672

6773
def testToDateTime7(self):
6874
assert datetime.datetime(2017, 7, 11, 0, 1) == useful.GeneralizedTime('201707110001').asDateTime
6975

7076
def testToDateTime8(self):
7177
assert datetime.datetime(2017, 7, 11, 0) == useful.GeneralizedTime('2017071100').asDateTime
7278

79+
def testToDateTimeTwoDigitFraction(self):
80+
"""Two-digit fraction .18 means 0.18 seconds = 180000 microseconds (issue #81)."""
81+
assert datetime.datetime(2016, 8, 6, 11, 59, 52, 180000, tzinfo=UTC) == useful.GeneralizedTime('20160806115952.18Z').asDateTime
82+
83+
def testToDateTimeThreeDigitFraction(self):
84+
"""Three-digit fraction .123 means 0.123 seconds = 123000 microseconds."""
85+
assert datetime.datetime(2017, 7, 11, 0, 1, 2, 123000, tzinfo=UTC) == useful.GeneralizedTime('20170711000102.123Z').asDateTime
86+
87+
def testToDateTimeSixDigitFraction(self):
88+
"""Six-digit fraction .123456 means 123456 microseconds."""
89+
assert datetime.datetime(2017, 7, 11, 0, 1, 2, 123456, tzinfo=UTC) == useful.GeneralizedTime('20170711000102.123456Z').asDateTime
90+
91+
def testToDateTimeSevenDigitFractionTruncated(self):
92+
"""Seven-digit fraction .1234567 is truncated to 123456 microseconds."""
93+
assert datetime.datetime(2017, 7, 11, 0, 1, 2, 123456, tzinfo=UTC) == useful.GeneralizedTime('20170711000102.1234567Z').asDateTime
94+
95+
def testFromDateTimeRoundTrip(self):
96+
"""fromDateTime and asDateTime should round-trip correctly."""
97+
original = datetime.datetime(2017, 7, 11, 0, 1, 2, 180000, tzinfo=UTC)
98+
encoded = useful.GeneralizedTime.fromDateTime(original)
99+
assert original == encoded.asDateTime
100+
73101
def testCopy(self):
74102
dt = useful.GeneralizedTime("20170916234254+0130").asDateTime
75103
assert dt == deepcopy(dt)

0 commit comments

Comments
 (0)