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.
@@ -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"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
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,9 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ ts.py
5
+ ts_toolkit.egg-info/PKG-INFO
6
+ ts_toolkit.egg-info/SOURCES.txt
7
+ ts_toolkit.egg-info/dependency_links.txt
8
+ ts_toolkit.egg-info/entry_points.txt
9
+ ts_toolkit.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ts = ts:main