time-manager 0.2.20__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.
app.py ADDED
@@ -0,0 +1,237 @@
1
+ """time-manager entry point.
2
+
3
+ Commands:
4
+ - `time-manager sw` Start a stopwatch
5
+ - `tm sw` Start a stopwatch
6
+ - `tm stopwatch` Start a stopwatch
7
+ - `time-manager cd <amount> [unit]` Start a countdown timer
8
+ - `tm cd <amount> [unit]` Start a countdown timer
9
+ - `tm countdown <amount> [unit]` Start a countdown timer
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import os
15
+
16
+ # Terminal emulators differ in how they advertise TrueColor support.
17
+ # These defaults help keep Textual/Rich rendering consistent across Windows Terminal,
18
+ # VS Code/Cursor terminals, etc. Users can still override by setting these env vars.
19
+ os.environ.setdefault("COLORTERM", "truecolor")
20
+ os.environ.setdefault("RICH_COLOR_SYSTEM", "truecolor")
21
+
22
+ from importlib import metadata as _metadata
23
+
24
+ import typer
25
+
26
+ from cli import run_countdown_cli, run_stopwatch_cli
27
+ from tui import CountdownTui, StopwatchTui
28
+
29
+ _ALIASES: dict[str, str] = {
30
+ "stopwatch": "sw",
31
+ "countdown": "cd",
32
+ }
33
+
34
+
35
+ class _TmGroup(typer.core.TyperGroup):
36
+ """Typer group with command aliases (so help doesn't list duplicates)."""
37
+
38
+ def get_command(self, ctx: typer.Context, cmd_name: str):
39
+ command = super().get_command(ctx, cmd_name)
40
+ if command is not None:
41
+ return command
42
+
43
+ alias = _ALIASES.get(cmd_name)
44
+ if alias is None:
45
+ return None
46
+
47
+ return super().get_command(ctx, alias)
48
+
49
+
50
+ # Create the Typer app
51
+ app = typer.Typer(
52
+ help=(
53
+ "A terminal based stopwatch and countdown timer.\n\n"
54
+ "Examples:\n"
55
+ " tm sw\n"
56
+ " tm sw -i\n"
57
+ " tm cd 5 m\n"
58
+ " tm cd 5 m -i\n"
59
+ " tm countdown 10 s\n"
60
+ ),
61
+ cls=_TmGroup,
62
+ add_completion=False,
63
+ )
64
+
65
+ INTERACTIVE = typer.Option(
66
+ False,
67
+ "--interactive",
68
+ "-i",
69
+ help="Run in interactive (Textual TUI) mode.",
70
+ )
71
+
72
+
73
+ def _get_version() -> str:
74
+ for dist_name in ("time-manager", "tm"):
75
+ try:
76
+ return _metadata.version(dist_name)
77
+ except _metadata.PackageNotFoundError:
78
+ continue
79
+ return "unknown"
80
+
81
+
82
+ def _version_callback(value: bool) -> None:
83
+ if not value:
84
+ return
85
+ typer.echo(_get_version())
86
+ raise typer.Exit()
87
+
88
+
89
+ _UNIT_SECONDS: dict[str, int] = {
90
+ # seconds
91
+ "s": 1,
92
+ "sec": 1,
93
+ "secs": 1,
94
+ "second": 1,
95
+ "seconds": 1,
96
+ # minutes
97
+ "m": 60,
98
+ "min": 60,
99
+ "mins": 60,
100
+ "minute": 60,
101
+ "minutes": 60,
102
+ # hours
103
+ "h": 3600,
104
+ "hr": 3600,
105
+ "hrs": 3600,
106
+ "hour": 3600,
107
+ "hours": 3600,
108
+ }
109
+
110
+
111
+ def _die(message: str) -> None:
112
+ typer.secho(f"Error: {message}", fg=typer.colors.RED, err=True)
113
+ raise typer.Exit(code=1)
114
+
115
+
116
+ def _parse_countdown_seconds(amount: int, unit: str) -> int:
117
+ if amount <= 0:
118
+ _die("Time must be greater than 0.")
119
+
120
+ normalized_unit = unit.lower().strip()
121
+ multiplier = _UNIT_SECONDS.get(normalized_unit)
122
+ if multiplier is None:
123
+ _die(f"Unknown unit '{normalized_unit}'. Please use 's', 'm', or 'h'.")
124
+
125
+ seconds = amount * multiplier
126
+ if seconds <= 0:
127
+ _die("Time must be greater than 0.")
128
+
129
+ return seconds
130
+
131
+
132
+ def _print_error_box(message: str) -> None:
133
+ """Print an error message in a boxed panel when Rich is available."""
134
+ try:
135
+ from rich import box
136
+ from rich.console import Console
137
+ from rich.panel import Panel
138
+ from rich.text import Text
139
+ except Exception:
140
+ typer.secho(f"Error: {message}", fg=typer.colors.RED, err=True)
141
+ return
142
+
143
+ Console(stderr=True).print(
144
+ Panel(
145
+ Text(message, style="bold red"),
146
+ title="Error",
147
+ border_style="red",
148
+ box=box.ROUNDED,
149
+ expand=True,
150
+ )
151
+ )
152
+
153
+
154
+ @app.callback(invoke_without_command=True)
155
+ def _root(
156
+ ctx: typer.Context,
157
+ interactive: bool = INTERACTIVE,
158
+ version: bool = typer.Option(
159
+ False,
160
+ "--version",
161
+ "-V",
162
+ help="Show the version and exit.",
163
+ callback=_version_callback,
164
+ is_eager=True,
165
+ ),
166
+ ) -> None:
167
+ """
168
+ A terminal based stopwatch and countdown timer.
169
+ """
170
+ ctx.ensure_object(dict)
171
+ ctx.obj["interactive"] = interactive
172
+
173
+ if ctx.invoked_subcommand is not None:
174
+ return
175
+
176
+ # Treat calling `tm` with no command as an error, but show help by default.
177
+ _print_error_box("Missing command.")
178
+ # Use Typer/Click's built-in help so it's complete and stays standard
179
+ # (includes e.g. completion flags and any future global options).
180
+ typer.echo(ctx.get_help())
181
+ raise typer.Exit(code=1)
182
+
183
+
184
+ @app.command(help="Start a stopwatch. (alias: stopwatch)")
185
+ def sw(ctx: typer.Context, interactive: bool = INTERACTIVE) -> None:
186
+ """
187
+ Start a stopwatch.
188
+
189
+ Examples:
190
+ tm sw
191
+ tm sw -i
192
+ tm stopwatch
193
+ """
194
+ effective_interactive = bool(
195
+ interactive or (ctx.obj or {}).get("interactive", False)
196
+ )
197
+ if effective_interactive:
198
+ StopwatchTui().run()
199
+ else:
200
+ run_stopwatch_cli()
201
+
202
+
203
+ @app.command(help="Start a countdown timer. (alias: countdown)")
204
+ def cd(
205
+ ctx: typer.Context,
206
+ amount: int = typer.Argument(..., help="The amount of time."),
207
+ unit: str = typer.Argument(
208
+ "m", help="The unit of time. [s]econds, [m]inutes, [h]ours."
209
+ ),
210
+ interactive: bool = INTERACTIVE,
211
+ ):
212
+ """
213
+ Start a countdown timer.
214
+
215
+ Examples:
216
+ tm cd 5 m
217
+ tm cd 5 m -i
218
+ tm countdown 10 s
219
+ """
220
+
221
+ seconds = _parse_countdown_seconds(amount, unit)
222
+
223
+ effective_interactive = bool(
224
+ interactive or (ctx.obj or {}).get("interactive", False)
225
+ )
226
+ if effective_interactive:
227
+ CountdownTui(seconds).run()
228
+ else:
229
+ run_countdown_cli(seconds)
230
+
231
+
232
+ def main() -> None:
233
+ app()
234
+
235
+
236
+ if __name__ == "__main__":
237
+ main()
cli/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ # time-manager CLI Components
2
+
3
+ from .cli import run_stopwatch_cli, run_countdown_cli
4
+
5
+ __all__ = ["run_stopwatch_cli", "run_countdown_cli"]
cli/cli.py ADDED
@@ -0,0 +1,140 @@
1
+ import time
2
+ import sys
3
+ import select
4
+ import termios
5
+ import tty
6
+ from rich.align import Align
7
+ from rich.console import Group
8
+ from rich.live import Live
9
+ from rich.panel import Panel
10
+ from rich.text import Text
11
+ from rich import box
12
+ from core.formatting import format_time
13
+ from core.termclock import Stopwatch, Countdown
14
+
15
+
16
+ class NonBlockingInput:
17
+ """Context manager for non-blocking terminal input."""
18
+
19
+ def __enter__(self):
20
+ self.old_settings = termios.tcgetattr(sys.stdin)
21
+ tty.setcbreak(sys.stdin.fileno())
22
+ return self
23
+
24
+ def __exit__(self, exc_type, exc_val, exc_tb):
25
+ termios.tcsetattr(sys.stdin, termios.TCSADRAIN, self.old_settings)
26
+
27
+ @staticmethod
28
+ def get_char():
29
+ """Check for and return a character if available, else None."""
30
+ if select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []):
31
+ return sys.stdin.read(1)
32
+ return None
33
+
34
+
35
+ def run_stopwatch_cli():
36
+ stopwatch = Stopwatch()
37
+ stopwatch.start()
38
+
39
+ subtitle = "Space: Start/Stop | r: Reset | q: Quit"
40
+
41
+ try:
42
+ with NonBlockingInput(), Live(refresh_per_second=60, screen=False) as live:
43
+ while True:
44
+ # Handle Input
45
+ char = NonBlockingInput.get_char()
46
+ if char:
47
+ if char.lower() == "q":
48
+ break
49
+ elif char == " ":
50
+ stopwatch.toggle()
51
+ elif char.lower() == "r":
52
+ stopwatch.reset()
53
+
54
+ # Update Display
55
+ elapsed = stopwatch.elapsed
56
+ time_str = format_time(elapsed, show_centiseconds=False)
57
+ # Always display HH:MM:SS (even when hours == 0)
58
+ if time_str.count(":") == 1:
59
+ time_str = f"00:{time_str}"
60
+
61
+ # Visual feedback for paused state
62
+ style = "bold green" if stopwatch.is_running else "dim green"
63
+ border_style = "green" if stopwatch.is_running else "white"
64
+
65
+ display = Group(
66
+ Align.center(Text(time_str, style=style)),
67
+ Align.center(Text("HH:MM:SS", style="dim")),
68
+ )
69
+
70
+ panel = Panel(
71
+ display,
72
+ title="Stopwatch",
73
+ subtitle=subtitle,
74
+ box=box.ROUNDED,
75
+ border_style=border_style,
76
+ padding=(1, 2),
77
+ )
78
+ live.update(panel)
79
+ time.sleep(1 / 60)
80
+ except KeyboardInterrupt:
81
+ pass
82
+
83
+
84
+ def run_countdown_cli(seconds: int):
85
+ countdown = Countdown(seconds)
86
+
87
+ subtitle = "Space: Pause/Resume | q: Quit"
88
+
89
+ try:
90
+ with NonBlockingInput(), Live(refresh_per_second=10, screen=False) as live:
91
+ while not countdown.is_finished:
92
+ # Handle Input
93
+ char = NonBlockingInput.get_char()
94
+ if char:
95
+ if char.lower() == "q":
96
+ break
97
+ elif char == " ":
98
+ countdown.toggle()
99
+
100
+ countdown.tick()
101
+ remaining = countdown.time_left
102
+ time_str = format_time(remaining, show_centiseconds=False)
103
+
104
+ # Change color based on urgency
105
+ color = "blue"
106
+ if remaining < 10:
107
+ color = "red"
108
+ elif remaining < 30:
109
+ color = "yellow"
110
+
111
+ # Visual feedback for paused state
112
+ style = f"bold {color}" if countdown.is_running else f"dim {color}"
113
+ border_style = color if countdown.is_running else "white"
114
+
115
+ panel = Panel(
116
+ Text(time_str, style=style, justify="center"),
117
+ title="Countdown",
118
+ subtitle=subtitle,
119
+ box=box.ROUNDED,
120
+ border_style=border_style,
121
+ padding=(1, 2),
122
+ )
123
+ live.update(panel)
124
+ time.sleep(0.1)
125
+
126
+ # Final "Time's Up" display
127
+ if countdown.is_finished:
128
+ panel = Panel(
129
+ Text("00:00", style="bold red blink", justify="center"),
130
+ title="Countdown",
131
+ subtitle="Time's Up!",
132
+ box=box.ROUNDED,
133
+ border_style="red",
134
+ padding=(1, 2),
135
+ )
136
+ live.update(panel)
137
+ time.sleep(2) # Show for a bit before exiting
138
+
139
+ except KeyboardInterrupt:
140
+ pass
core/formatting.py ADDED
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ def format_time(seconds: float, *, show_centiseconds: bool = True) -> str:
5
+ """Format a duration in seconds as MM:SS(.CC) or HH:MM:SS(.CC)."""
6
+
7
+ seconds = max(0.0, float(seconds))
8
+ minutes, secs = divmod(seconds, 60)
9
+ hours, minutes = divmod(minutes, 60)
10
+
11
+ if show_centiseconds:
12
+ centiseconds = int((seconds * 100) % 100)
13
+ if hours > 0:
14
+ return f"{int(hours):02}:{int(minutes):02}:{int(secs):02}.{centiseconds:02}"
15
+ return f"{int(minutes):02}:{int(secs):02}.{centiseconds:02}"
16
+
17
+ if hours > 0:
18
+ return f"{int(hours):02}:{int(minutes):02}:{int(secs):02}"
19
+ return f"{int(minutes):02}:{int(secs):02}"
core/termclock.py ADDED
@@ -0,0 +1,97 @@
1
+ from time import monotonic
2
+ from dataclasses import dataclass, field
3
+ from typing import Optional
4
+
5
+
6
+ @dataclass
7
+ class Stopwatch:
8
+ """Core logic for a stopwatch."""
9
+
10
+ _start_time: Optional[float] = None
11
+ _accumulated_time: float = 0.0
12
+ _running: bool = False
13
+
14
+ @property
15
+ def is_running(self) -> bool:
16
+ return self._running
17
+
18
+ @property
19
+ def elapsed(self) -> float:
20
+ """Return the total elapsed time in seconds."""
21
+ if self._running:
22
+ return self._accumulated_time + (monotonic() - self._start_time)
23
+ return self._accumulated_time
24
+
25
+ def start(self):
26
+ if not self._running:
27
+ self._start_time = monotonic()
28
+ self._running = True
29
+
30
+ def stop(self):
31
+ if self._running:
32
+ self._accumulated_time += monotonic() - self._start_time
33
+ self._start_time = None
34
+ self._running = False
35
+
36
+ def reset(self):
37
+ self._running = False
38
+ self._accumulated_time = 0.0
39
+ self._start_time = None
40
+
41
+ def toggle(self):
42
+ if self._running:
43
+ self.stop()
44
+ else:
45
+ self.start()
46
+
47
+
48
+ @dataclass
49
+ class Countdown:
50
+ """Core logic for a countdown timer."""
51
+
52
+ initial_seconds: int
53
+ _time_left: float = field(init=False)
54
+ _last_tick: Optional[float] = field(init=False, default=None)
55
+ _running: bool = field(init=False, default=True)
56
+
57
+ def __post_init__(self):
58
+ self._time_left = float(self.initial_seconds)
59
+ self._last_tick = monotonic()
60
+
61
+ @property
62
+ def time_left(self) -> float:
63
+ return max(0.0, self._time_left)
64
+
65
+ @property
66
+ def is_running(self) -> bool:
67
+ return self._running
68
+
69
+ @property
70
+ def is_finished(self) -> bool:
71
+ return self._time_left <= 0
72
+
73
+ def tick(self):
74
+ """Update the timer based on elapsed real time."""
75
+ if self._running and self._time_left > 0:
76
+ now = monotonic()
77
+ if self._last_tick:
78
+ delta = now - self._last_tick
79
+ self._time_left -= delta
80
+ self._last_tick = now
81
+ else:
82
+ self._last_tick = monotonic()
83
+
84
+ def pause(self):
85
+ self._running = False
86
+ self._last_tick = None
87
+
88
+ def resume(self):
89
+ if not self._running:
90
+ self._running = True
91
+ self._last_tick = monotonic()
92
+
93
+ def toggle(self):
94
+ if self._running:
95
+ self.pause()
96
+ else:
97
+ self.resume()
@@ -0,0 +1,192 @@
1
+ Metadata-Version: 2.4
2
+ Name: time-manager
3
+ Version: 0.2.20
4
+ Summary: A terminal based stopwatch and countdown timer
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: textual-dev>=1.8.0
8
+ Requires-Dist: textual>=6.11.0
9
+ Requires-Dist: typer>=0.21.0
10
+ Description-Content-Type: text/markdown
11
+
12
+ # time-manager
13
+
14
+ A powerful and visually stunning terminal-based timer application built with [Textual](https://textual.textualize.io/) and [Typer](https://typer.tiangolo.com/).
15
+
16
+ - **CLI Interface (default)**: Lightweight mode (no Textual UI).
17
+ - **TUI Interface**: Beautiful, responsive terminal user interface via `-i/--interactive`.
18
+ - **Notifications**: Visual and audio feedback (bell) when a countdown completes.
19
+
20
+ ![Stopwatch TUI Screenshot](./docs/stopwatch-tui.png)
21
+
22
+ ## Features
23
+
24
+ - **Stopwatch**: Precise stopwatch with centisecond resolution.
25
+ - **Countdown**: Configurable countdown timer with support for seconds, minutes, and hours.
26
+
27
+ ## Installation
28
+
29
+ - Requires **Python 3.10+**
30
+ - Installs two commands: `tm` (recommended) and `time-manager`
31
+
32
+ From PyPI:
33
+
34
+ ```bash
35
+ pip install time-manager
36
+ ```
37
+
38
+ If you use uv:
39
+
40
+ ```bash
41
+ uv tool install time-manager
42
+
43
+ # or (inside a project)
44
+ uv add time-manager
45
+ ```
46
+
47
+ ## Usage
48
+
49
+ time-manager provides two command names for convenience:
50
+
51
+ - `tm` - Short and convenient alias
52
+ - `time-manager` - Full command name
53
+
54
+ Both commands work identically. Examples:
55
+
56
+ ```bash
57
+ tm sw # or: time-manager sw (also: tm stopwatch)
58
+ tm cd 5 m # or: time-manager cd 5 m (also: tm countdown 5 m)
59
+ ```
60
+
61
+ ### Stopwatch
62
+
63
+ Start a stopwatch to track elapsed time:
64
+
65
+ ```bash
66
+ tm sw
67
+ ```
68
+
69
+ For interactive TUI mode:
70
+
71
+ ```bash
72
+ tm sw -i
73
+ ```
74
+
75
+ **Controls (TUI mode):**
76
+ - `Space`: Start/Stop
77
+ - `r`: Reset
78
+ - `q`: Quit
79
+
80
+ ### Countdown Timer
81
+
82
+ Start a countdown for a specific duration:
83
+
84
+ ```bash
85
+ tm cd 5 m # 5 minutes
86
+ tm cd 60 s # 60 seconds
87
+ tm cd 1 h # 1 hour
88
+ ```
89
+
90
+ For interactive TUI mode:
91
+
92
+ ```bash
93
+ tm cd 5 m -i
94
+ ```
95
+
96
+ **Controls (TUI mode):**
97
+ - `Space`: Pause/Resume
98
+ - `q`: Quit
99
+
100
+ ## Development
101
+
102
+ ### Prerequisites
103
+
104
+ - [uv](https://github.com/astral-sh/uv) installed on your system.
105
+ - Python 3.10+
106
+
107
+ ### Installation for Development
108
+
109
+ To install in editable mode for development:
110
+
111
+ ```bash
112
+ make local
113
+ ```
114
+
115
+ ### Global Installation (Recommended for Testing)
116
+
117
+ To install as a system-wide utility:
118
+
119
+ ```bash
120
+ make global
121
+ ```
122
+
123
+ This builds a standalone executable and copies it to `/usr/local/bin/time-manager`, and also creates a `/usr/local/bin/tm` symlink.
124
+
125
+ ### Make Commands
126
+
127
+ | Command | Description |
128
+ | ---------------------- | ------------------------------------------------- |
129
+ | `make local` | Install in editable mode for development |
130
+ | `make global` | Build and install system-wide to `/usr/local/bin` |
131
+ | `make build` | Build standalone executable (with version bump) |
132
+ | `make bump` | Bump patch version (default) |
133
+ | `TYPE=MINOR make bump` | Bump minor version |
134
+ | `TYPE=MAJOR make bump` | Bump major version |
135
+ | `make clean` | Remove build artifacts |
136
+ | `make uninstall` | Remove global installation |
137
+
138
+ ### Project Structure
139
+
140
+ ```
141
+ time-manager/
142
+ ├── src/
143
+ │ ├── app.py # CLI entry point using Typer
144
+ │ ├── cli/
145
+ │ │ ├── __init__.py # CLI package exports
146
+ │ │ └── cli.py # CLI implementations for timers
147
+ │ ├── core/
148
+ │ │ ├── formatting.py # Time formatting utilities
149
+ │ │ └── termclock.py # Core timer logic
150
+ │ └── tui/
151
+ │ ├── __init__.py # TUI package exports
152
+ │ ├── countdown.py # Countdown TUI
153
+ │ ├── stopwatch.py # Stopwatch TUI
154
+ │ └── theme.tcss # Textual CSS theme
155
+ ├── scripts/
156
+ │ └── bump.sh # Version bump script
157
+ ├── pyproject.toml # Project configuration
158
+ ├── uv.lock # Dependency lock file
159
+ ├── Makefile # Build and install commands
160
+ ├── LICENSE # Project license
161
+ └── README.md # This file
162
+ ```
163
+
164
+ ### Publishing to PyPI
165
+
166
+ This repo uses `make publish` (via `scripts/publish.sh`) and defaults to **TestPyPI**.
167
+
168
+ #### Test PyPI (default)
169
+
170
+ To publish to Test PyPI (uses `TEST_PYPI_PUBLISH_TOKEN`):
171
+
172
+ ```bash
173
+ make build
174
+ make publish
175
+ ```
176
+
177
+ #### Production PyPI
178
+
179
+ To publish to production PyPI (uses `PYPI_PUBLISH_TOKEN`):
180
+
181
+ ```bash
182
+ make build
183
+ PROD=TRUE make publish
184
+ ```
185
+
186
+ ### Uninstallation
187
+
188
+ To remove the global installation:
189
+
190
+ ```bash
191
+ make uninstall
192
+ ```
@@ -0,0 +1,14 @@
1
+ app.py,sha256=gCGHj1heTrg_pFafK-0ZJGAWaYSlVjnbcuvzuRul1Zo,5787
2
+ cli/__init__.py,sha256=438YugZ3QU07-5Spswk0OLcHC_B3JDjB8ZHRT9hn4SM,139
3
+ cli/cli.py,sha256=yZi5u8phWkSucZ9zPi1bF8jdtJmzNW4iUzV7SEna6pU,4639
4
+ core/formatting.py,sha256=3SVZ3xUaEN3mtEecFgMqxesSh5-XDUbAiwBzZ61846k,694
5
+ core/termclock.py,sha256=RptBEob2O__-tRu9siD8HUxw5UT2uPLN1B1HodyxkLI,2467
6
+ tui/__init__.py,sha256=TwfmFKYVnc3n4uIrm19PYSAcgdsaH2W4YxVlVbEDYCc,139
7
+ tui/countdown.py,sha256=krCZgbz4ILVcOsv-8Lo1iwPnnUhHVI37_x273CHcxqQ,3265
8
+ tui/stopwatch.py,sha256=11lFQ3q96oyL-Krt6_ePRc8eVvVf6Am1MOhJLMSmaV8,3841
9
+ tui/theme.tcss,sha256=-DtnprXFmvzuOVIVjLSkIhGT8Q4KD3eA50ry7Twzpt4,4510
10
+ time_manager-0.2.20.dist-info/METADATA,sha256=msDYBTDI5V4QfYeHTow_1HcWauzI80Dm-W2xIxDd0mI,4734
11
+ time_manager-0.2.20.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
12
+ time_manager-0.2.20.dist-info/entry_points.txt,sha256=m_D7FaJTbf0SyyQMkyqsTWjgNEo20-MbzF-1DyZEUv0,56
13
+ time_manager-0.2.20.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
14
+ time_manager-0.2.20.dist-info/RECORD,,