Passed
Pull Request — master (#34)
by
unknown
47s
created

jsons._datetime_impl.get_datetime_inst()   A

Complexity

Conditions 2

Size

Total Lines 9
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nop 2
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
"""
2
PRIVATE MODULE: do not import (from) it directly.
3
4
This module contains functionality for ``datetime`` related stuff.
5
"""
6
from datetime import datetime, timezone, timedelta, time, date
7
from typing import Union
8
from jsons._main_impl import RFC3339_DATETIME_PATTERN
9
from jsons.exceptions import DeserializationError
10
11
12
def to_str(
13
        dt: datetime,
14
        strip_microseconds: bool,
15
        fork_inst: type,
16
        pattern: str = RFC3339_DATETIME_PATTERN) -> str:
17
    offset = get_offset_str(dt, fork_inst)
18
    if not strip_microseconds and dt.microsecond:
19
        pattern += '.%f'
20
    return dt.strftime("{}{}".format(pattern, offset))
21
22
23
def get_offset_str(obj: Union[datetime, timedelta], fork_inst: type) -> str:
24
    """
25
    Return the textual offset of the given ``obj``.
26
    :param obj: a datetime or timedelta instance.
27
    :return: the offset following RFC3339.
28
    """
29
    func = (_datetime_offset_str if isinstance(obj, datetime)
30
            else _timedelta_offset_str)
31
    return func(obj, fork_inst)
32
33
34
def get_datetime_inst(obj: str, pattern: str) -> datetime:
35
    """
36
    Return a datetime instance with timezone info from the given ``obj``.
37
    :param obj: the ``obj`` in RFC3339 format.
38
    :param pattern: the datetime pattern.
39
    :return: a datetime instance with timezone info.
40
    """
41
    func = _datetime_utc_inst if obj[-1] == 'Z' else _datetime_offset_inst
42
    return func(obj, pattern)
43
44
45
def _datetime_offset_str(obj: datetime, fork_inst: type) -> str:
46
    """
47
    Return a textual offset (e.g. +01:00 or Z) for the given datetime.
48
    :param obj: the datetime instance.
49
    :return: the offset for ``obj``.
50
    """
51
    tzone = obj.tzinfo
52
    if not tzone:
53
        # datetimes without tzinfo are treated as local times.
54
        fork_inst._warn('The use of datetimes without timezone is dangerous '
55
                        'and can lead to undesired results.')
56
        tzone = datetime.now(timezone.utc).astimezone().tzinfo
57
        if tzone is timezone.utc or tzone.utc is timezone.utc:
58
            return '+00:00'
59
    offset = 'Z'
60
    if tzone.tzname(None) not in ('UTC', 'UTC+00:00'):
61
        tdelta = tzone.utcoffset(None) or tzone.adjusted_offset
62
        offset = _timedelta_offset_str(tdelta, fork_inst)
63
    return offset
64
65
66
def _timedelta_offset_str(tdelta: timedelta, fork_inst: type) -> str:
67
    """
68
    Return a textual offset (e.g. +01:00 or Z) for the given timedelta.
69
    :param tdelta: the timedelta instance.
70
    :return: the offset for ``tdelta``.
71
    """
72
    offset_s = tdelta.total_seconds()
73
    offset_h = int(offset_s / 3600)
74
    offset_m = int((offset_s / 60) % 60)
75
    offset_t = time(abs(offset_h), abs(offset_m))
76
    operator = '+' if offset_s > 0 else '-'
77
    offset = offset_t.strftime('{}%H:%M'.format(operator))
78
    return offset
79
80
81
def _datetime_utc_inst(obj: str, pattern: str) -> datetime:
82
    """
83
    Return a datetime instance with UTC timezone info.
84
    :param obj: a datetime in RFC3339 format.
85
    :param pattern: the datetime pattern that is used.
86
    :return: a datetime instance with timezone info.
87
    """
88
    dattim_str = obj[0:-1]
89
    dattim_obj = datetime.strptime(dattim_str, pattern)
90
    return _new_datetime(dattim_obj.date(), dattim_obj.time(), timezone.utc)
91
92
93
def _datetime_offset_inst(obj: str, pattern: str) -> datetime:
94
    """
95
    Return a datetime instance with timezone info.
96
    :param obj: a datetime in RFC3339 format.
97
    :param pattern: the datetime pattern that is used.
98
    :return: a datetime instance with timezone info.
99
    """
100
    try:
101
        dat_str, tim_str = obj.split('T')
102
        splitter, factor = ('+', 1) if '+' in tim_str else ('-', -1)
103
        naive_tim_str, offset = tim_str.split(splitter)
104
    except ValueError as err:
105
        raise DeserializationError(str(err), obj, datetime)    naive_dattim_str = '{}T{}'.format(dat_str, naive_tim_str)
106
    dattim_obj = datetime.strptime(naive_dattim_str, pattern)
107
    hrs_str, mins_str = offset.split(':')
108
    hrs = int(hrs_str) * factor
109
    mins = int(mins_str) * factor
110
    tz = timezone(offset=timedelta(hours=hrs, minutes=mins))
111
    return _new_datetime(dattim_obj.date(), dattim_obj.time(), tz)
112
113
114
def _new_datetime(date_inst: date, time_inst: time, tzinfo: timezone) \
115
        -> datetime:
116
    """
117
    Return a datetime instance from a date, time and timezone.
118
119
    This function was required due to the missing argument for tzinfo under the
120
    Linux Python distribution.
121
    :param date_inst: the date.
122
    :param time_inst: the time.
123
    :param tzinfo: the Timezone.
124
    :return: a combined datetime instance.
125
    """
126
    return datetime.combine(date_inst, time_inst).replace(tzinfo=tzinfo)
127