0

I have a program that is supposed to keep a running total of time spent doing something. I have a value stored, that is meant to start at "000d 00h 00m 00s" for 0 days, hours, minutes, and seconds. However, if I try to add a time to it, I receive "ValueError: time data '000d 00h 00m 00s' does not match format '%jd %Hh %Mm %Ss'".

If I change the starting string to '001d 00h 00m 00s' it will add the time no problem, but then I will have a value 24 hours greater than what is accurate. It will also function if I just remove the day counter and have it '00h 00m 00s', but it will then still reset the hours once it hits 24.

Being able to start at '000d 00h 00m 00s' would be preferable, but if that isn't possible, having the hours overflow (i.e. "25h 00m 00s') would work.

from datetime import *

EmptyTime = '000d 00h 00m 00s'
EmptyTimeThatWorks = '001d 00h 00m 00s'
ExampleTime = '174d 19h 07m 53s' # June 23 7:07 PM
FMT = "%jd %Hh %Mm %Ss"

def TaskEnded(RunningTotal, TimerStartTime):
    PresentTime = datetime.now().strftime(FMT) #PresnetTime is when the TaskEnded

    st = datetime.strptime(TimerStartTime, FMT)  #Brings things into the right format 
    pt = datetime.strptime(PresentTime, FMT)  #Brings things into the right format
    rt = datetime.strptime(RunningTotal, FMT)  #Brings things into the right format, but EmptyTime cant be
                                               # conveted to the right time because day '0' doenst exist
                                               # while hour, minute, and second 0 do

    NewTotal = rt + (pt - st)      #takes the running total and adds the timer value, which is the difference of start and end times
    NewTotal2 = datetime.strftime(NewTotal, FMT)  # Puts the Datetime value back into the right format FMT
    print(NewTotal2)
    return NewTotal2

TaskEnded(EmptyTimeThatWorks, ExampleTime)
TaskEnded(EmptyTime, ExampleTime)
TechSloth
  • 11
  • 5
  • 1
    `%j` is the day of the year in the range 001..., 366, so trying to use zero for it is invalid. – martineau Jun 24 '19 at 00:40
  • @TechSloth what time you need to calculate? Difference time from manually setup start time to the current time? – Zaraki Kenpachi Jun 24 '19 at 07:07
  • @ZarakiKenpachi Long story short its a video game usage tracker; when someone starts up a game it stores the startime, and that works fine. This part of the function then triggers when someone closes a game, takes the difference of the start and end times, then adds the difference to a running total of time spent playing a game. – TechSloth Jun 24 '19 at 20:15
  • @martineau Yeah I got that part (line 14), I'm looking for a workaround, like just a day counter so that I can start at 0 and not have an extra 24 hours on my timer. – TechSloth Jun 24 '19 at 20:16
  • @martineau Shoot you're right, had a typo at the top, should be good to go now – TechSloth Jun 24 '19 at 21:16
  • OK. Here's a couple more suggestions: 1) Use a [`datetime.timedelta`](https://docs.python.org/3/library/datetime.html#timedelta-objects) object as the timer value because it would provide a relatively easy way to do what you want. 2) Read and start following the [PEP 8 - Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) especially with respect to the naming conventions for variables, functions, etc. – martineau Jun 25 '19 at 00:29
  • @martineau Do you mean use like, the unformatted timedeltas? I used timedeltas earlier and couldnt get the timer to work even with a 001d, which is why I switched it to this format. I'm not quite sure where I would use timedelta objects now. – TechSloth Jun 25 '19 at 00:55
  • `timedelta` instance attributes are read-only, so, for example, after initially creating one with say `td = timedelta(days=0)` you would need to use `td = timedelta(days=td.days+2)` to replace it with another one containing an updated `days` attribute. – martineau Jun 25 '19 at 01:18
  • @martineau But if I want to add only 15 minutes lets say, then the days would remain 0, which I can't write, right? – TechSloth Jun 25 '19 at 01:34
  • No, `timedelta`s can be added together. Another example: Say you had one that had accumulated values equivalent to: `td = timedelta(hours=23, minutes=45)` — i.e. `23:45:00` — then did a `td = td + timedelta(minutes=15)`. The results displayed from a `print('td:', td)` afterwards would be: `td: 1 day, 0:00:00` — in other words they're fairly "smart". BTW, you can create an initial timer value with all attributes set to zero via `td = timedelta()`. Another important thing to note is if you subtract two `datetime`s, you get a `timedelta` result. Suggest you play around with them a little (more)… – martineau Jun 25 '19 at 01:58
  • @martineau Man, I don't know if I'm not understanding you or you're not understanding me here but I'm not getting it. I have to read a bit of text, a string that is "000d 00h 00m 00s" and I can't seem to convert that into a timedelta. I know I can add tds and fiddle around with them in that manner, but I can't get them to function the way I want them to, which is why I asked a question here. One that is generating a few comments (of which I am grateful) but no answers. Thank you for the help so far – TechSloth Jun 25 '19 at 20:02

1 Answers1

0

Here's what I meant about keeping the elapsed time in a timedelta and writing code that follows the PEP 8 guidelines:

from datetime import *


def task_ended(running_total, timer_start_time, fmt="%B %d, %Y %I:%M %p"):
    """ Add time elapsed between timer_start_time and now to
        running_total timedelta and return it.
    """
    present_time = datetime.now()   # When task ended.
    # Convert timer_start_time string into a datetime using fmt string.
    start_time = datetime.strptime(timer_start_time, fmt)
    # Add time elapsed between present time and timer start time to running
    # total and return it.
    return running_total + (present_time - start_time)

def format_timedelta(td):
    """ Format timedelta into custom string representation. """
    days = td.days
    hours, remainder = divmod(td.seconds, 3600)
    mins, secs = divmod(remainder, 60)
    return '{:03}d {:02}h {:02}m {:02}s'.format(days, hours, mins, secs)


running_total = timedelta()
example_start_time = 'June 25, 2019 5:00 PM'
running_total = task_ended(running_total, example_start_time)
print('running total:', format_timedelta(running_total))
print()
running_total = timedelta()
example_start_time = 'June 23, 2019 7:07 PM'
running_total = task_ended(running_total, example_start_time)
print('running total:', format_timedelta(running_total))

Here's a way to parse a running total string into a timedelta (based on a couple of the answers to the question How to construct a timedelta object from a simple string):

import re

regex = re.compile(r'^((?P<days>[\d]+?)d)? *'
                   r'((?P<hours>[\d]+?)h)? *'
                   r'((?P<minutes>[\d]+?)m)? *'
                   r'((?P<seconds>[\d]+?)s)?$')

def parse_timedelta(time_str):
    """
    Parse a time string e.g. (2h 13m) into a timedelta object.

    Modified from virhilo & Peter's answers at https://stackoverflow.com/a/4628148/355230

    :param time_str: A string identifying a duration.  (eg. 2h 13m)
    :return datetime.timedelta: A datetime.timedelta object
    """
    parts = regex.match(time_str)
    assert parts is not None, "Could not parse any time information from '{}'".format(time_str)
    time_params = {name: float(param) for name, param in parts.groupdict().items() if param}
    return timedelta(**time_params)


# Test string parser function.
test_str = '000d 21h 40m 53s'
td = parse_timedelta(test_str)
assert format_timedelta(td) == test_str

test_str = '002d 19h 33m 53s'
td = parse_timedelta(test_str)
assert format_timedelta(td) == test_str
martineau
  • 119,623
  • 25
  • 170
  • 301
  • Hi! Thanks for the answer, but I dont think this will be useful. I tried changing the format from "%B %d, %Y %I:%M %p" to "%jd %Hh %Mm %Ss" and changed `example_start_time` to "176d 19h 35m 28s". If I leave `running_total` as `timedelta()` it will return 43464 days, 17h:58:20.354514, which doesn't help much, and if I change the `running_total` to be a string in the format "000d 00h 00s 00m" it simply won't work, which is the problem I was having originally. `running_total` needs to be defined by a string in the right format – TechSloth Jun 26 '19 at 17:37
  • The default format string used by the `task_ended()` function can be changed to whatever is needed to parse the string argument `timer_start_time` that is passed to it into a `datetime`. The formatting of `timedelta` objects can be changed from the default the class provides to the way you want, as shown in the update I just made to my answer. The primary problem with your approach is you are trying to store something that is not a date-time into one. The running total _is_ a time measurement, so it makes since to store it in a `timedelta` (and avoids the problems you're encountering). – martineau Jun 26 '19 at 19:16
  • Okay, I do need to store it as a string however. Long story short this gets written to a spreadsheet, the function is supposed to read `running_total` from a cell, add the time elapsed from `timer_start_time` to now to `running_total` and then update the cell. It's my understanding that this version of the updated code cannot take strings as an input for `running_total` but does (thank you) give a string in the right format as the output, yes? – TechSloth Jun 26 '19 at 20:29
  • I think it would to be fairly easy to construct a `timedelta` value given a string in a predictable format by parsing the arguments needed to construct one — although it can't be done with `strftime()`. The `timedelta` class initializer accepts any combination of days, seconds, microseconds, milliseconds, minutes, hours, and weeks. (Internally it condenses everything down to the just equivalent days, seconds, and microseconds.) Note that if you _add_ a `timedelta` value to a `datetime` object, the result will be a get an updated `datetime` — which might be useful in your application. – martineau Jun 26 '19 at 20:50