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