fly-hour-timer 1.1.2__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.
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""fly_hour_timer - a timezone-free time library: <hour>.<min_sec>"""
|
|
2
|
+
|
|
3
|
+
from .fly_hour_timer import FlyHourTimer
|
|
4
|
+
|
|
5
|
+
#: The static tool (alias of the :class:`FlyHourTimer` class). Call the methods
|
|
6
|
+
#: directly, e.g. ``fly_hour_timer.hour_timer()``.
|
|
7
|
+
fly_hour_timer = FlyHourTimer
|
|
8
|
+
|
|
9
|
+
__all__ = ["FlyHourTimer", "fly_hour_timer"]
|
|
10
|
+
__version__ = "0.3.0"
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Core static utility of the fly_hour_timer library.
|
|
2
|
+
|
|
3
|
+
Time representation: <hour>.<min_sec>
|
|
4
|
+
|
|
5
|
+
The "hour" is simply ``unix_time // 3600`` -- the number of whole hours elapsed
|
|
6
|
+
since the unix epoch. ``<min_sec>`` is the minutes and seconds inside that
|
|
7
|
+
hour. Because everything is derived directly from the absolute unix time, the
|
|
8
|
+
string is fully timezone-free and identical on any machine anywhere.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import time as _time
|
|
12
|
+
from datetime import datetime, timezone as dt_timezone
|
|
13
|
+
|
|
14
|
+
__all__ = ["FlyHourTimer"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FlyHourTimer:
|
|
18
|
+
"""Static time tool: represents time as ``<hour>.<min_sec>``.
|
|
19
|
+
|
|
20
|
+
All methods are static; no instance is required.
|
|
21
|
+
|
|
22
|
+
>>> FlyHourTimer.hour_timer(1_000_000_000) # 2001-09-09 01:46:40 UTC
|
|
23
|
+
'277777.4640'
|
|
24
|
+
>>> FlyHourTimer.hour_timer_short(1_000_000_000)
|
|
25
|
+
'777.4640'
|
|
26
|
+
>>> FlyHourTimer.hour_timer(0) # the epoch
|
|
27
|
+
'0.0000'
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# ------------------------------------------------------------------
|
|
31
|
+
# public API
|
|
32
|
+
# ------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def hour(unix_time=None):
|
|
36
|
+
"""Return the hour: whole hours elapsed since the unix epoch.
|
|
37
|
+
|
|
38
|
+
Equivalent to ``unix_time // 3600``.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
unix_time: unix time in seconds (int/float/numeric str).
|
|
42
|
+
Defaults to the current time when omitted.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
str: e.g. ``'277777'``.
|
|
46
|
+
"""
|
|
47
|
+
hours, _, _ = FlyHourTimer._split(unix_time)
|
|
48
|
+
return str(hours)
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def min_sec(unix_time=None):
|
|
52
|
+
"""Return the ``<min_sec>`` part: minutes and seconds inside the hour.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
unix_time: unix time in seconds, defaults to the current time.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
str: 4 digits ``MMSS``, e.g. ``'0102'`` means 01 min 02 sec.
|
|
59
|
+
"""
|
|
60
|
+
_, minute, second = FlyHourTimer._split(unix_time)
|
|
61
|
+
return f"{minute:02d}{second:02d}"
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def hour_timer(unix_time=None):
|
|
65
|
+
"""Return time as ``<hour>.<min_sec>``, where hour = ``unix_time // 3600``.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
unix_time: unix time in seconds, defaults to the current time.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
str: e.g. ``'277777.4640'``.
|
|
72
|
+
"""
|
|
73
|
+
hours, minute, second = FlyHourTimer._split(unix_time)
|
|
74
|
+
return f"{hours}.{minute:02d}{second:02d}"
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def hour_timer_short(unix_time=None):
|
|
78
|
+
"""Return time as ``<last 3 digits of hour>.<min_sec>``.
|
|
79
|
+
|
|
80
|
+
The last 3 digits of the hour roll over roughly every 1000 hours
|
|
81
|
+
(~41.7 days), giving a compact, timezone-free time id for logs and
|
|
82
|
+
bot conversations.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
unix_time: unix time in seconds, defaults to the current time.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
str: e.g. hour ``277777`` -> ``'777.4640'``.
|
|
89
|
+
"""
|
|
90
|
+
hours, minute, second = FlyHourTimer._split(unix_time)
|
|
91
|
+
return f"{hours % 1000:03d}.{minute:02d}{second:02d}"
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def hour_timer_to_time(hour_timer):
|
|
95
|
+
"""Convert a ``<hour>.<min_sec>`` string into a ``time.struct_time``.
|
|
96
|
+
|
|
97
|
+
Because ``<hour>`` already encodes the total elapsed hours since the
|
|
98
|
+
epoch (and therefore the date), this is a complete, timezone-free
|
|
99
|
+
inverse of :meth:`hour_timer` -- no "today" anchor is needed. The
|
|
100
|
+
result is UTC based (``time.gmtime``).
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
hour_timer: the ``<hour>.<min_sec>`` string, e.g.
|
|
104
|
+
``'277777.4640'``.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
time.struct_time: UTC struct_time reconstructed from the string.
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
ValueError: if ``hour_timer`` is malformed or out of range.
|
|
111
|
+
"""
|
|
112
|
+
hours, minute, second = FlyHourTimer._parse_hour_timer(hour_timer)
|
|
113
|
+
unix_time = hours * 3600 + minute * 60 + second
|
|
114
|
+
return _time.gmtime(unix_time)
|
|
115
|
+
|
|
116
|
+
# ------------------------------------------------------------------
|
|
117
|
+
# internals
|
|
118
|
+
# ------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def _split(unix_time):
|
|
122
|
+
"""Split a unix time into (total_hours, minute, second)."""
|
|
123
|
+
if unix_time is None:
|
|
124
|
+
ts = datetime.now(dt_timezone.utc).timestamp()
|
|
125
|
+
else:
|
|
126
|
+
ts = float(unix_time)
|
|
127
|
+
hours = int(ts // 3600)
|
|
128
|
+
rem = ts - hours * 3600 # seconds inside the hour, always in [0, 3600)
|
|
129
|
+
minute = int(rem // 60)
|
|
130
|
+
second = int(rem % 60)
|
|
131
|
+
return hours, minute, second
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def _parse_hour_timer(hour_timer):
|
|
135
|
+
"""Parse ``'<hour>.<min_sec>'`` into (total_hours, minute, second)."""
|
|
136
|
+
if not isinstance(hour_timer, str):
|
|
137
|
+
raise ValueError(f"hour_timer must be a str, got {hour_timer!r}")
|
|
138
|
+
text = hour_timer.strip()
|
|
139
|
+
if text.count(".") != 1:
|
|
140
|
+
raise ValueError(
|
|
141
|
+
f"hour_timer must look like '<hour>.<min_sec>', got {hour_timer!r}"
|
|
142
|
+
)
|
|
143
|
+
hour_part, min_sec_part = text.split(".", 1)
|
|
144
|
+
if not min_sec_part.isdigit() or len(min_sec_part) != 4:
|
|
145
|
+
raise ValueError(
|
|
146
|
+
f"hour_timer '<min_sec>' must be 4 digits, got {hour_timer!r}"
|
|
147
|
+
)
|
|
148
|
+
try:
|
|
149
|
+
hours = int(hour_part)
|
|
150
|
+
except ValueError:
|
|
151
|
+
raise ValueError(
|
|
152
|
+
f"hour_timer '<hour>' must be an integer, got {hour_timer!r}"
|
|
153
|
+
) from None
|
|
154
|
+
minute, second = int(min_sec_part[:2]), int(min_sec_part[2:])
|
|
155
|
+
if not (0 <= minute <= 59 and 0 <= second <= 59):
|
|
156
|
+
raise ValueError(f"hour_timer '<min_sec>' out of range, got {hour_timer!r}")
|
|
157
|
+
return hours, minute, second
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fly_hour_timer
|
|
3
|
+
Version: 1.1.2
|
|
4
|
+
Summary: A timezone-free Python time library: represents time as <hour>.<min_sec>
|
|
5
|
+
Author: fly
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: time,timezone-free,hour-timer,ai-bot,unix-time
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
14
|
+
Classifier: Topic :: Utilities
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# fly_hour_timer
|
|
19
|
+
|
|
20
|
+
**English** | [简体中文](README_zh.md)
|
|
21
|
+
|
|
22
|
+
A timezone-free Python time library that represents time as **`<hour>.<min_sec>`**.
|
|
23
|
+
|
|
24
|
+
The core idea: express time as `<hour>.<min_sec>`, where the **hour is simply
|
|
25
|
+
`unix_time // 3600`** (whole hours elapsed since the unix epoch) and `<min_sec>`
|
|
26
|
+
is the minutes and seconds inside that hour. Because everything is derived
|
|
27
|
+
directly from the absolute unix time, the string is fully timezone-free and
|
|
28
|
+
identical on any machine anywhere — especially friendly for AI bots that
|
|
29
|
+
exchange timestamps across regions.
|
|
30
|
+
|
|
31
|
+
- No third-party dependency, pure standard library (`datetime` / `time` only)
|
|
32
|
+
- Fully timezone-free by construction
|
|
33
|
+
- Python >= 3.8
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install fly_hour_timer
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or from source:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
git clone https://github.com/<your-name>/fly_hour_timer.git
|
|
45
|
+
cd fly_hour_timer
|
|
46
|
+
pip install .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
`fly_hour_timer` is a static tool — no instance configuration required:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from fly_hour_timer import fly_hour_timer
|
|
55
|
+
|
|
56
|
+
fly_hour_timer.hour() # '277777' -> hour (unix_time // 3600), str
|
|
57
|
+
fly_hour_timer.min_sec() # '4640' -> minutes + seconds, str
|
|
58
|
+
fly_hour_timer.hour_timer() # '277777.4640' -> <hour>.<min_sec>
|
|
59
|
+
fly_hour_timer.hour_timer_short() # '777.4640' -> <last 3 digits of hour>.<min_sec>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
With an explicit unix time (seconds):
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from fly_hour_timer import FlyHourTimer
|
|
66
|
+
|
|
67
|
+
FlyHourTimer.hour_timer(1_000_000_000) # '277777.4640'
|
|
68
|
+
FlyHourTimer.hour_timer_short(1_000_000_000) # '777.4640'
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## API
|
|
72
|
+
|
|
73
|
+
| Method | Description | Returns |
|
|
74
|
+
| --- | --- | --- |
|
|
75
|
+
| `hour(unix_time=None)` | Whole hours since the epoch, `unix_time // 3600` | `str` |
|
|
76
|
+
| `min_sec(unix_time=None)` | Minutes + seconds inside the hour, 4 digits (`'0102'` = 01m02s) | `str` |
|
|
77
|
+
| `hour_timer(unix_time=None)` | `<hour>.<min_sec>` | `str` |
|
|
78
|
+
| `hour_timer_short(unix_time=None)` | `<last 3 digits of hour>.<min_sec>` | `str` |
|
|
79
|
+
| `hour_timer_to_time(hour_timer)` | Parse `<hour>.<min_sec>` back into a UTC `time.struct_time` | `time.struct_time` |
|
|
80
|
+
|
|
81
|
+
### Parameters
|
|
82
|
+
|
|
83
|
+
- **`unix_time`** — unix time in seconds (`int` / `float` / numeric `str`).
|
|
84
|
+
Omit to use the current time.
|
|
85
|
+
|
|
86
|
+
## How it works
|
|
87
|
+
|
|
88
|
+
Given a unix time in seconds:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
hour = unix_time // 3600 # whole hours since the epoch
|
|
92
|
+
rem = unix_time - hour * 3600 # seconds left inside the hour (0..3599)
|
|
93
|
+
min_sec = "%02d%02d" % (rem // 60, rem % 60)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- `hour_timer()` -> `f"{hour}.{min_sec}"`
|
|
97
|
+
- `hour_timer_short()` -> `f"{hour % 1000:03d}.{min_sec}"` — the last 3 digits
|
|
98
|
+
of the hour roll over roughly every 1000 hours (~41.7 days), giving a compact,
|
|
99
|
+
timezone-free time id for logs and bot conversations.
|
|
100
|
+
|
|
101
|
+
No timezone parameter is needed: the hour count and the leftover minutes/seconds
|
|
102
|
+
come straight from the absolute unix time, so the result is the same everywhere.
|
|
103
|
+
|
|
104
|
+
## Reverse Conversion: `hour_timer_to_time()`
|
|
105
|
+
|
|
106
|
+
Since `<hour>` already encodes the total elapsed hours since the epoch (and thus
|
|
107
|
+
the full date), the conversion back is complete and needs no "today" anchor. It
|
|
108
|
+
returns a UTC `time.struct_time`:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from fly_hour_timer import FlyHourTimer
|
|
112
|
+
|
|
113
|
+
st = FlyHourTimer.hour_timer_to_time("277777.4640")
|
|
114
|
+
# time.struct_time(... 2001, 9, 9, 1, 46, 40 ...)
|
|
115
|
+
st.tm_hour, st.tm_min, st.tm_sec # (1, 46, 40)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
It is the exact inverse of `hour_timer()` for the clock fields, and raises
|
|
119
|
+
`ValueError` on malformed or out-of-range input (e.g. minutes `>= 60`).
|
|
120
|
+
|
|
121
|
+
> Note: pass the **full** `hour_timer()` string here. A short string like
|
|
122
|
+
> `'777.4640'` (from `hour_timer_short()`) has lost the high-order hour digits,
|
|
123
|
+
> so it reconstructs a different (much earlier) instant.
|
|
124
|
+
|
|
125
|
+
## Examples
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
>>> from fly_hour_timer import FlyHourTimer
|
|
129
|
+
>>> FlyHourTimer.hour_timer(0)
|
|
130
|
+
'0.0000'
|
|
131
|
+
>>> FlyHourTimer.hour_timer(1_000_000_000)
|
|
132
|
+
'277777.4640'
|
|
133
|
+
>>> FlyHourTimer.hour_timer_short(1_000_000_000)
|
|
134
|
+
'777.4640'
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Run `python examples.py` in this repository for a live demo.
|
|
138
|
+
|
|
139
|
+
## Tests
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
python tests/test_fly_hour_timer.py
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
fly_hour_timer/__init__.py,sha256=rWgT2xQFUWC4kQJFGlz5tK8Y4hlhFkwrur8DlDbyJS4,352
|
|
2
|
+
fly_hour_timer/fly_hour_timer.py,sha256=XIOMrmSSDGEq4m2MKbr1S1jE31Z_n4__IhsQmpV-mj0,5779
|
|
3
|
+
fly_hour_timer-1.1.2.dist-info/METADATA,sha256=_h5zQ8ssbdnBnH6dua0twqbwd24F6QlafSd_Ll1O6gM,4836
|
|
4
|
+
fly_hour_timer-1.1.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
5
|
+
fly_hour_timer-1.1.2.dist-info/top_level.txt,sha256=mQA8VEQL6M4lIrgxX3pVW4C98ORt9LiKedvGwKNAzuY,15
|
|
6
|
+
fly_hour_timer-1.1.2.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fly_hour_timer
|