
Textual Timepiece
Various time management related widgets for the Textual framework.
Documentation | Changelog | PyPi
Included Widgets
ActivityHeatmap | Activity Heatmap for displaying yearly data similar to the GitHub contribution graph. |
HeatmapManager | Widget for browsing the Activity Heatmap with yearly navigation builtin. |
DateSelect | Date selection widget with calendar panes. |
TimeSelect | Time selection widget with various times in 30 minute intervals. |
DurationSelect | Duration selection widget with modifiers for adjust time or duration. |
DateInput | Date input which takes in a iso-format date. |
TimeInput | Time input that takes in 24 hour clocked in a HH:MM:SS format. |
DurationInput | Duration input with a duration up to 99 hours. |
DateTimeInput | An input with a combination of a date and time in iso-format. |
Demo
uvx --from textual-timepiece demo
pipx run textual-timepiece
Install
Pip
pip install textual-timepiece
uv add textual-timepiece
poetry add textual-timepiece
[!NOTE]
Requires whenever as an additional dependency.
Quick Start
DatePicker
Code
from textual.app import App, ComposeResult
from textual_timepiece.pickers import DatePicker
from whenever import Date
class DatePickerApp(App[None]):
def compose(self) -> ComposeResult:
yield DatePicker(Date(2025, 3, 4))
if __name__ == "__main__":
DatePickerApp().run()
Result

DateTimePicker
Code
from textual.app import App, ComposeResult
from textual_timepiece.pickers import DateTimePicker
from whenever import SystemDateTime
class DateTimePickerApp(App[None]):
def compose(self) -> ComposeResult:
yield DateTimePicker(SystemDateTime(2025, 3, 4, 9, 42, 47)))
if __name__ == "__main__":
DateTimePickerApp().run()
Result

ActivityHeatmap
Code
import random
from collections import defaultdict
from textual.app import App, ComposeResult
from textual_timepiece.activity_heatmap import ActivityHeatmap, HeatmapManager
class ActivityApp(App[None]):
def _on_heatmap_manager_year_changed(
self,
message: HeatmapManager.YearChanged,
) -> None:
message.stop()
self.set_heatmap_data(message.year)
def retrieve_data(self, year: int) -> ActivityHeatmap.ActivityData:
"""Placeholder example on how the data could be generated."""
random.seed(year)
template = ActivityHeatmap.generate_empty_activity(year)
return defaultdict(
lambda: 0,
{
day: random.randint(6000, 20000)
for week in template
for day in week
if day
},
)
def set_heatmap_data(self, year: int) -> None:
"""Sets the data based on the current data."""
self.query_one(ActivityHeatmap).values = self.retrieve_data(year)
def _on_mount(self) -> None:
self.set_heatmap_data(2025)
def compose(self) -> ComposeResult:
yield HeatmapManager(2025)
if __name__ == "__main__":
ActivityApp().run()
Result

- More examples can be found here
License
MIT. Check LICENSE for more information.