Countdown Timer in Python

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
4
down vote

favorite












I am a beginner in Python and I am yet to move on from the basics to things like OOP.I wrote all of this code (Python 3) in an hour or so for a simple countdown timer.Although some parts of it are seemingly excessive,is the core of the code pythonic enough or are there better ways of structuring it?I would also like to know a better way for writing comments and variables effectively.More specifically I think the IOTimer() function could be cleaner.



#modules
import datetime
import time
#TIMER
#Enter Preferences


supported_formats = ["hrs","min","sec"]

print("n")

def get_input():
global unit_input
unit_input = input("Enter the format in which you want to time yourself::".format(supported_formats))
unit_input.lower()

#Check if the format entered is valid.
get_input()

if unit_input in supported_formats:
pass
else:
print("Invalid Input.Please re-enter you desired format.nThe input is case insenseitive")
get_input()

def countdown_timer(x):
while x >= 0 :
x -= 1
print(" remaining".format(str(datetime.timedelta(seconds=x))))
print("n")
time.sleep(1)


#Check for input and assign variable to determine the total amount of seconds
def IOTimer():
while True:
try:
if unit_input == "hrs":
hours = int(input("Enter the number of hours: "))
print("n")
minutes = int(input("Enter the number of minutes: "))
print("n")
seconds = int(input("Enter the number of seconds: "))
print("n")
break
elif unit_input == "min":
hours = 0
minutes = int(input("Enter the number of minutes: "))
print("n")
seconds = int(input("Enter the number of seconds: "))
print("n")
break
elif unit_input == "sec":
hours = 0
minutes = 0
seconds = int(input("Enter the number of seconds: "))
print("n")
break
except:
print("Invalid Input.Re-enter the values")
print("n")
IOTimer()
hours_in_sec = hours*3600
minutes_in_sec = minutes*60
total_seconds = hours_in_sec + minutes_in_sec + seconds
print("Starting countdown for ".format(str(datetime.timedelta(seconds=total_seconds))))
print("n")
time.sleep(2)
countdown_timer(total_seconds)

IOTimer()






share|improve this question



























    up vote
    4
    down vote

    favorite












    I am a beginner in Python and I am yet to move on from the basics to things like OOP.I wrote all of this code (Python 3) in an hour or so for a simple countdown timer.Although some parts of it are seemingly excessive,is the core of the code pythonic enough or are there better ways of structuring it?I would also like to know a better way for writing comments and variables effectively.More specifically I think the IOTimer() function could be cleaner.



    #modules
    import datetime
    import time
    #TIMER
    #Enter Preferences


    supported_formats = ["hrs","min","sec"]

    print("n")

    def get_input():
    global unit_input
    unit_input = input("Enter the format in which you want to time yourself::".format(supported_formats))
    unit_input.lower()

    #Check if the format entered is valid.
    get_input()

    if unit_input in supported_formats:
    pass
    else:
    print("Invalid Input.Please re-enter you desired format.nThe input is case insenseitive")
    get_input()

    def countdown_timer(x):
    while x >= 0 :
    x -= 1
    print(" remaining".format(str(datetime.timedelta(seconds=x))))
    print("n")
    time.sleep(1)


    #Check for input and assign variable to determine the total amount of seconds
    def IOTimer():
    while True:
    try:
    if unit_input == "hrs":
    hours = int(input("Enter the number of hours: "))
    print("n")
    minutes = int(input("Enter the number of minutes: "))
    print("n")
    seconds = int(input("Enter the number of seconds: "))
    print("n")
    break
    elif unit_input == "min":
    hours = 0
    minutes = int(input("Enter the number of minutes: "))
    print("n")
    seconds = int(input("Enter the number of seconds: "))
    print("n")
    break
    elif unit_input == "sec":
    hours = 0
    minutes = 0
    seconds = int(input("Enter the number of seconds: "))
    print("n")
    break
    except:
    print("Invalid Input.Re-enter the values")
    print("n")
    IOTimer()
    hours_in_sec = hours*3600
    minutes_in_sec = minutes*60
    total_seconds = hours_in_sec + minutes_in_sec + seconds
    print("Starting countdown for ".format(str(datetime.timedelta(seconds=total_seconds))))
    print("n")
    time.sleep(2)
    countdown_timer(total_seconds)

    IOTimer()






    share|improve this question























      up vote
      4
      down vote

      favorite









      up vote
      4
      down vote

      favorite











      I am a beginner in Python and I am yet to move on from the basics to things like OOP.I wrote all of this code (Python 3) in an hour or so for a simple countdown timer.Although some parts of it are seemingly excessive,is the core of the code pythonic enough or are there better ways of structuring it?I would also like to know a better way for writing comments and variables effectively.More specifically I think the IOTimer() function could be cleaner.



      #modules
      import datetime
      import time
      #TIMER
      #Enter Preferences


      supported_formats = ["hrs","min","sec"]

      print("n")

      def get_input():
      global unit_input
      unit_input = input("Enter the format in which you want to time yourself::".format(supported_formats))
      unit_input.lower()

      #Check if the format entered is valid.
      get_input()

      if unit_input in supported_formats:
      pass
      else:
      print("Invalid Input.Please re-enter you desired format.nThe input is case insenseitive")
      get_input()

      def countdown_timer(x):
      while x >= 0 :
      x -= 1
      print(" remaining".format(str(datetime.timedelta(seconds=x))))
      print("n")
      time.sleep(1)


      #Check for input and assign variable to determine the total amount of seconds
      def IOTimer():
      while True:
      try:
      if unit_input == "hrs":
      hours = int(input("Enter the number of hours: "))
      print("n")
      minutes = int(input("Enter the number of minutes: "))
      print("n")
      seconds = int(input("Enter the number of seconds: "))
      print("n")
      break
      elif unit_input == "min":
      hours = 0
      minutes = int(input("Enter the number of minutes: "))
      print("n")
      seconds = int(input("Enter the number of seconds: "))
      print("n")
      break
      elif unit_input == "sec":
      hours = 0
      minutes = 0
      seconds = int(input("Enter the number of seconds: "))
      print("n")
      break
      except:
      print("Invalid Input.Re-enter the values")
      print("n")
      IOTimer()
      hours_in_sec = hours*3600
      minutes_in_sec = minutes*60
      total_seconds = hours_in_sec + minutes_in_sec + seconds
      print("Starting countdown for ".format(str(datetime.timedelta(seconds=total_seconds))))
      print("n")
      time.sleep(2)
      countdown_timer(total_seconds)

      IOTimer()






      share|improve this question













      I am a beginner in Python and I am yet to move on from the basics to things like OOP.I wrote all of this code (Python 3) in an hour or so for a simple countdown timer.Although some parts of it are seemingly excessive,is the core of the code pythonic enough or are there better ways of structuring it?I would also like to know a better way for writing comments and variables effectively.More specifically I think the IOTimer() function could be cleaner.



      #modules
      import datetime
      import time
      #TIMER
      #Enter Preferences


      supported_formats = ["hrs","min","sec"]

      print("n")

      def get_input():
      global unit_input
      unit_input = input("Enter the format in which you want to time yourself::".format(supported_formats))
      unit_input.lower()

      #Check if the format entered is valid.
      get_input()

      if unit_input in supported_formats:
      pass
      else:
      print("Invalid Input.Please re-enter you desired format.nThe input is case insenseitive")
      get_input()

      def countdown_timer(x):
      while x >= 0 :
      x -= 1
      print(" remaining".format(str(datetime.timedelta(seconds=x))))
      print("n")
      time.sleep(1)


      #Check for input and assign variable to determine the total amount of seconds
      def IOTimer():
      while True:
      try:
      if unit_input == "hrs":
      hours = int(input("Enter the number of hours: "))
      print("n")
      minutes = int(input("Enter the number of minutes: "))
      print("n")
      seconds = int(input("Enter the number of seconds: "))
      print("n")
      break
      elif unit_input == "min":
      hours = 0
      minutes = int(input("Enter the number of minutes: "))
      print("n")
      seconds = int(input("Enter the number of seconds: "))
      print("n")
      break
      elif unit_input == "sec":
      hours = 0
      minutes = 0
      seconds = int(input("Enter the number of seconds: "))
      print("n")
      break
      except:
      print("Invalid Input.Re-enter the values")
      print("n")
      IOTimer()
      hours_in_sec = hours*3600
      minutes_in_sec = minutes*60
      total_seconds = hours_in_sec + minutes_in_sec + seconds
      print("Starting countdown for ".format(str(datetime.timedelta(seconds=total_seconds))))
      print("n")
      time.sleep(2)
      countdown_timer(total_seconds)

      IOTimer()








      share|improve this question












      share|improve this question




      share|improve this question








      edited Jul 18 at 11:57









      Mathias Ettinger

      21.7k32875




      21.7k32875









      asked Jul 18 at 11:43









      Chirag M

      211




      211




















          1 Answer
          1






          active

          oldest

          votes

















          up vote
          3
          down vote













          Before entering into the meat of the answer, let's talk about time accuracy for a bit.



          For starter, time.sleep is not extremely accurate as it can be (a little bit) longer than the requested sleeping time. And even if it was, you’re performing work inbetween the repeated sleep calls, which take time and add up. So each cycle in the while loop of countdown_timer take more than a single second. Let's evaluate how much by using the following file:



          import time
          import datetime
          import timeit


          def countdown_timer(x):
          while x >= 0 :
          x -= 1
          print(" remaining".format(str(datetime.timedelta(seconds=x))))
          print("n")
          time.sleep(1)


          if __name__ == '__main__':
          print(timeit.timeit(lambda:countdown_timer(120), number=1))


          And the output is:



          0:01:59 remaining


          0:01:58 remaining

          [Snip for readability]

          0:00:01 remaining


          0:00:00 remaining


          -1 day, 23:59:59 remaining


          121.14761522800109


          Wow… That remaining time at the end… That's unexpected. Thing is that the way your while loop is setup, you are performing x + 1 iterations; which explains the 121 seconds your countdown take as well as that last remaining time. Just change the condition to while x > 0: and you’re good. But do you see that your counter drift of about 150ms over the course of two minutes? That will be 1 second and a half after 20 minutes, between 4 and 5 seconds on a 1 hour timer and several minutes for a 1 day timer…



          You need to account for that drift. And the easiest way to do it is to maintain an ideal time to sleep up to which is 1 second in the future, and then compute the difference between the current time and this ideal time to try and sleep this exact amount of time. Luckily, this difference is easily computed using timedelta.total_seconds:



          import time
          import datetime
          import timeit


          def countdown_timer(x, now=datetime.datetime.now):
          target = now()
          one_second_later = datetime.timedelta(seconds=1)
          for remaining in range(x, 0, -1):
          target += one_second_later
          print(datetime.timedelta(seconds=remaining), 'remaining', end='r')
          time.sleep((target - now()).total_seconds())
          print('nTIMER ended')


          if __name__ == '__main__':
          print(timeit.timeit(lambda:countdown_timer(120), number=1))


          And the timing is way better:



          0:00:00 remaining
          TIMER ended
          120.00124583899742


          Just a single millisecond of drift. In fact, this drift is always compensated for and never accumulate. A run for 1200 seconds outputs:



          0:00:00 remaining
          TIMER ended
          1200.0010585399978



          Now for the rest of the code. Did you note how the calling code was under an if __name__ == '__main__': clause? You should get into this habit as it allows to more easily import and test the code you are developping. This will also hopefully force you to gather your code into functions and to make better usage of parameters and return values.



          A coarse rewrite could look like:



          import time
          import datetime


          def countdown_timer(x, now=datetime.datetime.now):
          target = now()
          one_second_later = datetime.timedelta(seconds=1)
          for remaining in range(x, 0, -1):
          target += one_second_later
          print(datetime.timedelta(seconds=remaining), 'remaining', end='r')
          time.sleep((target - now()).total_seconds())
          print('nTIMER ended')


          def get_input_format(supported_formats=('hrs', 'min', 'sec')):
          while True:
          units = input('Enter the format in which you want to time yourself : '.format(supported_formats))
          if units in supported_formats:
          return units
          print('Invalid input. Please re-enter your desired format')
          print('The input is case sensitive')


          def IOTimer(unit_input):
          while True:
          try:
          if unit_input == "hrs":
          hours = int(input("Enter the number of hours: "))
          print("n")
          minutes = int(input("Enter the number of minutes: "))
          print("n")
          seconds = int(input("Enter the number of seconds: "))
          print("n")
          break
          elif unit_input == "min":
          hours = 0
          minutes = int(input("Enter the number of minutes: "))
          print("n")
          seconds = int(input("Enter the number of seconds: "))
          print("n")
          break
          elif unit_input == "sec":
          hours = 0
          minutes = 0
          seconds = int(input("Enter the number of seconds: "))
          print("n")
          break
          except:
          print("Invalid Input. Re-enter the values")
          print("n")
          hours_in_sec = hours*3600
          minutes_in_sec = minutes*60
          total_seconds = hours_in_sec + minutes_in_sec + seconds
          print("Starting countdown for ".format(str(datetime.timedelta(seconds=total_seconds))))
          print("n")
          time.sleep(2)
          countdown_timer(total_seconds)


          if __name__ == '__main__':
          IOTimer(get_input_format())


          But there is still improvement for the IOTimer function:



          • the name looks like it is a class as per PEP8, the official Python style guide, function names should be lower_snake_case;

          • I already removed the recursive call as it is already handled by the while True: loop;

          • there is a lot of repetitions in the various if statements;

          • you should not use a bare except statement, always specify the exceptions you’re expecting, otherwise it may create hard-to-diagnose bugs;

          • I hardly see a valid reason to sleep 2 seconds before starting the timer.

          Revised version look like:



          import time
          import datetime


          def countdown_timer(x, now=datetime.datetime.now):
          target = now()
          one_second_later = datetime.timedelta(seconds=1)
          for remaining in range(x, 0, -1):
          target += one_second_later
          print(datetime.timedelta(seconds=remaining), 'remaining', end='r')
          time.sleep((target - now()).total_seconds())
          print('nTIMER ended')


          def get_input_format(supported_formats=('hrs', 'min', 'sec')):
          while True:
          units = input('Enter the format in which you want to time yourself : '.format(supported_formats))
          if units in supported_formats:
          return units
          print('Invalid input. Please re-enter your desired format')
          print('The input is case sensitive')


          def main(unit_input):
          hours = minutes = seconds = 0
          while True:
          try:
          if unit_input == 'hrs':
          hours = int(input('Enter the number of hours: '))
          if unit_input != 'sec':
          minutes = int(input('Enter the number of minutes: '))
          seconds = int(input('Enter the number of seconds: '))
          except ValueError:
          print('Invalid Input. Re-enter the values')
          else:
          break
          delay = datetime.timedelta(hours=hours, minutes=minutes, seconds=seconds)
          print('Starting countdown for '.format(delay))
          countdown_timer(int(delay.total_seconds()))


          if __name__ == '__main__':
          main(get_input_format())



          But the whole point of these two extra functions is to only gather parameters for the script from the user. Let specialized modules do the work for you. Let me introduce you to argparse:



          import time
          import datetime
          import argparse


          def countdown_timer(x, now=datetime.datetime.now):
          target = now()
          one_second_later = datetime.timedelta(seconds=1)
          for remaining in range(x, 0, -1):
          target += one_second_later
          print(datetime.timedelta(seconds=remaining), 'remaining', end='r')
          time.sleep((target - now()).total_seconds())
          print('nTIMER ended')


          def command_line_parser():
          parser = argparse.ArgumentParser(description='Simple countdown timer', conflict_handler='resolve')
          parser.add_argument('seconds', type=int, help='amount of seconds to wait for')
          parser.add_argument('-m', '--minutes', '--min', type=int, help='additional amount of minutes to wait for')
          parser.add_argument('-h', '--hours', type=int, help='additional amount of hours to wait for')
          return parser


          if __name__ == '__main__':
          args = command_line_parser().parse_args()
          delay = datetime.timedelta(**vars(args))
          print('Starting countdown for', delay)
          countdown_timer(int(delay.total_seconds()))


          Usage being:





          $ python simple_timer.py -h 1 -m 23 45





          share|improve this answer





















            Your Answer




            StackExchange.ifUsing("editor", function ()
            return StackExchange.using("mathjaxEditing", function ()
            StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
            StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
            );
            );
            , "mathjax-editing");

            StackExchange.ifUsing("editor", function ()
            StackExchange.using("externalEditor", function ()
            StackExchange.using("snippets", function ()
            StackExchange.snippets.init();
            );
            );
            , "code-snippets");

            StackExchange.ready(function()
            var channelOptions =
            tags: "".split(" "),
            id: "196"
            ;
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function()
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled)
            StackExchange.using("snippets", function()
            createEditor();
            );

            else
            createEditor();

            );

            function createEditor()
            StackExchange.prepareEditor(
            heartbeatType: 'answer',
            convertImagesToLinks: false,
            noModals: false,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: null,
            bindNavPrevention: true,
            postfix: "",
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            );



            );








             

            draft saved


            draft discarded


















            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f199743%2fcountdown-timer-in-python%23new-answer', 'question_page');

            );

            Post as a guest






























            1 Answer
            1






            active

            oldest

            votes








            1 Answer
            1






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes








            up vote
            3
            down vote













            Before entering into the meat of the answer, let's talk about time accuracy for a bit.



            For starter, time.sleep is not extremely accurate as it can be (a little bit) longer than the requested sleeping time. And even if it was, you’re performing work inbetween the repeated sleep calls, which take time and add up. So each cycle in the while loop of countdown_timer take more than a single second. Let's evaluate how much by using the following file:



            import time
            import datetime
            import timeit


            def countdown_timer(x):
            while x >= 0 :
            x -= 1
            print(" remaining".format(str(datetime.timedelta(seconds=x))))
            print("n")
            time.sleep(1)


            if __name__ == '__main__':
            print(timeit.timeit(lambda:countdown_timer(120), number=1))


            And the output is:



            0:01:59 remaining


            0:01:58 remaining

            [Snip for readability]

            0:00:01 remaining


            0:00:00 remaining


            -1 day, 23:59:59 remaining


            121.14761522800109


            Wow… That remaining time at the end… That's unexpected. Thing is that the way your while loop is setup, you are performing x + 1 iterations; which explains the 121 seconds your countdown take as well as that last remaining time. Just change the condition to while x > 0: and you’re good. But do you see that your counter drift of about 150ms over the course of two minutes? That will be 1 second and a half after 20 minutes, between 4 and 5 seconds on a 1 hour timer and several minutes for a 1 day timer…



            You need to account for that drift. And the easiest way to do it is to maintain an ideal time to sleep up to which is 1 second in the future, and then compute the difference between the current time and this ideal time to try and sleep this exact amount of time. Luckily, this difference is easily computed using timedelta.total_seconds:



            import time
            import datetime
            import timeit


            def countdown_timer(x, now=datetime.datetime.now):
            target = now()
            one_second_later = datetime.timedelta(seconds=1)
            for remaining in range(x, 0, -1):
            target += one_second_later
            print(datetime.timedelta(seconds=remaining), 'remaining', end='r')
            time.sleep((target - now()).total_seconds())
            print('nTIMER ended')


            if __name__ == '__main__':
            print(timeit.timeit(lambda:countdown_timer(120), number=1))


            And the timing is way better:



            0:00:00 remaining
            TIMER ended
            120.00124583899742


            Just a single millisecond of drift. In fact, this drift is always compensated for and never accumulate. A run for 1200 seconds outputs:



            0:00:00 remaining
            TIMER ended
            1200.0010585399978



            Now for the rest of the code. Did you note how the calling code was under an if __name__ == '__main__': clause? You should get into this habit as it allows to more easily import and test the code you are developping. This will also hopefully force you to gather your code into functions and to make better usage of parameters and return values.



            A coarse rewrite could look like:



            import time
            import datetime


            def countdown_timer(x, now=datetime.datetime.now):
            target = now()
            one_second_later = datetime.timedelta(seconds=1)
            for remaining in range(x, 0, -1):
            target += one_second_later
            print(datetime.timedelta(seconds=remaining), 'remaining', end='r')
            time.sleep((target - now()).total_seconds())
            print('nTIMER ended')


            def get_input_format(supported_formats=('hrs', 'min', 'sec')):
            while True:
            units = input('Enter the format in which you want to time yourself : '.format(supported_formats))
            if units in supported_formats:
            return units
            print('Invalid input. Please re-enter your desired format')
            print('The input is case sensitive')


            def IOTimer(unit_input):
            while True:
            try:
            if unit_input == "hrs":
            hours = int(input("Enter the number of hours: "))
            print("n")
            minutes = int(input("Enter the number of minutes: "))
            print("n")
            seconds = int(input("Enter the number of seconds: "))
            print("n")
            break
            elif unit_input == "min":
            hours = 0
            minutes = int(input("Enter the number of minutes: "))
            print("n")
            seconds = int(input("Enter the number of seconds: "))
            print("n")
            break
            elif unit_input == "sec":
            hours = 0
            minutes = 0
            seconds = int(input("Enter the number of seconds: "))
            print("n")
            break
            except:
            print("Invalid Input. Re-enter the values")
            print("n")
            hours_in_sec = hours*3600
            minutes_in_sec = minutes*60
            total_seconds = hours_in_sec + minutes_in_sec + seconds
            print("Starting countdown for ".format(str(datetime.timedelta(seconds=total_seconds))))
            print("n")
            time.sleep(2)
            countdown_timer(total_seconds)


            if __name__ == '__main__':
            IOTimer(get_input_format())


            But there is still improvement for the IOTimer function:



            • the name looks like it is a class as per PEP8, the official Python style guide, function names should be lower_snake_case;

            • I already removed the recursive call as it is already handled by the while True: loop;

            • there is a lot of repetitions in the various if statements;

            • you should not use a bare except statement, always specify the exceptions you’re expecting, otherwise it may create hard-to-diagnose bugs;

            • I hardly see a valid reason to sleep 2 seconds before starting the timer.

            Revised version look like:



            import time
            import datetime


            def countdown_timer(x, now=datetime.datetime.now):
            target = now()
            one_second_later = datetime.timedelta(seconds=1)
            for remaining in range(x, 0, -1):
            target += one_second_later
            print(datetime.timedelta(seconds=remaining), 'remaining', end='r')
            time.sleep((target - now()).total_seconds())
            print('nTIMER ended')


            def get_input_format(supported_formats=('hrs', 'min', 'sec')):
            while True:
            units = input('Enter the format in which you want to time yourself : '.format(supported_formats))
            if units in supported_formats:
            return units
            print('Invalid input. Please re-enter your desired format')
            print('The input is case sensitive')


            def main(unit_input):
            hours = minutes = seconds = 0
            while True:
            try:
            if unit_input == 'hrs':
            hours = int(input('Enter the number of hours: '))
            if unit_input != 'sec':
            minutes = int(input('Enter the number of minutes: '))
            seconds = int(input('Enter the number of seconds: '))
            except ValueError:
            print('Invalid Input. Re-enter the values')
            else:
            break
            delay = datetime.timedelta(hours=hours, minutes=minutes, seconds=seconds)
            print('Starting countdown for '.format(delay))
            countdown_timer(int(delay.total_seconds()))


            if __name__ == '__main__':
            main(get_input_format())



            But the whole point of these two extra functions is to only gather parameters for the script from the user. Let specialized modules do the work for you. Let me introduce you to argparse:



            import time
            import datetime
            import argparse


            def countdown_timer(x, now=datetime.datetime.now):
            target = now()
            one_second_later = datetime.timedelta(seconds=1)
            for remaining in range(x, 0, -1):
            target += one_second_later
            print(datetime.timedelta(seconds=remaining), 'remaining', end='r')
            time.sleep((target - now()).total_seconds())
            print('nTIMER ended')


            def command_line_parser():
            parser = argparse.ArgumentParser(description='Simple countdown timer', conflict_handler='resolve')
            parser.add_argument('seconds', type=int, help='amount of seconds to wait for')
            parser.add_argument('-m', '--minutes', '--min', type=int, help='additional amount of minutes to wait for')
            parser.add_argument('-h', '--hours', type=int, help='additional amount of hours to wait for')
            return parser


            if __name__ == '__main__':
            args = command_line_parser().parse_args()
            delay = datetime.timedelta(**vars(args))
            print('Starting countdown for', delay)
            countdown_timer(int(delay.total_seconds()))


            Usage being:





            $ python simple_timer.py -h 1 -m 23 45





            share|improve this answer

























              up vote
              3
              down vote













              Before entering into the meat of the answer, let's talk about time accuracy for a bit.



              For starter, time.sleep is not extremely accurate as it can be (a little bit) longer than the requested sleeping time. And even if it was, you’re performing work inbetween the repeated sleep calls, which take time and add up. So each cycle in the while loop of countdown_timer take more than a single second. Let's evaluate how much by using the following file:



              import time
              import datetime
              import timeit


              def countdown_timer(x):
              while x >= 0 :
              x -= 1
              print(" remaining".format(str(datetime.timedelta(seconds=x))))
              print("n")
              time.sleep(1)


              if __name__ == '__main__':
              print(timeit.timeit(lambda:countdown_timer(120), number=1))


              And the output is:



              0:01:59 remaining


              0:01:58 remaining

              [Snip for readability]

              0:00:01 remaining


              0:00:00 remaining


              -1 day, 23:59:59 remaining


              121.14761522800109


              Wow… That remaining time at the end… That's unexpected. Thing is that the way your while loop is setup, you are performing x + 1 iterations; which explains the 121 seconds your countdown take as well as that last remaining time. Just change the condition to while x > 0: and you’re good. But do you see that your counter drift of about 150ms over the course of two minutes? That will be 1 second and a half after 20 minutes, between 4 and 5 seconds on a 1 hour timer and several minutes for a 1 day timer…



              You need to account for that drift. And the easiest way to do it is to maintain an ideal time to sleep up to which is 1 second in the future, and then compute the difference between the current time and this ideal time to try and sleep this exact amount of time. Luckily, this difference is easily computed using timedelta.total_seconds:



              import time
              import datetime
              import timeit


              def countdown_timer(x, now=datetime.datetime.now):
              target = now()
              one_second_later = datetime.timedelta(seconds=1)
              for remaining in range(x, 0, -1):
              target += one_second_later
              print(datetime.timedelta(seconds=remaining), 'remaining', end='r')
              time.sleep((target - now()).total_seconds())
              print('nTIMER ended')


              if __name__ == '__main__':
              print(timeit.timeit(lambda:countdown_timer(120), number=1))


              And the timing is way better:



              0:00:00 remaining
              TIMER ended
              120.00124583899742


              Just a single millisecond of drift. In fact, this drift is always compensated for and never accumulate. A run for 1200 seconds outputs:



              0:00:00 remaining
              TIMER ended
              1200.0010585399978



              Now for the rest of the code. Did you note how the calling code was under an if __name__ == '__main__': clause? You should get into this habit as it allows to more easily import and test the code you are developping. This will also hopefully force you to gather your code into functions and to make better usage of parameters and return values.



              A coarse rewrite could look like:



              import time
              import datetime


              def countdown_timer(x, now=datetime.datetime.now):
              target = now()
              one_second_later = datetime.timedelta(seconds=1)
              for remaining in range(x, 0, -1):
              target += one_second_later
              print(datetime.timedelta(seconds=remaining), 'remaining', end='r')
              time.sleep((target - now()).total_seconds())
              print('nTIMER ended')


              def get_input_format(supported_formats=('hrs', 'min', 'sec')):
              while True:
              units = input('Enter the format in which you want to time yourself : '.format(supported_formats))
              if units in supported_formats:
              return units
              print('Invalid input. Please re-enter your desired format')
              print('The input is case sensitive')


              def IOTimer(unit_input):
              while True:
              try:
              if unit_input == "hrs":
              hours = int(input("Enter the number of hours: "))
              print("n")
              minutes = int(input("Enter the number of minutes: "))
              print("n")
              seconds = int(input("Enter the number of seconds: "))
              print("n")
              break
              elif unit_input == "min":
              hours = 0
              minutes = int(input("Enter the number of minutes: "))
              print("n")
              seconds = int(input("Enter the number of seconds: "))
              print("n")
              break
              elif unit_input == "sec":
              hours = 0
              minutes = 0
              seconds = int(input("Enter the number of seconds: "))
              print("n")
              break
              except:
              print("Invalid Input. Re-enter the values")
              print("n")
              hours_in_sec = hours*3600
              minutes_in_sec = minutes*60
              total_seconds = hours_in_sec + minutes_in_sec + seconds
              print("Starting countdown for ".format(str(datetime.timedelta(seconds=total_seconds))))
              print("n")
              time.sleep(2)
              countdown_timer(total_seconds)


              if __name__ == '__main__':
              IOTimer(get_input_format())


              But there is still improvement for the IOTimer function:



              • the name looks like it is a class as per PEP8, the official Python style guide, function names should be lower_snake_case;

              • I already removed the recursive call as it is already handled by the while True: loop;

              • there is a lot of repetitions in the various if statements;

              • you should not use a bare except statement, always specify the exceptions you’re expecting, otherwise it may create hard-to-diagnose bugs;

              • I hardly see a valid reason to sleep 2 seconds before starting the timer.

              Revised version look like:



              import time
              import datetime


              def countdown_timer(x, now=datetime.datetime.now):
              target = now()
              one_second_later = datetime.timedelta(seconds=1)
              for remaining in range(x, 0, -1):
              target += one_second_later
              print(datetime.timedelta(seconds=remaining), 'remaining', end='r')
              time.sleep((target - now()).total_seconds())
              print('nTIMER ended')


              def get_input_format(supported_formats=('hrs', 'min', 'sec')):
              while True:
              units = input('Enter the format in which you want to time yourself : '.format(supported_formats))
              if units in supported_formats:
              return units
              print('Invalid input. Please re-enter your desired format')
              print('The input is case sensitive')


              def main(unit_input):
              hours = minutes = seconds = 0
              while True:
              try:
              if unit_input == 'hrs':
              hours = int(input('Enter the number of hours: '))
              if unit_input != 'sec':
              minutes = int(input('Enter the number of minutes: '))
              seconds = int(input('Enter the number of seconds: '))
              except ValueError:
              print('Invalid Input. Re-enter the values')
              else:
              break
              delay = datetime.timedelta(hours=hours, minutes=minutes, seconds=seconds)
              print('Starting countdown for '.format(delay))
              countdown_timer(int(delay.total_seconds()))


              if __name__ == '__main__':
              main(get_input_format())



              But the whole point of these two extra functions is to only gather parameters for the script from the user. Let specialized modules do the work for you. Let me introduce you to argparse:



              import time
              import datetime
              import argparse


              def countdown_timer(x, now=datetime.datetime.now):
              target = now()
              one_second_later = datetime.timedelta(seconds=1)
              for remaining in range(x, 0, -1):
              target += one_second_later
              print(datetime.timedelta(seconds=remaining), 'remaining', end='r')
              time.sleep((target - now()).total_seconds())
              print('nTIMER ended')


              def command_line_parser():
              parser = argparse.ArgumentParser(description='Simple countdown timer', conflict_handler='resolve')
              parser.add_argument('seconds', type=int, help='amount of seconds to wait for')
              parser.add_argument('-m', '--minutes', '--min', type=int, help='additional amount of minutes to wait for')
              parser.add_argument('-h', '--hours', type=int, help='additional amount of hours to wait for')
              return parser


              if __name__ == '__main__':
              args = command_line_parser().parse_args()
              delay = datetime.timedelta(**vars(args))
              print('Starting countdown for', delay)
              countdown_timer(int(delay.total_seconds()))


              Usage being:





              $ python simple_timer.py -h 1 -m 23 45





              share|improve this answer























                up vote
                3
                down vote










                up vote
                3
                down vote









                Before entering into the meat of the answer, let's talk about time accuracy for a bit.



                For starter, time.sleep is not extremely accurate as it can be (a little bit) longer than the requested sleeping time. And even if it was, you’re performing work inbetween the repeated sleep calls, which take time and add up. So each cycle in the while loop of countdown_timer take more than a single second. Let's evaluate how much by using the following file:



                import time
                import datetime
                import timeit


                def countdown_timer(x):
                while x >= 0 :
                x -= 1
                print(" remaining".format(str(datetime.timedelta(seconds=x))))
                print("n")
                time.sleep(1)


                if __name__ == '__main__':
                print(timeit.timeit(lambda:countdown_timer(120), number=1))


                And the output is:



                0:01:59 remaining


                0:01:58 remaining

                [Snip for readability]

                0:00:01 remaining


                0:00:00 remaining


                -1 day, 23:59:59 remaining


                121.14761522800109


                Wow… That remaining time at the end… That's unexpected. Thing is that the way your while loop is setup, you are performing x + 1 iterations; which explains the 121 seconds your countdown take as well as that last remaining time. Just change the condition to while x > 0: and you’re good. But do you see that your counter drift of about 150ms over the course of two minutes? That will be 1 second and a half after 20 minutes, between 4 and 5 seconds on a 1 hour timer and several minutes for a 1 day timer…



                You need to account for that drift. And the easiest way to do it is to maintain an ideal time to sleep up to which is 1 second in the future, and then compute the difference between the current time and this ideal time to try and sleep this exact amount of time. Luckily, this difference is easily computed using timedelta.total_seconds:



                import time
                import datetime
                import timeit


                def countdown_timer(x, now=datetime.datetime.now):
                target = now()
                one_second_later = datetime.timedelta(seconds=1)
                for remaining in range(x, 0, -1):
                target += one_second_later
                print(datetime.timedelta(seconds=remaining), 'remaining', end='r')
                time.sleep((target - now()).total_seconds())
                print('nTIMER ended')


                if __name__ == '__main__':
                print(timeit.timeit(lambda:countdown_timer(120), number=1))


                And the timing is way better:



                0:00:00 remaining
                TIMER ended
                120.00124583899742


                Just a single millisecond of drift. In fact, this drift is always compensated for and never accumulate. A run for 1200 seconds outputs:



                0:00:00 remaining
                TIMER ended
                1200.0010585399978



                Now for the rest of the code. Did you note how the calling code was under an if __name__ == '__main__': clause? You should get into this habit as it allows to more easily import and test the code you are developping. This will also hopefully force you to gather your code into functions and to make better usage of parameters and return values.



                A coarse rewrite could look like:



                import time
                import datetime


                def countdown_timer(x, now=datetime.datetime.now):
                target = now()
                one_second_later = datetime.timedelta(seconds=1)
                for remaining in range(x, 0, -1):
                target += one_second_later
                print(datetime.timedelta(seconds=remaining), 'remaining', end='r')
                time.sleep((target - now()).total_seconds())
                print('nTIMER ended')


                def get_input_format(supported_formats=('hrs', 'min', 'sec')):
                while True:
                units = input('Enter the format in which you want to time yourself : '.format(supported_formats))
                if units in supported_formats:
                return units
                print('Invalid input. Please re-enter your desired format')
                print('The input is case sensitive')


                def IOTimer(unit_input):
                while True:
                try:
                if unit_input == "hrs":
                hours = int(input("Enter the number of hours: "))
                print("n")
                minutes = int(input("Enter the number of minutes: "))
                print("n")
                seconds = int(input("Enter the number of seconds: "))
                print("n")
                break
                elif unit_input == "min":
                hours = 0
                minutes = int(input("Enter the number of minutes: "))
                print("n")
                seconds = int(input("Enter the number of seconds: "))
                print("n")
                break
                elif unit_input == "sec":
                hours = 0
                minutes = 0
                seconds = int(input("Enter the number of seconds: "))
                print("n")
                break
                except:
                print("Invalid Input. Re-enter the values")
                print("n")
                hours_in_sec = hours*3600
                minutes_in_sec = minutes*60
                total_seconds = hours_in_sec + minutes_in_sec + seconds
                print("Starting countdown for ".format(str(datetime.timedelta(seconds=total_seconds))))
                print("n")
                time.sleep(2)
                countdown_timer(total_seconds)


                if __name__ == '__main__':
                IOTimer(get_input_format())


                But there is still improvement for the IOTimer function:



                • the name looks like it is a class as per PEP8, the official Python style guide, function names should be lower_snake_case;

                • I already removed the recursive call as it is already handled by the while True: loop;

                • there is a lot of repetitions in the various if statements;

                • you should not use a bare except statement, always specify the exceptions you’re expecting, otherwise it may create hard-to-diagnose bugs;

                • I hardly see a valid reason to sleep 2 seconds before starting the timer.

                Revised version look like:



                import time
                import datetime


                def countdown_timer(x, now=datetime.datetime.now):
                target = now()
                one_second_later = datetime.timedelta(seconds=1)
                for remaining in range(x, 0, -1):
                target += one_second_later
                print(datetime.timedelta(seconds=remaining), 'remaining', end='r')
                time.sleep((target - now()).total_seconds())
                print('nTIMER ended')


                def get_input_format(supported_formats=('hrs', 'min', 'sec')):
                while True:
                units = input('Enter the format in which you want to time yourself : '.format(supported_formats))
                if units in supported_formats:
                return units
                print('Invalid input. Please re-enter your desired format')
                print('The input is case sensitive')


                def main(unit_input):
                hours = minutes = seconds = 0
                while True:
                try:
                if unit_input == 'hrs':
                hours = int(input('Enter the number of hours: '))
                if unit_input != 'sec':
                minutes = int(input('Enter the number of minutes: '))
                seconds = int(input('Enter the number of seconds: '))
                except ValueError:
                print('Invalid Input. Re-enter the values')
                else:
                break
                delay = datetime.timedelta(hours=hours, minutes=minutes, seconds=seconds)
                print('Starting countdown for '.format(delay))
                countdown_timer(int(delay.total_seconds()))


                if __name__ == '__main__':
                main(get_input_format())



                But the whole point of these two extra functions is to only gather parameters for the script from the user. Let specialized modules do the work for you. Let me introduce you to argparse:



                import time
                import datetime
                import argparse


                def countdown_timer(x, now=datetime.datetime.now):
                target = now()
                one_second_later = datetime.timedelta(seconds=1)
                for remaining in range(x, 0, -1):
                target += one_second_later
                print(datetime.timedelta(seconds=remaining), 'remaining', end='r')
                time.sleep((target - now()).total_seconds())
                print('nTIMER ended')


                def command_line_parser():
                parser = argparse.ArgumentParser(description='Simple countdown timer', conflict_handler='resolve')
                parser.add_argument('seconds', type=int, help='amount of seconds to wait for')
                parser.add_argument('-m', '--minutes', '--min', type=int, help='additional amount of minutes to wait for')
                parser.add_argument('-h', '--hours', type=int, help='additional amount of hours to wait for')
                return parser


                if __name__ == '__main__':
                args = command_line_parser().parse_args()
                delay = datetime.timedelta(**vars(args))
                print('Starting countdown for', delay)
                countdown_timer(int(delay.total_seconds()))


                Usage being:





                $ python simple_timer.py -h 1 -m 23 45





                share|improve this answer













                Before entering into the meat of the answer, let's talk about time accuracy for a bit.



                For starter, time.sleep is not extremely accurate as it can be (a little bit) longer than the requested sleeping time. And even if it was, you’re performing work inbetween the repeated sleep calls, which take time and add up. So each cycle in the while loop of countdown_timer take more than a single second. Let's evaluate how much by using the following file:



                import time
                import datetime
                import timeit


                def countdown_timer(x):
                while x >= 0 :
                x -= 1
                print(" remaining".format(str(datetime.timedelta(seconds=x))))
                print("n")
                time.sleep(1)


                if __name__ == '__main__':
                print(timeit.timeit(lambda:countdown_timer(120), number=1))


                And the output is:



                0:01:59 remaining


                0:01:58 remaining

                [Snip for readability]

                0:00:01 remaining


                0:00:00 remaining


                -1 day, 23:59:59 remaining


                121.14761522800109


                Wow… That remaining time at the end… That's unexpected. Thing is that the way your while loop is setup, you are performing x + 1 iterations; which explains the 121 seconds your countdown take as well as that last remaining time. Just change the condition to while x > 0: and you’re good. But do you see that your counter drift of about 150ms over the course of two minutes? That will be 1 second and a half after 20 minutes, between 4 and 5 seconds on a 1 hour timer and several minutes for a 1 day timer…



                You need to account for that drift. And the easiest way to do it is to maintain an ideal time to sleep up to which is 1 second in the future, and then compute the difference between the current time and this ideal time to try and sleep this exact amount of time. Luckily, this difference is easily computed using timedelta.total_seconds:



                import time
                import datetime
                import timeit


                def countdown_timer(x, now=datetime.datetime.now):
                target = now()
                one_second_later = datetime.timedelta(seconds=1)
                for remaining in range(x, 0, -1):
                target += one_second_later
                print(datetime.timedelta(seconds=remaining), 'remaining', end='r')
                time.sleep((target - now()).total_seconds())
                print('nTIMER ended')


                if __name__ == '__main__':
                print(timeit.timeit(lambda:countdown_timer(120), number=1))


                And the timing is way better:



                0:00:00 remaining
                TIMER ended
                120.00124583899742


                Just a single millisecond of drift. In fact, this drift is always compensated for and never accumulate. A run for 1200 seconds outputs:



                0:00:00 remaining
                TIMER ended
                1200.0010585399978



                Now for the rest of the code. Did you note how the calling code was under an if __name__ == '__main__': clause? You should get into this habit as it allows to more easily import and test the code you are developping. This will also hopefully force you to gather your code into functions and to make better usage of parameters and return values.



                A coarse rewrite could look like:



                import time
                import datetime


                def countdown_timer(x, now=datetime.datetime.now):
                target = now()
                one_second_later = datetime.timedelta(seconds=1)
                for remaining in range(x, 0, -1):
                target += one_second_later
                print(datetime.timedelta(seconds=remaining), 'remaining', end='r')
                time.sleep((target - now()).total_seconds())
                print('nTIMER ended')


                def get_input_format(supported_formats=('hrs', 'min', 'sec')):
                while True:
                units = input('Enter the format in which you want to time yourself : '.format(supported_formats))
                if units in supported_formats:
                return units
                print('Invalid input. Please re-enter your desired format')
                print('The input is case sensitive')


                def IOTimer(unit_input):
                while True:
                try:
                if unit_input == "hrs":
                hours = int(input("Enter the number of hours: "))
                print("n")
                minutes = int(input("Enter the number of minutes: "))
                print("n")
                seconds = int(input("Enter the number of seconds: "))
                print("n")
                break
                elif unit_input == "min":
                hours = 0
                minutes = int(input("Enter the number of minutes: "))
                print("n")
                seconds = int(input("Enter the number of seconds: "))
                print("n")
                break
                elif unit_input == "sec":
                hours = 0
                minutes = 0
                seconds = int(input("Enter the number of seconds: "))
                print("n")
                break
                except:
                print("Invalid Input. Re-enter the values")
                print("n")
                hours_in_sec = hours*3600
                minutes_in_sec = minutes*60
                total_seconds = hours_in_sec + minutes_in_sec + seconds
                print("Starting countdown for ".format(str(datetime.timedelta(seconds=total_seconds))))
                print("n")
                time.sleep(2)
                countdown_timer(total_seconds)


                if __name__ == '__main__':
                IOTimer(get_input_format())


                But there is still improvement for the IOTimer function:



                • the name looks like it is a class as per PEP8, the official Python style guide, function names should be lower_snake_case;

                • I already removed the recursive call as it is already handled by the while True: loop;

                • there is a lot of repetitions in the various if statements;

                • you should not use a bare except statement, always specify the exceptions you’re expecting, otherwise it may create hard-to-diagnose bugs;

                • I hardly see a valid reason to sleep 2 seconds before starting the timer.

                Revised version look like:



                import time
                import datetime


                def countdown_timer(x, now=datetime.datetime.now):
                target = now()
                one_second_later = datetime.timedelta(seconds=1)
                for remaining in range(x, 0, -1):
                target += one_second_later
                print(datetime.timedelta(seconds=remaining), 'remaining', end='r')
                time.sleep((target - now()).total_seconds())
                print('nTIMER ended')


                def get_input_format(supported_formats=('hrs', 'min', 'sec')):
                while True:
                units = input('Enter the format in which you want to time yourself : '.format(supported_formats))
                if units in supported_formats:
                return units
                print('Invalid input. Please re-enter your desired format')
                print('The input is case sensitive')


                def main(unit_input):
                hours = minutes = seconds = 0
                while True:
                try:
                if unit_input == 'hrs':
                hours = int(input('Enter the number of hours: '))
                if unit_input != 'sec':
                minutes = int(input('Enter the number of minutes: '))
                seconds = int(input('Enter the number of seconds: '))
                except ValueError:
                print('Invalid Input. Re-enter the values')
                else:
                break
                delay = datetime.timedelta(hours=hours, minutes=minutes, seconds=seconds)
                print('Starting countdown for '.format(delay))
                countdown_timer(int(delay.total_seconds()))


                if __name__ == '__main__':
                main(get_input_format())



                But the whole point of these two extra functions is to only gather parameters for the script from the user. Let specialized modules do the work for you. Let me introduce you to argparse:



                import time
                import datetime
                import argparse


                def countdown_timer(x, now=datetime.datetime.now):
                target = now()
                one_second_later = datetime.timedelta(seconds=1)
                for remaining in range(x, 0, -1):
                target += one_second_later
                print(datetime.timedelta(seconds=remaining), 'remaining', end='r')
                time.sleep((target - now()).total_seconds())
                print('nTIMER ended')


                def command_line_parser():
                parser = argparse.ArgumentParser(description='Simple countdown timer', conflict_handler='resolve')
                parser.add_argument('seconds', type=int, help='amount of seconds to wait for')
                parser.add_argument('-m', '--minutes', '--min', type=int, help='additional amount of minutes to wait for')
                parser.add_argument('-h', '--hours', type=int, help='additional amount of hours to wait for')
                return parser


                if __name__ == '__main__':
                args = command_line_parser().parse_args()
                delay = datetime.timedelta(**vars(args))
                print('Starting countdown for', delay)
                countdown_timer(int(delay.total_seconds()))


                Usage being:





                $ python simple_timer.py -h 1 -m 23 45






                share|improve this answer













                share|improve this answer



                share|improve this answer











                answered Jul 18 at 14:19









                Mathias Ettinger

                21.7k32875




                21.7k32875






















                     

                    draft saved


                    draft discarded


























                     


                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function ()
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f199743%2fcountdown-timer-in-python%23new-answer', 'question_page');

                    );

                    Post as a guest













































































                    Popular posts from this blog

                    Python Lists

                    Aion

                    JavaScript Array Iteration Methods