2
\$\begingroup\$

I made a program that will take in a time and a weekday, and return which parts of the world are at times closest to the one specified. Part of the program focuses on extracting a date and country out of a user input.

Here are some example inputs and outputs:

"In what part of the world is it 4:00 PM Friday right now?" (datetime.datetime(2021, 1, 1, 16, 0), 'World')
"Where in America is it 6:40 AM Thu?"                       (datetime.datetime(2020, 12, 31, 6, 40), 'America')
"It is 10:30 pm fri in which part of Asia?"                 (datetime.datetime(2021, 1, 1, 22, 30), 'Asia')
"Where would the time be 10:10 AM Sat?"                     (datetime.datetime(2021, 1, 2, 10, 10), 'World')
"Which state of brazil has the time 8:30 AM Saturday now?"  (datetime.datetime(2021, 1, 2, 8, 30), 'Brazil')

The reason that the only part of the date that needs to be given is the weekday, is because the maximum possible days in difference between a timezone and the timezone the user is currently at is one day, either ahead or behind.

So the program checks if the weekday given is at the current day, one day before the current day, or one day after the current day, and can return the rest of the date.

Here is my code:

import pytz
from datetime import datetime, timedelta
import re

def return_words(string):
    return re.findall(r'\b\S+\b', string.lower())

def string_to_datetime(string):
    weekdays = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"]
    string = string.lower()
    words = sorted(return_words(string), key=len)[::-1]
    countries = [t.split("/")[0] for t in pytz.all_timezones]

    for word in words:
        in_day = [weekday for weekday in weekdays if word in weekday]
        if len(in_day) == 1: # Make sure that the word found is a weekday name, not just something like "day"
            weekday = in_day[0]
            break
    else:
        return
    
    for word in words:
        if word.title() in countries:
            country = word.title()
            break
    else:
        country = "World" # If no country name found, use "World"
            
    date = datetime.now()
    one_day = timedelta(days=1)
    if (datetime.now() + one_day).strftime("%A").lower() == weekday:
        date += one_day
    elif (datetime.now() - one_day).strftime("%A").lower() == weekday:
        date -= one_day
    elif datetime.now().strftime("%A").lower() != weekday:
        return
    
    year, month, day = date.year, date.month, date.day
    hours_minutes = re.findall("\d+\:\d+", string)[0]
    
    half = re.findall(f"(?<={hours_minutes}).{{0,3}}[ap]m", string) # Play it safe, and only extract the am or pm that is at most 3 steps away from the hours:minutes
    if half: # If found an am or pm
        half = half[0].strip()
    else: # If not found when playing it safe, resort to finding any am or pm in the string
        half = re.findall("[ap]m", string)[0].strip()
    string = f"{year} {month} {day} {hours_minutes} {half}"
    try:
        return datetime.strptime(string, "%Y %m %d %I:%M %p"), country
    except ValueError:
        return

while True:
    print(string_to_datetime(input("Input your string >>> ")))

Can you show me how to improve the efficiency of my code? Also, any tips on tidying up would be of big help.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$
    in_day = [weekday for weekday in weekdays if word in weekday]
    if len(in_day) == 1:

seems odd. You make the entire list, put it in memory, take its length and then throw it away. Consider instead

n_matching = sum(1 for weekday in weekdays if word in weekday)
if n_matching == 1:

This loop:

for word in words:
    in_day = [weekday for weekday in weekdays if word in weekday]
    if len(in_day) == 1: # Make sure that the word found is a weekday name, not just something like "day"
        weekday = in_day[0]
        break

can be simplified if you use set operations; lower everything in words before this:

countries = {t.split("/", 1)[0].lower() for t in pytz.all_timezones}
try:
    country = next(iter(countries & set(words))).title()
except StopIteration:
    country = "World" # If no country name found, use "World"
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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