Passed
Pull Request — master (#23)
by Ramon
55s
created

jsons._datetime_impl._datetime_offset_inst()   A

Complexity

Conditions 2

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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