0

In python, what I am trying to do is to add/to substract one time given in a "xhxx" format to another. Some examples...

substractions :

  • "10h00" - "5h00" = "5h00"
  • "3h30" - "10h00" = "-6h30
  • "1h00" - "2h00" = "-1h00"

additions :

  • "10h00" - "5h00" = "15h00"
  • "0h30" - "1h00" = "1h30
  • "-6h00" - "2h00" = "-4h00"

I tried using timedeltas from datetime but I got issues with negative values


total_duration_worked = "xhxx"
duration_to_add_or_remove_label = "yhyy"

(ho_worked, mi_worked) = total_duration_worked_label.split("h")
worked_timedelta = timedelta(hours=int(ho_worked), minutes=int(mi_worked))

(ho, mi) = duration_to_add_or_remove_label.split("h")
to_add_or_remove_timedelta = timedelta(hours=int(ho), minutes=int(mi))

if operation == Operation.ADDITION:
    worked_timedelta += to_add_or_remove_timedelta
elif operation == Operation.SUBSTRACTION:
    worked_timedelta -= to_add_or_remove_timedelta

# Formatting
seconds = worked_timedelta.total_seconds()
min, _ = divmod(seconds, 60)
hour, min = divmod(min, 60)
total_time_worked = "%dh%02d" % (hour, min)

I am stuck to be honest, your help would be really appreciated

Fealo
  • 99
  • 1
  • 4
  • 14

2 Answers2

0

How about this:

from typing import Union


class Hour:
    MINUTES = 60

    def __init__(self, s: Union[str, int] = 0, minute: int = 0):
        if isinstance(s, str) and "h" in s:
            self.hour, self.minute = map(int, s.split("h"))
        else:
            self.hour, self.minute = int(s), minute

    def __str__(self):
        return f"{self.hour}h{self.minute:02}"

    @property
    def mins(self) -> int:
        if self.hour < 0:
            return self.hour * self.MINUTES - self.minute
        return self.hour * self.MINUTES + self.minute

    @classmethod
    def from_minutes(cls, value: str):
        if value == 0:
            return cls()
        if value > 0:
            hour = value // cls.MINUTES
        else:
            value *= -1
            hour = -(value // cls.MINUTES)
        minute = value % cls.MINUTES
        return cls(hour, minute)

    def __add__(self, other):
        return self.from_minutes(self.mins + other.mins)

    def __sub__(self, other):
        return self.from_minutes(self.mins - other.mins)


def main():
    assert str(Hour("10h00") - Hour("5h00")) == "5h00"
    print(
        Hour("3h30"),
        "-",
        Hour("10h00"),
        "=",
        Hour("3h30") - Hour("10h00"),
        '  # expected: "-6h30"',
    )
    print('"1h00" - "2h00" = "-1h00"')
    print(Hour("1h00") - Hour("2h00"))
    print(Hour("1h00") - Hour("-2h00"), "  # expected: 3h00")
    print(Hour("1h30") - Hour("-2h00"), "  # expected: 3h30")
    print(Hour("-1h00") - Hour("2h00"), "  # expected: -3h00")
    print(Hour("-1h30") - Hour("-2h00"), "  # expected: 0h30")


if __name__ == "__main__":
    main()

Output:

3h30 - 10h00 = -6h30   # expected: "-6h30"
"1h00" - "2h00" = "-1h00"
-1h00
3h00   # expected: 3h00
3h30   # expected: 3h30
-3h00   # expected: -3h00
0h30   # expected: 0h30
Waket Zheng
  • 5,065
  • 2
  • 17
  • 30
  • thank you for your answer. That's a pretty clean code :) However it doesn't seem to work if you do `Hour("-1h30") + Hour("0h30")` Output : -1h30 + 0h30 = 0h00 # expected: "-1h00" – Fealo Aug 02 '22 at 19:40
  • The new code worked as expected. – Waket Zheng Aug 02 '22 at 21:23
0

The datetime.timedelta could have a side-effect for string representation of negative value, see doc point 5. To Bypass it just define a new class with custom __add__ and __sub__ methods.

Used a boolean flag to distinguish positive from negative "values".

import datetime


class TD:
    # Time Delta arithmetics (addition/subtraction only)

    def str2td(self, tstr):
        # is_positive attribute just care about the minus symbol, hence 00h00 is considered positive
        if tstr.startswith('-'):
            self.is_positive = False
            tstr = tstr.lstrip('-')
        else:
            self.is_positive = True
        return datetime.timedelta(**dict(zip(('hours', 'minutes'), map(int, tstr.split('h')))))

    def __init__(self, tstr):
        self.tstr = tstr
        self.td = self.str2td(tstr)

    def __str__(self):
        return self.__str_format(str(self.td)) if self.is_positive else self.__str_format(f"-{self.td}")

    def __str_format(self, fstr):
        # -10:00:00 --> -10h:00
        h, m = fstr.split(':')[:2]
        return f'{h}h{m}'

    def __add__(self, td_new):
        if self.is_positive and td_new.is_positive:
            return TD(self.__str_format(str(self.td + td_new.td)))
        elif not self.is_positive and td_new.is_positive:
            if self.td == td_new.td:
                return TD('00h00')
            elif self.td > td_new.td:
                return TD(self.__str_format(f"-{str(self.td - td_new.td)}"))
            else:
                return TD(self.__str_format(str(td_new.td - self.td)))
        elif self.is_positive and not td_new.is_positive:
            return td_new + self
        else:
            return TD(self.__str_format(f"-{str(self.td + td_new.td)}"))

    def __sub__(self, td_new):
        # change sign of td_new
        td_new_opp = f'-{td_new.tstr}' if td_new.is_positive else td_new.tstr.lstrip('-')
        return self + TD(td_new_opp)

Here a test:

# auxiliary functions
def check_add(t1, t2):
    td1, td2 = TD(t1), TD(t2)
    tot = td1 + td2
    print(f'{td1} + {td2} = {str(tot)}')

def check_sub(t1, t2):
    td1, td2 = TD(t1), TD(t2)
    print(f'{td1} - {td2} = {td1 - td2}')


# tests
t1, t2 = "10h00", "5h00"
check_add(t1, t2)
t1, t2 = "-10h00", "5h00"
check_add(t1, t2)
t1, t2 = "10h00", "-5h00"
check_add(t1, t2)
t1, t2 = "-10h00", "-5h00"
check_add(t1, t2)
t1, t2 = "-10h00", "10h00"
check_add(t1, t2)
t1, t2 = "-10h00", "-5h00"
check_sub(t1, t2)
t1, t2 = "3h30", "10h00"
check_sub(t1, t2)
t1, t2 = "1h00", "2h00"
check_sub(t1, t2)
t1, t2 = "10h00", "5h00"
check_sub(t1, t2)
t1, t2 = "0h30", "1h00"
check_sub(t1, t2)
t1, t2 = "-6h00", "2h00"
check_sub(t1, t2)
t1, t2 = "-1h30", "0h30"
check_add(t1, t2)

# output
10h00 + 5h00 = 15h00
-10h00 + 5h00 = -5h00
10h00 + -5h00 = 5h00
-10h00 + -5h00 = -15h00
-10h00 + 10h00 = 0h00
-10h00 - -5h00 = -5h00
3h30 - 10h00 = -6h30
1h00 - 2h00 = -1h00
10h00 - 5h00 = 5h00
0h30 - 1h00 = -0h30
-6h00 - 2h00 = -8h00
-1h30 + 0h30 = -1h00

Remark: it can be easily extended to days, weeks, ... once the input formatting string is standardized. Then modify accordingly the self.str2td and self.__str_format methods

cards
  • 3,936
  • 1
  • 7
  • 25