iker-python-common 1.0.40__tar.gz → 1.0.42__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 (83) hide show
  1. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/.github/workflows/pr.yml +3 -3
  2. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/.github/workflows/push.yml +1 -1
  3. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/PKG-INFO +3 -1
  4. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/pyproject.toml +24 -0
  5. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker/common/utils/dtutils.py +56 -5
  6. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker_python_common.egg-info/PKG-INFO +3 -1
  7. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker_python_common.egg-info/requires.txt +3 -0
  8. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_tests/common/utils/dtutils_test.py +57 -1
  9. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/.editorconfig +0 -0
  10. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/.gitignore +0 -0
  11. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/MANIFEST.in +0 -0
  12. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/README.md +0 -0
  13. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/VERSION +0 -0
  14. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/config/config.cfg +0 -0
  15. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/csv/data.csv +0 -0
  16. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/csv/data.tsv +0 -0
  17. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/s3utils/dir.baz/file.bar.baz +0 -0
  18. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/s3utils/dir.baz/file.foo.bar +0 -0
  19. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/s3utils/dir.baz/file.foo.baz +0 -0
  20. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/s3utils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
  21. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.bar.baz +0 -0
  22. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.bar +0 -0
  23. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/s3utils/dir.foo/dir.foo.bar/file.foo.baz +0 -0
  24. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/s3utils/dir.foo/file.bar +0 -0
  25. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/s3utils/dir.foo/file.baz +0 -0
  26. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/s3utils/dir.foo/file.foo +0 -0
  27. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/shutils/dir.baz/file.bar.baz +0 -0
  28. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/shutils/dir.baz/file.foo.bar +0 -0
  29. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/shutils/dir.baz/file.foo.baz +0 -0
  30. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/shutils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
  31. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/shutils/dir.foo/dir.foo.bar/file.bar.baz +0 -0
  32. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/shutils/dir.foo/dir.foo.bar/file.foo.bar +0 -0
  33. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/shutils/dir.foo/dir.foo.bar/file.foo.baz +0 -0
  34. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/shutils/dir.foo/file.bar +0 -0
  35. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/shutils/dir.foo/file.baz +0 -0
  36. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/resources/unittest/shutils/dir.foo/file.foo +0 -0
  37. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/setup.cfg +0 -0
  38. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/setup.py +0 -0
  39. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker/common/__init__.py +0 -0
  40. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker/common/utils/__init__.py +0 -0
  41. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker/common/utils/argutils.py +0 -0
  42. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker/common/utils/config.py +0 -0
  43. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker/common/utils/csv.py +0 -0
  44. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker/common/utils/dbutils.py +0 -0
  45. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker/common/utils/dockerutils.py +0 -0
  46. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker/common/utils/funcutils.py +0 -0
  47. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker/common/utils/jsonutils.py +0 -0
  48. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker/common/utils/logger.py +0 -0
  49. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker/common/utils/numutils.py +0 -0
  50. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker/common/utils/randutils.py +0 -0
  51. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker/common/utils/retry.py +0 -0
  52. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker/common/utils/s3utils.py +0 -0
  53. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker/common/utils/sequtils.py +0 -0
  54. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker/common/utils/shutils.py +0 -0
  55. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker/common/utils/span.py +0 -0
  56. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker/common/utils/strutils.py +0 -0
  57. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker/common/utils/testutils.py +0 -0
  58. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker/common/utils/typeutils.py +0 -0
  59. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker_python_common.egg-info/SOURCES.txt +0 -0
  60. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker_python_common.egg-info/dependency_links.txt +0 -0
  61. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker_python_common.egg-info/not-zip-safe +0 -0
  62. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/src/iker_python_common.egg-info/top_level.txt +0 -0
  63. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_test.py +0 -0
  64. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_tests/__init__.py +0 -0
  65. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_tests/common/utils/argutils_test.py +0 -0
  66. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_tests/common/utils/config_test.py +0 -0
  67. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_tests/common/utils/csv_test.py +0 -0
  68. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_tests/common/utils/dbutils_test.py +0 -0
  69. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_tests/common/utils/dockerutils_test.py +0 -0
  70. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_tests/common/utils/funcutils_test.py +0 -0
  71. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_tests/common/utils/jsonutils_test.py +0 -0
  72. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_tests/common/utils/logger_test.py +0 -0
  73. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_tests/common/utils/numutils_test.py +0 -0
  74. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_tests/common/utils/randutils_test.py +0 -0
  75. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_tests/common/utils/retry_test.py +0 -0
  76. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_tests/common/utils/s3utils_test.py +0 -0
  77. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_tests/common/utils/sequtils_test.py +0 -0
  78. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_tests/common/utils/shutils_test.py +0 -0
  79. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_tests/common/utils/span_test.py +0 -0
  80. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_tests/common/utils/strutils_test.py +0 -0
  81. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_tests/common/utils/testutils_test.py +0 -0
  82. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_tests/common/utils/typeutils_test.py +0 -0
  83. {iker_python_common-1.0.40 → iker_python_common-1.0.42}/test/iker_tests/docker_fixtures.py +0 -0
@@ -24,7 +24,7 @@ jobs:
24
24
  sudo apt-get update
25
25
  sudo apt-get install libxml2-dev libxslt1-dev llvm-14-dev
26
26
  python -m pip install --upgrade pip
27
- python -m pip install .[test]
27
+ python -m pip install .[test,all]
28
28
  python -m pytest . --cov --cov-report xml --cov-config pyproject.toml
29
29
 
30
30
  build-python312:
@@ -43,7 +43,7 @@ jobs:
43
43
  sudo apt-get update
44
44
  sudo apt-get install libxml2-dev libxslt1-dev llvm-14-dev
45
45
  python -m pip install --upgrade pip
46
- python -m pip install .[test]
46
+ python -m pip install .[test,all]
47
47
  python -m pytest . --cov --cov-report xml --cov-config pyproject.toml
48
48
 
49
49
  build-python313:
@@ -62,5 +62,5 @@ jobs:
62
62
  sudo apt-get update
63
63
  sudo apt-get install libxml2-dev libxslt1-dev llvm-14-dev
64
64
  python -m pip install --upgrade pip
65
- python -m pip install .[test]
65
+ python -m pip install .[test,all]
66
66
  python -m pytest . --cov --cov-report xml --cov-config pyproject.toml
@@ -24,7 +24,7 @@ jobs:
24
24
  sudo apt-get update
25
25
  sudo apt-get install libxml2-dev libxslt1-dev llvm-14-dev
26
26
  python -m pip install --upgrade pip build twine
27
- python -m pip install .[test]
27
+ python -m pip install .[test,all]
28
28
  python -m pytest . --cov --cov-report xml --cov-config pyproject.toml
29
29
  python -m build -sw .
30
30
  python -m twine upload --username __token__ --password ${{ secrets.PYPI_TOKEN }} dist/*
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iker-python-common
3
- Version: 1.0.40
3
+ Version: 1.0.42
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.11
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -13,6 +13,8 @@ Requires-Dist: psycopg>=3.2
13
13
  Requires-Dist: pymysql>=1.1
14
14
  Requires-Dist: sqlalchemy>=1.4
15
15
  Requires-Dist: typing-extensions; python_version < "3.12"
16
+ Provides-Extra: all
17
+ Requires-Dist: iker-python-common; extra == "all"
16
18
  Provides-Extra: test
17
19
  Requires-Dist: ddt>=1.7; extra == "test"
18
20
  Requires-Dist: moto[all,ec2,s3]>=5.0; extra == "test"
@@ -6,6 +6,27 @@ requires = [
6
6
  ]
7
7
  build-backend = "setuptools.build_meta"
8
8
 
9
+ [dependency-groups]
10
+ dev = [
11
+ "boto3>=1.35",
12
+ "docker>=7.1",
13
+ "numpy>=1.26",
14
+ "psycopg>=3.2",
15
+ "pymysql>=1.1",
16
+ "sqlalchemy>=1.4",
17
+ "typing-extensions; python_version<'3.12'"
18
+ ]
19
+ test = [
20
+ "ddt>=1.7",
21
+ "moto[ec2,s3,all]>=5.0",
22
+ "pytest-cov>=5.0",
23
+ "pytest-mysql>=3.0",
24
+ "pytest-order>=1.3",
25
+ "pytest-postgresql>=6.1",
26
+ "pytest>=8.3",
27
+ "sqlalchemy>=2.0",
28
+ ]
29
+
9
30
  [project]
10
31
  name = "iker-python-common"
11
32
  dynamic = ["version"]
@@ -27,6 +48,9 @@ dependencies = [
27
48
  ]
28
49
 
29
50
  [project.optional-dependencies]
51
+ all = [
52
+ "iker-python-common",
53
+ ]
30
54
  test = [
31
55
  "ddt>=1.7",
32
56
  "moto[ec2,s3,all]>=5.0",
@@ -23,7 +23,9 @@ __all__ = [
23
23
  "dt_utc_now",
24
24
  "dt_utc",
25
25
  "dt_to_ts",
26
+ "dt_to_ts_us",
26
27
  "dt_from_ts",
28
+ "dt_from_ts_us",
27
29
  "dt_parse",
28
30
  "dt_format",
29
31
  "dt_parse_iso",
@@ -152,6 +154,17 @@ def dt_utc(
152
154
  return datetime.datetime(year, month, day, hour, minute, second, microsecond, tzinfo=datetime.timezone.utc)
153
155
 
154
156
 
157
+ def dt_to_td(dt: datetime.datetime) -> datetime.timedelta:
158
+ """
159
+ Returns corresponding timedelta of the given datetime
160
+
161
+ :param dt: the given datetime
162
+
163
+ :return: timedelta from POSIX epoch
164
+ """
165
+ return dt.replace(tzinfo=datetime.timezone.utc) - dt_utc_epoch()
166
+
167
+
155
168
  def dt_to_ts(dt: datetime.datetime) -> float:
156
169
  """
157
170
  Returns corresponding timestamp in seconds of the given datetime
@@ -160,7 +173,30 @@ def dt_to_ts(dt: datetime.datetime) -> float:
160
173
 
161
174
  :return: timestamp in seconds from POSIX epoch
162
175
  """
163
- return (dt.replace(tzinfo=datetime.timezone.utc) - dt_utc_epoch()).total_seconds()
176
+ return dt_to_ts_us(dt) / 1.0e6
177
+
178
+
179
+ def dt_to_ts_us(dt: datetime.datetime) -> int:
180
+ """
181
+ Returns corresponding timestamp in microseconds of the given datetime
182
+
183
+ :param dt: the given datetime
184
+
185
+ :return: timestamp in microseconds from POSIX epoch
186
+ """
187
+ td = dt_to_td(dt)
188
+ return (td.days * 86400 + td.seconds) * 1000000 + td.microseconds
189
+
190
+
191
+ def dt_from_td(td: datetime.timedelta) -> datetime.datetime:
192
+ """
193
+ Returns corresponding datetime in UTC of the given timedelta
194
+
195
+ :param td: timedelta from POSIX epoch
196
+
197
+ :return: the given datetime in UTC
198
+ """
199
+ return dt_utc_epoch() + td
164
200
 
165
201
 
166
202
  def dt_from_ts(ts: float) -> datetime.datetime:
@@ -171,11 +207,26 @@ def dt_from_ts(ts: float) -> datetime.datetime:
171
207
 
172
208
  :return: the given datetime in UTC
173
209
  """
174
- return datetime.datetime.fromtimestamp(ts, tz=datetime.timezone.utc)
210
+ return dt_from_ts_us(round(ts * 1.0e6))
211
+
212
+
213
+ def dt_from_ts_us(ts: int) -> datetime.datetime:
214
+ """
215
+ Returns corresponding datetime in UTC of the given timestamp in microseconds
216
+
217
+ :param ts: timestamp in microseconds from POSIX epoch
218
+
219
+ :return: the given datetime in UTC
220
+ """
221
+ return dt_from_td(datetime.timedelta(microseconds=ts))
175
222
 
176
223
 
177
- basic_tz_regexp = re.compile(r"([+-])(\d{2})(\d{2})")
178
- extended_tz_regexp = re.compile(r"([+-])(\d{2}):(\d{2})")
224
+ basic_date_regex: re.Pattern[str] = re.compile(r"(\d{4})(\d{2})(\d{2})")
225
+ extended_date_regex: re.Pattern[str] = re.compile(r"(\d{4})-(\d{2})-(\d{2})")
226
+ basic_time_regex: re.Pattern[str] = re.compile(r"T(\d{2})(\d{2})(\d{2})(\.\d{1,6})?")
227
+ extended_time_regex: re.Pattern[str] = re.compile(r"T(\d{2}):(\d{2}):(\d{2})(\.\d{1,6})?")
228
+ basic_tz_regexp: re.Pattern[str] = re.compile(r"([+-])(\d{2})(\d{2})")
229
+ extended_tz_regexp: re.Pattern[str] = re.compile(r"([+-])(\d{2}):(\d{2})")
179
230
 
180
231
 
181
232
  def dt_parse(dt_str: str, fmt_str: str | Sequence[str]) -> datetime.datetime | None:
@@ -191,7 +242,7 @@ def dt_parse(dt_str: str, fmt_str: str | Sequence[str]) -> datetime.datetime | N
191
242
  return None
192
243
 
193
244
  if isinstance(fmt_str, str):
194
- for tz_directive in ["%z", "%:z", "%Z"]:
245
+ for tz_directive in ["%z", "%:z"]:
195
246
  if tz_directive in fmt_str:
196
247
  if tz_directive == "%:z":
197
248
  # Replaces ISO 8601 timezone format "%:z" (e.g., "+01:00") with "%z" (e.g., "+0100")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iker-python-common
3
- Version: 1.0.40
3
+ Version: 1.0.42
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.11
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -13,6 +13,8 @@ Requires-Dist: psycopg>=3.2
13
13
  Requires-Dist: pymysql>=1.1
14
14
  Requires-Dist: sqlalchemy>=1.4
15
15
  Requires-Dist: typing-extensions; python_version < "3.12"
16
+ Provides-Extra: all
17
+ Requires-Dist: iker-python-common; extra == "all"
16
18
  Provides-Extra: test
17
19
  Requires-Dist: ddt>=1.7; extra == "test"
18
20
  Requires-Dist: moto[all,ec2,s3]>=5.0; extra == "test"
@@ -8,6 +8,9 @@ sqlalchemy>=1.4
8
8
  [:python_version < "3.12"]
9
9
  typing-extensions
10
10
 
11
+ [all]
12
+ iker-python-common
13
+
11
14
  [test]
12
15
  ddt>=1.7
13
16
  moto[all,ec2,s3]>=5.0
@@ -4,7 +4,7 @@ import ddt
4
4
 
5
5
  from iker.common.utils.dtutils import basic_format, extended_format, iso_format
6
6
  from iker.common.utils.dtutils import dt_format, dt_format_iso, dt_parse, dt_parse_iso
7
- from iker.common.utils.dtutils import dt_from_ts, dt_to_ts
7
+ from iker.common.utils.dtutils import dt_from_ts, dt_from_ts_us, dt_to_ts, dt_to_ts_us
8
8
  from iker.common.utils.dtutils import dt_utc
9
9
  from iker.common.utils.dtutils import dt_utc_epoch, dt_utc_max, dt_utc_min
10
10
 
@@ -63,6 +63,34 @@ class DtUtilsTest(unittest.TestCase):
63
63
  def test_dt_to_ts(self, data, expect):
64
64
  self.assertEqual(expect, dt_to_ts(data))
65
65
 
66
+ data_dt_to_ts_us = [
67
+ (dt_utc(1970, 1, 1), 0),
68
+ (dt_utc(1969, 12, 31, 23, 59, 59), -1000000),
69
+ (dt_utc(1969, 12, 31, 23, 59, 59, 1), -999999),
70
+ (dt_utc(1969, 12, 31, 23, 59, 59, 1000), -999000),
71
+ (dt_utc(1969, 12, 31, 23, 59, 59, 999999), -1),
72
+ (dt_utc(1970, 1, 1, 0, 0, 1), 1000000),
73
+ (dt_utc(1970, 1, 1, 0, 0, 1, 1), 1000001),
74
+ (dt_utc(1970, 1, 1, 0, 0, 1, 1000), 1001000),
75
+ (dt_utc(1970, 1, 1, 0, 0, 1, 999999), 1999999),
76
+ (dt_utc(1970, 1, 1, 0, 0, 0, 1), 1),
77
+ (dt_utc(1970, 1, 1, 0, 0, 0, 1000), 1000),
78
+ (dt_utc(1970, 1, 1, 0, 0, 0, 999999), 999999),
79
+ (dt_utc(2025, 1, 1, 0, 0, 0), 1735689600000000),
80
+ (dt_utc(2025, 1, 1, 0, 0, 0, 1), 1735689600000001),
81
+ (dt_utc(2025, 1, 1, 0, 0, 0, 1000), 1735689600001000),
82
+ (dt_utc(2025, 1, 1, 0, 0, 0, 999999), 1735689600999999),
83
+ (dt_utc(2100, 1, 1, 0, 0, 0), 4102444800000000),
84
+ (dt_utc(2100, 1, 1, 0, 0, 0, 1), 4102444800000001),
85
+ (dt_utc(2100, 1, 1, 0, 0, 0, 1000), 4102444800001000),
86
+ (dt_utc(2100, 1, 1, 0, 0, 0, 999999), 4102444800999999),
87
+ ]
88
+
89
+ @ddt.idata(data_dt_to_ts_us)
90
+ @ddt.unpack
91
+ def test_dt_to_ts_us(self, data, expect):
92
+ self.assertEqual(expect, dt_to_ts_us(data))
93
+
66
94
  data_dt_from_ts = [
67
95
  (0.0, dt_utc(1970, 1, 1)),
68
96
  (-1.0, dt_utc(1969, 12, 31, 23, 59, 59)),
@@ -91,6 +119,34 @@ class DtUtilsTest(unittest.TestCase):
91
119
  def test_dt_from_ts(self, data, expect):
92
120
  self.assertEqual(expect, dt_from_ts(data))
93
121
 
122
+ data_dt_from_ts_us = [
123
+ (0, dt_utc(1970, 1, 1)),
124
+ (-1000000, dt_utc(1969, 12, 31, 23, 59, 59)),
125
+ (-999999, dt_utc(1969, 12, 31, 23, 59, 59, 1)),
126
+ (-999000, dt_utc(1969, 12, 31, 23, 59, 59, 1000)),
127
+ (-1, dt_utc(1969, 12, 31, 23, 59, 59, 999999)),
128
+ (1000000, dt_utc(1970, 1, 1, 0, 0, 1)),
129
+ (1000001, dt_utc(1970, 1, 1, 0, 0, 1, 1)),
130
+ (1001000, dt_utc(1970, 1, 1, 0, 0, 1, 1000)),
131
+ (1999999, dt_utc(1970, 1, 1, 0, 0, 1, 999999)),
132
+ (1, dt_utc(1970, 1, 1, 0, 0, 0, 1)),
133
+ (1000, dt_utc(1970, 1, 1, 0, 0, 0, 1000)),
134
+ (999999, dt_utc(1970, 1, 1, 0, 0, 0, 999999)),
135
+ (1735689600000000, dt_utc(2025, 1, 1, 0, 0, 0)),
136
+ (1735689600000001, dt_utc(2025, 1, 1, 0, 0, 0, 1)),
137
+ (1735689600001000, dt_utc(2025, 1, 1, 0, 0, 0, 1000)),
138
+ (1735689600999999, dt_utc(2025, 1, 1, 0, 0, 0, 999999)),
139
+ (4102444800000000, dt_utc(2100, 1, 1, 0, 0, 0)),
140
+ (4102444800000001, dt_utc(2100, 1, 1, 0, 0, 0, 1)),
141
+ (4102444800001000, dt_utc(2100, 1, 1, 0, 0, 0, 1000)),
142
+ (4102444800999999, dt_utc(2100, 1, 1, 0, 0, 0, 999999)),
143
+ ]
144
+
145
+ @ddt.idata(data_dt_from_ts_us)
146
+ @ddt.unpack
147
+ def test_dt_from_ts_us(self, data, expect):
148
+ self.assertEqual(expect, dt_from_ts_us(data))
149
+
94
150
  data_dt_parse = [
95
151
  ("00010101T000000", basic_format(), dt_utc(1, 1, 1, 0, 0, 0)),
96
152
  ("00010101T000000.000000", basic_format(with_ms=True), dt_utc(1, 1, 1, 0, 0, 0)),