7
\$\begingroup\$

This project is from the subreddit for beginner Python projects:

The goal of the project is to create a program that allows the user to choose a time and date, and then prints out a message at given intervals (such as, every 3 seconds) that tells the user how much longer there is until the selected time. If the chosen time has passed, the program lets the user know. When selecting the month, the user has the option of entering the month name or number.

Is there a simple and succinct way to remove the microseconds from being printed out? And is there a better way to set up the while loop?

import datetime
import time

# a simple program that allows the user to pick a time and the program counts down
# until that time has been reached

month_strings = ['jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec',]

# This function creates a datetime object chosen by the user and compares
# this object to the current time and counts down until that chosen time
def count_down():


    year = int(raw_input('What is the year of the date? '))
    month = raw_input('What is the month of the date? ')
    # this part allows the user to type in the month as a string or integer
    if len(month) < 3:
        month = int(month)
    else:   
        month = (month_strings.index(month[0:3].lower()) + 1)
    day = int(raw_input('What is the day of the month? '))
    hour = int(raw_input('What is the hour according to a 24 hour clock? '))
    minute = int(raw_input('At which minute? '))
    second = int(raw_input('At which second? '))

    chosen_time = datetime.datetime(year,month,day,hour,minute,second)

    print 'The time you chose is: ' + chosen_time.__str__()

    current_time = chosen_time.now()
    # If the time has passed, let the user know and exit the program
    if chosen_time < chosen_time.now():
        print 'This time has passed.'
        exit()

    else:
        # This loop prints how much time is left until the chosen time every 3 seconds
        while True:
            time_difference = (chosen_time - chosen_time.now())
            print str(time_difference) + ' until the time.'
            time.sleep(3)
            if chosen_time <= chosen_time.now():
                break
        print "It's time!"
        exit()

if __name__ == '__main__':  
    count_down()
\$\endgroup\$
  • 8
    \$\begingroup\$ Arrest him! He's trying to build a bomb! \$\endgroup\$ – Digital Trauma Sep 17 '15 at 17:34
8
\$\begingroup\$

I'll first just suggest that you read the Python style guide as it has great tips on readability.

The best way you can format time is using datetime.strftime. You can call it on a datetime object and pass a string formatted to represent each time value and it will format the data into the string format you provide. This way you can format an exact code you'd like instead of just removing milliseconds:

print (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
>>> 2015-09-17 15:43:30

Using the table here you can see all the available options if you'd like to go more specific. While I'm on the datetime, it is not clear when you call .now on your stored object. It reads awkwardly. Instead use datetime.datetime.now(). But, since that's a lot to type I'd instead suggest calling from datetime import datetime at the start, so you can then just call datetime.now().

On your second question, yes there is a better way to do the while loop. Instead of using if to check conditions and break on them, make that condition the condition of your while loop.

while chosen_time >= datetime.now():
    time_difference = (chosen_time - chosen_time.now())
    print str(time_difference) + ' until the time.'
    time.sleep(3)

The reason people usually use while True is that either the condition is not easy to make into an expression, the condition can't be evaluated when the loop is firt encountered or that they want to emulate a do while loop. But none of these is true for you, so it's not needed.

Other Notes

You almost never need to call __str__. The point of functions with two underscores on either side is that they allow on object to be passed to another function and run that way. In this case, you could use str(chosen_time). But I wouldn't use that either, instead use str.format.

print 'The time you chose is: {}'.format(chosen_time)

It replaces the {} with the parameter you pass into format. It's very useful when you have multiple parameters or need to apply particular formatting to them, so you should get used to using it.

You don't need brackets around the `time_difference calculation, so leave them out.

time_difference = chosen_time - chosen_time.now()

I agree with Ethan about the input validation, but I want to specifically note that someone could enter "12 ". That would seem too long to be an integer, but would actually require that you call int on it, which you currently don't. I suggest that you call strip on the month parameter. It will remove whitespace at the start and end of the user's input, preventing this issue.

month = raw_input('What is the month of the date? ').strip()
\$\endgroup\$
  • 1
    \$\begingroup\$ I appreciate the feedback. You inspired me to go over a lot of my old code with the style guide to make it more readable. Thank you. \$\endgroup\$ – Tad Codes Sep 20 '15 at 15:17
6
\$\begingroup\$

I'm going to give a general review, since I'm not too familiar with working with dates.


Proper input validation

Suppose one has a line, like the following, similar to some lines in your code:

foo_bar = int(raw_input("Baz Bar Foo: "))

At first glance, nothing appears to be wrong, but what happens if we copy-paste this into the command line, like this?

>>> foo_bar = int(raw_input("Baz Bar Foo: "))

And then enter something like this?

Baz Bar Foo:  blah

Nothing good:

Traceback (most recent call last):
  File "python", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'blah'

You need to be sure that you're properly validating user input, and making sure it isn't invalid. The best way to do this when parsing integers is to use a try/except clause. A simple example might look something like this:

try:
    foo_bar = int(raw_input("Baz Bar Foo: "))
    ...
except ValueError:
    print "Uh-oh!, invalid integer input!"
    ...

Now, when the user enters invalid input, like this:

Baz Bar Foo:  blah

They'll be greeted with a friendly message, like this:

Uh-oh! Invalid integer input!

Nitpicks

These two comments:

# a simple program that allows the user to pick a time and the program counts down
# until that time has been reached

...

# This function creates a datetime object chosen by the user and compares
# this object to the current time and counts down until that chosen time

Should be docstrings, not regular inline comments.

Rather than exiting your program in a not so clean way, from the count_down function, you should just replace any exit()s, like this:

exit()

With an empty return, like this:

return

This line here confused me at first:

month = (month_strings.index(month[0:3].lower()) + 1)

It almost seems like you're trying to check whether the month is valid, using the .index method, as far as I can tell. At first glance, this line is very hard to read, preferably, if you want to check if what the user has entered, you could just do something like this:

if month[0:3].lower() in month_strings:
    ...
\$\endgroup\$
  • \$\begingroup\$ An empty return is not necessary, as it is implicit at the end of the function. PEP8 recommends making it explicit (return None) it if there are other returned expressions in the same function. The first exit should be an exception instead. \$\endgroup\$ – Davidmh Sep 19 '15 at 20:37
  • \$\begingroup\$ Thanks, this gives me some more areas to work on. As a beginner, I've had trouble knowing when to use return or how try and except work. I'll be hitting google and stackoverflow hard on this one. \$\endgroup\$ – Tad Codes Sep 20 '15 at 15:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.