Source code for wombat.utilities.time
"""General methods for time-based calculations."""
from __future__ import annotations
import datetime
from dateutil.parser import parse
HOURS_IN_DAY = 24
HOURS_IN_YEAR = 8760
[docs]
def parse_date(value: str | None | datetime.datetime) -> datetime.datetime | None:
"""Thin wrapper for ``dateutil.parser.parse`` that converts string dates and returns
back None or the original value if it's None or a ``datetime.datetime`` object,
respectively.
Parameters
----------
value : str | None | datetime.datetime
A month/date or month-date formatted string to be converted to a
``datetime.datetime`` object, or ``datetime.datetime`` object, or None.
Returns
-------
datetime.datetime | None
A converted ``datetime.datetime`` object or None.
"""
if value is None:
return value
if isinstance(value, datetime.datetime):
return value
# Ensure there is a common comparison year for all datetime values
return parse(value).replace(year=2022)
[docs]
def convert_dt_to_hours(diff: datetime.timedelta) -> float:
"""Convert a ``datetime.timedelta`` object to number of hours at the seconds
resolution.
Parameters
----------
diff : datetime.timedelta
The difference between two ``datetime.datetime`` objects.
Returns
-------
float
Number of hours between to ``datetime.datetime`` objects.
"""
days = diff.days * 24 if diff.days > 0 else 0
seconds = diff.seconds / 60 / 60
return days + seconds
[docs]
def hours_until_future_hour(dt: datetime.datetime, hour: int) -> float:
"""Number of hours until a future hour in the same day for ``hour`` <= 24,
otherwise, it is the number of hours until a time in the proceeding days.
Parameters
----------
dt : datetime.datetime
Focal datetime.
hour : int
Hour that is later in the day, in 24 hour time.
Returns
-------
float
Number of hours between the two times.
"""
if hour >= 24:
days, hour = divmod(hour, 24)
# Convert to python int
days = int(days)
hour = int(hour)
new_dt = dt + datetime.timedelta(days=days)
new_dt = new_dt.replace(hour=hour, minute=0, second=0)
else:
new_dt = dt.replace(hour=hour, minute=0, second=0)
diff = new_dt - dt
return convert_dt_to_hours(diff)
[docs]
def check_working_hours(
env_start: int, env_end: int, workday_start: int, workday_end: int
) -> tuple[int, int]:
"""Checks the working hours of a port or servicing equipment, and overrides a
default (-1) to the environment's settings, otherwise returns back the input hours.
Parameters
----------
env_start : int
The starting hour for the environment's shift
env_end : int
The ending hour for the environment's shift
workday_start : int
The starting hour to be checked.
workday_end : int
The ending hour to be checked.
Returns
-------
tuple[int, int]
The starting and ending hour to be applied back to the port or servicing
equipment.
"""
start_is_invalid = workday_start == -1
end_is_invalid = workday_end == -1
start = env_start if start_is_invalid else workday_start
end = env_end if end_is_invalid else workday_end
return start, end
[docs]
def calculate_cost(
duration: int | float, rate: float, n_rate: int = 1, daily_rate: bool = False
) -> float:
"""Calculates the equipment cost, or labor cost for either salaried or hourly
employees.
Parameters
----------
duration : int | float
Length of time, in hours.
rate : float
The labor or equipment rate, in $USD/hour.
n_rate : int
Total number of of the rate to be applied, more than one if``rate`` is broken
down by number of individual laborers (if rate is a labor rate), by default 1.
daily_rate : bool, optional
Indicator for if the ``rate`` is a daily rate (True), or hourly rate (False), by
default False.
Returns
-------
float
The total cost of the labor performed.
"""
multiplier = duration / HOURS_IN_DAY if daily_rate else duration
return multiplier * rate * n_rate