A question about time

I have here a very basic timer, just a standalone working snippet of a large project.
It takes an integer (15 in this demo) and counts down in seconds to 0.
The display is H:M:S. As the integer will never be higher than 20 the display is largely redundant, it really needs to be: mins:secs:10ths. Can this be achieved? Thanks.

import time
import wx

########################################################################
class MyFrame(wx.Frame):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, title='Timer')
        panel = wx.Panel(self)
        
        
        self.countdown = 15 #seconds
        lbl = '00:00:' + str(self.countdown)

        font = wx.Font(24, wx.FONTFAMILY_ROMAN,
                       wx.FONTSTYLE_NORMAL,
                       wx.FONTWEIGHT_BOLD)

        self.lbl = wx.StaticText(panel, label=lbl)
        self.lbl.SetFont(font)

        btn = wx.Button(panel, label='Start Countdown')
        btn.Bind(wx.EVT_BUTTON, self.start)

        self.timer1 = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.prevTimer, self.timer1)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.lbl, 0, wx.ALL, 5)
        sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
        panel.SetSizer(sizer)

        self.Show()

    def start(self, event):
        self.timer1.Start(1000)

    def prevTimer(self, evt):
        self.countdown -= 1
        seg = time.strftime("%H:%M:%S", time.gmtime(self.countdown))
        if seg == "00:00:00": self.timer1.Stop()

        self.lbl.SetLabel(str(seg))


if __name__ == '__main__':
    app = wx.App(False)
    frame = MyFrame()
    app.MainLoop()

You could try the wx.TimeSpan class.

import wx

ts = wx.TimeSpan(0, sec=15)

while ts.IsPositive():
    print(ts.Format("%M:%S:%l"))
    # Snip off last character, if preferred:
    # print(ts.Format("%M:%S:%l")[:-1])
    ts -= wx.TimeSpan(0, msec=500)

The "%M:%S:%l" format will return the value in minutes, seconds and milliseconds.

This example decrements by 500msec just to show the milliseconds are changing. In your example you would use wx.TimeSpan(0, sec=1).

If 3 digits for milliseconds are too many, you could use string slicing to snip off unwanted trailing characters (as in the commented out line).

I think the format such as 00:00:500 where 500 indicates milliseconds is a bit strange to me. :roll_eyes: Maybe 00:00.500?

The timedelta module can be another option.

>>> from datetime import timedelta
>>> '{}'.format(timedelta(seconds=11.111)).rstrip('0')
'0:00:11.111'

EDIT Sorry that rstrip('0') was a bad idea; it will return the wrong string if seconds is an integer.

Sorry, I don’t understand. How is making use of the wx.timer?

My code was just demonstrating how to format the time string.

In your example, you could do something like:

import wx


########################################################################
class MyFrame(wx.Frame):
    """"""


    # ----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, title='Timer')
        panel = wx.Panel(self)

        self.countdown = wx.TimeSpan(0, sec=15)
        lbl = self.getTimeStr()

        font = wx.Font(24, wx.FONTFAMILY_ROMAN,
                       wx.FONTSTYLE_NORMAL,
                       wx.FONTWEIGHT_BOLD)

        self.lbl = wx.StaticText(panel, label=lbl)
        self.lbl.SetFont(font)

        btn = wx.Button(panel, label='Start Countdown')
        btn.Bind(wx.EVT_BUTTON, self.start)

        self.timer1 = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.prevTimer, self.timer1)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.lbl, 0, wx.ALL, 5)
        sizer.Add(btn, 0, wx.ALL | wx.CENTER, 5)
        panel.SetSizer(sizer)

        self.Show()


    def start(self, event):
        self.timer1.Start(1000)


    def getTimeStr(self):
        return self.countdown.Format("%M:%S:%l")[:-1]


    def prevTimer(self, evt):
        self.countdown -= wx.TimeSpan(0, sec=1)
        seg = self.getTimeStr()
        self.lbl.SetLabel(seg)
        if seg == "00:00:00": self.timer1.Stop()



if __name__ == '__main__':
    app = wx.App(False)
    frame = MyFrame()
    app.MainLoop()


I’m not getting any 10ths with this script. Just the seconds counting down.

Maybe make some changes???

In start() use:
self.timer1.Start(100)

In prevTimer() use:
self.countdown -= wx.TimeSpan(0, msec=100)

must the event queue be in between ? :woozy_face:

I had need for a timer a while back so I wrote this up. I’ve attached the project as a zip. There is a MarkDownPad file with a brief explanation.

timer 2022-09-02 14-00.zip (1.5 MB)

@ReverendJim I have to say it’s getting moving: why not use wx.SingleInstanceChecker ? too boring ? but I hope it’s more general ! :roll_eyes:

RE: SingleInstanceChecker - I didn’t use it because I didn’t know about it.

well @floatingshed , before that large project is drowning your float try this slight modification :cowboy_hat_face:

import time
import wx

########################################################################
class MyFrame(wx.Frame):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, title='Timer')
        panel = wx.Panel(self)


        self.countdown = 15 #seconds
        lbl = '00:00:' + str(self.countdown)

        font = wx.Font(24, wx.FONTFAMILY_ROMAN,
                       wx.FONTSTYLE_NORMAL,
                       wx.FONTWEIGHT_BOLD)

        self.lbl = wx.StaticText(panel, label=lbl)
        self.lbl.SetFont(font)

        btn = wx.Button(panel, label='Start Countdown')
        btn.Bind(wx.EVT_BUTTON, self.start)

        self.timer1 = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.prevTimer, self.timer1)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.lbl, 0, wx.ALL, 5)
        sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
        panel.SetSizer(sizer)

        self.Show()

    def start(self, event):
        self.timer1.Start(100)
        self.st = time.time()

    def prevTimer(self, evt):
        # self.countdown -= 1
        # seg = time.strftime("%H:%M:%S", time.gmtime(self.countdown))
        seg = round(self.countdown - time.time() + self.st, 1)
        # if seg == "00:00:00": self.timer1.Stop()
        if not seg:
            self.timer1.Stop()

        self.lbl.SetLabel(str(seg))


if __name__ == '__main__':
    app = wx.App(False)
    frame = MyFrame()
    app.MainLoop()

@komoto48g the pythonista version (I’m an alien :alien:)

import time
from threading import Thread, Event
import wx

########################################################################
class MyFrame(wx.Frame):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, title='Timer')
        panel = wx.Panel(self)


        self.countdown = 15 #seconds
        self.lbl = '00:00:' + str(self.countdown)

        font = wx.Font(24, wx.FONTFAMILY_ROMAN,
                       wx.FONTSTYLE_NORMAL,
                       wx.FONTWEIGHT_BOLD)

        self.lbl = wx.StaticText(panel, label=self.lbl)
        self.lbl.SetFont(font)

        btn = wx.Button(panel, label='Start Countdown')
        btn.Bind(wx.EVT_BUTTON, self.start)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.lbl, 0, wx.ALL, 5)
        sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
        panel.SetSizer(sizer)

        self.Show()

    def start(self, event):
        if 'evt' in vars(self):
            self.evt.set()
        self.evt = Event()
        Counter(self.evt, self.countdown, self.lbl).start()

class Counter(Thread):
    def __init__(self, evt, cnt_down, label):
        Thread.__init__(self)
        self.cnt_down = cnt_down
        self.lbl = label
        self.finished = evt

    def run(self):
        st = time.time()
        try:
            while not self.finished.is_set():
                self.finished.wait(0.1)
                seg = round(self.cnt_down - time.time() + st, 1)
                if seg < 0.0:
                    self.finished.set()
                else:
                    self.lbl.SetLabel(str(seg))
        except Exception:
            pass

if __name__ == '__main__':
    app = wx.App(False)
    frame = MyFrame()
    app.MainLoop()

@floatingshed and if the QC is breathing down your neck (even pylint could smell it, but I didn’t change the overall ambiance of your float) here is something more acceptable to them: more involved, but they always gave me the feeling that is their main & only objective (just look at that beautiful & effective try construct) :innocent:

import time
from threading import Thread, Event
import wx

########################################################################
class MyFrame(wx.Frame):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Frame.__init__(self, None, title='Timer')
        panel = wx.Panel(self)


        self.countdown = 15 #seconds
        self.lbl = '00:00:' + str(self.countdown)

        font = wx.Font(24, wx.FONTFAMILY_ROMAN,
                       wx.FONTSTYLE_NORMAL,
                       wx.FONTWEIGHT_BOLD)

        self.lbl = wx.StaticText(panel, label=self.lbl)
        self.lbl.SetFont(font)

        btn = wx.Button(panel, label='Start Countdown')
        btn.Bind(wx.EVT_BUTTON, self.start)
        self.evt = None
        def on_destroy(_):
            if self.evt:
                self.evt.set()
        self.Bind(wx.EVT_WINDOW_DESTROY, on_destroy)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.lbl, 0, wx.ALL, 5)
        sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
        panel.SetSizer(sizer)

        self.Show()

    def start(self, _):
        if self.evt:
            self.evt.set()
        self.evt = Event()
        Counter(self.evt, self.countdown, self.lbl).start()

class Counter(Thread):
    def __init__(self, evt, cnt_down, label):
        Thread.__init__(self)
        self.cnt_down = cnt_down
        self.lbl = label
        self.finished = evt

    def run(self):
        st = time.time()
        while True:
            self.finished.wait(0.1)
            seg = round(self.cnt_down - time.time() + st, 1)
            if seg < 0.0 or self.finished.is_set():
                break
            self.lbl.SetLabel(str(seg))

if __name__ == '__main__':
    app = wx.App(False)
    frame = MyFrame()
    app.MainLoop()