Python Forum
Clock\time calculation script
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Clock\time calculation script
#1
I’m trying to write a dynamic script which takes in various hours/minutes as input data and then formats the output to Hour:Minute.

I figure to achieve this I can leverage the built-in .strftime("%H:%M")) method as well as leverage the timedelta method form the datetime module.

The end goal is to be able to add, subtract, compare, and reformat all kinds of different time input using various class methods. The unit test processes a hundred or so different assertions. But for now I am breaking down this larger task down to basic, bite-size smaller steps. For example, I’m focussing on just the first four unit test assertions (copied at the very bottom of this post). I am also not even bothering with classes or even functions (for now) and instead just focusing on the raw calculations and logic.

Here is one script I came up with:

import datetime

duration = datetime.timedelta(hours=8, minutes=0)
print(duration)
totsec = duration.total_seconds()
h = totsec//3600
m = (totsec%3600) // 60
sec = (totsec%3600)%60 #just for reference
print(f"{h}:{m}")
print(f"{int(h)}:{int(m)}")
print(str(duration)[:-3])
As you can see above, where I invoke the timedelta method, two variables are 8 hours and 0 minutes. The unit test expects this output: 08:00. When I run my script, I get this output:

Quote:$ python clock.py
8:00:00
8.0:0.0
8:0
8:00
08:00

The output here of all five print statements is wrong.

The timedelta output assigned to the duration variable shows seconds but the task I am working on expects hours and minutes to show and not seconds.

The second print statement shows the numbers as floats, which is also not what I want.

With the third print statement, when I convert the float results to integers, the significant digits are off. I need 08 to show and not just 8.

In the fourth line of output, I am getting closer to the desired end result by converting the duration to a string and then slicing off the trailing 3 characters. But the hour output needs to be 2 digits and right now it is only 1 digit.

In the final line of output, it’s formatted exactly as I need it by adding 0 to the string.

So in this one specific unit test where the two variables entered are 8 and 0, it would pass. But because I have micromanaged all the variables, it’s not dynamic. Like, in the next unit test, when 11 is entered as the hour value, the output would be: 011:00 which is not what I want. There obviously has got to be a more dynamic, flexible and Pythonic solution.

When I first encountered this exercise, I immediately went to the .strftime("%H:%M")) method but as soon as I combined it with timedelta(), I quickly learned that timedelta() doesn’t support strftime(). They just aren’t compatible. That’s what someone clarified on reddit:

Quote:Subtracting two datetimes gives you a timedelta object, not a datetime. timedelta objects are really just a number of seconds, they're not real "dates", so they can't be formatted with strftime.
Source link.

I found a Stack Overflow question on converting date time and deltas to hours and minutes which has a lot of answers that demonstrate many different ways to do this.

I found a similar SO question with answers demonstrating the same task by performing basic arithmetic operations using moduli and floor division which I leveraged for my script above.

Based on a SO question on converting seconds to string formatting, I played around some more with datetime and strftime with a fresh script:

import datetime

duration = int(datetime.timedelta(hours=8,minutes=0).total_seconds().strftime("%H:%M"))
print(duration)
...which produced this output:
Quote:› python clock_func3.py
Traceback (most recent call last):
File "/home/gnull/dev/projects/python/2018-and-2020/exercism-v3/python/clock/clock_func3.py", line 3, in <module>
duration = int(datetime.timedelta(hours=8,minutes=0).total_seconds().strftime("%H:%M"))
AttributeError: 'float' object has no attribute 'strftime'

I tried this next:
import datetime

duration = int(datetime.datetime(year=0,month=0,day=0,hour=8,minute=0).total_seconds().strftime("%H:%M"))
print(duration)
...which produces this output:
Quote:$ python clock_func3.py
Traceback (most recent call last):
File "/home/gnull/dev/projects/python/2018-and-2020/exercism-v3/python/clock/clock_func3.py", line 3, in <module>
duration = int(datetime.datetime(year=0,month=0,day=0,hour=8,minute=0).total_seconds().strftime("%H:%M"))
ValueError: year 0 is out of range

At this point I am stabbing in the dark.

What I need is a way to get:
  • “08:00” as output when 8 hours and 0 minutes are the entered input.
  • “00:00” as output when 24 hours and 0 minutes are entered
  • “01:00” as output when 25 hours and 0 minutes are entered
  • “04:00” as output when 100 hours and 0 minutes are entered

This is for non-accredited self-study online courseware. I’ve put in enough effort that I feel I am ready to see a solution, at least for these four output scenarios.
Reply
#2
Note: duration = datetime.timedelta(hours=8, minutes=0)
returns total seconds for 8 hours, minutes not needed if zero, otherwise will add to duration.

see: https://pymotw.com/3/datetime/
Reply
#3
Use datetime.timedelta for durations.
The timedelta can also be divided by another timedelta.

To get the hours as float:

from datetime import timedelta

# hours
print(timedelta(days=1) / timedelta(hours=1))

# seconds
print(timedelta(days=1) / timedelta(seconds=1))
Then you can convert seconds into minutes and hours.
divmod is helpful for this task.
It takes two arguments: numerator and denominator, and it returns quotient and reminder.


from datetime import datetime, timedelta


def duration_hours_minutes(duration: timedelta) -> str:
    duration /= timedelta(seconds=1)

    minutes, seconds = divmod(duration, 60)
    hours, minutes = divmod(minutes, 60)

    # limit to 24 hours
    # 24 hours will result in 0 hours
    hours %= 24

    return f"{hours:02.0f}:{minutes:02.0f}"


duration1 = timedelta(days=1, minutes=60, seconds=60)
duration2 = timedelta(minutes=4000, seconds=62)
duration3 = timedelta(hours=5, minutes=33)

print(duration_hours_minutes(duration1))
print(duration_hours_minutes(duration2))
print(duration_hours_minutes(duration3))
Drone4four, ibreeden, tester_V like this post
Almost dead, but too lazy to die: https://sourceserver.info
All humans together. We don't need politicians!
Reply
#4
Hi @Drone4four ,
That is an interesting problem. One would expect to be able to do something like timedeltaobject.strftime("%H:%M") but indeed I also noticed: they forgot to implement that.
Now I see Larz60+ and Dead_EyE already came up with good remarks and solutions. ( @DeaD_EyE : thanks for mentioning divmod. I wasn't aware of it's existence.) So I had to come up with something even better: a function that takes a timedelta object AND a format string! (like strftime() and strptime() Format Codes) Perhaps you find it overdone, but on the other hand: perhaps you can use it.

from datetime import timedelta, datetime

def delta2str(delta: timedelta, fstring: str = "%H:%M") -> str:
    """ Convert timedelta to formatted string. """
    secs = delta.total_seconds()
    seconds = int(secs)
    microseconds = int((secs - seconds) * 1E6)
    days, seconds = divmod(seconds, 86400)
    hours, seconds = divmod(seconds, 3600)
    minutes, seconds = divmod(seconds, 60)
    conversions = {"%d": f"{days:02d}",
                   "%H": f"{hours:02d}",
                   "%M": f"{minutes:02d}",
                   "%S": f"{seconds:02d}",
                   "%f": f"{microseconds:06d}",
                   "%%": "%"}
    result = ""
    i = 0
    while i < len(fstring):
        if fstring[i] == "%":
            sub = fstring[i:i+2]
            if sub in conversions:
                result += conversions[sub]
                i += 1
            else:
                result += fstring[i]
        else:
            result += fstring[i]
        i += 1
    return result

first = timedelta(hours=8)
second = timedelta(hours=24)
third = timedelta(hours=25)
fourth = timedelta(hours=100)
print(delta2str(first))
print(delta2str(second))
print(delta2str(third))
print(delta2str(fourth))
print()
print(delta2str(third, "%d %H:%M"))
print(delta2str(fourth, "%d days and %H:%M"))
Output:
08:00 00:00 01:00 04:00 01 01:00 04 days and 04:00
Drone4four likes this post
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Understanding venv; How do I ensure my python script uses the environment every time? Calab 1 2,158 May-10-2023, 02:13 PM
Last Post: Calab
  Module 'time' has no attribute 'clock' Sophie 4 3,032 Jan-25-2022, 08:05 PM
Last Post: Sophie
  Real-Time output of server script on a client script. throwaway34 2 2,010 Oct-03-2021, 09:37 AM
Last Post: ibreeden
  time.clock not functioning Oldman45 3 2,666 Apr-07-2021, 08:51 AM
Last Post: Oldman45
  Stumped by my own code (ratio & epoch-time calculation). MvGulik 2 2,090 Dec-30-2020, 12:04 AM
Last Post: MvGulik
  PyCharm Script Execution Time? muzikman 3 8,356 Dec-14-2020, 11:22 PM
Last Post: muzikman
  Loop independent of excecution time of a script Forelli 8 3,680 Feb-02-2020, 10:49 PM
Last Post: snippsat
  Running multiple script at the same time LoganSulpizio 1 6,926 Dec-07-2019, 04:30 PM
Last Post: Clunk_Head
  Cannot use function with timer/clock theangryprogrammer 1 3,288 Jan-22-2019, 04:22 PM
Last Post: Larz60+
  Pythonista script with iPhone's native Clock app Sharkman157 0 2,576 Sep-27-2018, 05:23 PM
Last Post: Sharkman157

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020