pymillis 1.0.0__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.
- pymillis-1.0.0/LICENSE +21 -0
- pymillis-1.0.0/PKG-INFO +252 -0
- pymillis-1.0.0/README.md +226 -0
- pymillis-1.0.0/pymillis/__init__.py +20 -0
- pymillis-1.0.0/pymillis/_version.py +3 -0
- pymillis-1.0.0/pymillis/ms.py +223 -0
- pymillis-1.0.0/pyproject.toml +49 -0
pymillis-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-PRESENT WuChenDi<https://github.com/WuChenDi>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pymillis-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pymillis
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Use this package to easily convert various time formats to milliseconds.
|
|
5
|
+
Keywords: time,milliseconds,ms,duration,conversion
|
|
6
|
+
Author-email: wudi <wuchendi96@gmail.com>
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Project-URL: Bug Reports, https://github.com/WuChenDi/pymillis/issues
|
|
24
|
+
Project-URL: Source, https://github.com/WuChenDi/pymillis
|
|
25
|
+
|
|
26
|
+
# pymillis
|
|
27
|
+
|
|
28
|
+
Use this package to easily convert various time formats to milliseconds.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install pymillis
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
### Basic Usage
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from pymillis import ms
|
|
42
|
+
|
|
43
|
+
# Parse time strings to milliseconds
|
|
44
|
+
ms('2 days') # 172800000
|
|
45
|
+
ms('1d') # 86400000
|
|
46
|
+
ms('10h') # 36000000
|
|
47
|
+
ms('2.5 hrs') # 9000000
|
|
48
|
+
ms('2h') # 7200000
|
|
49
|
+
ms('1m') # 60000
|
|
50
|
+
ms('5s') # 5000
|
|
51
|
+
ms('1y') # 31557600000
|
|
52
|
+
ms('100') # 100
|
|
53
|
+
ms('-3 days') # -259200000
|
|
54
|
+
ms('-1h') # -3600000
|
|
55
|
+
ms('-200') # -200
|
|
56
|
+
|
|
57
|
+
# Format milliseconds to strings
|
|
58
|
+
ms(60000) # '1m'
|
|
59
|
+
ms(2 * 60000) # '2m'
|
|
60
|
+
ms(-3 * 60000) # '-3m'
|
|
61
|
+
ms(172800000) # '2d'
|
|
62
|
+
|
|
63
|
+
# Use long format
|
|
64
|
+
ms(60000, long=True) # '1 minute'
|
|
65
|
+
ms(2 * 60000, long=True) # '2 minutes'
|
|
66
|
+
ms(172800000, long=True) # '2 days'
|
|
67
|
+
ms(ms('10 hours'), long=True) # '10 hours'
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### API
|
|
71
|
+
|
|
72
|
+
#### `ms(value, *, long=False)`
|
|
73
|
+
|
|
74
|
+
Parse or format the given value.
|
|
75
|
+
|
|
76
|
+
**Parameters:**
|
|
77
|
+
- `value` (str | int | float): The string or number to convert
|
|
78
|
+
- `long` (bool, optional): Set to `True` to use verbose formatting. Defaults to `False`.
|
|
79
|
+
|
|
80
|
+
**Returns:**
|
|
81
|
+
- If `value` is a string, returns milliseconds as `float`
|
|
82
|
+
- If `value` is a number, returns formatted string as `str`
|
|
83
|
+
|
|
84
|
+
**Raises:**
|
|
85
|
+
- `MSError`: If value is not a non-empty string or a number
|
|
86
|
+
|
|
87
|
+
#### `parse(value)`
|
|
88
|
+
|
|
89
|
+
Parse the given string and return milliseconds.
|
|
90
|
+
|
|
91
|
+
**Parameters:**
|
|
92
|
+
- `value` (str): A string to parse to milliseconds
|
|
93
|
+
|
|
94
|
+
**Returns:**
|
|
95
|
+
- `float`: The parsed value in milliseconds
|
|
96
|
+
|
|
97
|
+
**Raises:**
|
|
98
|
+
- `MSError`: If the string is invalid or cannot be parsed
|
|
99
|
+
|
|
100
|
+
#### `format(ms_value, *, long=False)`
|
|
101
|
+
|
|
102
|
+
Format the given milliseconds as a string.
|
|
103
|
+
|
|
104
|
+
**Parameters:**
|
|
105
|
+
- `ms_value` (int | float): Milliseconds to format
|
|
106
|
+
- `long` (bool, optional): Use verbose formatting if `True`
|
|
107
|
+
|
|
108
|
+
**Returns:**
|
|
109
|
+
- `str`: The formatted string
|
|
110
|
+
|
|
111
|
+
**Raises:**
|
|
112
|
+
- `MSError`: If ms_value is not a finite number
|
|
113
|
+
|
|
114
|
+
### Import Options
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
# Import main function
|
|
118
|
+
from pymillis import ms
|
|
119
|
+
|
|
120
|
+
# Import specific functions
|
|
121
|
+
from pymillis import parse, format, parse_strict
|
|
122
|
+
|
|
123
|
+
# Import exception
|
|
124
|
+
from pymillis import MSError
|
|
125
|
+
|
|
126
|
+
# Import everything
|
|
127
|
+
from pymillis import ms, parse, format, MSError
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Supported Time Units
|
|
131
|
+
|
|
132
|
+
### Short Format
|
|
133
|
+
|
|
134
|
+
- `ms`, `msec`, `msecs`, `millisecond`, `milliseconds` - Milliseconds
|
|
135
|
+
- `s`, `sec`, `secs`, `second`, `seconds` - Seconds
|
|
136
|
+
- `m`, `min`, `mins`, `minute`, `minutes` - Minutes
|
|
137
|
+
- `h`, `hr`, `hrs`, `hour`, `hours` - Hours
|
|
138
|
+
- `d`, `day`, `days` - Days
|
|
139
|
+
- `w`, `week`, `weeks` - Weeks
|
|
140
|
+
- `mo`, `month`, `months` - Months (calculated as 1/12 of a year)
|
|
141
|
+
- `y`, `yr`, `yrs`, `year`, `years` - Years (calculated as 365.25 days)
|
|
142
|
+
|
|
143
|
+
### Case Insensitive
|
|
144
|
+
|
|
145
|
+
All units are case-insensitive, so `1D`, `1d`, `1 Day`, `1 DAY` are all equivalent.
|
|
146
|
+
|
|
147
|
+
## Features
|
|
148
|
+
|
|
149
|
+
- 🚀 Simple and intuitive API
|
|
150
|
+
- 📦 Zero dependencies
|
|
151
|
+
- 🔄 Bidirectional conversion (string ↔ milliseconds)
|
|
152
|
+
- ⏱️ Supports negative time values
|
|
153
|
+
- 📝 Long and short format options
|
|
154
|
+
- 🎯 Type hints for better IDE support
|
|
155
|
+
- ✅ Comprehensive error handling
|
|
156
|
+
|
|
157
|
+
## Common Use Cases
|
|
158
|
+
|
|
159
|
+
### Setting Timeouts
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
import time
|
|
163
|
+
from pymillis import ms
|
|
164
|
+
|
|
165
|
+
# Convert to seconds for time.sleep()
|
|
166
|
+
timeout = ms('5s') / 1000
|
|
167
|
+
time.sleep(timeout)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Caching
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
import time
|
|
174
|
+
from pymillis import ms
|
|
175
|
+
|
|
176
|
+
# Set cache expiration
|
|
177
|
+
cache_duration = ms('1h')
|
|
178
|
+
expires_at = time.time() * 1000 + cache_duration
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Rate Limiting
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from pymillis import ms
|
|
185
|
+
|
|
186
|
+
# Define rate limit window
|
|
187
|
+
rate_limit_window = ms('1m')
|
|
188
|
+
max_requests = 100
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Calculating Durations
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from pymillis import ms
|
|
195
|
+
|
|
196
|
+
# Calculate time differences
|
|
197
|
+
meeting_duration = ms('2h') - ms('30m') # 5400000.0 ms (1.5 hours)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Error Handling
|
|
201
|
+
|
|
202
|
+
The library raises `MSError` for invalid inputs:
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
from pymillis import ms, MSError
|
|
206
|
+
|
|
207
|
+
# Invalid format
|
|
208
|
+
try:
|
|
209
|
+
ms('invalid')
|
|
210
|
+
except MSError as e:
|
|
211
|
+
print(f"Error: {e}")
|
|
212
|
+
|
|
213
|
+
# Non-finite number
|
|
214
|
+
try:
|
|
215
|
+
ms(float('nan'))
|
|
216
|
+
except MSError as e:
|
|
217
|
+
print(f"Error: {e}")
|
|
218
|
+
|
|
219
|
+
# Empty string
|
|
220
|
+
try:
|
|
221
|
+
ms('')
|
|
222
|
+
except MSError as e:
|
|
223
|
+
print(f"Error: {e}")
|
|
224
|
+
|
|
225
|
+
# String too long (>100 characters)
|
|
226
|
+
try:
|
|
227
|
+
ms('a' * 101)
|
|
228
|
+
except MSError as e:
|
|
229
|
+
print(f"Error: {e}")
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Notes
|
|
233
|
+
|
|
234
|
+
### Precision
|
|
235
|
+
|
|
236
|
+
- Results are returned as `float` for precision
|
|
237
|
+
- **Month calculation**: 1 month = 1/12 year ≈ 30.44 days (average value)
|
|
238
|
+
- **Year calculation**: 1 year = 365.25 days (accounting for leap years)
|
|
239
|
+
|
|
240
|
+
### Rounding
|
|
241
|
+
|
|
242
|
+
When formatting, values are rounded to the nearest integer for the selected unit:
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
ms(1500) # '2s' (rounded from 1.5s)
|
|
246
|
+
ms(90000) # '2m' (rounded from 1.5m)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## 📜 License
|
|
250
|
+
|
|
251
|
+
[MIT](./LICENSE) License © 2025-PRESENT [wudi](https://github.com/WuChenDi)
|
|
252
|
+
|
pymillis-1.0.0/README.md
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# pymillis
|
|
2
|
+
|
|
3
|
+
Use this package to easily convert various time formats to milliseconds.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install pymillis
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Basic Usage
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from pymillis import ms
|
|
17
|
+
|
|
18
|
+
# Parse time strings to milliseconds
|
|
19
|
+
ms('2 days') # 172800000
|
|
20
|
+
ms('1d') # 86400000
|
|
21
|
+
ms('10h') # 36000000
|
|
22
|
+
ms('2.5 hrs') # 9000000
|
|
23
|
+
ms('2h') # 7200000
|
|
24
|
+
ms('1m') # 60000
|
|
25
|
+
ms('5s') # 5000
|
|
26
|
+
ms('1y') # 31557600000
|
|
27
|
+
ms('100') # 100
|
|
28
|
+
ms('-3 days') # -259200000
|
|
29
|
+
ms('-1h') # -3600000
|
|
30
|
+
ms('-200') # -200
|
|
31
|
+
|
|
32
|
+
# Format milliseconds to strings
|
|
33
|
+
ms(60000) # '1m'
|
|
34
|
+
ms(2 * 60000) # '2m'
|
|
35
|
+
ms(-3 * 60000) # '-3m'
|
|
36
|
+
ms(172800000) # '2d'
|
|
37
|
+
|
|
38
|
+
# Use long format
|
|
39
|
+
ms(60000, long=True) # '1 minute'
|
|
40
|
+
ms(2 * 60000, long=True) # '2 minutes'
|
|
41
|
+
ms(172800000, long=True) # '2 days'
|
|
42
|
+
ms(ms('10 hours'), long=True) # '10 hours'
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### API
|
|
46
|
+
|
|
47
|
+
#### `ms(value, *, long=False)`
|
|
48
|
+
|
|
49
|
+
Parse or format the given value.
|
|
50
|
+
|
|
51
|
+
**Parameters:**
|
|
52
|
+
- `value` (str | int | float): The string or number to convert
|
|
53
|
+
- `long` (bool, optional): Set to `True` to use verbose formatting. Defaults to `False`.
|
|
54
|
+
|
|
55
|
+
**Returns:**
|
|
56
|
+
- If `value` is a string, returns milliseconds as `float`
|
|
57
|
+
- If `value` is a number, returns formatted string as `str`
|
|
58
|
+
|
|
59
|
+
**Raises:**
|
|
60
|
+
- `MSError`: If value is not a non-empty string or a number
|
|
61
|
+
|
|
62
|
+
#### `parse(value)`
|
|
63
|
+
|
|
64
|
+
Parse the given string and return milliseconds.
|
|
65
|
+
|
|
66
|
+
**Parameters:**
|
|
67
|
+
- `value` (str): A string to parse to milliseconds
|
|
68
|
+
|
|
69
|
+
**Returns:**
|
|
70
|
+
- `float`: The parsed value in milliseconds
|
|
71
|
+
|
|
72
|
+
**Raises:**
|
|
73
|
+
- `MSError`: If the string is invalid or cannot be parsed
|
|
74
|
+
|
|
75
|
+
#### `format(ms_value, *, long=False)`
|
|
76
|
+
|
|
77
|
+
Format the given milliseconds as a string.
|
|
78
|
+
|
|
79
|
+
**Parameters:**
|
|
80
|
+
- `ms_value` (int | float): Milliseconds to format
|
|
81
|
+
- `long` (bool, optional): Use verbose formatting if `True`
|
|
82
|
+
|
|
83
|
+
**Returns:**
|
|
84
|
+
- `str`: The formatted string
|
|
85
|
+
|
|
86
|
+
**Raises:**
|
|
87
|
+
- `MSError`: If ms_value is not a finite number
|
|
88
|
+
|
|
89
|
+
### Import Options
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
# Import main function
|
|
93
|
+
from pymillis import ms
|
|
94
|
+
|
|
95
|
+
# Import specific functions
|
|
96
|
+
from pymillis import parse, format, parse_strict
|
|
97
|
+
|
|
98
|
+
# Import exception
|
|
99
|
+
from pymillis import MSError
|
|
100
|
+
|
|
101
|
+
# Import everything
|
|
102
|
+
from pymillis import ms, parse, format, MSError
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Supported Time Units
|
|
106
|
+
|
|
107
|
+
### Short Format
|
|
108
|
+
|
|
109
|
+
- `ms`, `msec`, `msecs`, `millisecond`, `milliseconds` - Milliseconds
|
|
110
|
+
- `s`, `sec`, `secs`, `second`, `seconds` - Seconds
|
|
111
|
+
- `m`, `min`, `mins`, `minute`, `minutes` - Minutes
|
|
112
|
+
- `h`, `hr`, `hrs`, `hour`, `hours` - Hours
|
|
113
|
+
- `d`, `day`, `days` - Days
|
|
114
|
+
- `w`, `week`, `weeks` - Weeks
|
|
115
|
+
- `mo`, `month`, `months` - Months (calculated as 1/12 of a year)
|
|
116
|
+
- `y`, `yr`, `yrs`, `year`, `years` - Years (calculated as 365.25 days)
|
|
117
|
+
|
|
118
|
+
### Case Insensitive
|
|
119
|
+
|
|
120
|
+
All units are case-insensitive, so `1D`, `1d`, `1 Day`, `1 DAY` are all equivalent.
|
|
121
|
+
|
|
122
|
+
## Features
|
|
123
|
+
|
|
124
|
+
- 🚀 Simple and intuitive API
|
|
125
|
+
- 📦 Zero dependencies
|
|
126
|
+
- 🔄 Bidirectional conversion (string ↔ milliseconds)
|
|
127
|
+
- ⏱️ Supports negative time values
|
|
128
|
+
- 📝 Long and short format options
|
|
129
|
+
- 🎯 Type hints for better IDE support
|
|
130
|
+
- ✅ Comprehensive error handling
|
|
131
|
+
|
|
132
|
+
## Common Use Cases
|
|
133
|
+
|
|
134
|
+
### Setting Timeouts
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
import time
|
|
138
|
+
from pymillis import ms
|
|
139
|
+
|
|
140
|
+
# Convert to seconds for time.sleep()
|
|
141
|
+
timeout = ms('5s') / 1000
|
|
142
|
+
time.sleep(timeout)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Caching
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
import time
|
|
149
|
+
from pymillis import ms
|
|
150
|
+
|
|
151
|
+
# Set cache expiration
|
|
152
|
+
cache_duration = ms('1h')
|
|
153
|
+
expires_at = time.time() * 1000 + cache_duration
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Rate Limiting
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from pymillis import ms
|
|
160
|
+
|
|
161
|
+
# Define rate limit window
|
|
162
|
+
rate_limit_window = ms('1m')
|
|
163
|
+
max_requests = 100
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Calculating Durations
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from pymillis import ms
|
|
170
|
+
|
|
171
|
+
# Calculate time differences
|
|
172
|
+
meeting_duration = ms('2h') - ms('30m') # 5400000.0 ms (1.5 hours)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Error Handling
|
|
176
|
+
|
|
177
|
+
The library raises `MSError` for invalid inputs:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
from pymillis import ms, MSError
|
|
181
|
+
|
|
182
|
+
# Invalid format
|
|
183
|
+
try:
|
|
184
|
+
ms('invalid')
|
|
185
|
+
except MSError as e:
|
|
186
|
+
print(f"Error: {e}")
|
|
187
|
+
|
|
188
|
+
# Non-finite number
|
|
189
|
+
try:
|
|
190
|
+
ms(float('nan'))
|
|
191
|
+
except MSError as e:
|
|
192
|
+
print(f"Error: {e}")
|
|
193
|
+
|
|
194
|
+
# Empty string
|
|
195
|
+
try:
|
|
196
|
+
ms('')
|
|
197
|
+
except MSError as e:
|
|
198
|
+
print(f"Error: {e}")
|
|
199
|
+
|
|
200
|
+
# String too long (>100 characters)
|
|
201
|
+
try:
|
|
202
|
+
ms('a' * 101)
|
|
203
|
+
except MSError as e:
|
|
204
|
+
print(f"Error: {e}")
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Notes
|
|
208
|
+
|
|
209
|
+
### Precision
|
|
210
|
+
|
|
211
|
+
- Results are returned as `float` for precision
|
|
212
|
+
- **Month calculation**: 1 month = 1/12 year ≈ 30.44 days (average value)
|
|
213
|
+
- **Year calculation**: 1 year = 365.25 days (accounting for leap years)
|
|
214
|
+
|
|
215
|
+
### Rounding
|
|
216
|
+
|
|
217
|
+
When formatting, values are rounded to the nearest integer for the selected unit:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
ms(1500) # '2s' (rounded from 1.5s)
|
|
221
|
+
ms(90000) # '2m' (rounded from 1.5m)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## 📜 License
|
|
225
|
+
|
|
226
|
+
[MIT](./LICENSE) License © 2025-PRESENT [wudi](https://github.com/WuChenDi)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pymillis - Milliseconds conversion utility
|
|
3
|
+
|
|
4
|
+
A Python port of the popular JavaScript ms library.
|
|
5
|
+
Convert between milliseconds and human-readable time strings.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
>>> from pymillis import ms
|
|
9
|
+
>>> ms('2 days')
|
|
10
|
+
172800000
|
|
11
|
+
>>> ms(172800000)
|
|
12
|
+
'2d'
|
|
13
|
+
>>> ms(172800000, long=True)
|
|
14
|
+
'2 days'
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from .ms import ms, parse, parse_strict, format, MSError
|
|
18
|
+
from ._version import __version__
|
|
19
|
+
|
|
20
|
+
__all__ = ['ms', 'parse', 'parse_strict', 'format', 'MSError', '__version__']
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pymillis.ms - Core milliseconds conversion functionality
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import math
|
|
7
|
+
from typing import Union
|
|
8
|
+
|
|
9
|
+
__all__ = ['ms', 'parse', 'parse_strict', 'format', 'MSError']
|
|
10
|
+
|
|
11
|
+
# Time constants in milliseconds
|
|
12
|
+
S = 1000
|
|
13
|
+
M = S * 60
|
|
14
|
+
H = M * 60
|
|
15
|
+
D = H * 24
|
|
16
|
+
W = D * 7
|
|
17
|
+
Y = D * 365.25
|
|
18
|
+
MO = Y / 12
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MSError(Exception):
|
|
22
|
+
"""Base exception for ms library errors."""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def ms(value: Union[str, int, float], *, long: bool = False) -> Union[float, str]:
|
|
27
|
+
"""
|
|
28
|
+
Parse or format the given value.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
value: The string or number to convert
|
|
32
|
+
long: Set to True to use verbose formatting. Defaults to False.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
If value is a string, returns milliseconds as number (float).
|
|
36
|
+
If value is a number, returns formatted string.
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
MSError: If value is not a non-empty string or a number
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
>>> ms('2 days')
|
|
43
|
+
172800000.0
|
|
44
|
+
>>> ms(172800000)
|
|
45
|
+
'2d'
|
|
46
|
+
>>> ms(172800000, long=True)
|
|
47
|
+
'2 days'
|
|
48
|
+
"""
|
|
49
|
+
if isinstance(value, str):
|
|
50
|
+
return parse(value)
|
|
51
|
+
elif isinstance(value, (int, float)):
|
|
52
|
+
return format(value, long=long)
|
|
53
|
+
else:
|
|
54
|
+
raise MSError(
|
|
55
|
+
f"Value provided to ms() must be a string or number. value={repr(value)}"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def parse(value: str) -> float:
|
|
60
|
+
"""
|
|
61
|
+
Parse the given string and return milliseconds.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
value: A string to parse to milliseconds
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
The parsed value in milliseconds, or raises MSError if invalid
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
MSError: If the string is invalid or cannot be parsed
|
|
71
|
+
|
|
72
|
+
Examples:
|
|
73
|
+
>>> parse('2d')
|
|
74
|
+
172800000.0
|
|
75
|
+
>>> parse('1.5 hours')
|
|
76
|
+
5400000.0
|
|
77
|
+
>>> parse('1y')
|
|
78
|
+
31557600000.0
|
|
79
|
+
"""
|
|
80
|
+
if not isinstance(value, str):
|
|
81
|
+
raise MSError(
|
|
82
|
+
f"Value provided to ms.parse() must be a string. value={repr(value)}"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if len(value) == 0 or len(value) > 100:
|
|
86
|
+
raise MSError(
|
|
87
|
+
f"Value provided to ms.parse() must be a string with length between 1 and 99. value={repr(value)}"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
pattern = r'^(?P<value>-?\d*\.?\d+)\s*(?P<unit>milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|months?|mo|years?|yrs?|y)?$'
|
|
91
|
+
match = re.match(pattern, value, re.IGNORECASE)
|
|
92
|
+
|
|
93
|
+
if not match:
|
|
94
|
+
raise MSError(f"Invalid time string format. value={repr(value)}")
|
|
95
|
+
|
|
96
|
+
groups = match.groupdict()
|
|
97
|
+
num_value = float(groups['value'])
|
|
98
|
+
unit = (groups['unit'] or 'ms').lower()
|
|
99
|
+
|
|
100
|
+
# Years
|
|
101
|
+
if unit in ('years', 'year', 'yrs', 'yr', 'y'):
|
|
102
|
+
return num_value * Y
|
|
103
|
+
# Months
|
|
104
|
+
elif unit in ('months', 'month', 'mo'):
|
|
105
|
+
return num_value * MO
|
|
106
|
+
# Weeks
|
|
107
|
+
elif unit in ('weeks', 'week', 'w'):
|
|
108
|
+
return num_value * W
|
|
109
|
+
# Days
|
|
110
|
+
elif unit in ('days', 'day', 'd'):
|
|
111
|
+
return num_value * D
|
|
112
|
+
# Hours
|
|
113
|
+
elif unit in ('hours', 'hour', 'hrs', 'hr', 'h'):
|
|
114
|
+
return num_value * H
|
|
115
|
+
# Minutes
|
|
116
|
+
elif unit in ('minutes', 'minute', 'mins', 'min', 'm'):
|
|
117
|
+
return num_value * M
|
|
118
|
+
# Seconds
|
|
119
|
+
elif unit in ('seconds', 'second', 'secs', 'sec', 's'):
|
|
120
|
+
return num_value * S
|
|
121
|
+
# Milliseconds
|
|
122
|
+
elif unit in ('milliseconds', 'millisecond', 'msecs', 'msec', 'ms'):
|
|
123
|
+
return num_value
|
|
124
|
+
else:
|
|
125
|
+
raise MSError(
|
|
126
|
+
f'Unknown unit "{unit}" provided to ms.parse(). value={repr(value)}'
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def parse_strict(value: str) -> float:
|
|
131
|
+
"""
|
|
132
|
+
Parse the given string and return milliseconds (strict version).
|
|
133
|
+
|
|
134
|
+
This is an alias for parse() provided for API compatibility.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
value: A string to parse to milliseconds
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
The parsed value in milliseconds
|
|
141
|
+
"""
|
|
142
|
+
return parse(value)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def format(ms_value: Union[int, float], *, long: bool = False) -> str:
|
|
146
|
+
"""
|
|
147
|
+
Format the given milliseconds as a string.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
ms_value: Milliseconds to format
|
|
151
|
+
long: Use verbose formatting if True
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
The formatted string
|
|
155
|
+
|
|
156
|
+
Raises:
|
|
157
|
+
MSError: If ms_value is not a finite number
|
|
158
|
+
|
|
159
|
+
Examples:
|
|
160
|
+
>>> format(172800000)
|
|
161
|
+
'2d'
|
|
162
|
+
>>> format(172800000, long=True)
|
|
163
|
+
'2 days'
|
|
164
|
+
>>> format(3600000)
|
|
165
|
+
'1h'
|
|
166
|
+
"""
|
|
167
|
+
if not isinstance(ms_value, (int, float)):
|
|
168
|
+
raise MSError('Value provided to ms.format() must be of type number.')
|
|
169
|
+
|
|
170
|
+
# Check if the number is finite (not NaN, not Infinity)
|
|
171
|
+
if not math.isfinite(ms_value):
|
|
172
|
+
raise MSError('Value provided to ms.format() must be of type number.')
|
|
173
|
+
|
|
174
|
+
return _fmt_long(ms_value) if long else _fmt_short(ms_value)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _fmt_short(ms_value: Union[int, float]) -> str:
|
|
178
|
+
"""Short format for ms."""
|
|
179
|
+
ms_abs = abs(ms_value)
|
|
180
|
+
|
|
181
|
+
if ms_abs >= Y:
|
|
182
|
+
return f"{round(ms_value / Y)}y"
|
|
183
|
+
if ms_abs >= MO:
|
|
184
|
+
return f"{round(ms_value / MO)}mo"
|
|
185
|
+
if ms_abs >= W:
|
|
186
|
+
return f"{round(ms_value / W)}w"
|
|
187
|
+
if ms_abs >= D:
|
|
188
|
+
return f"{round(ms_value / D)}d"
|
|
189
|
+
if ms_abs >= H:
|
|
190
|
+
return f"{round(ms_value / H)}h"
|
|
191
|
+
if ms_abs >= M:
|
|
192
|
+
return f"{round(ms_value / M)}m"
|
|
193
|
+
if ms_abs >= S:
|
|
194
|
+
return f"{round(ms_value / S)}s"
|
|
195
|
+
return f"{int(ms_value)}ms"
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _fmt_long(ms_value: Union[int, float]) -> str:
|
|
199
|
+
"""Long format for ms."""
|
|
200
|
+
ms_abs = abs(ms_value)
|
|
201
|
+
|
|
202
|
+
if ms_abs >= Y:
|
|
203
|
+
return _plural(ms_value, ms_abs, Y, 'year')
|
|
204
|
+
if ms_abs >= MO:
|
|
205
|
+
return _plural(ms_value, ms_abs, MO, 'month')
|
|
206
|
+
if ms_abs >= W:
|
|
207
|
+
return _plural(ms_value, ms_abs, W, 'week')
|
|
208
|
+
if ms_abs >= D:
|
|
209
|
+
return _plural(ms_value, ms_abs, D, 'day')
|
|
210
|
+
if ms_abs >= H:
|
|
211
|
+
return _plural(ms_value, ms_abs, H, 'hour')
|
|
212
|
+
if ms_abs >= M:
|
|
213
|
+
return _plural(ms_value, ms_abs, M, 'minute')
|
|
214
|
+
if ms_abs >= S:
|
|
215
|
+
return _plural(ms_value, ms_abs, S, 'second')
|
|
216
|
+
return f"{int(ms_value)} ms"
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _plural(ms_value: Union[int, float], ms_abs: float, n: float, name: str) -> str:
|
|
220
|
+
"""Pluralization helper."""
|
|
221
|
+
is_plural = ms_abs >= n * 1.5
|
|
222
|
+
rounded = round(ms_value / n)
|
|
223
|
+
return f"{rounded} {name}{'s' if is_plural else ''}"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["flit_core >=3.11,<4"]
|
|
3
|
+
build-backend = "flit_core.buildapi"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pymillis"
|
|
7
|
+
authors = [{name = "wudi", email = "wuchendi96@gmail.com"}]
|
|
8
|
+
description = "Use this package to easily convert various time formats to milliseconds."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
dynamic = ["version"]
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
keywords = ["time", "milliseconds", "ms", "duration", "conversion"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Programming Language :: Python :: 3.14",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
"Bug Reports" = "https://github.com/WuChenDi/pymillis/issues"
|
|
32
|
+
"Source" = "https://github.com/WuChenDi/pymillis"
|
|
33
|
+
|
|
34
|
+
[tool.pytest.ini_options]
|
|
35
|
+
testpaths = ["tests"]
|
|
36
|
+
python_files = ["test_*.py"]
|
|
37
|
+
python_classes = ["Test*"]
|
|
38
|
+
python_functions = ["test_*"]
|
|
39
|
+
|
|
40
|
+
[tool.black]
|
|
41
|
+
line-length = 100
|
|
42
|
+
target-version = ['py39', 'py310', 'py311', 'py312', 'py313']
|
|
43
|
+
include = '\.pyi?$'
|
|
44
|
+
|
|
45
|
+
[tool.mypy]
|
|
46
|
+
python_version = "3.9"
|
|
47
|
+
warn_return_any = true
|
|
48
|
+
warn_unused_configs = true
|
|
49
|
+
disallow_untyped_defs = false
|