ts-toolkit 0.1.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.
- ts_toolkit-0.1.0/LICENSE +21 -0
- ts_toolkit-0.1.0/PKG-INFO +74 -0
- ts_toolkit-0.1.0/README.md +59 -0
- ts_toolkit-0.1.0/pyproject.toml +26 -0
- ts_toolkit-0.1.0/setup.cfg +4 -0
- ts_toolkit-0.1.0/ts.py +265 -0
- ts_toolkit-0.1.0/ts_toolkit.egg-info/PKG-INFO +74 -0
- ts_toolkit-0.1.0/ts_toolkit.egg-info/SOURCES.txt +9 -0
- ts_toolkit-0.1.0/ts_toolkit.egg-info/dependency_links.txt +1 -0
- ts_toolkit-0.1.0/ts_toolkit.egg-info/entry_points.txt +2 -0
- ts_toolkit-0.1.0/ts_toolkit.egg-info/top_level.txt +1 -0
ts_toolkit-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Eric Joye
|
|
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.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ts-toolkit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Instant timestamp converter — zero-dependency CLI for epoch ↔ human-readable conversion
|
|
5
|
+
Author-email: Eric Joye <eric@joye.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Topic :: Utilities
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# ts — Instant Timestamp Converter
|
|
17
|
+
|
|
18
|
+
Zero-dependency CLI tool for bidirectional epoch ↔ human-readable timestamp conversion.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
### Option 1: curl (fastest)
|
|
23
|
+
```bash
|
|
24
|
+
curl -fsSL https://raw.githubusercontent.com/ericjoye/ts-cli/main/ts \
|
|
25
|
+
-o /usr/local/bin/ts && chmod +x /usr/local/bin/ts
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Option 2: pip
|
|
29
|
+
```bash
|
|
30
|
+
pip install ts-cli
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Option 3: copy
|
|
34
|
+
```bash
|
|
35
|
+
cp ts /usr/local/bin/ts && chmod +x /usr/local/bin/ts
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Requires **Python 3.9+** (uses `zoneinfo` from stdlib). No pip dependencies.
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
ts # Current time: epoch (s/ms/μs) + UTC + local
|
|
44
|
+
ts 1718901234 # Epoch seconds → human
|
|
45
|
+
ts 1718901234000 # Epoch milliseconds → human (auto-detected)
|
|
46
|
+
ts 1718901234000000 # Epoch microseconds → human (auto-detected)
|
|
47
|
+
ts "2024-06-20T14:33:54Z" # ISO 8601 → epoch
|
|
48
|
+
ts "2024-06-20 14:33:54" # Human → epoch
|
|
49
|
+
ts now # Current epoch in seconds
|
|
50
|
+
ts 1718901234 1718902000 # Batch: multiple timestamps
|
|
51
|
+
cat timestamps.txt | ts --stdin # Pipe mode
|
|
52
|
+
ts 1718901234 --tz America/New_York # Convert to timezone
|
|
53
|
+
ts 1718901234 --local # Convert to local timezone
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Features
|
|
57
|
+
|
|
58
|
+
- **Auto-detection**: Distinguishes seconds (≤10 digits), milliseconds (11–13), microseconds (16+)
|
|
59
|
+
- **Bidirectional**: Epoch → human AND human → epoch
|
|
60
|
+
- **ISO 8601**: Full support for `YYYY-MM-DDTHH:MM:SSZ` and variants
|
|
61
|
+
- **Timezone support**: `--tz IANA_NAME` or `--local` for system timezone
|
|
62
|
+
- **Batch mode**: Multiple args or `--stdin` pipe
|
|
63
|
+
- **Zero dependencies**: Single Python file, stdlib only
|
|
64
|
+
|
|
65
|
+
## Running tests
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install pytest
|
|
69
|
+
pytest test_ts.py -v
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
MIT
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# ts — Instant Timestamp Converter
|
|
2
|
+
|
|
3
|
+
Zero-dependency CLI tool for bidirectional epoch ↔ human-readable timestamp conversion.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
### Option 1: curl (fastest)
|
|
8
|
+
```bash
|
|
9
|
+
curl -fsSL https://raw.githubusercontent.com/ericjoye/ts-cli/main/ts \
|
|
10
|
+
-o /usr/local/bin/ts && chmod +x /usr/local/bin/ts
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Option 2: pip
|
|
14
|
+
```bash
|
|
15
|
+
pip install ts-cli
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Option 3: copy
|
|
19
|
+
```bash
|
|
20
|
+
cp ts /usr/local/bin/ts && chmod +x /usr/local/bin/ts
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Requires **Python 3.9+** (uses `zoneinfo` from stdlib). No pip dependencies.
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
ts # Current time: epoch (s/ms/μs) + UTC + local
|
|
29
|
+
ts 1718901234 # Epoch seconds → human
|
|
30
|
+
ts 1718901234000 # Epoch milliseconds → human (auto-detected)
|
|
31
|
+
ts 1718901234000000 # Epoch microseconds → human (auto-detected)
|
|
32
|
+
ts "2024-06-20T14:33:54Z" # ISO 8601 → epoch
|
|
33
|
+
ts "2024-06-20 14:33:54" # Human → epoch
|
|
34
|
+
ts now # Current epoch in seconds
|
|
35
|
+
ts 1718901234 1718902000 # Batch: multiple timestamps
|
|
36
|
+
cat timestamps.txt | ts --stdin # Pipe mode
|
|
37
|
+
ts 1718901234 --tz America/New_York # Convert to timezone
|
|
38
|
+
ts 1718901234 --local # Convert to local timezone
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
- **Auto-detection**: Distinguishes seconds (≤10 digits), milliseconds (11–13), microseconds (16+)
|
|
44
|
+
- **Bidirectional**: Epoch → human AND human → epoch
|
|
45
|
+
- **ISO 8601**: Full support for `YYYY-MM-DDTHH:MM:SSZ` and variants
|
|
46
|
+
- **Timezone support**: `--tz IANA_NAME` or `--local` for system timezone
|
|
47
|
+
- **Batch mode**: Multiple args or `--stdin` pipe
|
|
48
|
+
- **Zero dependencies**: Single Python file, stdlib only
|
|
49
|
+
|
|
50
|
+
## Running tests
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install pytest
|
|
54
|
+
pytest test_ts.py -v
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
|
|
59
|
+
MIT
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ts-toolkit"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Instant timestamp converter — zero-dependency CLI for epoch ↔ human-readable conversion"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Eric Joye", email = "eric@joye.com"},
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Topic :: Utilities",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
ts = "ts:main"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools]
|
|
26
|
+
py-modules = ["ts"]
|
ts_toolkit-0.1.0/ts.py
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""ts — Instant timestamp converter for developers.
|
|
3
|
+
|
|
4
|
+
Zero-dependency CLI tool for bidirectional epoch ↔ human-readable
|
|
5
|
+
timestamp conversion. Auto-detects seconds, milliseconds, and microseconds.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import sys
|
|
10
|
+
import time
|
|
11
|
+
from datetime import datetime, timezone, timedelta
|
|
12
|
+
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
# Epoch detection helpers
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
def detect_epoch_unit(value: int) -> str:
|
|
20
|
+
"""Return 's', 'ms', or 'us' based on digit-length heuristics."""
|
|
21
|
+
digits = len(str(abs(value)))
|
|
22
|
+
if digits <= 10:
|
|
23
|
+
return "s"
|
|
24
|
+
elif digits <= 13:
|
|
25
|
+
return "ms"
|
|
26
|
+
else:
|
|
27
|
+
return "us"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def epoch_to_dt(value: int, tz: ZoneInfo | None = None) -> datetime:
|
|
31
|
+
"""Convert an epoch value to a timezone-aware datetime (UTC base)."""
|
|
32
|
+
unit = detect_epoch_unit(value)
|
|
33
|
+
if unit == "s":
|
|
34
|
+
dt = datetime.fromtimestamp(value, tz=timezone.utc)
|
|
35
|
+
elif unit == "ms":
|
|
36
|
+
dt = datetime.fromtimestamp(value / 1000, tz=timezone.utc)
|
|
37
|
+
else:
|
|
38
|
+
dt = datetime.fromtimestamp(value / 1_000_000, tz=timezone.utc)
|
|
39
|
+
if tz is not None:
|
|
40
|
+
dt = dt.astimezone(tz)
|
|
41
|
+
return dt
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
# Human-readable → epoch parsing
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
def parse_human_to_epoch(text: str, tz: ZoneInfo | None = None) -> int:
|
|
49
|
+
"""Parse a human-readable datetime string and return epoch seconds.
|
|
50
|
+
|
|
51
|
+
Supports: ISO 8601, common date/datetime formats, and the keyword 'now'.
|
|
52
|
+
"""
|
|
53
|
+
text = text.strip()
|
|
54
|
+
|
|
55
|
+
if text.lower() == "now":
|
|
56
|
+
return int(time.time())
|
|
57
|
+
|
|
58
|
+
# Ordered list of formats to try
|
|
59
|
+
formats = [
|
|
60
|
+
"%Y-%m-%dT%H:%M:%S%z",
|
|
61
|
+
"%Y-%m-%dT%H:%M:%S.%f%z",
|
|
62
|
+
"%Y-%m-%dT%H:%M:%SZ",
|
|
63
|
+
"%Y-%m-%dT%H:%M:%S.%fZ",
|
|
64
|
+
"%Y-%m-%dT%H:%M:%S",
|
|
65
|
+
"%Y-%m-%dT%H:%M:%S.%f",
|
|
66
|
+
"%Y-%m-%d %H:%M:%S",
|
|
67
|
+
"%Y-%m-%d %H:%M:%S.%f",
|
|
68
|
+
"%Y-%m-%d",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
dt: datetime | None = None
|
|
72
|
+
for fmt in formats:
|
|
73
|
+
try:
|
|
74
|
+
dt = datetime.strptime(text, fmt)
|
|
75
|
+
break
|
|
76
|
+
except ValueError:
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
if dt is None:
|
|
80
|
+
raise ValueError(f"Cannot parse datetime: {text!r}")
|
|
81
|
+
|
|
82
|
+
# If no timezone info, treat as UTC unless --tz / --local was given
|
|
83
|
+
if dt.tzinfo is None:
|
|
84
|
+
if tz is not None:
|
|
85
|
+
dt = dt.replace(tzinfo=tz)
|
|
86
|
+
else:
|
|
87
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
88
|
+
|
|
89
|
+
return int(dt.timestamp())
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
# Formatting helpers
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
def fmt_utc(dt: datetime) -> str:
|
|
97
|
+
"""Return 'YYYY-MM-DD HH:MM:SS UTC'."""
|
|
98
|
+
utc_dt = dt.astimezone(timezone.utc)
|
|
99
|
+
return utc_dt.strftime("%Y-%m-%d %H:%M:%S UTC")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def fmt_tz(dt: datetime) -> str:
|
|
103
|
+
"""Return 'YYYY-MM-DD HH:MM:SS <TZ_NAME>'."""
|
|
104
|
+
tzname = dt.tzinfo.tzname(dt) if hasattr(dt.tzinfo, "tzname") else str(dt.tzinfo)
|
|
105
|
+
return dt.strftime(f"%Y-%m-%d %H:%M:%S {tzname}")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# ---------------------------------------------------------------------------
|
|
109
|
+
# Core conversion logic
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
def convert_one(token: str, tz: ZoneInfo | None = None) -> str:
|
|
113
|
+
"""Convert a single token (epoch int or human string) and return a display line."""
|
|
114
|
+
token = token.strip()
|
|
115
|
+
if not token:
|
|
116
|
+
return ""
|
|
117
|
+
|
|
118
|
+
# Try integer epoch first
|
|
119
|
+
try:
|
|
120
|
+
value = int(token)
|
|
121
|
+
except ValueError:
|
|
122
|
+
pass
|
|
123
|
+
else:
|
|
124
|
+
unit = detect_epoch_unit(value)
|
|
125
|
+
dt = epoch_to_dt(value, tz=tz)
|
|
126
|
+
if tz is not None:
|
|
127
|
+
return f"{token} → {fmt_tz(dt)}"
|
|
128
|
+
return f"{token} → {fmt_utc(dt)}"
|
|
129
|
+
|
|
130
|
+
# Human-readable → epoch
|
|
131
|
+
try:
|
|
132
|
+
epoch_s = parse_human_to_epoch(token, tz=tz)
|
|
133
|
+
except ValueError:
|
|
134
|
+
return f"{token} → ERROR: unrecognised format"
|
|
135
|
+
|
|
136
|
+
if tz is not None:
|
|
137
|
+
dt = datetime.fromtimestamp(epoch_s, tz=tz)
|
|
138
|
+
return f"{token} → {epoch_s} ({fmt_tz(dt)})"
|
|
139
|
+
dt = datetime.fromtimestamp(epoch_s, tz=timezone.utc)
|
|
140
|
+
return f"{token} → {epoch_s} ({fmt_utc(dt)})"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def show_current(tz: ZoneInfo | None = None) -> list[str]:
|
|
144
|
+
"""Return lines describing the current time."""
|
|
145
|
+
now = time.time()
|
|
146
|
+
epoch_s = int(now)
|
|
147
|
+
epoch_ms = int(now * 1000)
|
|
148
|
+
epoch_us = int(now * 1_000_000)
|
|
149
|
+
lines = [
|
|
150
|
+
f"Epoch s : {epoch_s}",
|
|
151
|
+
f"Epoch ms: {epoch_ms}",
|
|
152
|
+
f"Epoch μs: {epoch_us}",
|
|
153
|
+
]
|
|
154
|
+
dt_utc = datetime.fromtimestamp(now, tz=timezone.utc)
|
|
155
|
+
lines.append(f"UTC : {fmt_utc(dt_utc)}")
|
|
156
|
+
|
|
157
|
+
if tz is not None:
|
|
158
|
+
dt_tz = datetime.fromtimestamp(now, tz=tz)
|
|
159
|
+
lines.append(f"{tz.key}: {fmt_tz(dt_tz)}")
|
|
160
|
+
else:
|
|
161
|
+
# Show local time
|
|
162
|
+
dt_local = datetime.fromtimestamp(now)
|
|
163
|
+
local_tz = dt_local.astimezone().tzinfo
|
|
164
|
+
local_name = local_tz.tzname(dt_local) if hasattr(local_tz, "tzname") else str(local_tz)
|
|
165
|
+
lines.append(f"Local : {dt_local.strftime('%Y-%m-%d %H:%M:%S')} {local_name}")
|
|
166
|
+
|
|
167
|
+
return lines
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# ---------------------------------------------------------------------------
|
|
171
|
+
# CLI entry point
|
|
172
|
+
# ---------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
175
|
+
parser = argparse.ArgumentParser(
|
|
176
|
+
prog="ts",
|
|
177
|
+
description="Instant timestamp converter — epoch ↔ human-readable",
|
|
178
|
+
epilog="Examples:\n ts\n ts 1718901234\n ts 1718901234000 --tz America/New_York\n ts 2024-06-20T14:33:54Z\n ts now\n ts 1718901234 1718902000\n cat timestamps.txt | ts --stdin\n",
|
|
179
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
180
|
+
)
|
|
181
|
+
parser.add_argument(
|
|
182
|
+
"values",
|
|
183
|
+
nargs="*",
|
|
184
|
+
help="Timestamp(s) to convert: epoch int, ISO date, or 'now'",
|
|
185
|
+
)
|
|
186
|
+
parser.add_argument(
|
|
187
|
+
"--tz",
|
|
188
|
+
metavar="TZ",
|
|
189
|
+
default=None,
|
|
190
|
+
help="Target timezone (e.g. America/New_York, Europe/London)",
|
|
191
|
+
)
|
|
192
|
+
parser.add_argument(
|
|
193
|
+
"--local",
|
|
194
|
+
action="store_true",
|
|
195
|
+
default=False,
|
|
196
|
+
help="Use system local timezone (overrides --tz)",
|
|
197
|
+
)
|
|
198
|
+
parser.add_argument(
|
|
199
|
+
"--stdin",
|
|
200
|
+
action="store_true",
|
|
201
|
+
default=False,
|
|
202
|
+
help="Read timestamps from stdin (one per line)",
|
|
203
|
+
)
|
|
204
|
+
return parser
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def resolve_tz(args) -> ZoneInfo | None:
|
|
208
|
+
"""Return the ZoneInfo to use, or None for UTC."""
|
|
209
|
+
if args.local:
|
|
210
|
+
# Derive local timezone
|
|
211
|
+
local_dt = datetime.now().astimezone()
|
|
212
|
+
tzinfo = local_dt.tzinfo
|
|
213
|
+
if isinstance(tzinfo, ZoneInfo):
|
|
214
|
+
return tzinfo
|
|
215
|
+
# Fallback: try common resolution via time module
|
|
216
|
+
try:
|
|
217
|
+
local_tz_name = time.tzname[0]
|
|
218
|
+
# Not all names are IANA — best effort
|
|
219
|
+
return ZoneInfo(local_tz_name)
|
|
220
|
+
except (ZoneInfoNotFoundError, Exception):
|
|
221
|
+
# Last resort: fixed offset
|
|
222
|
+
offset = timedelta(seconds=-time.timezone if time.daylight == 0 else -time.altzone)
|
|
223
|
+
# Can't make a ZoneInfo from fixed offset easily; return UTC
|
|
224
|
+
return None
|
|
225
|
+
if args.tz:
|
|
226
|
+
try:
|
|
227
|
+
return ZoneInfo(args.tz)
|
|
228
|
+
except ZoneInfoNotFoundError:
|
|
229
|
+
print(f"ERROR: unknown timezone {args.tz!r}", file=sys.stderr)
|
|
230
|
+
sys.exit(1)
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def main() -> None:
|
|
235
|
+
parser = build_parser()
|
|
236
|
+
args = parser.parse_args()
|
|
237
|
+
tz = resolve_tz(args)
|
|
238
|
+
|
|
239
|
+
# No args → show current time
|
|
240
|
+
if not args.values and not args.stdin:
|
|
241
|
+
for line in show_current(tz):
|
|
242
|
+
print(line)
|
|
243
|
+
return
|
|
244
|
+
|
|
245
|
+
# Collect tokens
|
|
246
|
+
tokens: list[str] = []
|
|
247
|
+
if args.stdin:
|
|
248
|
+
for line in sys.stdin:
|
|
249
|
+
stripped = line.strip()
|
|
250
|
+
if stripped:
|
|
251
|
+
tokens.append(stripped)
|
|
252
|
+
tokens.extend(args.values)
|
|
253
|
+
|
|
254
|
+
if not tokens:
|
|
255
|
+
parser.print_help()
|
|
256
|
+
sys.exit(1)
|
|
257
|
+
|
|
258
|
+
for token in tokens:
|
|
259
|
+
result = convert_one(token, tz=tz)
|
|
260
|
+
if result:
|
|
261
|
+
print(result)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
if __name__ == "__main__":
|
|
265
|
+
main()
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ts-toolkit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Instant timestamp converter — zero-dependency CLI for epoch ↔ human-readable conversion
|
|
5
|
+
Author-email: Eric Joye <eric@joye.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Topic :: Utilities
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# ts — Instant Timestamp Converter
|
|
17
|
+
|
|
18
|
+
Zero-dependency CLI tool for bidirectional epoch ↔ human-readable timestamp conversion.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
### Option 1: curl (fastest)
|
|
23
|
+
```bash
|
|
24
|
+
curl -fsSL https://raw.githubusercontent.com/ericjoye/ts-cli/main/ts \
|
|
25
|
+
-o /usr/local/bin/ts && chmod +x /usr/local/bin/ts
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Option 2: pip
|
|
29
|
+
```bash
|
|
30
|
+
pip install ts-cli
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Option 3: copy
|
|
34
|
+
```bash
|
|
35
|
+
cp ts /usr/local/bin/ts && chmod +x /usr/local/bin/ts
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Requires **Python 3.9+** (uses `zoneinfo` from stdlib). No pip dependencies.
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
ts # Current time: epoch (s/ms/μs) + UTC + local
|
|
44
|
+
ts 1718901234 # Epoch seconds → human
|
|
45
|
+
ts 1718901234000 # Epoch milliseconds → human (auto-detected)
|
|
46
|
+
ts 1718901234000000 # Epoch microseconds → human (auto-detected)
|
|
47
|
+
ts "2024-06-20T14:33:54Z" # ISO 8601 → epoch
|
|
48
|
+
ts "2024-06-20 14:33:54" # Human → epoch
|
|
49
|
+
ts now # Current epoch in seconds
|
|
50
|
+
ts 1718901234 1718902000 # Batch: multiple timestamps
|
|
51
|
+
cat timestamps.txt | ts --stdin # Pipe mode
|
|
52
|
+
ts 1718901234 --tz America/New_York # Convert to timezone
|
|
53
|
+
ts 1718901234 --local # Convert to local timezone
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Features
|
|
57
|
+
|
|
58
|
+
- **Auto-detection**: Distinguishes seconds (≤10 digits), milliseconds (11–13), microseconds (16+)
|
|
59
|
+
- **Bidirectional**: Epoch → human AND human → epoch
|
|
60
|
+
- **ISO 8601**: Full support for `YYYY-MM-DDTHH:MM:SSZ` and variants
|
|
61
|
+
- **Timezone support**: `--tz IANA_NAME` or `--local` for system timezone
|
|
62
|
+
- **Batch mode**: Multiple args or `--stdin` pipe
|
|
63
|
+
- **Zero dependencies**: Single Python file, stdlib only
|
|
64
|
+
|
|
65
|
+
## Running tests
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install pytest
|
|
69
|
+
pytest test_ts.py -v
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ts
|