bear-epoch-time 1.0.1__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.
- bear_epoch_time-1.0.1/.bumpversion.cfg +12 -0
- bear_epoch_time-1.0.1/.gitignore +211 -0
- bear_epoch_time-1.0.1/AGENTS.md +15 -0
- bear_epoch_time-1.0.1/PKG-INFO +14 -0
- bear_epoch_time-1.0.1/README.md +1 -0
- bear_epoch_time-1.0.1/TODO.md +16 -0
- bear_epoch_time-1.0.1/directory_structure.txt +34 -0
- bear_epoch_time-1.0.1/maskfile.md +58 -0
- bear_epoch_time-1.0.1/noxfile.py +70 -0
- bear_epoch_time-1.0.1/pyproject.toml +35 -0
- bear_epoch_time-1.0.1/src/bear_epoch_time/__init__.py +25 -0
- bear_epoch_time-1.0.1/src/bear_epoch_time/_helpers.py +92 -0
- bear_epoch_time-1.0.1/src/bear_epoch_time/_time_class.py +327 -0
- bear_epoch_time-1.0.1/src/bear_epoch_time/_time_class_legacy.py +315 -0
- bear_epoch_time-1.0.1/src/bear_epoch_time/_timer.py +77 -0
- bear_epoch_time-1.0.1/src/bear_epoch_time/_tools.py +17 -0
- bear_epoch_time-1.0.1/src/bear_epoch_time/_tz.py +172 -0
- bear_epoch_time-1.0.1/src/bear_epoch_time/basic_logger/__init__.py +3 -0
- bear_epoch_time-1.0.1/src/bear_epoch_time/basic_logger/logger.py +79 -0
- bear_epoch_time-1.0.1/src/bear_epoch_time/basic_logger/logger.pyi +19 -0
- bear_epoch_time-1.0.1/src/bear_epoch_time/constants/__init__.py +8 -0
- bear_epoch_time-1.0.1/src/bear_epoch_time/constants/date_related.py +32 -0
- bear_epoch_time-1.0.1/src/bear_epoch_time/constants/time_related.py +22 -0
- bear_epoch_time-1.0.1/src/bear_epoch_time/time_manager.py +238 -0
- bear_epoch_time-1.0.1/tests/__init__.py +0 -0
- bear_epoch_time-1.0.1/tests/test_basic_logger.py +93 -0
- bear_epoch_time-1.0.1/tests/test_epoch_timestamp.py +354 -0
- bear_epoch_time-1.0.1/tests/test_time_parsing.py +26 -0
- bear_epoch_time-1.0.1/uv.lock +572 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
[bumpversion]
|
|
2
|
+
current_version = 1.0.1
|
|
3
|
+
|
|
4
|
+
[bumpversion:file:pyproject.toml]
|
|
5
|
+
|
|
6
|
+
[bumpversion:file:README.md]
|
|
7
|
+
search = # Bear Epoch Time v{current_version}
|
|
8
|
+
replace = # Bear Epoch Time v{new_version}
|
|
9
|
+
|
|
10
|
+
[bumpversion:file:src/bear_epoch_time/__init__.py]
|
|
11
|
+
search = __version__ = "{current_version}"
|
|
12
|
+
replace = __version__ = "{new_version}"
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Created by https://www.toptal.com/developers/gitignore/api/python,macos,shell
|
|
2
|
+
# Edit at https://www.toptal.com/developers/gitignore?templates=python,macos,shell
|
|
3
|
+
|
|
4
|
+
### macOS ###
|
|
5
|
+
# General
|
|
6
|
+
.DS_Store
|
|
7
|
+
.AppleDouble
|
|
8
|
+
.LSOverride
|
|
9
|
+
|
|
10
|
+
# Icon must end with two \r
|
|
11
|
+
Icon
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Thumbnails
|
|
15
|
+
._*
|
|
16
|
+
|
|
17
|
+
# Files that might appear in the root of a volume
|
|
18
|
+
.DocumentRevisions-V100
|
|
19
|
+
.fseventsd
|
|
20
|
+
.Spotlight-V100
|
|
21
|
+
.TemporaryItems
|
|
22
|
+
.Trashes
|
|
23
|
+
.VolumeIcon.icns
|
|
24
|
+
.com.apple.timemachine.donotpresent
|
|
25
|
+
|
|
26
|
+
# Directories potentially created on remote AFP share
|
|
27
|
+
.AppleDB
|
|
28
|
+
.AppleDesktop
|
|
29
|
+
Network Trash Folder
|
|
30
|
+
Temporary Items
|
|
31
|
+
.apdisk
|
|
32
|
+
|
|
33
|
+
### macOS Patch ###
|
|
34
|
+
# iCloud generated files
|
|
35
|
+
*.icloud
|
|
36
|
+
|
|
37
|
+
### Python ###
|
|
38
|
+
# Byte-compiled / optimized / DLL files
|
|
39
|
+
__pycache__/
|
|
40
|
+
*.py[cod]
|
|
41
|
+
*$py.class
|
|
42
|
+
|
|
43
|
+
# C extensions
|
|
44
|
+
*.so
|
|
45
|
+
|
|
46
|
+
# Distribution / packaging
|
|
47
|
+
.Python
|
|
48
|
+
build/
|
|
49
|
+
develop-eggs/
|
|
50
|
+
dist/
|
|
51
|
+
downloads/
|
|
52
|
+
eggs/
|
|
53
|
+
.eggs/
|
|
54
|
+
lib/
|
|
55
|
+
lib64/
|
|
56
|
+
parts/
|
|
57
|
+
sdist/
|
|
58
|
+
var/
|
|
59
|
+
wheels/
|
|
60
|
+
share/python-wheels/
|
|
61
|
+
*.egg-info/
|
|
62
|
+
.installed.cfg
|
|
63
|
+
*.egg
|
|
64
|
+
MANIFEST
|
|
65
|
+
|
|
66
|
+
# PyInstaller
|
|
67
|
+
# Usually these files are written by a python script from a template
|
|
68
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
69
|
+
*.manifest
|
|
70
|
+
*.spec
|
|
71
|
+
|
|
72
|
+
# Installer logs
|
|
73
|
+
pip-log.txt
|
|
74
|
+
pip-delete-this-directory.txt
|
|
75
|
+
|
|
76
|
+
# Unit test / coverage reports
|
|
77
|
+
htmlcov/
|
|
78
|
+
.tox/
|
|
79
|
+
.nox/
|
|
80
|
+
.coverage
|
|
81
|
+
.coverage.*
|
|
82
|
+
.cache
|
|
83
|
+
nosetests.xml
|
|
84
|
+
coverage.xml
|
|
85
|
+
*.cover
|
|
86
|
+
*.py,cover
|
|
87
|
+
.hypothesis/
|
|
88
|
+
.pytest_cache/
|
|
89
|
+
cover/
|
|
90
|
+
|
|
91
|
+
# Translations
|
|
92
|
+
*.mo
|
|
93
|
+
*.pot
|
|
94
|
+
|
|
95
|
+
# Django stuff:
|
|
96
|
+
*.log
|
|
97
|
+
local_settings.py
|
|
98
|
+
db.sqlite3
|
|
99
|
+
db.sqlite3-journal
|
|
100
|
+
|
|
101
|
+
# Flask stuff:
|
|
102
|
+
instance/
|
|
103
|
+
.webassets-cache
|
|
104
|
+
|
|
105
|
+
# Scrapy stuff:
|
|
106
|
+
.scrapy
|
|
107
|
+
|
|
108
|
+
# Sphinx documentation
|
|
109
|
+
docs/_build/
|
|
110
|
+
|
|
111
|
+
# PyBuilder
|
|
112
|
+
.pybuilder/
|
|
113
|
+
target/
|
|
114
|
+
|
|
115
|
+
# Jupyter Notebook
|
|
116
|
+
.ipynb_checkpoints
|
|
117
|
+
|
|
118
|
+
# IPython
|
|
119
|
+
profile_default/
|
|
120
|
+
ipython_config.py
|
|
121
|
+
|
|
122
|
+
# pyenv
|
|
123
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
124
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
125
|
+
.python-version
|
|
126
|
+
|
|
127
|
+
# pipenv
|
|
128
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
129
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
130
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
131
|
+
# install all needed dependencies.
|
|
132
|
+
#Pipfile.lock
|
|
133
|
+
|
|
134
|
+
# poetry
|
|
135
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
136
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
137
|
+
# commonly ignored for libraries.
|
|
138
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
139
|
+
#poetry.lock
|
|
140
|
+
|
|
141
|
+
# pdm
|
|
142
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
143
|
+
#pdm.lock
|
|
144
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
145
|
+
# in version control.
|
|
146
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
147
|
+
.pdm.toml
|
|
148
|
+
|
|
149
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
150
|
+
__pypackages__/
|
|
151
|
+
|
|
152
|
+
# Celery stuff
|
|
153
|
+
celerybeat-schedule
|
|
154
|
+
celerybeat.pid
|
|
155
|
+
|
|
156
|
+
# SageMath parsed files
|
|
157
|
+
*.sage.py
|
|
158
|
+
|
|
159
|
+
# Environments
|
|
160
|
+
.env
|
|
161
|
+
.venv
|
|
162
|
+
env/
|
|
163
|
+
venv/
|
|
164
|
+
ENV/
|
|
165
|
+
env.bak/
|
|
166
|
+
venv.bak/
|
|
167
|
+
|
|
168
|
+
# Spyder project settings
|
|
169
|
+
.spyderproject
|
|
170
|
+
.spyproject
|
|
171
|
+
|
|
172
|
+
# Rope project settings
|
|
173
|
+
.ropeproject
|
|
174
|
+
|
|
175
|
+
# mkdocs documentation
|
|
176
|
+
/site
|
|
177
|
+
|
|
178
|
+
# mypy
|
|
179
|
+
.mypy_cache/
|
|
180
|
+
.dmypy.json
|
|
181
|
+
dmypy.json
|
|
182
|
+
|
|
183
|
+
# Pyre type checker
|
|
184
|
+
.pyre/
|
|
185
|
+
|
|
186
|
+
# pytype static type analyzer
|
|
187
|
+
.pytype/
|
|
188
|
+
|
|
189
|
+
# Cython debug symbols
|
|
190
|
+
cython_debug/
|
|
191
|
+
|
|
192
|
+
# PyCharm
|
|
193
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
194
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
195
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
196
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
197
|
+
.idea/
|
|
198
|
+
|
|
199
|
+
### Python Patch ###
|
|
200
|
+
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
|
201
|
+
poetry.toml
|
|
202
|
+
|
|
203
|
+
# ruff
|
|
204
|
+
.ruff_cache/
|
|
205
|
+
|
|
206
|
+
# LSP config files
|
|
207
|
+
pyrightconfig.json
|
|
208
|
+
|
|
209
|
+
.vscode/
|
|
210
|
+
|
|
211
|
+
*.db
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Contributor Guide for AI Agents
|
|
2
|
+
|
|
3
|
+
## Dev Environment Guidelines
|
|
4
|
+
|
|
5
|
+
### Claude Only
|
|
6
|
+
|
|
7
|
+
<antml:thinking_mode>interleaved</antml:thinking_mode><antml:max_thinking_length>16000</antml:max_thinking_length>
|
|
8
|
+
|
|
9
|
+
- Python Version and Typing: Assume Python 3.12 or higher is being used. Because of that we SHOULD use the latest typing features available in Python 3.12 and later. Never use `Optional`, `Union`, or `Dict, List, Tuple` from `typing` module. Instead, use the built-in types directly, e.g., `list`, `dict`, `tuple`, and use `|` for union types. This is not an exhaustive list, so be thoughtful about any potentially deprecated types you might use. If you are not sure about something, ask the user.
|
|
10
|
+
- Type Annotations: Use type annotations as often as possible. If a function has no return value, use `None` as the return type. If a function returns a value, use the appropriate type annotation but don't bend over backwards to make it perfect. If you are not sure about the type, use `Any` as a fallback but use this sparingly, see it as a failure and you should endeavor to avoid using `Any` as much as possible.
|
|
11
|
+
- Docstrings: Use docstrings for most (95%) public functions and classes. The docstring should describe the purpose of the function or class, its parameters, and its return value. Use the Google style for docstrings. Because of this it is highly preferred to not use comments in the code unless absolutely necessary. If you feel like you need to use a comment, consider if the code can be refactored to make it more readable instead.
|
|
12
|
+
- Imports: Use relative imports for everything (beyond the obvious imports for external packages) in this current package. Keep imports clean using `uv run isort src tests` and `uv run black src tests` to format the code in that order.
|
|
13
|
+
- Endeavor to use a TDD approach when writing new code. Write tests first, then write the code to make the tests pass and then refactor the code to make it cleaner and more readable.
|
|
14
|
+
- There is a `directory_structure.txt` file in the root of the repository that describes the directory structure of the project. Use this as a quick way to understand the project structure and where to place new files or modules.
|
|
15
|
+
- While projects might partially use Poetry, UV is generally the preferred tool for managing dependencies and virtual environments. It is taking me some time to get used to it, so please bear with me. If you are not sure about something, ask the user.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bear-epoch-time
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Elegant epoch timestamp handling with timezone-aware operations, fluent API, and smart defaults for Python developers who hate datetime complexity.
|
|
5
|
+
Author-email: chaz <bright.lid5647@fastmail.com>
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: python-dateutil>=2.9.0.post0
|
|
8
|
+
Requires-Dist: pytz>=2025.2
|
|
9
|
+
Requires-Dist: rich-tree-cli>=0.3.14
|
|
10
|
+
Requires-Dist: rich>=14.0.0
|
|
11
|
+
Requires-Dist: tzlocal>=5.3.1
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# Bear Epoch Time v1.0.1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Bear Epoch Time v1.0.1
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags: ["todo", "python", "module", "time", "pypi"]
|
|
3
|
+
project_path: "/Users/chaz/Documents/repos/github/sicksubroutine/bear-epoch-time"
|
|
4
|
+
---
|
|
5
|
+
# bear-epoch-time
|
|
6
|
+
|
|
7
|
+
## Tasks
|
|
8
|
+
|
|
9
|
+
- [ ] Do a once over of everything with Codex (Hi! That is you! :D)
|
|
10
|
+
- [ ] Ensure there is good test coverage
|
|
11
|
+
- [ ] use Nox for ensure full Python Version coverage
|
|
12
|
+
- [/] Return Default Constants for timezone related things (Basically have it use specific defaults for `BEAR` (the dev) and check for local values for everybody else) ([/] means the task is in progress)
|
|
13
|
+
|
|
14
|
+
## Notes
|
|
15
|
+
|
|
16
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
bear-epoch-time
|
|
2
|
+
├── .bumpversion.cfg (327 bytes) (12 lines)
|
|
3
|
+
├── .gitignore (3937 bytes) (211 lines)
|
|
4
|
+
├── .python-version (5 bytes) (1 lines)
|
|
5
|
+
├── AGENTS.md (2448 bytes) (15 lines)
|
|
6
|
+
├── directory_structure.txt (1794 bytes) (34 lines)
|
|
7
|
+
├── maskfile.md (819 bytes) (53 lines)
|
|
8
|
+
├── noxfile.py (1507 bytes) (70 lines)
|
|
9
|
+
├── pyproject.toml (855 bytes) (35 lines)
|
|
10
|
+
├── README.md (25 bytes) (1 lines)
|
|
11
|
+
├── TODO.md (554 bytes) (16 lines)
|
|
12
|
+
├── src
|
|
13
|
+
│ └── bear_epoch_time
|
|
14
|
+
│ ├── __init__.py (544 bytes) (25 lines)
|
|
15
|
+
│ ├── _helpers.py (2868 bytes) (92 lines)
|
|
16
|
+
│ ├── _time_class.py (11783 bytes) (327 lines)
|
|
17
|
+
│ ├── _time_class_legacy.py (11504 bytes) (315 lines)
|
|
18
|
+
│ ├── _timer.py (2349 bytes) (77 lines)
|
|
19
|
+
│ ├── _tools.py (456 bytes) (17 lines)
|
|
20
|
+
│ ├── _tz.py (4930 bytes) (172 lines)
|
|
21
|
+
│ ├── time_manager.py (8518 bytes) (238 lines)
|
|
22
|
+
│ ├── basic_logger
|
|
23
|
+
│ │ ├── __init__.py (117 bytes) (3 lines)
|
|
24
|
+
│ │ ├── logger.py (2459 bytes) (79 lines)
|
|
25
|
+
│ │ └── logger.pyi (948 bytes) (19 lines)
|
|
26
|
+
│ └── constants
|
|
27
|
+
│ ├── __init__.py (298 bytes) (8 lines)
|
|
28
|
+
│ ├── date_related.py (1003 bytes) (32 lines)
|
|
29
|
+
│ └── time_related.py (638 bytes) (22 lines)
|
|
30
|
+
└── tests
|
|
31
|
+
├── __init__.py (0 bytes) (0 lines)
|
|
32
|
+
├── test_basic_logger.py (3274 bytes) (93 lines)
|
|
33
|
+
├── test_epoch_timestamp.py (14011 bytes) (354 lines)
|
|
34
|
+
└── test_time_parsing.py (992 bytes) (26 lines)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Python Project Mask File
|
|
2
|
+
|
|
3
|
+
## clean
|
|
4
|
+
|
|
5
|
+
> This command cleans the build artifacts
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
rm -rf dist/
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## bump (patch_version)
|
|
12
|
+
|
|
13
|
+
> Bump the version of the local project specifying the patch level: `minor`, `major`, `patch`
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bump2version ${patch_version} --allow-dirty
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## build
|
|
20
|
+
|
|
21
|
+
> This command builds the project via uv
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
uv build
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## test
|
|
28
|
+
|
|
29
|
+
> This command runs the tests using pytest
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pytest -s
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## publish
|
|
36
|
+
|
|
37
|
+
> This command publishes the package to PyPI officially, isn't that great?
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
if [ "${publish_location}" = "local" ]; then
|
|
41
|
+
twine upload -r local dist/* # uploads to a local repository
|
|
42
|
+
else
|
|
43
|
+
export UV_PUBLISH_TOKEN=$(op read "op://Private/PyPI Prod/api_key")
|
|
44
|
+
uv publish --index pypi dist/* --token $UV_PUBLISH_TOKEN
|
|
45
|
+
fi
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## full (patch_version) (publish_location)
|
|
49
|
+
|
|
50
|
+
> This command runs the full build and publish process
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
$MASK clean
|
|
54
|
+
$MASK bump ${patch_version}
|
|
55
|
+
$MASK build
|
|
56
|
+
$MASK publish ${publish_location}
|
|
57
|
+
pytest -s
|
|
58
|
+
```
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import nox
|
|
2
|
+
|
|
3
|
+
VERSIONS = ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# @nox.session(venv_backend="uv", tags=["lint"])
|
|
7
|
+
# def flake8(session):
|
|
8
|
+
# """Run flake8 linting"""
|
|
9
|
+
# session.install("flake8")
|
|
10
|
+
|
|
11
|
+
# session.run(
|
|
12
|
+
# "flake8",
|
|
13
|
+
# ".",
|
|
14
|
+
# "--count",
|
|
15
|
+
# "--select=E9,F63,F7,F82",
|
|
16
|
+
# "--show-source",
|
|
17
|
+
# "--statistics",
|
|
18
|
+
# "--exclude=.venv,.nox",
|
|
19
|
+
# )
|
|
20
|
+
|
|
21
|
+
# session.run(
|
|
22
|
+
# "flake8",
|
|
23
|
+
# ".",
|
|
24
|
+
# "--count",
|
|
25
|
+
# "--exit-zero",
|
|
26
|
+
# "--max-complexity=10",
|
|
27
|
+
# "--max-line-length=127",
|
|
28
|
+
# "--statistics",
|
|
29
|
+
# "--exclude=.venv,.nox",
|
|
30
|
+
# )
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# @nox.session(venv_backend="uv", tags=["lint"])
|
|
34
|
+
# def isort(session):
|
|
35
|
+
# """Check import sorting with isort"""
|
|
36
|
+
# session.install("isort")
|
|
37
|
+
# session.run(
|
|
38
|
+
# "isort",
|
|
39
|
+
# ".",
|
|
40
|
+
# "--check-only",
|
|
41
|
+
# "--diff",
|
|
42
|
+
# "--skip",
|
|
43
|
+
# ".venv",
|
|
44
|
+
# "--skip",
|
|
45
|
+
# ".nox",
|
|
46
|
+
# "--line-length",
|
|
47
|
+
# "120",
|
|
48
|
+
# )
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# @nox.session(venv_backend="uv", tags=["lint"])
|
|
52
|
+
# def black(session):
|
|
53
|
+
# """Check formatting with black"""
|
|
54
|
+
# session.install("black")
|
|
55
|
+
# session.run(
|
|
56
|
+
# "black",
|
|
57
|
+
# ".",
|
|
58
|
+
# "--check",
|
|
59
|
+
# "--diff",
|
|
60
|
+
# "--extend-exclude=.venv|.nox",
|
|
61
|
+
# "-l",
|
|
62
|
+
# "120",
|
|
63
|
+
# )
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@nox.session(python=VERSIONS, venv_backend="uv")
|
|
67
|
+
def test_all_tests(session):
|
|
68
|
+
session.install("-e", ".")
|
|
69
|
+
session.install("pytest")
|
|
70
|
+
session.run("pytest")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "bear-epoch-time"
|
|
3
|
+
version = "1.0.1"
|
|
4
|
+
description = "Elegant epoch timestamp handling with timezone-aware operations, fluent API, and smart defaults for Python developers who hate datetime complexity."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "chaz", email = "bright.lid5647@fastmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"python-dateutil>=2.9.0.post0",
|
|
12
|
+
"pytz>=2025.2",
|
|
13
|
+
"rich>=14.0.0",
|
|
14
|
+
"rich-tree-cli>=0.3.14",
|
|
15
|
+
"tzlocal>=5.3.1",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["hatchling"]
|
|
20
|
+
build-backend = "hatchling.build"
|
|
21
|
+
|
|
22
|
+
[dependency-groups]
|
|
23
|
+
dev = [
|
|
24
|
+
"black>=25.1.0",
|
|
25
|
+
"bump2version>=1.0.1",
|
|
26
|
+
"flake8>=7.2.0",
|
|
27
|
+
"isort>=6.0.1",
|
|
28
|
+
"nox>=2025.5.1",
|
|
29
|
+
"pytest>=8.4.0",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[tool.pytest.ini_options]
|
|
33
|
+
markers = [
|
|
34
|
+
"visual: marks tests as visual verification tests (deselect with '-m \"not visual\"')"
|
|
35
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
__version__ = "1.0.1"
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
if sys.version_info < (3, 11):
|
|
6
|
+
from ._time_class_legacy import EpochTimestamp
|
|
7
|
+
else:
|
|
8
|
+
from ._time_class import EpochTimestamp
|
|
9
|
+
|
|
10
|
+
from ._timer import TimerData, create_timer, timer
|
|
11
|
+
from ._tools import add_ord_suffix
|
|
12
|
+
from .constants.date_related import DATE_FORMAT, DATE_TIME_FORMAT
|
|
13
|
+
from .time_manager import TimeTools
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"EpochTimestamp",
|
|
17
|
+
"TimerData",
|
|
18
|
+
"create_timer",
|
|
19
|
+
"timer",
|
|
20
|
+
"TimeTools",
|
|
21
|
+
"add_ord_suffix",
|
|
22
|
+
"DATE_FORMAT",
|
|
23
|
+
"DATE_TIME_FORMAT",
|
|
24
|
+
"__version__",
|
|
25
|
+
]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from datetime import timedelta
|
|
3
|
+
|
|
4
|
+
from .constants.time_related import (SECONDS_IN_DAY, SECONDS_IN_HOUR,
|
|
5
|
+
SECONDS_IN_MINUTE, SECONDS_IN_MONTH)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def convert_to_seconds(time_str: str) -> int:
|
|
9
|
+
"""Convert a time string to seconds.
|
|
10
|
+
|
|
11
|
+
Examples
|
|
12
|
+
--------
|
|
13
|
+
>>> convert_to_seconds("1M 30m")
|
|
14
|
+
2610000
|
|
15
|
+
|
|
16
|
+
Notes
|
|
17
|
+
-----
|
|
18
|
+
* ``M`` or ``mo`` denotes **months**.
|
|
19
|
+
* ``m`` denotes **minutes**.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
time_parts: list[tuple[str, str]] = re.findall(r"(\d+)\s*(M|mo|[dhms])", time_str)
|
|
23
|
+
total_seconds = 0
|
|
24
|
+
for value, unit in time_parts:
|
|
25
|
+
if not value.isdigit():
|
|
26
|
+
raise ValueError(f"Invalid time value: {value}")
|
|
27
|
+
value = int(value)
|
|
28
|
+
|
|
29
|
+
if unit == "M" or unit.lower() == "mo":
|
|
30
|
+
total_seconds += value * SECONDS_IN_MONTH
|
|
31
|
+
elif unit == "d":
|
|
32
|
+
total_seconds += value * SECONDS_IN_DAY
|
|
33
|
+
elif unit == "h":
|
|
34
|
+
total_seconds += value * SECONDS_IN_HOUR
|
|
35
|
+
elif unit == "m":
|
|
36
|
+
total_seconds += value * SECONDS_IN_MINUTE
|
|
37
|
+
elif unit == "s":
|
|
38
|
+
total_seconds += value
|
|
39
|
+
else:
|
|
40
|
+
raise ValueError(f"Invalid time unit: {unit}")
|
|
41
|
+
return total_seconds
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def timedelta_to_seconds(td: timedelta) -> int:
|
|
45
|
+
"""Convert a timedelta object to seconds."""
|
|
46
|
+
if not isinstance(td, timedelta):
|
|
47
|
+
raise ValueError("Input must be a timedelta object")
|
|
48
|
+
return int(td.total_seconds())
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def convert_to_milliseconds(time_str: str) -> int:
|
|
52
|
+
return convert_to_seconds(time_str) * 1000
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def milliseconds_to_time(milliseconds: int) -> str:
|
|
56
|
+
"""Convert milliseconds to a human-readable time string."""
|
|
57
|
+
if milliseconds < 0:
|
|
58
|
+
raise ValueError("Milliseconds cannot be negative")
|
|
59
|
+
seconds = milliseconds // 1000
|
|
60
|
+
return seconds_to_time(seconds)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def seconds_to_timedelta(seconds: int) -> timedelta:
|
|
64
|
+
"""Convert seconds to a timedelta object."""
|
|
65
|
+
if seconds < 0:
|
|
66
|
+
raise ValueError("Seconds cannot be negative")
|
|
67
|
+
return timedelta(seconds=seconds)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def seconds_to_time(seconds: int) -> str:
|
|
71
|
+
"""Convert seconds to a human-readable time string.
|
|
72
|
+
|
|
73
|
+
Months are represented with ``M`` while minutes use ``m``.
|
|
74
|
+
"""
|
|
75
|
+
if seconds < 0:
|
|
76
|
+
raise ValueError("Seconds cannot be negative")
|
|
77
|
+
months, remainder = divmod(seconds, SECONDS_IN_MONTH)
|
|
78
|
+
days, remainder = divmod(remainder, SECONDS_IN_DAY)
|
|
79
|
+
hours, remainder = divmod(remainder, SECONDS_IN_HOUR)
|
|
80
|
+
minutes, seconds = divmod(remainder, SECONDS_IN_MINUTE)
|
|
81
|
+
time_parts = []
|
|
82
|
+
if months > 0:
|
|
83
|
+
time_parts.append(f"{months}M")
|
|
84
|
+
if days > 0:
|
|
85
|
+
time_parts.append(f"{days}d")
|
|
86
|
+
if hours > 0:
|
|
87
|
+
time_parts.append(f"{hours}h")
|
|
88
|
+
if minutes > 0:
|
|
89
|
+
time_parts.append(f"{minutes}m")
|
|
90
|
+
if seconds > 0:
|
|
91
|
+
time_parts.append(f"{seconds}s")
|
|
92
|
+
return " ".join(time_parts)
|