1#
2# This file is part of pyasn1 software.
3#
4# Copyright (c) 2005-2019, Ilya Etingof <etingof@gmail.com>
5# License: http://snmplabs.com/pyasn1/license.html
6#
7import datetime
8
9from pyasn1 import error
10from pyasn1.compat import dateandtime
11from pyasn1.compat import string
12from pyasn1.type import char
13from pyasn1.type import tag
14from pyasn1.type import univ
15
16__all__ = ['ObjectDescriptor', 'GeneralizedTime', 'UTCTime']
17
18NoValue = univ.NoValue
19noValue = univ.noValue
20
21
22class ObjectDescriptor(char.GraphicString):
23    __doc__ = char.GraphicString.__doc__
24
25    #: Default :py:class:`~pyasn1.type.tag.TagSet` object for |ASN.1| objects
26    tagSet = char.GraphicString.tagSet.tagImplicitly(
27        tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 7)
28    )
29
30    # Optimization for faster codec lookup
31    typeId = char.GraphicString.getTypeId()
32
33
34class TimeMixIn(object):
35
36    _yearsDigits = 4
37    _hasSubsecond = False
38    _optionalMinutes = False
39    _shortTZ = False
40
41    class FixedOffset(datetime.tzinfo):
42        """Fixed offset in minutes east from UTC."""
43
44        # defaulted arguments required
45        # https: // docs.python.org / 2.3 / lib / datetime - tzinfo.html
46        def __init__(self, offset=0, name='UTC'):
47            self.__offset = datetime.timedelta(minutes=offset)
48            self.__name = name
49
50        def utcoffset(self, dt):
51            return self.__offset
52
53        def tzname(self, dt):
54            return self.__name
55
56        def dst(self, dt):
57            return datetime.timedelta(0)
58
59    UTC = FixedOffset()
60
61    @property
62    def asDateTime(self):
63        """Create :py:class:`datetime.datetime` object from a |ASN.1| object.
64
65        Returns
66        -------
67        :
68            new instance of :py:class:`datetime.datetime` object
69        """
70        text = str(self)
71        if text.endswith('Z'):
72            tzinfo = TimeMixIn.UTC
73            text = text[:-1]
74
75        elif '-' in text or '+' in text:
76            if '+' in text:
77                text, plusminus, tz = string.partition(text, '+')
78            else:
79                text, plusminus, tz = string.partition(text, '-')
80
81            if self._shortTZ and len(tz) == 2:
82                tz += '00'
83
84            if len(tz) != 4:
85                raise error.PyAsn1Error('malformed time zone offset %s' % tz)
86
87            try:
88                minutes = int(tz[:2]) * 60 + int(tz[2:])
89                if plusminus == '-':
90                    minutes *= -1
91
92            except ValueError:
93                raise error.PyAsn1Error('unknown time specification %s' % self)
94
95            tzinfo = TimeMixIn.FixedOffset(minutes, '?')
96
97        else:
98            tzinfo = None
99
100        if '.' in text or ',' in text:
101            if '.' in text:
102                text, _, ms = string.partition(text, '.')
103            else:
104                text, _, ms = string.partition(text, ',')
105
106            try:
107                ms = int(ms) * 1000
108
109            except ValueError:
110                raise error.PyAsn1Error('bad sub-second time specification %s' % self)
111
112        else:
113            ms = 0
114
115        if self._optionalMinutes and len(text) - self._yearsDigits == 6:
116            text += '0000'
117        elif len(text) - self._yearsDigits == 8:
118            text += '00'
119
120        try:
121            dt = dateandtime.strptime(text, self._yearsDigits == 4 and '%Y%m%d%H%M%S' or '%y%m%d%H%M%S')
122
123        except ValueError:
124            raise error.PyAsn1Error('malformed datetime format %s' % self)
125
126        return dt.replace(microsecond=ms, tzinfo=tzinfo)
127
128    @classmethod
129    def fromDateTime(cls, dt):
130        """Create |ASN.1| object from a :py:class:`datetime.datetime` object.
131
132        Parameters
133        ----------
134        dt: :py:class:`datetime.datetime` object
135            The `datetime.datetime` object to initialize the |ASN.1| object
136            from
137
138        Returns
139        -------
140        :
141            new instance of |ASN.1| value
142        """
143        text = dt.strftime(cls._yearsDigits == 4 and '%Y%m%d%H%M%S' or '%y%m%d%H%M%S')
144        if cls._hasSubsecond:
145            text += '.%d' % (dt.microsecond // 1000)
146
147        if dt.utcoffset():
148            seconds = dt.utcoffset().seconds
149            if seconds < 0:
150                text += '-'
151            else:
152                text += '+'
153            text += '%.2d%.2d' % (seconds // 3600, seconds % 3600)
154        else:
155            text += 'Z'
156
157        return cls(text)
158
159
160class GeneralizedTime(char.VisibleString, TimeMixIn):
161    __doc__ = char.VisibleString.__doc__
162
163    #: Default :py:class:`~pyasn1.type.tag.TagSet` object for |ASN.1| objects
164    tagSet = char.VisibleString.tagSet.tagImplicitly(
165        tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 24)
166    )
167
168    # Optimization for faster codec lookup
169    typeId = char.VideotexString.getTypeId()
170
171    _yearsDigits = 4
172    _hasSubsecond = True
173    _optionalMinutes = True
174    _shortTZ = True
175
176
177class UTCTime(char.VisibleString, TimeMixIn):
178    __doc__ = char.VisibleString.__doc__
179
180    #: Default :py:class:`~pyasn1.type.tag.TagSet` object for |ASN.1| objects
181    tagSet = char.VisibleString.tagSet.tagImplicitly(
182        tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 23)
183    )
184
185    # Optimization for faster codec lookup
186    typeId = char.VideotexString.getTypeId()
187
188    _yearsDigits = 2
189    _hasSubsecond = False
190    _optionalMinutes = False
191    _shortTZ = False
192