invesytoolbox 0.0.20__tar.gz → 0.0.22__tar.gz

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.
Files changed (30) hide show
  1. invesytoolbox-0.0.22/HISTORY.md +91 -0
  2. {invesytoolbox-0.0.20/src/invesytoolbox.egg-info → invesytoolbox-0.0.22}/PKG-INFO +26 -4
  3. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/setup.cfg +4 -1
  4. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/src/invesytoolbox/itb_data.py +14 -15
  5. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/src/invesytoolbox/itb_date_time.py +195 -22
  6. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/src/invesytoolbox/itb_email_phone.py +20 -10
  7. invesytoolbox-0.0.22/src/invesytoolbox/itb_html.py +63 -0
  8. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/src/invesytoolbox/itb_locales.py +52 -21
  9. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/src/invesytoolbox/itb_restricted_python.py +1 -2
  10. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/src/invesytoolbox/itb_security.py +0 -1
  11. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/src/invesytoolbox/itb_text_name.py +26 -11
  12. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/src/invesytoolbox/itb_www.py +4 -4
  13. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22/src/invesytoolbox.egg-info}/PKG-INFO +26 -4
  14. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/src/invesytoolbox.egg-info/SOURCES.txt +1 -0
  15. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/src/tests/test_date_time.py +48 -1
  16. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/src/tests/test_html.py +7 -0
  17. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/src/tests/test_locales.py +79 -22
  18. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/src/tests/test_text_name.py +48 -1
  19. invesytoolbox-0.0.20/src/invesytoolbox/itb_html.py +0 -63
  20. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/LICENSE.txt +0 -0
  21. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/README.md +0 -0
  22. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/setup.py +0 -0
  23. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/src/invesytoolbox/__init__.py +0 -0
  24. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/src/invesytoolbox.egg-info/dependency_links.txt +0 -0
  25. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/src/invesytoolbox.egg-info/requires.txt +0 -0
  26. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/src/invesytoolbox.egg-info/top_level.txt +0 -0
  27. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/src/tests/__init__.py +0 -0
  28. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/src/tests/test_data.py +0 -0
  29. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/src/tests/test_email_phone.py +0 -0
  30. {invesytoolbox-0.0.20 → invesytoolbox-0.0.22}/src/tests/test_www.py +0 -0
@@ -0,0 +1,91 @@
1
+ # History
2
+ ## 0.0.22 (2023-10-14)
3
+ * **normalize_name** and **could_be_a_name** now have the boolean parameter *lastname* that indicates that a single-word name is to be treated as a last name, not a first name.
4
+ * **get_locale**: locale can be only the language, without the country.
5
+ * if get_locale fails to get the locale from the system (because invesytoolbox may be imported from a program called from an applicatio rather than the termial), it defaults to `language: 'de'` and `country: 'AT'`.
6
+ * added support for pendulum DateTime
7
+ * **create_email_message**: fixed MIMEMultipart settings
8
+
9
+ ## 0.0.21 (2023-05-21)
10
+ * New function **unravel_duration**: turns a duration string (for ex. '5T23:05:20') into a list or dictionary.
11
+
12
+ ## 0.0.20 (2023-05-06)
13
+ * New function **add_time**: adds (or subtracts) time to (or from) datetime and DateTime
14
+
15
+ ## 0.0.19 (2023-04-07)
16
+ * Corrected bug in **fetch_holidays** (argument *length* resulted in an error)
17
+
18
+ ## 0.0.18 (2023-04-07)
19
+ * new function **change_h_tags** (in new module **itb_html**)
20
+
21
+ ## 0.0.17 (2023-03-01)
22
+ * new function *compare_phonenumbers*: compares two phone numbers after normalizing them (*process_phonenumber*).
23
+
24
+ ## 0.0.16
25
+ * *map_special_chars*: works now.
26
+
27
+ ## 0.0.15
28
+ * *could_be_a_name*: can now handle names with multiple parts like "Robert De La Mancha". Per word, 2 capitals are allowed (i.e. "MacArthur" or "DeLa Cruz)
29
+
30
+ ## 0.0.14
31
+ * *could_be_a_name* and *sort_names*: now working also with prename-only and prenames including a hyphen.
32
+
33
+ ## 0.0.13
34
+ * *get_dateformat* now also processes time
35
+ * *str_to_dt* now checks for valid string
36
+ * *is_valid_datetime_string*: wrapper for checking with *str_to_dt*
37
+ * *remove_time* from datetime or DateTime
38
+
39
+ ## 0.0.12
40
+ * removed documentation from the README file, instead a link to the gitlab pages
41
+ * *map_specialChars* now recognizes Unicode character U+0308 (UTF-8 cc 88 = "COMBINING DIAERESIS").
42
+ * *any_2boolean*
43
+ * *get_dateformat*: argument *checkonly*
44
+ * data functions: argument *json_data* changed to *metadata* (it's a dictionary)
45
+
46
+ ## 0.0.11
47
+ * BeautifulSoup and nameparser added to requirements.txt
48
+ * Removed "Date" conversions (modified DateTime) because it's a bad idea
49
+ * *normalize_name* (using nameparser) added to *itb_text_name*
50
+ * *capitalize_name* rewritten (now quasi a wrapper for normalize_name)
51
+ * *could_be_a_name* rewritten using *normalize_name* (nameparser)
52
+
53
+ ## 0.0.10
54
+ * *check_spam* for web forms
55
+ * *dictlist_2datatypes*: iterates through a list of dictionaries and applies *dict_2datatypes*
56
+ * *prettify_html*: provides *prettify* from BeautifulSoup, because it can't be used directly from restricted Python.
57
+ * *could_be_a_name*: checks if a string could be a name
58
+
59
+ ## 0.0.9
60
+ * *change\_query\_string* respects Zope parameter converters (like `paramname:int`)
61
+
62
+ ## 0.0.8
63
+ * New submodule www
64
+ * *change\_query\_string*
65
+
66
+ ## 0.0.7
67
+ * shorter submodule names (no _tools suffix)
68
+ * *is_holiday* works without argument
69
+ * *create\_email\_message*: new argument **encoding**
70
+ * *process_phonenumbers*: cleaned up arguments
71
+ * *DT_date*: strip a DateTime of its time (get a "naked" date)
72
+ * *could\_be\_a\_name*: check if a string could possibly be a name
73
+
74
+ ## 0.0.6
75
+ * renamed *capitalize\_text* to *capitalize_name* and removed name argument
76
+ * added Sphinx documentation
77
+
78
+ ## 0.0.5 (2022-06-11)
79
+ * removed **terminal_tools** (will be included in a separate package)
80
+
81
+ ## 0.0.4 (2022-06-09)
82
+ * better formatted README
83
+
84
+ ## 0.0.3 (2022-06-09)
85
+ * updated README (list of functions and a short description)
86
+
87
+ ## 0.0.2 (2022-06-09)
88
+ * removed VERSION file
89
+
90
+ ## 0.0.1 (2022-06-09)
91
+ * first version
@@ -1,22 +1,35 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: invesytoolbox
3
- Version: 0.0.20
3
+ Version: 0.0.22
4
4
  Summary: Tools for Python scripts or terminal
5
5
  Home-page: https://gitlab.com/Rastaf/invesytoolbox
6
6
  Author: Georg Pfolz
7
7
  Author-email: georg.pfolz@invesy.at
8
8
  License: MIT License
9
9
  Project-URL: Bug Tracker, https://gitlab.com/Rastaf/invesytoolbox/-/issues
10
- Platform: UNKNOWN
10
+ Project-URL: Documentation, https://rastaf.gitlab.io/invesytoolbox/
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.7
13
13
  Classifier: Programming Language :: Python :: 3.8
14
14
  Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
15
17
  Classifier: License :: OSI Approved :: MIT License
16
18
  Classifier: Operating System :: OS Independent
17
19
  Requires-Python: >=3.7
18
20
  Description-Content-Type: text/markdown
19
21
  License-File: LICENSE.txt
22
+ Requires-Dist: bs4
23
+ Requires-Dist: babel
24
+ Requires-Dist: colorama
25
+ Requires-Dist: DateTime
26
+ Requires-Dist: gender_guesser
27
+ Requires-Dist: holidays>0.13
28
+ Requires-Dist: nameparser
29
+ Requires-Dist: phonenumbers
30
+ Requires-Dist: pycountry
31
+ Requires-Dist: unidecode
32
+ Requires-Dist: vobject
20
33
 
21
34
  # invesytoolbox
22
35
 
@@ -31,8 +44,18 @@ That's also why all date and time functions also take into account the old DateT
31
44
  The documentation can be found [here](https://rastaf.gitlab.io/invesytoolbox/).
32
45
 
33
46
  # History
47
+ ## 0.0.22 (2023-10-14)
48
+ * **normalize_name** and **could_be_a_name** now have the boolean parameter *lastname* that indicates that a single-word name is to be treated as a last name, not a first name.
49
+ * **get_locale**: locale can be only the language, without the country.
50
+ * if get_locale fails to get the locale from the system (because invesytoolbox may be imported from a program called from an applicatio rather than the termial), it defaults to `language: 'de'` and `country: 'AT'`.
51
+ * added support for pendulum DateTime
52
+ * **create_email_message**: fixed MIMEMultipart settings
53
+
54
+ ## 0.0.21 (2023-05-21)
55
+ * New function **unravel_duration**: turns a duration string (for ex. '5T23:05:20') into a list or dictionary.
56
+
34
57
  ## 0.0.20 (2023-05-06)
35
- * **add_time**: adds time to datetime and DateTime
58
+ * New function **add_time**: adds (or subtracts) time to (or from) datetime and DateTime
36
59
 
37
60
  ## 0.0.19 (2023-04-07)
38
61
  * Corrected bug in **fetch_holidays** (argument *length* resulted in an error)
@@ -111,4 +134,3 @@ The documentation can be found [here](https://rastaf.gitlab.io/invesytoolbox/).
111
134
 
112
135
  ## 0.0.1 (2022-06-09)
113
136
  * first version
114
-
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = invesytoolbox
3
- version = 0.0.20
3
+ version = 0.0.22
4
4
  author = Georg Pfolz
5
5
  author_email = georg.pfolz@invesy.at
6
6
  description = Tools for Python scripts or terminal
@@ -11,11 +11,14 @@ license_files = LICENSE.txt
11
11
  url = https://gitlab.com/Rastaf/invesytoolbox
12
12
  project_urls =
13
13
  Bug Tracker = https://gitlab.com/Rastaf/invesytoolbox/-/issues
14
+ Documentation = https://rastaf.gitlab.io/invesytoolbox/
14
15
  classifiers =
15
16
  Programming Language :: Python :: 3
16
17
  Programming Language :: Python :: 3.7
17
18
  Programming Language :: Python :: 3.8
18
19
  Programming Language :: Python :: 3.9
20
+ Programming Language :: Python :: 3.10
21
+ Programming Language :: Python :: 3.11
19
22
  License :: OSI Approved :: MIT License
20
23
  Operating System :: OS Independent
21
24
 
@@ -1,11 +1,10 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """
3
- ==========
4
3
  data_tools
5
4
  ==========
6
5
  """
7
6
 
8
- from typing import Union
7
+ from typing import Union, List, Dict, Any, Optional
9
8
  import DateTime
10
9
  import datetime
11
10
  import vobject
@@ -19,7 +18,7 @@ time_chars = set('1234567890:')
19
18
 
20
19
 
21
20
  def any_2boolean(
22
- value
21
+ value: Any
23
22
  ) -> bool:
24
23
  """ return a boolean for any value
25
24
 
@@ -34,10 +33,10 @@ def any_2boolean(
34
33
 
35
34
 
36
35
  def dict_from_dict_list(
37
- dict_list: list,
38
- key,
39
- single_value: bool = None,
40
- include_key: str = False
36
+ dict_list: List[dict],
37
+ key: str,
38
+ single_value: Optional[bool] = None,
39
+ include_key: Optional[bool] = False
41
40
  ) -> dict:
42
41
  """Create a dictionary from a list of dictionaries
43
42
 
@@ -68,7 +67,7 @@ def dict_from_dict_list(
68
67
 
69
68
 
70
69
  def create_vcard(
71
- data: dict,
70
+ data: Dict[str, str],
72
71
  returning: str = 'vcard'
73
72
  ) -> str:
74
73
  """create a vCard from a dictionary
@@ -132,7 +131,7 @@ def create_vcard(
132
131
 
133
132
 
134
133
  def dict_2unicode(
135
- d: dict,
134
+ d: Dict[Union[str, bytes], Any],
136
135
  encoding: str = 'utf-8'
137
136
  ) -> dict:
138
137
  """ Converts all keys and values in a dictionary from bytes to unicode
@@ -153,12 +152,12 @@ def dict_2unicode(
153
152
 
154
153
  def dict_2datatypes(
155
154
  d: dict,
156
- metadata: dict = {},
155
+ metadata: Dict[str, str] = {},
157
156
  convert_keys: bool = False,
158
157
  convert_to_unicode: bool = False,
159
158
  dt: str = 'datetime',
160
159
  fmt: str = '%d.%m.%Y'
161
- ) -> dict:
160
+ ) -> Dict[Any, Any]:
162
161
  """ convert all data of a dictionary to specific (json metadata) or guessed types """
163
162
 
164
163
  if convert_keys:
@@ -193,8 +192,8 @@ def dict_2datatypes(
193
192
 
194
193
 
195
194
  def dictlist_2datatypes(
196
- dictlist: str,
197
- metadata: dict = {},
195
+ dictlist: List[Dict[Any, Any]],
196
+ metadata: Dict[Any, Any] = {},
198
197
  convert_keys: bool = False,
199
198
  convert_to_unicode: bool = False,
200
199
  dt: str = 'datetime',
@@ -214,8 +213,8 @@ def dictlist_2datatypes(
214
213
 
215
214
 
216
215
  def sort_dictlist(
217
- dictlist: list,
218
- keys: (str, list),
216
+ dictlist: List[dict],
217
+ keys: Union[str, List[str]],
219
218
  reverse: bool = False
220
219
  ) -> list:
221
220
  """ Sorts a list of dictionaries
@@ -1,12 +1,12 @@
1
1
  # coding=utf-8
2
2
  """
3
- ===============
4
3
  date_time_tools
5
4
  ===============
6
5
  """
7
-
6
+ from typing import Union
8
7
  import datetime
9
8
  import DateTime
9
+ import pendulum
10
10
  from dateutil.parser import parse
11
11
  from dateutil.parser._parser import ParserError
12
12
 
@@ -123,7 +123,7 @@ def get_dateformat(
123
123
 
124
124
  def str_to_dt(
125
125
  datestring: str,
126
- fmt: (str, None) = None,
126
+ fmt: str = None,
127
127
  checkonly: bool = False,
128
128
  returning: str = None
129
129
  ) -> datetime.datetime:
@@ -191,7 +191,7 @@ def str_to_dt(
191
191
 
192
192
  def str_to_DT(
193
193
  datestring: str,
194
- fmt: (str, None) = None
194
+ fmt: str = None
195
195
  ) -> DateTime.DateTime:
196
196
  """ Convert a string to Datetime.Datetime
197
197
 
@@ -213,9 +213,24 @@ def str_to_DT(
213
213
  )
214
214
 
215
215
 
216
+ def str_to_pendulum(
217
+ datestring: str,
218
+ fmt: str = None
219
+ ) -> pendulum.DateTime:
220
+ """ Convert a string to pendulum.DateTime
221
+
222
+ :param fmt: default: %d.%m.%Y (if not specified, the datestring is assumed to be valid ISO8601)
223
+ """
224
+
225
+ if not fmt:
226
+ return pendulum.parse(datestring)
227
+ else:
228
+ return pendulum.datetime.strptime(datestring, fmt)
229
+
230
+
216
231
  def str_to_date(
217
232
  datestring: str,
218
- fmt: (str, None) = None
233
+ fmt: str = None
219
234
  ) -> datetime.date:
220
235
  """ Convert a string to datetime.date
221
236
 
@@ -256,6 +271,38 @@ def DT_to_date(
256
271
  )
257
272
 
258
273
 
274
+ def DT_to_pendulum(
275
+ DT: DateTime.DateTime
276
+ ) -> pendulum.DateTime:
277
+ """ Convert DateTime.DateTime to pendulum.DateTime """
278
+
279
+ tz = DT.timezone()
280
+
281
+ offset = None
282
+
283
+ if '+' in tz:
284
+ offset = int(tz.split('+')[-1])
285
+ elif '-' in tz:
286
+ offset = - int(tz.split('-')[-1])
287
+
288
+ if offset is not None:
289
+ if not offset:
290
+ tz = 'UTC'
291
+ else:
292
+ tz = pendulum.FixedTimezone(offset * 3600)
293
+ else:
294
+ tz = pendulum.timezone(tz)
295
+
296
+ return pendulum.datetime(
297
+ DT.year(),
298
+ DT.month(),
299
+ DT.day(),
300
+ DT.hour(),
301
+ DT.minute(),
302
+ tz = tz
303
+ )
304
+
305
+
259
306
  def date_to_dt(
260
307
  date: datetime.date,
261
308
  H=None, M=None
@@ -290,16 +337,46 @@ def date_to_DT(
290
337
  return DateTime.DateTime(date_to_dt(date))
291
338
 
292
339
 
340
+ def dt_to_pendulum(
341
+ dt: datetime.datetime
342
+ ) -> pendulum.DateTime:
343
+ """ Convert datetime.datetime to pendulum.DateTime """
344
+
345
+ return pendulum.datetime(
346
+ dt.year,
347
+ dt.month,
348
+ dt.day,
349
+ dt.hour,
350
+ dt.minute
351
+
352
+ )
353
+
354
+
355
+ def pendulum_to_DT(
356
+ pendulum_dt: pendulum.DateTime
357
+ ) -> DateTime.DateTime:
358
+ """ Convert pendulum.DateTime to DateTime.DateTime """
359
+
360
+ return DateTime.DateTime(
361
+ pendulum_dt.year,
362
+ pendulum_dt.month,
363
+ pendulum_dt.day,
364
+ pendulum_dt.hour,
365
+ pendulum_dt.minute
366
+ ).toZone(pendulum_dt.timezone.name)
367
+
368
+
293
369
  def convert_datetime(
294
- date: (
370
+ date: Union[
295
371
  str,
296
372
  datetime.datetime,
297
373
  datetime.date,
298
- DateTime.DateTime
299
- ),
374
+ DateTime.DateTime,
375
+ pendulum.DateTime
376
+ ],
300
377
  convert_to: str,
301
378
  fmt: str = None
302
- ) -> (datetime.datetime, datetime.date, DateTime.DateTime, str):
379
+ ) -> Union[datetime.datetime, datetime.date, DateTime.DateTime, str]:
303
380
  """ Conversion of date and time formats
304
381
 
305
382
  :param convertTo:
@@ -321,6 +398,17 @@ def convert_datetime(
321
398
  return DT_to_dt(date)
322
399
  elif convert_to in ('str', 'string'):
323
400
  return date.strftime(fmt)
401
+ elif convert_to == 'pendulum':
402
+ return DT_to_pendulum(date)
403
+ elif isinstance(date, pendulum.DateTime):
404
+ if convert_to == 'datetime':
405
+ return date
406
+ elif convert_to == 'date':
407
+ return date.date()
408
+ elif convert_to == 'DateTime':
409
+ return pendulum_to_DT(date)
410
+ elif convert_to in ('str', 'string'):
411
+ return date.strftime(fmt)
324
412
  elif isinstance(date, str):
325
413
  if convert_to in ('str', 'string'):
326
414
  if str_to_date(date, checkonly=True, fmt=fmt):
@@ -333,6 +421,8 @@ def convert_datetime(
333
421
  return str_to_dt(date, fmt=fmt)
334
422
  elif convert_to == 'DateTime':
335
423
  return str_to_DT(date, fmt=fmt)
424
+ elif convert_to == 'pendulum':
425
+ return pendulum.parse(date)
336
426
  elif isinstance(date, datetime.datetime):
337
427
  # datetime.datetime has to be checked BEFORE
338
428
  # datetime.date (isinstance(datetime.datetime, datetime.date) == True !)
@@ -344,6 +434,8 @@ def convert_datetime(
344
434
  return DateTime.DateTime(date)
345
435
  elif convert_to in ('str', 'string'):
346
436
  return date.strftime(fmt)
437
+ elif convert_to == 'pendulum':
438
+ return dt_to_pendulum(date)
347
439
  elif type(date) is datetime.date: # isinstance does not work
348
440
  if convert_to == 'date':
349
441
  return date
@@ -353,15 +445,21 @@ def convert_datetime(
353
445
  return date_to_DT(date)
354
446
  elif convert_to in ('str', 'string'):
355
447
  return date.strftime(fmt)
448
+ elif convert_to == 'pendulum':
449
+ return pendulum.datetime(
450
+ date.year,
451
+ date.month,
452
+ date.day
453
+ )
356
454
 
357
455
 
358
456
  def get_calendar_week(
359
- date: (
457
+ date: Union[
360
458
  str,
361
459
  datetime.datetime,
362
460
  datetime.date,
363
461
  DateTime.DateTime
364
- )
462
+ ]
365
463
  ) -> int:
366
464
  """ Get calendard week (week nb of year) from a date
367
465
 
@@ -374,12 +472,12 @@ def get_calendar_week(
374
472
 
375
473
 
376
474
  def get_dow_number(
377
- date: (
475
+ date: Union[
378
476
  str,
379
477
  datetime.datetime,
380
478
  datetime.date,
381
479
  DateTime.DateTime
382
- )
480
+ ]
383
481
  ) -> int:
384
482
  """ Get day of week number from a date
385
483
 
@@ -392,12 +490,12 @@ def get_dow_number(
392
490
 
393
491
 
394
492
  def get_isocalendar(
395
- date: (
493
+ date: Union[
396
494
  str,
397
495
  datetime.datetime,
398
496
  datetime.date,
399
497
  DateTime.DateTime
400
- )
498
+ ]
401
499
  ) -> datetime.date:
402
500
  """ Returns the isocalendar tuple: (year, woy, downb)
403
501
 
@@ -451,6 +549,7 @@ def daterange_from_week(
451
549
  - DateTime
452
550
  - datetime (default)
453
551
  - date
552
+ - pendulum
454
553
  :param fmt: default: '%d.%m.%Y'
455
554
 
456
555
  .. note:: Author: Andreas Bruhn, https://groups.google.com/forum/#!topic/de.comp.lang.python/p8LfbNMIJ5c
@@ -488,6 +587,7 @@ def dates_from_week(
488
587
  - DateTime (default)
489
588
  - datetime
490
589
  - date
590
+ - pendulum
491
591
 
492
592
  :param fmt: default: '%d.%m.%Y'
493
593
  :param endofweek: 1 to 7 (monday to sunday)
@@ -525,7 +625,7 @@ def day_from_week(
525
625
  weekday: int = 1,
526
626
  returning: str = 'date',
527
627
  fmt: str = '%d.%m.%Y'
528
- ) -> (datetime.datetime, datetime.date, DateTime.DateTime):
628
+ ) -> Union[datetime.datetime, datetime.date, DateTime.DateTime, pendulum.DateTime]:
529
629
  """Get day from a week
530
630
 
531
631
  :param weekday: begins with 1 = monday
@@ -534,6 +634,7 @@ def day_from_week(
534
634
  - DateTime
535
635
  - datetime (default)
536
636
  - date
637
+ - pendulum
537
638
 
538
639
  :param fmt: default: '%d.%m.%Y'
539
640
  """
@@ -551,7 +652,7 @@ def monday_from_week(
551
652
  week: int,
552
653
  returning: str = 'date',
553
654
  fmt='%d.%m.%Y'
554
- ) -> (datetime.datetime, datetime.date, DateTime.DateTime):
655
+ ) -> Union[datetime.datetime, datetime.date, DateTime.DateTime, pendulum.DateTime]:
555
656
  """Get monday from a week
556
657
 
557
658
  :param: weekday begins with 1 = monday
@@ -582,7 +683,7 @@ def last_week_of_year(
582
683
 
583
684
 
584
685
  def remove_time(
585
- date_time: (datetime.datetime, DateTime.DateTime),
686
+ date_time: Union[datetime.datetime, DateTime.DateTime, pendulum.DateTime],
586
687
  returning: str = None
587
688
  ):
588
689
  """Remove time from a datetime or DateTime object
@@ -627,6 +728,19 @@ def remove_time(
627
728
  date_time.month(),
628
729
  date_time.day()
629
730
  )
731
+ elif isinstance(date_time, pendulum.DateTime):
732
+ if not returning or returning == 'pendulum':
733
+ return date_time.start_of('day')
734
+ elif returning == 'date':
735
+ return date_time.date()
736
+ elif returning == 'datetime':
737
+ return date_time.datetime()
738
+ elif returning == 'DateTime':
739
+ return DateTime.DateTime(
740
+ date_time.year,
741
+ date_time.month,
742
+ date_time.day
743
+ )
630
744
 
631
745
 
632
746
  def create_timedelta(
@@ -636,7 +750,7 @@ def create_timedelta(
636
750
  """create a timedelta or a number from a time string
637
751
 
638
752
  :param timestr: format or H:M or H:M:S or H:M:S:ms, possibly with day: 4T03:12:31
639
- :param returningdt: dt or DT
753
+ :param returningdt: dt, DT or pendulum
640
754
  """
641
755
  days_time = timestr.split('T')
642
756
  timestr = days_time[-1]
@@ -665,13 +779,22 @@ def create_timedelta(
665
779
  t_delta = days
666
780
  for el in TIME_ELS:
667
781
  t_delta += time_elements[el] * time_dividers[el]
782
+ elif dt == 'pendulum':
783
+ t_delta = pendulum.duration(
784
+ days=days,
785
+ hours=time_elements['h'],
786
+ minutes=time_elements['m'],
787
+ seconds=time_elements['s'],
788
+ milliseconds=time_elements['ms'],
789
+ )
668
790
 
669
791
  return t_delta
670
792
 
671
793
 
672
794
  def add_time(
673
- date_time: (datetime.datetime, DateTime.DateTime),
795
+ date_time: Union[datetime.datetime, DateTime.DateTime, pendulum.DateTime],
674
796
  timestr: str,
797
+ subtract: bool = False,
675
798
  returning: str = None
676
799
  ):
677
800
  """add or remove time from a datetime or DateTime object
@@ -685,8 +808,7 @@ def add_time(
685
808
  or
686
809
  dTH:M etc... (ex.: 4T03:12:31)
687
810
  """
688
- subtract = False
689
- if timestr.startswith('-'):
811
+ if subtract or timestr.startswith('-'):
690
812
  subtract = True
691
813
  timestr = timestr[1:]
692
814
 
@@ -697,6 +819,8 @@ def add_time(
697
819
  dt = 'datetime'
698
820
  elif isinstance(date_time, DateTime.DateTime):
699
821
  dt = 'DateTime'
822
+ elif isinstance(date_time, pendulum.DateTime):
823
+ dt = 'pendulum'
700
824
  else:
701
825
  raise Exception('date_time has to be datetime.datetime, datetime.date or DateTime.DateTime!')
702
826
 
@@ -709,3 +833,52 @@ def add_time(
709
833
  return date_time - t_delta
710
834
  else:
711
835
  return date_time + t_delta
836
+
837
+
838
+ def unravel_duration(
839
+ duration: str,
840
+ returning: str = 'list'
841
+ ):
842
+ """split a duration string into components
843
+
844
+ duration comes in the format nTH:M:S:ms
845
+ (days, hours, minutes, seconds, milliseconds)
846
+
847
+ @param returning: list, tuple oder dictionary
848
+ """
849
+ if 'T' in duration:
850
+ days, rest = duration.split('T')
851
+ days = int(days)
852
+ else:
853
+ days = 0
854
+ rest = duration
855
+
856
+ elements = rest.split(':')
857
+
858
+ hours = minutes = seconds = milliseconds = 0
859
+ time_elements = [hours, minutes, seconds, milliseconds]
860
+
861
+ for i, (element, time_measure) in enumerate(zip(elements, time_elements.copy())):
862
+ time_elements[i] = int(element)
863
+
864
+ time_elements.insert(0, days)
865
+
866
+ for i, divider in zip(
867
+ range(4, -1, -1),
868
+ (1000, 60, 60, 24)
869
+ ):
870
+ time_element = int(time_elements[i] / divider)
871
+ time_elements[i] -= time_element * divider
872
+ time_elements[i - 1] += time_element
873
+
874
+ if returning == 'list':
875
+ return time_elements
876
+ elif returning in ('dict', 'dictionary'):
877
+ d = {}
878
+ for name, val in zip(('days', 'hours', 'minutes', 'seconds', 'milliseconds'), time_elements):
879
+ d[name] = val
880
+ return d
881
+ elif returning == 'tuple':
882
+ tuple(time_elements)
883
+ else:
884
+ raise Exception('argument returning must be list, dictionary or tuple.')