Countdown timer

I made a simple countdown timer to count to a fixed point in time. I’m using a datetime stored in a data table and it counts from now. Both are timezone aware and use UTC. Shows countdown in format “X Days HH:MM:SS”. The “s” in the server call ensures there is no spinner every second.

Client code:

  def __init__(self, **properties):
    # Set Form properties and Data Bindings.
    self.init_components(**properties)

    # Any code you write here will run when the form opens.
    self.ending_countdown.text = anvil.server.call('get_countdown', self.end_date.text)

  def timer_1_tick(self, **event_args):
    """This method is called Every [interval] seconds. Does not trigger if [interval] is 0."""
    self.ending_countdown.text = anvil.server.call_s('get_countdown', self.end_date.text)

Note: A timer has to be added to the form. I set the tick to be 1 second.

Server code:

from datetime import datetime as dt #the datetime people really should fix this namespace
from datetime import timedelta, timezone

@anvil.server.callable
def get_countdown(end_time):
  total_seconds = abs(dt.now(timezone.utc) - end_time).total_seconds()
  #calculate days, hours, minutes, and seconds remaining
  days = total_seconds // (3600 * 24)
  hours = (total_seconds // 3600) % 24
  minutes = (total_seconds // 60) % 60
  seconds = total_seconds % 60
  countdown = '{} хоног, {:02d}:{:02d}:{:02d}'.format(int(days), int(hours), int(minutes), int(seconds))
  return countdown

Another solution (not mine) exists as a component here: Countdown Component

1 Like

Seems like a lot of server calls. does it need to be on the server?

here’s the approach for timezone aware on the client:

Client code:

from datetime import datetime, timedelta
from anvil import tz

  def __init__(self, **properties):
    # Set Form properties and Data Bindings.
    self.init_components(**properties)

    # Any code you write here will run when the form opens.
    self.timer_1_tick()

  def timer_1_tick(self, **event_args):
    """This method is called Every [interval] seconds. Does not trigger if [interval] is 0."""
    self.ending_countdown.text = get_countdown(self.end_date.text)


def get_countdown(end_time):
    local = tz.tzlocal()
    total_seconds = abs(datetime.now(local) - end_time).total_seconds()
    #calculate days, hours, minutes, and seconds remaining
    days = total_seconds // (3600 * 24)
    hours = (total_seconds // 3600) % 24
    minutes = (total_seconds // 60) % 60
    seconds = total_seconds % 60
    countdown = '{} хоног, {:02d}:{:02d}:{:02d}'.format(int(days), int(hours), int(minutes), int(seconds))
    return countdown

edit:
some work with timezones recently changed my approach

2 Likes

Using that code gives me a timezone aware issue:

TypeError: can't subtract offset-naive and offset-aware datetimes

What I will need to do is convert the data table time to a string then back to a datetime that isn’t timezone aware. That’s the only way I figured out how to do it client side. Let me see if I can do it…

ah yes - sorry i remember I had that issue when I was using timezones in the client

Edited previous post solution with timezone aware solution

timezone Naive:

from anvil import tz 

def get_countdown(end_time):
    local = tz.tzlocal()
    offset = datetime.now(local).utcoffset() #timedelta object
    now = datetime.now().replace(tzinfo=None) + offset
    end_time = end_time.replace(tzinfo=None)
    total_seconds = abs(now-end_time).total_seconds()
5 Likes

Excellent that works perfectly. It will definitely be good to have this on the client. Thanks!