d8s-dates 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
d8s_dates/__init__.py
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
"""Democritus functions for working with dates and times in Python."""
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import functools
|
|
5
|
+
import re
|
|
6
|
+
import time
|
|
7
|
+
from typing import Iterable, List, Optional, Union
|
|
8
|
+
|
|
9
|
+
import dateutil.parser
|
|
10
|
+
import maya
|
|
11
|
+
import parsedatetime
|
|
12
|
+
from d8s_hypothesis import hypothesis_get_strategy_results
|
|
13
|
+
from d8s_math import number_zero_pad
|
|
14
|
+
from d8s_strings import string_remove_from_end
|
|
15
|
+
from d8s_timezones import pytz_timezone_object
|
|
16
|
+
from hypothesis.strategies import dates, datetimes, timedeltas, times
|
|
17
|
+
|
|
18
|
+
DateOrString = Union[datetime.date, datetime.datetime, str]
|
|
19
|
+
|
|
20
|
+
DAY_NAMES = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
|
|
21
|
+
DAY_ABBREVIATIONS = ("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")
|
|
22
|
+
MONTH_NAMES = (
|
|
23
|
+
"January",
|
|
24
|
+
"February",
|
|
25
|
+
"March",
|
|
26
|
+
"April",
|
|
27
|
+
"May",
|
|
28
|
+
"June",
|
|
29
|
+
"July",
|
|
30
|
+
"August",
|
|
31
|
+
"September",
|
|
32
|
+
"October",
|
|
33
|
+
"November",
|
|
34
|
+
"December",
|
|
35
|
+
)
|
|
36
|
+
MONTH_ABBREVIATIONS = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
|
|
37
|
+
|
|
38
|
+
STRF_DATA = (
|
|
39
|
+
{"patterns": DAY_ABBREVIATIONS, "replacement": "%a"},
|
|
40
|
+
{"patterns": DAY_NAMES, "replacement": "%A"},
|
|
41
|
+
{"patterns": MONTH_ABBREVIATIONS, "replacement": "%b"},
|
|
42
|
+
{"patterns": MONTH_NAMES, "replacement": "%B"},
|
|
43
|
+
{"regex": r"[0123]?[0-9]/%b/[0-9]{4}", "replacement": "%d/%b/%Y"},
|
|
44
|
+
{"regex": r"[0-9]?[0-9]:[0-9]{2}:[0-9]{2}", "replacement": "%X"},
|
|
45
|
+
{"regex": r"[01]?[0-9]/[0123]?[0-9]/[0-9]{3,4}", "replacement": "%-m/%-d/%Y"},
|
|
46
|
+
{"regex": r"[01]?[0-9]/[0123]?[0-9]/[0-9]{2}", "replacement": "%x"},
|
|
47
|
+
{"regex": r"[01]?[0-9]/[0123]?[0-9]/[0-9]", "replacement": "%-m/%-d/%Y"},
|
|
48
|
+
{"regex": r"[0-9]{4}-[01]?[0-9]-[0123]?[0-9]", "replacement": "%Y-%-m-%-d"},
|
|
49
|
+
{"regex": r"\*[0-9]{3,6}", "replacement": "*%f"},
|
|
50
|
+
{"regex": r"\.[0-9]{3,6}", "replacement": ".%f"},
|
|
51
|
+
{"regex": r"\,[0-9]{3,6}", "replacement": ",%f"},
|
|
52
|
+
{"patterns": [f"-{number_zero_pad(i, 4)}" for i in range(1200, -1, -100)], "replacement": "%z"},
|
|
53
|
+
{"patterns": [f"+{number_zero_pad(i, 4)}" for i in range(1200, -1, -100)], "replacement": "%z"},
|
|
54
|
+
{"patterns": [str(i) for i in range(3000, 1600, -1)], "replacement": "%Y"},
|
|
55
|
+
{"patterns": [number_zero_pad(i, 2) for i in range(1, 31)], "replacement": "%d"},
|
|
56
|
+
{"patterns": [str(i) for i in range(31, 0, -1)], "replacement": "%-d"},
|
|
57
|
+
{"patterns": [number_zero_pad(i, 2) for i in range(0, 12)], "replacement": "%m"},
|
|
58
|
+
{"patterns": [str(i) for i in range(12, 0, -1)], "replacement": "%-m"},
|
|
59
|
+
{"patterns": [number_zero_pad(i, 2) for i in range(99, 0, -1)], "replacement": "%y"},
|
|
60
|
+
{"patterns": ["AM", "PM"], "replacement": "%p"},
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _handle_patterns(patterns: List[str], replacement: str, date_string: str) -> str:
|
|
65
|
+
for pattern in patterns:
|
|
66
|
+
if pattern in date_string:
|
|
67
|
+
date_string = date_string.replace(pattern, replacement)
|
|
68
|
+
break
|
|
69
|
+
return date_string
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def date_string_to_strftime_format(date_string):
|
|
73
|
+
"""Predict the strftime format from the given date_string."""
|
|
74
|
+
for data in STRF_DATA:
|
|
75
|
+
if data.get("patterns"):
|
|
76
|
+
date_string = _handle_patterns(data["patterns"], data["replacement"], date_string)
|
|
77
|
+
elif data.get("regex"):
|
|
78
|
+
date_string = re.sub(data["regex"], data["replacement"], date_string)
|
|
79
|
+
|
|
80
|
+
return date_string
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def date_parse( # noqa: CCR001
|
|
84
|
+
date: DateOrString, *, convert_to_current_timezone: bool = False
|
|
85
|
+
) -> Union[datetime.datetime, datetime.date, datetime.time]:
|
|
86
|
+
"""Parse the given date (can parse dates in most formats) (returns a datetime object)."""
|
|
87
|
+
if isinstance(date, (datetime.date, datetime.time, datetime.datetime)):
|
|
88
|
+
return date
|
|
89
|
+
|
|
90
|
+
# try to parse the date as an epoch datetime...
|
|
91
|
+
# we start with epoch datetime as it is the most discrete form of a date
|
|
92
|
+
try:
|
|
93
|
+
date = epoch_to_date(date)
|
|
94
|
+
except ValueError:
|
|
95
|
+
# try to parse the given date with the dateutil module
|
|
96
|
+
try:
|
|
97
|
+
date = _dateutil_parser_parse(date)
|
|
98
|
+
# if the given date could not be parsed by the dateutil module, try to parse the date using parsedatetime
|
|
99
|
+
except ValueError as e:
|
|
100
|
+
parsed_time_struct, parse_status = _parsedatetime_parse(date)
|
|
101
|
+
|
|
102
|
+
# convert the parsed_time_struct to a datetime object and return it
|
|
103
|
+
if parse_status > 0:
|
|
104
|
+
date = time_struct_to_datetime(parsed_time_struct)
|
|
105
|
+
else:
|
|
106
|
+
message = f'Unable to convert the date "{date}" into a standard date format.'
|
|
107
|
+
raise ValueError(message) from e
|
|
108
|
+
|
|
109
|
+
if convert_to_current_timezone:
|
|
110
|
+
date = date_make_timezone_aware(date)
|
|
111
|
+
|
|
112
|
+
return date # type: ignore
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def date_now(*, convert_to_current_timezone: bool = False, utc: bool = False):
|
|
116
|
+
"""Get the current date.
|
|
117
|
+
|
|
118
|
+
If convert_to_current_timezone is True, convert the date to the current timezone.
|
|
119
|
+
If utc is True, convert the date to UTC.
|
|
120
|
+
"""
|
|
121
|
+
now = datetime.datetime.now()
|
|
122
|
+
|
|
123
|
+
if convert_to_current_timezone and utc:
|
|
124
|
+
raise ValueError("Only one input parameter from utc and convert_to_current_timezone can be true.")
|
|
125
|
+
|
|
126
|
+
if convert_to_current_timezone:
|
|
127
|
+
now = date_make_timezone_aware(now)
|
|
128
|
+
|
|
129
|
+
if utc:
|
|
130
|
+
now = date_to_utc(now)
|
|
131
|
+
|
|
132
|
+
return now
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def date_parse_first_argument(func):
|
|
136
|
+
"""."""
|
|
137
|
+
|
|
138
|
+
@functools.wraps(func)
|
|
139
|
+
def wrapper(*args, **kwargs):
|
|
140
|
+
date_arg = args[0]
|
|
141
|
+
other_args = args[1:]
|
|
142
|
+
|
|
143
|
+
parsed_date_arg = date_parse(date_arg)
|
|
144
|
+
return func(parsed_date_arg, *other_args, **kwargs)
|
|
145
|
+
|
|
146
|
+
return wrapper
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@date_parse_first_argument
|
|
150
|
+
def date_2_string(date, date_format_string: str):
|
|
151
|
+
"""."""
|
|
152
|
+
formatted_date_string = date.strftime(date_format_string)
|
|
153
|
+
return formatted_date_string
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@date_parse_first_argument
|
|
157
|
+
def date_hour(date):
|
|
158
|
+
"""Find the hour from the given date."""
|
|
159
|
+
return date.hour
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@date_parse_first_argument
|
|
163
|
+
def date_minute(date):
|
|
164
|
+
"""Find the minute from the given date."""
|
|
165
|
+
return date.minute
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@date_parse_first_argument
|
|
169
|
+
def date_second(date):
|
|
170
|
+
"""Find the second from the given date."""
|
|
171
|
+
return date.second
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@date_parse_first_argument
|
|
175
|
+
def date_day(date):
|
|
176
|
+
"""Find the day of the month from the given date."""
|
|
177
|
+
return date_day_of_month(date)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@date_parse_first_argument
|
|
181
|
+
def date_day_of_month(date):
|
|
182
|
+
"""Find the day of the month from the given date."""
|
|
183
|
+
return date.day
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@date_parse_first_argument
|
|
187
|
+
def date_month(date):
|
|
188
|
+
"""Find the month from the given date."""
|
|
189
|
+
return date.month
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@date_parse_first_argument
|
|
193
|
+
def date_year(date):
|
|
194
|
+
"""Find the year from the given date."""
|
|
195
|
+
return date.year
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@date_parse_first_argument
|
|
199
|
+
def date_convert_to_timezone(date, timezone_string):
|
|
200
|
+
"""Convert the given date to the given timezone_string.
|
|
201
|
+
|
|
202
|
+
This will actually **convert** time given date; it will change the hour/day of the date to the given timezone).
|
|
203
|
+
"""
|
|
204
|
+
# if the given date does not have a timezone, use the system's timezone
|
|
205
|
+
if date.tzinfo is None:
|
|
206
|
+
date = date_make_timezone_aware(date)
|
|
207
|
+
|
|
208
|
+
timezone_object = pytz_timezone_object(timezone_string)
|
|
209
|
+
converted_date = date.astimezone(timezone_object)
|
|
210
|
+
return converted_date
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def date_make_timezone_aware(datetime_object, timezone_string=None):
|
|
214
|
+
"""Make the given datetime_object timezone aware.
|
|
215
|
+
|
|
216
|
+
This function does NOT convert the datetime_object.
|
|
217
|
+
It will never change the hour/day or any value of the datetime...
|
|
218
|
+
it will simply make the given datetime timezone aware.
|
|
219
|
+
"""
|
|
220
|
+
if timezone_string:
|
|
221
|
+
# make the date timezone aware using the given timezone_string
|
|
222
|
+
timezone_object = pytz_timezone_object(timezone_string)
|
|
223
|
+
timezone_aware_datetime_object = timezone_object.localize(datetime_object)
|
|
224
|
+
else:
|
|
225
|
+
# make the date timezone aware using the timezone of the current system
|
|
226
|
+
timezone_aware_datetime_object = datetime_object.astimezone()
|
|
227
|
+
|
|
228
|
+
return timezone_aware_datetime_object
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def time_delta_examples(n=10, *, time_deltas_as_strings: bool = True):
|
|
232
|
+
"""Return n time deltas."""
|
|
233
|
+
time_delta_objects = hypothesis_get_strategy_results(timedeltas, n=n)
|
|
234
|
+
if time_deltas_as_strings:
|
|
235
|
+
return [str(time_delta) for time_delta in time_delta_objects]
|
|
236
|
+
else:
|
|
237
|
+
return time_delta_objects
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def time_examples(n=10, *, times_as_strings: bool = True):
|
|
241
|
+
"""Return n times."""
|
|
242
|
+
time_objects = hypothesis_get_strategy_results(times, n=n)
|
|
243
|
+
if times_as_strings:
|
|
244
|
+
return [str(time) for time in time_objects]
|
|
245
|
+
else:
|
|
246
|
+
return time_objects
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def date_examples(n=10, *, dates_as_strings: bool = True, date_string_format: Optional[str] = None):
|
|
250
|
+
"""Return n dates."""
|
|
251
|
+
date_objects = hypothesis_get_strategy_results(dates, n=n)
|
|
252
|
+
if dates_as_strings:
|
|
253
|
+
if date_string_format is None:
|
|
254
|
+
return [str(date) for date in date_objects]
|
|
255
|
+
else:
|
|
256
|
+
return [date_2_string(date, date_string_format) for date in date_objects]
|
|
257
|
+
else:
|
|
258
|
+
return date_objects
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def datetime_examples(n=10, *, datetimes_as_strings: bool = True, datetime_string_format: Optional[str] = None):
|
|
262
|
+
"""Return n datetimes."""
|
|
263
|
+
datetime_objects = hypothesis_get_strategy_results(datetimes, n=n)
|
|
264
|
+
if datetimes_as_strings:
|
|
265
|
+
if datetime_string_format is None:
|
|
266
|
+
return [str(datetime) for datetime in datetime_objects]
|
|
267
|
+
else:
|
|
268
|
+
return [date_2_string(datetime, datetime_string_format) for datetime in datetime_objects]
|
|
269
|
+
else:
|
|
270
|
+
return datetime_objects
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def time_struct_to_datetime(struct_time_object):
|
|
274
|
+
"""Convert a python time.struct_time object into a datetime object."""
|
|
275
|
+
return datetime.datetime(*struct_time_object[:6])
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _parsedatetime_parse(date_string):
|
|
279
|
+
"""Parse the given date_string using the parsedatetime module."""
|
|
280
|
+
# for more details on how the parsedatetime.Calendar.parse function works, see:
|
|
281
|
+
# https://github.com/bear/parsedatetime/blob/830775dc5e36395622b41f12317f5e10c303d3a2/parsedatetime/__init__.py#L1779
|
|
282
|
+
cal = parsedatetime.Calendar()
|
|
283
|
+
parsed_date = cal.parse(date_string)
|
|
284
|
+
return parsed_date
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _dateutil_parser_parse(date_string):
|
|
288
|
+
"""Parse the given date_string using the dateutil.parser module."""
|
|
289
|
+
parsed_date = dateutil.parser.parse(date_string)
|
|
290
|
+
return parsed_date
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _maya_time_parse(date_object, *, convert_to_utc: bool = True):
|
|
294
|
+
"""Parse the given date_object using maya (see https://github.com/timofurrer/maya).
|
|
295
|
+
|
|
296
|
+
By default, the given date_object is converted to UTC because maya will assume that any given date is in UTC.
|
|
297
|
+
"""
|
|
298
|
+
if convert_to_utc:
|
|
299
|
+
# convert the given date to UTC (this is necessary b/c maya will assume that the given date is in UTC)
|
|
300
|
+
date = date_to_utc(date_object)
|
|
301
|
+
|
|
302
|
+
maya_date = maya.parse(date)
|
|
303
|
+
return maya_date
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def epoch_time_now():
|
|
307
|
+
"""Get the current epoch time."""
|
|
308
|
+
return int(time.time())
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def is_date(possible_date_string):
|
|
312
|
+
"""Determine if the given possible_date_string can be processed as a date."""
|
|
313
|
+
try:
|
|
314
|
+
date_parse(possible_date_string)
|
|
315
|
+
except Exception: # pylint: disable=broad-except
|
|
316
|
+
return False
|
|
317
|
+
else:
|
|
318
|
+
return True
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def time_now():
|
|
322
|
+
"""Return the current, epoch time."""
|
|
323
|
+
return time.time()
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
@date_parse_first_argument
|
|
327
|
+
def time_since(date):
|
|
328
|
+
"""Return a time of the time since the given date."""
|
|
329
|
+
now = date_now()
|
|
330
|
+
return now - date
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
@date_parse_first_argument
|
|
334
|
+
def time_until(date):
|
|
335
|
+
"""Return an English description of the time since the given date."""
|
|
336
|
+
now = date_now()
|
|
337
|
+
return date - now
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
@date_parse_first_argument
|
|
341
|
+
def time_since_slang(date):
|
|
342
|
+
"""Return an English description of the time since the given date."""
|
|
343
|
+
maya_date = _maya_time_parse(date)
|
|
344
|
+
slang_time = maya_date.slang_time()
|
|
345
|
+
return slang_time
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
@date_parse_first_argument
|
|
349
|
+
def time_until_slang(date):
|
|
350
|
+
"""Return an English description of the time until the given date."""
|
|
351
|
+
maya_date = _maya_time_parse(date)
|
|
352
|
+
slang_time = maya_date.slang_time()
|
|
353
|
+
return slang_time
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
@date_parse_first_argument
|
|
357
|
+
def date_to_utc(date):
|
|
358
|
+
"""Convert the given date to UTC. Assume that the given date is in the system's timezone and convert it to UTC."""
|
|
359
|
+
utc_date = date_convert_to_timezone(date, "utc")
|
|
360
|
+
return utc_date
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def time_after(time_a, time_b=None) -> bool:
|
|
364
|
+
"""Check if one time is before the other."""
|
|
365
|
+
if time_b is None:
|
|
366
|
+
time_b = time_now()
|
|
367
|
+
|
|
368
|
+
# make sure both times are floats
|
|
369
|
+
time_a = float(date_to_epoch(time_a))
|
|
370
|
+
time_b = float(date_to_epoch(time_b))
|
|
371
|
+
return time_a > time_b
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def time_before(time_a, time_b=None) -> bool:
|
|
375
|
+
"""Check if one time is before the other."""
|
|
376
|
+
if time_b is None:
|
|
377
|
+
time_b = time_now()
|
|
378
|
+
|
|
379
|
+
# make sure both times are floats
|
|
380
|
+
time_a = float(date_to_epoch(time_a))
|
|
381
|
+
time_b = float(date_to_epoch(time_b))
|
|
382
|
+
return time_a < time_b
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
@date_parse_first_argument
|
|
386
|
+
def date_in_future(date) -> bool:
|
|
387
|
+
"""Return whether or not the given date is in the future."""
|
|
388
|
+
is_in_the_future = time_after(date)
|
|
389
|
+
return is_in_the_future
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def time_is() -> str:
|
|
393
|
+
"""Time and money spent in helping men to do more for themselves is far better than mere giving. -Henry Ford"""
|
|
394
|
+
return "$"
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
@date_parse_first_argument
|
|
398
|
+
def date_to_iso(date, *, timezone_is_utc: bool = False, use_trailing_z: bool = False):
|
|
399
|
+
"""Return the ISO 8601 version of the given date as a string (see https://en.wikipedia.org/wiki/ISO_8601)."""
|
|
400
|
+
if timezone_is_utc:
|
|
401
|
+
# replace any timezones on the date with UTC - this is not a conversion - it is a hard-replace...
|
|
402
|
+
# if there is a timezone on the given date, it will NOT be *converted* to UTC...
|
|
403
|
+
# the time will remain the same, but the timezone will change to UTC
|
|
404
|
+
date = date.replace(tzinfo=datetime.timezone.utc)
|
|
405
|
+
|
|
406
|
+
iso_format_date = date.isoformat()
|
|
407
|
+
|
|
408
|
+
if use_trailing_z and iso_format_date.endswith("+00:00"):
|
|
409
|
+
# remove the timezone from the end
|
|
410
|
+
iso_format_date = string_remove_from_end(iso_format_date, "+00:00")
|
|
411
|
+
# add a 'Z'
|
|
412
|
+
iso_format_date = iso_format_date + "Z"
|
|
413
|
+
|
|
414
|
+
return iso_format_date
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def epoch_time_standardization(epoch_time):
|
|
418
|
+
"""Convert the given epoch time to an epoch time in seconds."""
|
|
419
|
+
epoch_time_string = str(epoch_time)
|
|
420
|
+
# if the given epoch time appears to include milliseconds (or some other level of precision)...
|
|
421
|
+
# and does not have a decimal in it, add a decimal point
|
|
422
|
+
if len(epoch_time_string) > 10 and "." not in epoch_time_string:
|
|
423
|
+
epoch_time = f"{epoch_time_string[:10]}.{epoch_time_string[10:]}"
|
|
424
|
+
return epoch_time
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def epoch_to_date(epoch_time) -> datetime.datetime:
|
|
428
|
+
"""Convert the epoch_time into a datetime."""
|
|
429
|
+
epoch_time = float(epoch_time_standardization(epoch_time))
|
|
430
|
+
return datetime.datetime.fromtimestamp(epoch_time)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
@date_parse_first_argument
|
|
434
|
+
def date_day_of_week(date):
|
|
435
|
+
"""Return the day of the week on which the given date occurred."""
|
|
436
|
+
day_of_week = date.strftime("%A")
|
|
437
|
+
return day_of_week
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
@date_parse_first_argument
|
|
441
|
+
def date_week_of_year(date, *, sunday_is_first_day_of_week: bool = False):
|
|
442
|
+
"""Find the week of the year for the given date. If no date is given, return the week of the current date."""
|
|
443
|
+
if sunday_is_first_day_of_week:
|
|
444
|
+
return date.strftime("%U")
|
|
445
|
+
else:
|
|
446
|
+
return date.strftime("%V")
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
@date_parse_first_argument
|
|
450
|
+
def date_to_epoch(date):
|
|
451
|
+
"""Convert a datetime stamp to epoch time."""
|
|
452
|
+
epoch_time = date.strftime("%s")
|
|
453
|
+
return int(epoch_time)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def chrome_timestamp_to_epoch(chrome_timestamp):
|
|
457
|
+
"""Convert the given Chrome timestamp to epoch time.
|
|
458
|
+
|
|
459
|
+
For more information, see: https://stackoverflow.com/questions/20458406/what-is-the-format-of-chromes-timestamps.
|
|
460
|
+
"""
|
|
461
|
+
return (chrome_timestamp / 1000000) - 11644473600
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def time_waste(n=3):
|
|
465
|
+
"""If time be of all things the most precious, wasting time must be the greatest prodigality. -Benjamin Franklin"""
|
|
466
|
+
time.sleep(n)
|
|
467
|
+
message = f"I just wasted {n} seconds of your life."
|
|
468
|
+
print(message)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def time_as_float(time_string: str) -> float:
|
|
472
|
+
"""converts a given HH:MM time string to float"""
|
|
473
|
+
try:
|
|
474
|
+
hours, minutes = list(map(int, time_string.split(":"))) # parse given time string
|
|
475
|
+
except ValueError as e:
|
|
476
|
+
message = f"Invalid time string, ensure that the argument is in HH:MM format. Provided value: {time_string}"
|
|
477
|
+
raise ValueError(message) from e
|
|
478
|
+
else:
|
|
479
|
+
if hours > 23 or minutes > 59:
|
|
480
|
+
message = f"Invalid time string, should be between 00:00 and 23:59. Provided value: {time_string}"
|
|
481
|
+
raise ValueError(message)
|
|
482
|
+
|
|
483
|
+
return hours + (minutes / 60)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
@date_parse_first_argument
|
|
487
|
+
def datetime_date(date: DateOrString) -> datetime.date:
|
|
488
|
+
"""Return a datetime.date version of the given date."""
|
|
489
|
+
if isinstance(date, datetime.datetime):
|
|
490
|
+
date = date.date()
|
|
491
|
+
return date # type: ignore
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
@date_parse_first_argument
|
|
495
|
+
def age(date_of_birth: DateOrString, as_of: Optional[DateOrString] = None):
|
|
496
|
+
"""Find the age of a person with the given date_of_birth."""
|
|
497
|
+
# Set as_of to today if it doesn't exist already
|
|
498
|
+
if not as_of:
|
|
499
|
+
as_of = date_now()
|
|
500
|
+
else:
|
|
501
|
+
as_of = date_parse(as_of) # type: ignore
|
|
502
|
+
|
|
503
|
+
# Get everything into date format
|
|
504
|
+
if isinstance(date_of_birth, datetime.datetime):
|
|
505
|
+
date_of_birth = date_of_birth.date()
|
|
506
|
+
if isinstance(as_of, datetime.datetime):
|
|
507
|
+
as_of = as_of.date()
|
|
508
|
+
|
|
509
|
+
try:
|
|
510
|
+
tmp = date_of_birth.replace(year=as_of.year) # type: ignore
|
|
511
|
+
# ValueError is raised when date_of_birth is February 29 and the current year is not a leap year
|
|
512
|
+
except ValueError:
|
|
513
|
+
tmp = date_of_birth.replace(year=as_of.year, day=date_of_birth.day - 1) # type: ignore
|
|
514
|
+
|
|
515
|
+
if tmp > as_of: # type: ignore
|
|
516
|
+
return as_of.year - date_of_birth.year - 1 # type: ignore
|
|
517
|
+
else:
|
|
518
|
+
return as_of.year - date_of_birth.year # type: ignore
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
@date_parse_first_argument
|
|
522
|
+
def date_range_days(start_date: DateOrString, end_date: DateOrString) -> Iterable[datetime.date]:
|
|
523
|
+
"""Yield datetime.date objects representing each day between the given start and end dates (inclusive)."""
|
|
524
|
+
start_date = datetime_date(start_date)
|
|
525
|
+
end_date = datetime_date(end_date)
|
|
526
|
+
|
|
527
|
+
while True:
|
|
528
|
+
yield start_date # type: ignore
|
|
529
|
+
|
|
530
|
+
start_date = start_date + datetime.timedelta(days=1) # type: ignore
|
|
531
|
+
if start_date > end_date: # type: ignore
|
|
532
|
+
break
|