jsons._datetime_impl   A
last analyzed

Complexity

Total Complexity 20

Size/Duplication

Total Lines 141
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 20
eloc 71
dl 0
loc 141
rs 10
c 0
b 0
f 0

8 Functions

Rating   Name   Duplication   Size   Complexity  
A get_offset_str() 0 15 3
A to_str() 0 9 3
A get_datetime_inst() 0 14 3
A _datetime_utc_inst() 0 10 1
A _timedelta_offset_str() 0 13 2
A _new_datetime() 0 13 1
A _datetime_offset_inst() 0 17 2
A _datetime_offset_str() 0 21 5
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
9
RFC3339_DATE_PATTERN = '%Y-%m-%d'
10
RFC3339_TIME_PATTERN = '%H:%M:%S'
11
RFC3339_DATETIME_PATTERN = '{}T{}'.format(
12
    RFC3339_DATE_PATTERN, RFC3339_TIME_PATTERN)
13
14
15
def to_str(
16
        dt: Union[datetime, date],
17
        strip_microseconds: bool,
18
        fork_inst: type,
19
        pattern: str = RFC3339_DATETIME_PATTERN) -> str:
20
    offset = get_offset_str(dt, fork_inst)
21
    if not strip_microseconds and getattr(dt, 'microsecond', None):
22
        pattern += '.%f'
23
    return dt.strftime("{}{}".format(pattern, offset))
24
25
26
def get_offset_str(
27
        obj: Union[datetime, date, timedelta],
28
        fork_inst: type) -> str:
29
    """
30
    Return the textual offset of the given ``obj``.
31
    :param obj: a datetime or timedelta instance.
32
    :param fork_inst: the state holder that is used.
33
    :return: the offset following RFC3339.
34
    """
35
    result = ''
36
    if isinstance(obj, datetime):
37
        result = _datetime_offset_str(obj, fork_inst)
38
    elif isinstance(obj, timedelta):
39
        result = _timedelta_offset_str(obj)
40
    return result
41
42
43
def get_datetime_inst(obj: str, pattern: str) -> datetime:
44
    """
45
    Return a datetime instance with timezone info from the given ``obj``.
46
    :param obj: the ``obj`` in RFC3339 format.
47
    :param pattern: the datetime pattern.
48
    :return: a datetime instance with timezone info.
49
    """
50
    if obj[-1] == 'Z':
51
        result = _datetime_utc_inst(obj, pattern)
52
    elif 'T' in pattern:
53
        result = _datetime_offset_inst(obj, pattern)
54
    else:
55
        result = datetime.strptime(obj, pattern)
56
    return result
57
58
59
def _datetime_offset_str(obj: datetime, fork_inst: type) -> str:
60
    """
61
    Return a textual offset (e.g. +01:00 or Z) for the given datetime.
62
    :param obj: the datetime instance.
63
    :return: the offset for ``obj``.
64
    """
65
    tzone = obj.tzinfo
66
    if not tzone:
67
        # datetimes without tzinfo are treated as local times.
68
        fork_inst._warn('The use of datetimes without timezone is dangerous '
69
                        'and can lead to undesired results.',
70
                        'datetime-without-tz')
71
        tzone = datetime.now(timezone.utc).astimezone().tzinfo
72
        if tzone is timezone.utc or tzone.utc is timezone.utc:
73
            return '+00:00'
74
    offset = 'Z'
75
    if tzone.tzname(None) not in ('UTC', 'UTC+00:00'):
76
        tdelta = tzone.utcoffset(None) or \
77
                 getattr(tzone, 'adjusted_offset', tzone.utcoffset(obj))
78
        offset = _timedelta_offset_str(tdelta)
79
    return offset
80
81
82
def _timedelta_offset_str(tdelta: timedelta) -> str:
83
    """
84
    Return a textual offset (e.g. +01:00 or Z) for the given timedelta.
85
    :param tdelta: the timedelta instance.
86
    :return: the offset for ``tdelta``.
87
    """
88
    offset_s = tdelta.total_seconds()
89
    offset_h = int(offset_s / 3600)
90
    offset_m = int((offset_s / 60) % 60)
91
    offset_t = time(abs(offset_h), abs(offset_m))
92
    operator = '+' if offset_s > 0 else '-'
93
    offset = offset_t.strftime('{}%H:%M'.format(operator))
94
    return offset
95
96
97
def _datetime_utc_inst(obj: str, pattern: str) -> datetime:
98
    """
99
    Return a datetime instance with UTC timezone info.
100
    :param obj: a datetime in RFC3339 format.
101
    :param pattern: the datetime pattern that is used.
102
    :return: a datetime instance with timezone info.
103
    """
104
    dattim_str = obj[0:-1]
105
    dattim_obj = datetime.strptime(dattim_str, pattern)
106
    return _new_datetime(dattim_obj.date(), dattim_obj.time(), timezone.utc)
107
108
109
def _datetime_offset_inst(obj: str, pattern: str) -> datetime:
110
    """
111
    Return a datetime instance with timezone info.
112
    :param obj: a datetime in RFC3339 format.
113
    :param pattern: the datetime pattern that is used.
114
    :return: a datetime instance with timezone info.
115
    """
116
    dat_str, tim_str = obj.split('T')
117
    splitter, factor = ('+', 1) if '+' in tim_str else ('-', -1)
118
    naive_tim_str, offset = tim_str.split(splitter)
119
    naive_dattim_str = '{}T{}'.format(dat_str, naive_tim_str)
120
    dattim_obj = datetime.strptime(naive_dattim_str, pattern)
121
    hrs_str, mins_str = offset.split(':')
122
    hrs = int(hrs_str) * factor
123
    mins = int(mins_str) * factor
124
    tz = timezone(offset=timedelta(hours=hrs, minutes=mins))
125
    return _new_datetime(dattim_obj.date(), dattim_obj.time(), tz)
126
127
128
def _new_datetime(date_inst: date, time_inst: time, tzinfo: timezone) \
129
        -> datetime:
130
    """
131
    Return a datetime instance from a date, time and timezone.
132
133
    This function was required due to the missing argument for tzinfo under the
134
    Linux Python distribution.
135
    :param date_inst: the date.
136
    :param time_inst: the time.
137
    :param tzinfo: the Timezone.
138
    :return: a combined datetime instance.
139
    """
140
    return datetime.combine(date_inst, time_inst).replace(tzinfo=tzinfo)
141