Passed
Pull Request — 2.x (#1930)
by Ramon
04:58
created

senaite.core.api.dtime.to_zone()   A

Complexity

Conditions 5

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 19
rs 9.3333
c 0
b 0
f 0
cc 5
nop 2
1
# -*- coding: utf-8 -*-
2
3
import os
4
import time
5
from datetime import date
6
from datetime import datetime
7
8
import six
9
10
import pytz
11
from bika.lims import logger
12
from bika.lims.api import APIError
13
from DateTime import DateTime
14
from DateTime.DateTime import DateError
15
from DateTime.DateTime import SyntaxError
16
from DateTime.DateTime import TimeError
17
18
19
def is_str(obj):
20
    """Check if the given object is a string
21
22
    :param obj: arbitrary object
23
    :returns: True when the object is a string
24
    """
25
    return isinstance(obj, six.string_types)
26
27
28
def is_d(dt):
29
    """Check if the date is a Python `date` object
30
31
    :param dt: date to check
32
    :returns: True when the date is a Python `date`
33
    """
34
    return type(dt) is date
35
36
37
def is_dt(dt):
38
    """Check if the date is a Python `datetime` object
39
40
    :param dt: date to check
41
    :returns: True when the date is a Python `datetime`
42
    """
43
    return type(dt) is datetime
44
45
46
def is_DT(dt):
47
    """Check if the date is a Zope `DateTime` object
48
49
    :param dt: object to check
50
    :returns: True when the object is a Zope `DateTime`
51
    """
52
    return type(dt) is DateTime
53
54
55
def is_date(dt):
56
    """Check if the date is a datetime or DateTime object
57
58
    :param dt: date to check
59
    :returns: True when the object is either a datetime or DateTime
60
    """
61
    if is_str(dt):
62
        DT = to_DT(dt)
63
        return is_date(DT)
64
    if is_d(dt):
65
        return True
66
    if is_dt(dt):
67
        return True
68
    if is_DT(dt):
69
        return True
70
    return False
71
72
73
def is_timezone_naive(dt):
74
    """Check if the date is timezone naive
75
76
    :param dt: date to check
77
    :returns: True when the date has no timezone
78
    """
79
    if is_d(dt):
80
        return True
81
    elif is_DT(dt):
82
        return dt.timezoneNaive()
83
    elif is_dt(dt):
84
        return dt.tzinfo is None
85
    elif is_str(dt):
86
        DT = to_DT(dt)
87
        return is_timezone_naive(DT)
88
    raise APIError("Expected a date type, got '%r'" % type(dt))
89
90
91
def is_timezone_aware(dt):
92
    """Check if the date is timezone aware
93
94
    :param dt: date to check
95
    :returns: True when the date has a timezone
96
    """
97
    return not is_timezone_naive(dt)
98
99
100
def to_DT(dt):
101
    """Convert to DateTime
102
103
    :param dt: DateTime/datetime/date
104
    :returns: DateTime object
105
    """
106
    if is_DT(dt):
107
        return dt
108
    elif is_str(dt):
109
        try:
110
            return DateTime(dt)
111
        except (DateError, TimeError, SyntaxError, IndexError):
112
            return None
113
    elif is_dt(dt):
114
        return DateTime(dt.isoformat())
115
    elif is_d(dt):
116
        dt = datetime(dt.year, dt.month, dt.day)
117
        return DateTime(dt.isoformat())
118
    else:
119
        return None
120
121
122
def to_dt(dt):
123
    """Convert to datetime
124
125
    :param dt: DateTime/datetime/date
126
    :returns: datetime object
127
    """
128
    if is_DT(dt):
129
        # get a valid pytz timezone
130
        tz = get_timezone(dt)
131
        dt = dt.asdatetime()
132
        if is_valid_timezone(tz):
133
            dt = to_zone(dt, tz)
134
        return dt
135
    elif is_str(dt):
136
        DT = to_DT(dt)
137
        return to_dt(DT)
138
    elif is_dt(dt):
139
        return dt
140
    elif is_d(dt):
141
        return datetime(dt.year, dt.month, dt.day)
142
    else:
143
        return None
144
145
146
def get_timezone(dt, default="UTC"):
147
    """Get a valid pytz timezone of the datetime object
148
149
    :param dt: date object
150
    :returns: timezone as string, e.g. Etc/GMT or CET
151
    """
152
    tz = None
153
    if is_dt(dt):
154
        tz = dt.tzname()
155
    elif is_DT(dt):
156
        tz = dt.timezone()
157
    elif is_d(dt):
158
        tz = default
159
160
    if tz:
161
        # convert DateTime `GMT` to `Etc/GMT` timezones
162
        # NOTE: `GMT+1` get `Etc/GMT-1`!
163
        if tz.startswith("GMT+0"):
164
            tz = tz.replace("GMT+0", "Etc/GMT")
165
        elif tz.startswith("GMT+"):
166
            tz = tz.replace("GMT+", "Etc/GMT-")
167
        elif tz.startswith("GMT-"):
168
            tz = tz.replace("GMT-", "Etc/GMT+")
169
        elif tz.startswith("GMT"):
170
            tz = tz.replace("GMT", "Etc/GMT")
171
    else:
172
        tz = default
173
174
    return tz
175
176
177
def is_valid_timezone(timezone):
178
    """Checks if the timezone is a valid pytz/Olson name
179
180
    :param timezone: pytz/Olson timezone name
181
    :returns: True when the timezone is a valid zone
182
    """
183
    try:
184
        pytz.timezone(timezone)
185
        return True
186
    except pytz.UnknownTimeZoneError:
187
        return False
188
189
190
def get_os_timezone():
191
    """Return the default timezone of the system
192
193
    :returns: OS timezone or UTC
194
    """
195
    fallback = "UTC"
196
    timezone = None
197
    if "TZ" in os.environ.keys():
198
        # Timezone from OS env var
199
        timezone = os.environ["TZ"]
200
    if not timezone:
201
        # Timezone from python time
202
        zones = time.tzname
203
        if zones and len(zones) > 0:
204
            timezone = zones[0]
205
        else:
206
            # Default fallback = UTC
207
            logger.warn(
208
                "Operating system\'s timezone cannot be found. "
209
                "Falling back to UTC.")
210
            timezone = fallback
211
    if not is_valid_timezone(timezone):
212
        return fallback
213
    return timezone
214
215
216
def to_zone(dt, timezone):
217
    """Convert date to timezone
218
219
    Adds the timezone for timezone naive datetimes
220
221
    :param dt: date object
222
    :param timezone: timezone
223
    :returns: date converted to timezone
224
    """
225
    if is_dt(dt) or is_d(dt):
226
        dt = to_dt(dt)
227
        zone = pytz.timezone(timezone)
228
        if is_timezone_aware(dt):
229
            return dt.astimezone(zone)
230
        return zone.localize(dt)
231
    elif is_DT(dt):
232
        # NOTE: This shifts the time according to the TZ offset
233
        return dt.toZone(timezone)
234
    raise TypeError("Expected a date, got '%r'" % type(dt))
235
236
237
def to_timestamp(dt):
238
    """Generate a Portable Operating System Interface (POSIX) timestamp
239
240
    :param dt: date object
241
    :returns: timestamp in seconds
242
    """
243
    timestamp = 0
244
    if is_DT(dt):
245
        timestamp = dt.timeTime()
246
    elif is_dt(dt):
247
        timestamp = time.mktime(dt.timetuple())
248
    elif is_str(dt):
249
        DT = to_DT(dt)
250
        return to_timestamp(DT)
251
    return timestamp
252
253
254
def from_timestamp(timestamp):
255
    """Generate a datetime object from a POSIX timestamp
256
257
    :param timestamp: POSIX timestamp
258
    :returns: datetime object
259
    """
260
    return datetime.utcfromtimestamp(timestamp)
261
262
263
def to_iso_format(dt):
264
    """Convert to ISO format
265
    """
266
    if is_dt(dt):
267
        return dt.isoformat()
268
    elif is_DT(dt):
269
        return dt.ISO()
270
    elif is_str(dt):
271
        DT = to_DT(dt)
272
        return to_iso_format(DT)
273
    return None
274