#!/bin/env python3 import datetime from tkinter import Tk, CENTER, Label, font import click import typing as t DELTA_SECONDS = "+%Ss" DELTA_MINUTES = "+%Mm" DELTA_HOURS = "+%Hh" TIME_TODAY = "%H:%M" DATETIME_FORMAT = [ "%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", TIME_TODAY, DELTA_HOURS, DELTA_MINUTES, DELTA_SECONDS, ] FONT = "Liberation Sans" class DateTime(click.DateTime): def _try_to_convert_date(self, value: t.Any, format: str) -> t.Optional[datetime]: try: if value[0] == "+": if format == DELTA_SECONDS and value[-1] == "s": return datetime.datetime.now() + datetime.timedelta( seconds=int(value[1:-1]) ) if format == DELTA_MINUTES and value[-1] == "m": return datetime.datetime.now() + datetime.timedelta( minutes=int(value[1:-1]) ) if format == DELTA_HOURS and value[-1] == "h": return datetime.datetime.now() + datetime.timedelta( hours=int(value[1:-1]) ) if format == TIME_TODAY: return datetime.datetime.combine( datetime.datetime.now().date(), datetime.datetime.strptime(value, format).time(), ) return datetime.datetime.strptime(value, format) except ValueError: return None class Timer: def __init__(self, end_date): self.end_date = end_date self.update() def update(self): self.delta = self.end_date - datetime.datetime.now() def calc_delta(self): days, remainder = divmod(self.delta.total_seconds(), 60 * 60 * 24) hours, remainder = divmod(remainder, 3600) minutes, seconds = divmod(remainder, 60) return int(days), int(hours), int(minutes), int(seconds) @classmethod def get_format(cls, days, hours, minutes, seconds): if days == 0: if hours == 0: if minutes == 0: return "{seconds:02d}" return "{minutes:02d}:{seconds:02d}" return "{hours:02d}:{minutes:02d}:{seconds:02d}" return "{days:02d}:{hours:02d}:{minutes:02d}:{seconds:02d}" def __str__(self): days, hours, minutes, seconds = self.calc_delta() return self.get_format(days, hours, minutes, seconds).format( days=days, hours=hours, minutes=minutes, seconds=seconds ) current_size = { "format": Timer.get_format(9, 9, 9, 9), "height": 800, "width": 800, } def start_app(end_date): timer = Timer(end_date) root = Tk() root.geometry("300x100+200+200") root.configure(background="#000") font_instance = font.Font(font=(FONT, -100)) timer_label = Label(root, text="", font=font_instance, fg="#fff", bg="#000") timer_label.pack() timer_label.place(relx=0.5, rely=0.5, anchor=CENTER) fixed_font_instance = font.Font(font=(FONT, -100)) def resize(_): global current_size try: next_size = { "format": timer.get_format( *(99 if x else 0 for x in timer.calc_delta()) ), "height": root.winfo_height(), "width": root.winfo_width(), } if current_size != next_size: next_font_height = int( min( root.winfo_height(), ( root.winfo_width() / fixed_font_instance.measure(next_size["format"]) ) * 250, ) ) font_instance.config(size=next_font_height) timer_label.configure(font=font_instance) current_size = next_size except ZeroDivisionError: pass def update(): timer.update() if timer.delta.total_seconds() <= 0: timer_label.configure(text="Now") timer_label.config(fg="red") else: timer_label.configure(text=str(timer)) root.after(1000 - datetime.datetime.now().microsecond // 1000, update) root.bind("", resize) root.bind("", lambda x: root.destroy()) root.after(1, update) root.mainloop() @click.command() @click.argument("end_date", type=DateTime(formats=DATETIME_FORMAT)) def main(end_date): start_app(end_date) if __name__ == "__main__": main()