0
\$\begingroup\$

This is simple program to calculate working hours in given shift.. It is also return user sessions and presence of user shift.

while this works fine. I would like suggestion to improve it.

from datetime import datetime, timedelta
user_attendence = [{"id": 1, "punch": 1, "date": "22-04-2022 02:10:00"},
                   {"id": 1, "punch": 1, "date": "22-04-2022 03:00:00"},

                   {"id": 1, "punch": 1, "date": "22-04-2022 04:00:00"},
                   {"id": 1, "punch": 1, "date": "22-04-2022 05:00:00"},

                   {"id": 1, "punch": 1, "date": "22-04-2022 06:00:00"},
                   {"id": 1, "punch": 0, "date": "22-04-2022 08:00:10"},

                   {"id": 2, "punch": 1, "date": "22-04-2022 04:00:00"},
                   {"id": 2, "punch": 1, "date": "22-04-2022 05:00:00"},

                   ]
time_format = '%H:%M:%S'
shift = ["22-04-2022 02:00:00", "22-04-2022 09:00:00"]


def getDays(obj):
    return (datetime.strptime(obj[1], '%d-%m-%Y %H:%M:%S') - datetime.strptime(obj[0], '%d-%m-%Y %H:%M:%S')).days


def set_timeformat(self, formatstr=time_format):
    self.time_format = formatstr


def converTime(obj):
    return str(datetime.strptime(obj, '%d-%m-%Y %H:%M:%S').time())


def calculate(shift1, user_attendence, indate, id):
    """"
        timeList -> 02:00 AM, 08:00:AM
        date -> 22-04-2022
    """
    man_in = False
    man_out = False
    sessions = []
    is_present = False
    total_hours = datetime.strptime("00:00:00", '%H:%M:%S')
    in_time = "null"

    attendance = [i for i in user_attendence if shift1[0] <= i["date"] <= shift1[1] and i.get("id").__eq__(id)]
    if [i for i in attendance if i.get("punch").__eq__(1)]:
        is_present = True

    if is_present:
        for k in attendance:
            if k["punch"].__eq__(1):
                if not man_in:
                    in_time = converTime(k["date"])
                    man_in = True
                    man_out = False
                else:
                    dif = datetime.strptime(converTime(k["date"]), time_format) - datetime.strptime(in_time, time_format)
                    in_time = converTime(k["date"])
                    total_hours += dif
                    man_out = False
                    sessions.append(convert(dif.seconds))

            if k["punch"].__eq__(0):
                if not man_out:
                    man_in = False
                    man_out = True
                    if in_time != "null":
                        dif1 = (datetime.strptime(converTime(k["date"]), time_format) - datetime.strptime(in_time, time_format))
                        total_hours += dif1
                        sessions.append(convert(dif1.seconds))
        return is_present, total_hours.time().__str__(), sessions
    else:
        return is_present


def convert(seconds):
    seconds = seconds % (24 * 3600)
    hour = seconds // 3600
    seconds %= 3600
    minutes = seconds // 60
    seconds %= 60

    return "%d:%02d:%02d" % (hour, minutes, seconds)


t = calculate(shift, user_attendence, "22-04-2022", 1)
print(t)

output: (True, '05:00:10', ['1:00:00', '1:00:00', '1:00:00', '2:00:10'])

\$\endgroup\$
3
  • \$\begingroup\$ Where does the user_attendance list actually come from? Is this a placeholder literal for what will eventually be some kind of user interface, or does the user interface exist already? Is it retrieved from a file? \$\endgroup\$
    – Reinderien
    Apr 11, 2022 at 12:28
  • \$\begingroup\$ For the sample input data you've provided, your code does not output what you've shown. \$\endgroup\$
    – Reinderien
    Apr 11, 2022 at 15:20
  • \$\begingroup\$ user_attendace will be retrieved form file. \$\endgroup\$
    – 1258259
    Apr 12, 2022 at 8:05

1 Answer 1

1
\$\begingroup\$

There's no evidence that your dates and datetimes should ever be represented as strings. Use real date, datetime and timedelta; and don't call into strptime or strftime.

getDays, set_timeformat, convert and converTime can all go away.

user_attendance should not be a global, and its members should have at least a simple object representation instead of being untyped dictionaries. NamedTuple is well-suited.

time_format can go away.

shift should be a tuple instead of a list for immutability, should be datetimes instead of strings, and should not be global.

set_timeformat seems like it was meant to be a class method due to self, but it isn't - so including self makes no sense.

Add PEP484 type hints.

shift1 should at least be a tuple of two datetimes that gets unpacked to a shift start and shift end variable; but more likely it should just be accepted as two separate parameters.

Rather than

shift1[0] <= i["date"] <= shift1[1]

which is a closed interval, consider instead a half-open interval:

shift1[0] <= i["date"] < shift1[1]

This has more useful properties when it comes to algorithmic design, and is usually what the Python built-ins do.

Don't i.get("punch").__eq__(1); just i.punch, which itself should be represented as a boolean.

You should probably return the same number of arguments regardless of whether is_present is True or not. But beyond that: why not just unconditionally return total_hours (which should be a timedelta) and sessions, which will be zero and empty if is_present is False?

Suggested

This still needs improvement, but is a start covering some of the above:

from datetime import datetime, timedelta
from pprint import pprint
from typing import NamedTuple, Sequence


class UserAttendance(NamedTuple):
    id: int
    punch: bool
    date: datetime


def calculate(
    shift1: tuple[datetime, datetime],
    user_attendence: Sequence[UserAttendance],
    id: int,
) -> tuple[
    timedelta,        # total hours present
    list[timedelta],  # session durations
]:
    man_in = False
    man_out = False
    sessions = []
    total_hours = timedelta()
    in_time = None

    shift_start, shift_end = shift1

    attendance = [
        i for i in user_attendence
        if shift_start <= i.date < shift_end
        and i.id == id
    ]
    is_present = any(i.punch for i in attendance)

    if is_present:
        for k in attendance:
            if k.punch:
                if man_in:
                    dif = k.date - in_time
                    in_time = k.date
                    total_hours += dif
                    man_out = False
                    sessions.append(dif)
                else:
                    in_time = k.date
                    man_in = True
                    man_out = False
            elif not man_out:
                man_in = False
                man_out = True
                if in_time is not None:
                    dif1 = k.date - in_time
                    total_hours += dif1
                    sessions.append(dif1)
    return total_hours, sessions


def main() -> None:
    user_attendence = [
        UserAttendance(**kwargs)
        for kwargs in (
            {"id": 1, "punch":  True, "date": datetime(2022, 4, 22, 2, 10, 0)},
            {"id": 1, "punch":  True, "date": datetime(2022, 4, 22, 3, 0,  0)},

            {"id": 1, "punch":  True, "date": datetime(2022, 4, 22, 4, 0,  0)},
            {"id": 1, "punch":  True, "date": datetime(2022, 4, 22, 5, 0,  0)},

            {"id": 1, "punch":  True, "date": datetime(2022, 4, 22, 6, 0,  0)},
            {"id": 1, "punch": False, "date": datetime(2022, 4, 22, 8, 0, 10)},

            {"id": 2, "punch":  True, "date": datetime(2022, 4, 22, 4, 0,  0)},
            {"id": 2, "punch":  True, "date": datetime(2022, 4, 22, 5, 0,  0)},
        )
    ]

    shift = (
        datetime(2022, 4, 22, 2, 0, 0),
        datetime(2022, 4, 22, 9, 0, 0),
    )
    total_hours, sessions = calculate(shift, user_attendence, id=1)
    print(total_hours)
    pprint(sessions)


if __name__ == '__main__':
    main()

Output

5:50:10
[datetime.timedelta(seconds=3000),
 datetime.timedelta(seconds=3600),
 datetime.timedelta(seconds=3600),
 datetime.timedelta(seconds=3600),
 datetime.timedelta(seconds=7210)]
\$\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.