qsu 0.1.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.
Files changed (137) hide show
  1. qsu/__init__.py +56 -0
  2. qsu/array/__init__.py +29 -0
  3. qsu/array/arrCount.py +8 -0
  4. qsu/array/arrGroupByMaxCount.py +15 -0
  5. qsu/array/arrMove.py +9 -0
  6. qsu/array/arrPick.py +8 -0
  7. qsu/array/arrRepeat.py +17 -0
  8. qsu/array/arrShuffle.py +15 -0
  9. qsu/array/arrTo1dArray.py +18 -0
  10. qsu/array/arrUnique.py +28 -0
  11. qsu/array/arrWithDefault.py +5 -0
  12. qsu/array/arrWithNumber.py +5 -0
  13. qsu/array/average.py +2 -0
  14. qsu/array/sortByObjectKey.py +20 -0
  15. qsu/array/sortNumeric.py +16 -0
  16. qsu/crypto/__init__.py +23 -0
  17. qsu/crypto/decodeBase64.py +5 -0
  18. qsu/crypto/decrypt.py +34 -0
  19. qsu/crypto/encodeBase64.py +5 -0
  20. qsu/crypto/encrypt.py +35 -0
  21. qsu/crypto/md5Hash.py +19 -0
  22. qsu/crypto/numberHash.py +14 -0
  23. qsu/crypto/objectId.py +9 -0
  24. qsu/crypto/sha1Hash.py +19 -0
  25. qsu/crypto/sha256Hash.py +19 -0
  26. qsu/crypto/sha512Hash.py +19 -0
  27. qsu/date/__init__.py +13 -0
  28. qsu/date/createDateListFromRange.py +55 -0
  29. qsu/date/dateToYYYYMMDD.py +20 -0
  30. qsu/date/dayDiff.py +10 -0
  31. qsu/date/isValidDate.py +27 -0
  32. qsu/date/today.py +19 -0
  33. qsu/file/__init__.py +51 -0
  34. qsu/file/_readLines.py +17 -0
  35. qsu/file/createDirectory.py +13 -0
  36. qsu/file/createFile.py +17 -0
  37. qsu/file/createFileWithDummy.py +21 -0
  38. qsu/file/deleteAllFileFromDirectory.py +15 -0
  39. qsu/file/deleteFile.py +16 -0
  40. qsu/file/getCopyFileName.py +21 -0
  41. qsu/file/getFileExtension.py +8 -0
  42. qsu/file/getFileHashFromPath.py +14 -0
  43. qsu/file/getFileHashFromStream.py +21 -0
  44. qsu/file/getFileInfo.py +42 -0
  45. qsu/file/getFileName.py +26 -0
  46. qsu/file/getFilePathLevel.py +12 -0
  47. qsu/file/getFileSize.py +10 -0
  48. qsu/file/getParentFilePath.py +10 -0
  49. qsu/file/headFile.py +19 -0
  50. qsu/file/isFileExists.py +9 -0
  51. qsu/file/isFileHidden.py +27 -0
  52. qsu/file/isValidFileName.py +14 -0
  53. qsu/file/joinFilePath.py +41 -0
  54. qsu/file/moveFile.py +8 -0
  55. qsu/file/normalizeFile.py +13 -0
  56. qsu/file/tailFile.py +24 -0
  57. qsu/file/toPosixFilePath.py +9 -0
  58. qsu/file/toValidFilePath.py +33 -0
  59. qsu/format/__init__.py +13 -0
  60. qsu/format/duration.py +45 -0
  61. qsu/format/fileSizeFormat.py +30 -0
  62. qsu/format/numberFormat.py +23 -0
  63. qsu/format/safeJSONParse.py +21 -0
  64. qsu/format/safeParseInt.py +39 -0
  65. qsu/math/__init__.py +15 -0
  66. qsu/math/div.py +9 -0
  67. qsu/math/mul.py +9 -0
  68. qsu/math/numPick.py +13 -0
  69. qsu/math/numUnique.py +10 -0
  70. qsu/math/sub.py +9 -0
  71. qsu/math/sum.py +9 -0
  72. qsu/misc/__init__.py +11 -0
  73. qsu/misc/debounce.py +14 -0
  74. qsu/misc/funcTimes.py +10 -0
  75. qsu/misc/logBox.py +174 -0
  76. qsu/misc/sleep.py +7 -0
  77. qsu/net/__init__.py +5 -0
  78. qsu/net/fetchData.py +186 -0
  79. qsu/object/__init__.py +19 -0
  80. qsu/object/objDeleteKeyByValue.py +29 -0
  81. qsu/object/objFindItemRecursiveByKey.py +25 -0
  82. qsu/object/objMergeNewKey.py +36 -0
  83. qsu/object/objTo1d.py +25 -0
  84. qsu/object/objToArray.py +16 -0
  85. qsu/object/objToPrettyStr.py +5 -0
  86. qsu/object/objToQueryString.py +27 -0
  87. qsu/object/objUpdate.py +25 -0
  88. qsu/os/__init__.py +17 -0
  89. qsu/os/getCpu.py +7 -0
  90. qsu/os/getHostname.py +28 -0
  91. qsu/os/getMachineId.py +33 -0
  92. qsu/os/getRamSize.py +34 -0
  93. qsu/os/getSid.py +60 -0
  94. qsu/os/getUptime.py +19 -0
  95. qsu/os/runCommand.py +28 -0
  96. qsu/string/__init__.py +41 -0
  97. qsu/string/capitalizeEachWords.py +43 -0
  98. qsu/string/capitalizeEverySentence.py +23 -0
  99. qsu/string/capitalizeFirst.py +5 -0
  100. qsu/string/getGroupKeys.py +103 -0
  101. qsu/string/getStrBytes.py +5 -0
  102. qsu/string/removeNewLine.py +8 -0
  103. qsu/string/removeSpecialChar.py +16 -0
  104. qsu/string/replaceBetween.py +15 -0
  105. qsu/string/split.py +45 -0
  106. qsu/string/strBlindRandom.py +26 -0
  107. qsu/string/strCount.py +12 -0
  108. qsu/string/strRandom.py +14 -0
  109. qsu/string/strShuffle.py +11 -0
  110. qsu/string/strToAscii.py +7 -0
  111. qsu/string/strUnique.py +5 -0
  112. qsu/string/trim.py +8 -0
  113. qsu/string/truncate.py +10 -0
  114. qsu/string/truncateExpect.py +25 -0
  115. qsu/string/urlJoin.py +27 -0
  116. qsu/verify/__init__.py +25 -0
  117. qsu/verify/between.py +7 -0
  118. qsu/verify/contains.py +12 -0
  119. qsu/verify/is2dArray.py +2 -0
  120. qsu/verify/isEmail.py +15 -0
  121. qsu/verify/isEmpty.py +12 -0
  122. qsu/verify/isEqual.py +32 -0
  123. qsu/verify/isEqualStrict.py +21 -0
  124. qsu/verify/isObject.py +2 -0
  125. qsu/verify/isTrueMinimumNumberOfTimes.py +8 -0
  126. qsu/verify/isUrl.py +20 -0
  127. qsu/verify/len.py +21 -0
  128. qsu/web/__init__.py +13 -0
  129. qsu/web/generateLicense.py +48 -0
  130. qsu/web/isBotAgent.py +10 -0
  131. qsu/web/isMatchPathname.py +24 -0
  132. qsu/web/isMobile.py +17 -0
  133. qsu/web/removeLocalePrefix.py +40 -0
  134. qsu-0.1.0.dist-info/METADATA +77 -0
  135. qsu-0.1.0.dist-info/RECORD +137 -0
  136. qsu-0.1.0.dist-info/WHEEL +4 -0
  137. qsu-0.1.0.dist-info/licenses/LICENSE +21 -0
qsu/__init__.py ADDED
@@ -0,0 +1,56 @@
1
+ """qsu - Quick & Simple Utility.
2
+
3
+ A utility library that collects frequently used functions.
4
+ This package mirrors the JavaScript implementation of qsu.
5
+
6
+ Unlike the JavaScript package (where filesystem/OS/network/crypto helpers live
7
+ under `qsu/node`), Python has no browser/runtime split, so every function is
8
+ importable directly from the top-level `qsu` package.
9
+ """
10
+
11
+ from . import (
12
+ array,
13
+ crypto,
14
+ date,
15
+ file,
16
+ format,
17
+ math,
18
+ misc,
19
+ net,
20
+ object,
21
+ os,
22
+ string,
23
+ verify,
24
+ web,
25
+ )
26
+ from .array import * # noqa: F401,F403
27
+ from .crypto import * # noqa: F401,F403
28
+ from .date import * # noqa: F401,F403
29
+ from .file import * # noqa: F401,F403
30
+ from .format import * # noqa: F401,F403
31
+ from .math import * # noqa: F401,F403
32
+ from .misc import * # noqa: F401,F403
33
+ from .net import * # noqa: F401,F403
34
+ from .object import * # noqa: F401,F403
35
+ from .os import * # noqa: F401,F403
36
+ from .string import * # noqa: F401,F403
37
+ from .verify import * # noqa: F401,F403
38
+ from .web import * # noqa: F401,F403
39
+
40
+ __version__ = '0.1.0'
41
+
42
+ __all__ = [
43
+ *array.__all__,
44
+ *crypto.__all__,
45
+ *date.__all__,
46
+ *file.__all__,
47
+ *format.__all__,
48
+ *math.__all__,
49
+ *misc.__all__,
50
+ *net.__all__,
51
+ *object.__all__,
52
+ *os.__all__,
53
+ *string.__all__,
54
+ *verify.__all__,
55
+ *web.__all__,
56
+ ]
qsu/array/__init__.py ADDED
@@ -0,0 +1,29 @@
1
+ from .arrCount import arrCount
2
+ from .arrGroupByMaxCount import arrGroupByMaxCount
3
+ from .arrMove import arrMove
4
+ from .arrPick import arrPick
5
+ from .arrRepeat import arrRepeat
6
+ from .arrShuffle import arrShuffle
7
+ from .arrTo1dArray import arrTo1dArray
8
+ from .arrUnique import arrUnique
9
+ from .arrWithDefault import arrWithDefault
10
+ from .arrWithNumber import arrWithNumber
11
+ from .average import average
12
+ from .sortByObjectKey import sortByObjectKey
13
+ from .sortNumeric import sortNumeric
14
+
15
+ __all__ = [
16
+ 'arrCount',
17
+ 'arrGroupByMaxCount',
18
+ 'arrMove',
19
+ 'arrPick',
20
+ 'arrRepeat',
21
+ 'arrShuffle',
22
+ 'arrTo1dArray',
23
+ 'arrUnique',
24
+ 'arrWithDefault',
25
+ 'arrWithNumber',
26
+ 'average',
27
+ 'sortByObjectKey',
28
+ 'sortNumeric',
29
+ ]
qsu/array/arrCount.py ADDED
@@ -0,0 +1,8 @@
1
+ def arrCount(array: list) -> dict:
2
+ result = {}
3
+
4
+ for item in array:
5
+ key = str(item)
6
+ result[key] = result.get(key, 0) + 1
7
+
8
+ return result
@@ -0,0 +1,15 @@
1
+ def arrGroupByMaxCount(array: list, maxLengthPerGroup: int = 1) -> list:
2
+ result = []
3
+ tempArray = []
4
+
5
+ for item in array:
6
+ if len(tempArray) == maxLengthPerGroup:
7
+ result.append(tempArray)
8
+ tempArray = []
9
+
10
+ tempArray.append(item)
11
+
12
+ if len(tempArray) > 0:
13
+ result.append(tempArray)
14
+
15
+ return result
qsu/array/arrMove.py ADDED
@@ -0,0 +1,9 @@
1
+ def arrMove(array: list, fromIndex: int, to: int) -> list:
2
+ arrayLength = len(array)
3
+
4
+ if arrayLength <= fromIndex or arrayLength <= to:
5
+ raise Exception('Invalid move params')
6
+
7
+ array.insert(to, array.pop(fromIndex))
8
+
9
+ return array
qsu/array/arrPick.py ADDED
@@ -0,0 +1,8 @@
1
+ import random
2
+
3
+
4
+ def arrPick(array: list):
5
+ if not array or not isinstance(array, list) or len(array) < 1:
6
+ return None
7
+
8
+ return array[int(random.random() * len(array))]
qsu/array/arrRepeat.py ADDED
@@ -0,0 +1,17 @@
1
+ from ..verify.isObject import isObject
2
+
3
+
4
+ def arrRepeat(array, count: int) -> list:
5
+ if not array or count < 1 or not isinstance(array, (list, tuple, dict)):
6
+ return []
7
+
8
+ isObj = isObject(array)
9
+ result = []
10
+
11
+ for _ in range(count):
12
+ if isObj:
13
+ result.append(array)
14
+ else:
15
+ result.extend(array)
16
+
17
+ return result
@@ -0,0 +1,15 @@
1
+ import math
2
+ import random
3
+
4
+
5
+ def arrShuffle(array: list):
6
+ if len(array) == 1:
7
+ return array[0]
8
+
9
+ newArray = array
10
+
11
+ for i in range(len(array) - 1, 0, -1):
12
+ j = math.floor(random.random() * (i + 1))
13
+ newArray[i], newArray[j] = array[j], array[i]
14
+
15
+ return newArray
@@ -0,0 +1,18 @@
1
+ from ..verify.is2dArray import is2dArray
2
+
3
+
4
+ def arrTo1dArray(array: list) -> list:
5
+ def convert1dArray(arr: list) -> list:
6
+ tempArr = []
7
+
8
+ for item in arr:
9
+ if not isinstance(item, (list, tuple)):
10
+ tempArr.append(item)
11
+ elif is2dArray(item):
12
+ tempArr.extend(convert1dArray(item))
13
+ else:
14
+ tempArr.extend(item)
15
+
16
+ return tempArr
17
+
18
+ return convert1dArray(array)
qsu/array/arrUnique.py ADDED
@@ -0,0 +1,28 @@
1
+ import json
2
+
3
+ from ..verify.is2dArray import is2dArray
4
+
5
+
6
+ def arrUnique(array: list) -> list:
7
+ if is2dArray(array):
8
+ seen = set()
9
+ result = []
10
+
11
+ for item in array:
12
+ key = json.dumps(item, separators=(',', ':'))
13
+
14
+ if key not in seen:
15
+ seen.add(key)
16
+ result.append(json.loads(key))
17
+
18
+ return result
19
+
20
+ seen = []
21
+ result = []
22
+
23
+ for item in array:
24
+ if not any(existing is item or (type(existing) is type(item) and existing == item) for existing in seen):
25
+ seen.append(item)
26
+ result.append(item)
27
+
28
+ return result
@@ -0,0 +1,5 @@
1
+ def arrWithDefault(defaultValue, length: int = 0) -> list:
2
+ if length < 1:
3
+ return []
4
+
5
+ return [defaultValue] * length
@@ -0,0 +1,5 @@
1
+ def arrWithNumber(start: int, end: int) -> list:
2
+ if start > end:
3
+ raise Exception('`end` is greater than `start`.')
4
+
5
+ return list(range(start, end + 1))
qsu/array/average.py ADDED
@@ -0,0 +1,2 @@
1
+ def average(array: list) -> float:
2
+ return sum(array) / len(array)
@@ -0,0 +1,20 @@
1
+ from .sortNumeric import _naturalKey
2
+
3
+
4
+ def sortByObjectKey(
5
+ array: list,
6
+ key: str,
7
+ descending: bool = False,
8
+ numerically: bool = False,
9
+ ) -> list:
10
+ if numerically:
11
+ array.sort(key=lambda item: _naturalKey(item[key]))
12
+
13
+ if descending:
14
+ array.reverse()
15
+
16
+ return array
17
+
18
+ array.sort(key=lambda item: item[key], reverse=descending)
19
+
20
+ return array
@@ -0,0 +1,16 @@
1
+ import re
2
+
3
+
4
+ def _naturalKey(value: str):
5
+ parts = re.split(r'(\d+)', str(value))
6
+
7
+ return [(0, int(part)) if part.isdigit() else (1, part) for part in parts if part != '']
8
+
9
+
10
+ def sortNumeric(array: list, descending: bool = False) -> list:
11
+ array.sort(key=_naturalKey)
12
+
13
+ if descending:
14
+ array.reverse()
15
+
16
+ return array
qsu/crypto/__init__.py ADDED
@@ -0,0 +1,23 @@
1
+ from .decodeBase64 import decodeBase64
2
+ from .decrypt import decrypt
3
+ from .encodeBase64 import encodeBase64
4
+ from .encrypt import encrypt
5
+ from .md5Hash import md5Hash
6
+ from .numberHash import numberHash
7
+ from .objectId import objectId
8
+ from .sha1Hash import sha1Hash
9
+ from .sha256Hash import sha256Hash
10
+ from .sha512Hash import sha512Hash
11
+
12
+ __all__ = [
13
+ 'decodeBase64',
14
+ 'decrypt',
15
+ 'encodeBase64',
16
+ 'encrypt',
17
+ 'md5Hash',
18
+ 'numberHash',
19
+ 'objectId',
20
+ 'sha1Hash',
21
+ 'sha256Hash',
22
+ 'sha512Hash',
23
+ ]
@@ -0,0 +1,5 @@
1
+ import base64
2
+
3
+
4
+ def decodeBase64(encodedStr: str) -> str:
5
+ return base64.b64decode(encodedStr).decode('utf-8')
qsu/crypto/decrypt.py ADDED
@@ -0,0 +1,34 @@
1
+ import base64
2
+
3
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
4
+
5
+
6
+ def decrypt(
7
+ str: str,
8
+ secret: str,
9
+ algorithm: str = 'aes-256-cbc',
10
+ toBase64: bool = False,
11
+ ) -> str:
12
+ if not str or len(str) < 1:
13
+ return ''
14
+
15
+ arrStr = str.split(':')
16
+ iv_part = arrStr.pop(0)
17
+ enc_part = ':'.join(arrStr)
18
+
19
+ if toBase64:
20
+ iv = base64.b64decode(iv_part)
21
+ enc = base64.b64decode(enc_part)
22
+ else:
23
+ iv = bytes.fromhex(iv_part)
24
+ enc = bytes.fromhex(enc_part)
25
+
26
+ cipher = Cipher(algorithms.AES(secret.encode('utf-8')), modes.CBC(iv))
27
+ decryptor = cipher.decryptor()
28
+ decrypted = decryptor.update(enc) + decryptor.finalize()
29
+
30
+ # Remove PKCS7 padding.
31
+ pad_len = decrypted[-1]
32
+ decrypted = decrypted[:-pad_len]
33
+
34
+ return decrypted.decode('utf-8')
@@ -0,0 +1,5 @@
1
+ import base64
2
+
3
+
4
+ def encodeBase64(str: str) -> str:
5
+ return base64.b64encode(str.encode('utf-8')).decode('utf-8')
qsu/crypto/encrypt.py ADDED
@@ -0,0 +1,35 @@
1
+ import base64
2
+ import os
3
+
4
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
5
+
6
+
7
+ def encrypt(
8
+ str: str,
9
+ secret: str,
10
+ algorithm: str = 'aes-256-cbc',
11
+ ivSize: int = 16,
12
+ toBase64: bool = False,
13
+ ) -> str:
14
+ if not str or len(str) < 1:
15
+ return ''
16
+
17
+ iv = os.urandom(ivSize)
18
+ cipher = Cipher(algorithms.AES(secret.encode('utf-8')), modes.CBC(iv))
19
+ encryptor = cipher.encryptor()
20
+
21
+ data = str.encode('utf-8')
22
+ block_size = 16
23
+ pad_len = block_size - (len(data) % block_size)
24
+ padded = data + bytes([pad_len]) * pad_len
25
+
26
+ enc = encryptor.update(padded) + encryptor.finalize()
27
+
28
+ if toBase64:
29
+ iv_str = base64.b64encode(iv).decode('ascii')
30
+ enc_str = base64.b64encode(enc).decode('ascii')
31
+ else:
32
+ iv_str = iv.hex()
33
+ enc_str = enc.hex()
34
+
35
+ return f'{iv_str}:{enc_str}'
qsu/crypto/md5Hash.py ADDED
@@ -0,0 +1,19 @@
1
+ import base64
2
+ import hashlib
3
+ from typing import Optional
4
+
5
+
6
+ def md5Hash(str: str, encoding: Optional[str] = None) -> str:
7
+ digest = hashlib.md5(str.encode('utf-8')).digest()
8
+ enc = encoding or 'hex'
9
+
10
+ if enc == 'hex':
11
+ return digest.hex()
12
+ if enc == 'base64':
13
+ return base64.b64encode(digest).decode('ascii')
14
+ if enc == 'base64url':
15
+ return base64.urlsafe_b64encode(digest).decode('ascii').rstrip('=')
16
+ if enc == 'binary':
17
+ return digest.decode('latin-1')
18
+
19
+ raise ValueError(f'Unsupported encoding: {encoding}')
@@ -0,0 +1,14 @@
1
+ def numberHash(str: str) -> int:
2
+ if not str:
3
+ return 0
4
+
5
+ hash = 0
6
+
7
+ for ch in str:
8
+ hash = (hash << 5) - hash + ord(ch)
9
+ # Emulate JS `hash |= 0`: keep low 32 bits and interpret as signed.
10
+ hash &= 0xFFFFFFFF
11
+ if hash >= 0x80000000:
12
+ hash -= 0x100000000
13
+
14
+ return hash
qsu/crypto/objectId.py ADDED
@@ -0,0 +1,9 @@
1
+ import secrets
2
+ import time
3
+
4
+
5
+ def objectId() -> str:
6
+ timestamp = format(int(time.time()), 'x')
7
+ random_hex = ''.join(format(secrets.randbelow(16), 'x') for _ in range(16))
8
+
9
+ return timestamp + random_hex
qsu/crypto/sha1Hash.py ADDED
@@ -0,0 +1,19 @@
1
+ import base64
2
+ import hashlib
3
+ from typing import Optional
4
+
5
+
6
+ def sha1Hash(str: str, encoding: Optional[str] = None) -> str:
7
+ digest = hashlib.sha1(str.encode('utf-8')).digest()
8
+ enc = encoding or 'hex'
9
+
10
+ if enc == 'hex':
11
+ return digest.hex()
12
+ if enc == 'base64':
13
+ return base64.b64encode(digest).decode('ascii')
14
+ if enc == 'base64url':
15
+ return base64.urlsafe_b64encode(digest).decode('ascii').rstrip('=')
16
+ if enc == 'binary':
17
+ return digest.decode('latin-1')
18
+
19
+ raise ValueError(f'Unsupported encoding: {encoding}')
@@ -0,0 +1,19 @@
1
+ import base64
2
+ import hashlib
3
+ from typing import Optional
4
+
5
+
6
+ def sha256Hash(str: str, encoding: Optional[str] = None) -> str:
7
+ digest = hashlib.sha256(str.encode('utf-8')).digest()
8
+ enc = encoding or 'hex'
9
+
10
+ if enc == 'hex':
11
+ return digest.hex()
12
+ if enc == 'base64':
13
+ return base64.b64encode(digest).decode('ascii')
14
+ if enc == 'base64url':
15
+ return base64.urlsafe_b64encode(digest).decode('ascii').rstrip('=')
16
+ if enc == 'binary':
17
+ return digest.decode('latin-1')
18
+
19
+ raise ValueError(f'Unsupported encoding: {encoding}')
@@ -0,0 +1,19 @@
1
+ import base64
2
+ import hashlib
3
+ from typing import Optional
4
+
5
+
6
+ def sha512Hash(str: str, encoding: Optional[str] = None) -> str:
7
+ digest = hashlib.sha512(str.encode('utf-8')).digest()
8
+ enc = encoding or 'hex'
9
+
10
+ if enc == 'hex':
11
+ return digest.hex()
12
+ if enc == 'base64':
13
+ return base64.b64encode(digest).decode('ascii')
14
+ if enc == 'base64url':
15
+ return base64.urlsafe_b64encode(digest).decode('ascii').rstrip('=')
16
+ if enc == 'binary':
17
+ return digest.decode('latin-1')
18
+
19
+ raise ValueError(f'Unsupported encoding: {encoding}')
qsu/date/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ from .createDateListFromRange import createDateListFromRange
2
+ from .dateToYYYYMMDD import dateToYYYYMMDD
3
+ from .dayDiff import dayDiff
4
+ from .isValidDate import isValidDate
5
+ from .today import today
6
+
7
+ __all__ = [
8
+ 'createDateListFromRange',
9
+ 'dateToYYYYMMDD',
10
+ 'dayDiff',
11
+ 'isValidDate',
12
+ 'today',
13
+ ]
@@ -0,0 +1,55 @@
1
+ import math
2
+ import re
3
+ from datetime import datetime
4
+
5
+ from .dateToYYYYMMDD import dateToYYYYMMDD
6
+ from .isValidDate import isValidDate
7
+
8
+ _YEAR_END_RE = re.compile(r'[0-9]{4}-12-31')
9
+
10
+
11
+ def createDateListFromRange(startDate: datetime, endDate: datetime):
12
+ if not isValidDate(dateToYYYYMMDD(startDate)) or not isValidDate(
13
+ dateToYYYYMMDD(endDate)
14
+ ):
15
+ raise ValueError('Either the start date or end date is an invalid date.')
16
+
17
+ date_diff = math.floor((endDate.timestamp() - startDate.timestamp()) / 86400)
18
+
19
+ if date_diff < 0:
20
+ raise ValueError('The start date is more recent than the end date.')
21
+
22
+ end_date_str = dateToYYYYMMDD(endDate)
23
+ all_date = []
24
+ current_year = startDate.year
25
+ current_month = startDate.month
26
+ current_day = startDate.day
27
+ current_date_str = ''
28
+
29
+ def create_new_date_str(year: int, month: int, day: int) -> str:
30
+ m = f'0{month}' if month < 10 else f'{month}'
31
+ d = f'0{day}' if day < 10 else f'{day}'
32
+ return f'{year}-{m}-{d}'
33
+
34
+ while end_date_str != current_date_str:
35
+ if _YEAR_END_RE.search(current_date_str):
36
+ current_year += 1
37
+ current_month = 1
38
+ current_day = 1
39
+
40
+ current_new_date_str = create_new_date_str(
41
+ current_year, current_month, current_day
42
+ )
43
+
44
+ if isValidDate(current_new_date_str):
45
+ current_day += 1
46
+ all_date.append(current_new_date_str)
47
+ current_date_str = current_new_date_str
48
+ else:
49
+ current_month += 1
50
+ current_day = 1
51
+ current_date_str = create_new_date_str(
52
+ current_year, current_month, current_day
53
+ )
54
+
55
+ return all_date
@@ -0,0 +1,20 @@
1
+ from datetime import date as _date
2
+ from datetime import datetime
3
+
4
+
5
+ def dateToYYYYMMDD(date, separator: str = '-') -> str:
6
+ if isinstance(date, datetime):
7
+ year = date.year
8
+ month = date.month
9
+ day = date.day
10
+ elif isinstance(date, _date):
11
+ year = date.year
12
+ month = date.month
13
+ day = date.day
14
+ else:
15
+ raise TypeError('date must be a datetime or date instance')
16
+
17
+ month_str = f'0{month}' if month < 10 else f'{month}'
18
+ day_str = f'0{day}' if day < 10 else f'{day}'
19
+
20
+ return f'{year}{separator}{month_str}{separator}{day_str}'
qsu/date/dayDiff.py ADDED
@@ -0,0 +1,10 @@
1
+ import math
2
+ from datetime import datetime
3
+
4
+
5
+ def dayDiff(date1: datetime, date2: datetime = None) -> int:
6
+ date2c = date2 if date2 is not None else datetime.now()
7
+
8
+ diff_ms = abs(date2c.timestamp() - date1.timestamp()) * 1000
9
+
10
+ return math.ceil(diff_ms / (1000 * 3600 * 24))
@@ -0,0 +1,27 @@
1
+ import re
2
+
3
+ _FORMAT_RE = re.compile(r'^[0-9]{4}-[0-9]{2}-[0-9]{2}$')
4
+
5
+ # Support range: 1600-01-01 ~ 9999/12/31
6
+ _VALID_RE = re.compile(
7
+ r'^(?=\d)(?:(?:31(?!.(?:0?[2469]|11))|(?:30|29)(?!.0?2)|29(?=.0?2.(?:(?:1[6-9]|[2-9]\d)?'
8
+ r'(?:0[48]|[2468][048]|[13579][26])|(?:16|[2468][048]|[3579][26])00)(?:\x20|$))|'
9
+ r'(?:2[0-8]|1\d|0?[1-9]))([-./])(?:1[012]|0?[1-9])\1(?:1[6-9]|[2-9]\d)?\d\d'
10
+ r'(?:(?=\x20\d)\x20|$))?(((0?[1-9]|1[012])(:[0-5]\d){0,2}(\x20[AP]M))|'
11
+ r'([01]\d|2[0-3])(:[0-5]\d){1,2})?$'
12
+ )
13
+
14
+
15
+ def isValidDate(dateYYYYMMDD: str) -> bool:
16
+ if not _FORMAT_RE.match(dateYYYYMMDD):
17
+ raise ValueError("The date format must be 'YYYY-MM-DD'")
18
+
19
+ converted_date = dateYYYYMMDD.split('-')
20
+
21
+ candidate = (
22
+ f'{int(converted_date[2], 10)}-'
23
+ f'{int(converted_date[1], 10)}-'
24
+ f'{int(converted_date[0], 10)}'
25
+ )
26
+
27
+ return bool(_VALID_RE.match(candidate))
qsu/date/today.py ADDED
@@ -0,0 +1,19 @@
1
+ from datetime import datetime
2
+
3
+
4
+ def today(separator: str = '-', yearFirst: bool = True) -> str:
5
+ date = datetime.now()
6
+ month = date.month
7
+ day = date.day
8
+
9
+ date_arr = [
10
+ f"{'0' if month < 10 else ''}{month}",
11
+ f"{'0' if day < 10 else ''}{day}",
12
+ ]
13
+
14
+ if yearFirst:
15
+ date_arr.insert(0, str(date.year))
16
+ else:
17
+ date_arr.append(str(date.year))
18
+
19
+ return separator.join(date_arr)