# coding: utf-8
from __future__ import annotations
import datetime
import time
from typing import cast, Any, Tuple
from ooo.dyn.util.date_time import DateTime as UnoDateTime
from ooo.dyn.util.date import Date as UnoDate
from ooo.dyn.util.time import Time as UnoTime
from ooodev.loader import lo as mLo
from ooodev.meta.static_meta import classproperty
from ooodev.io.log.logging import info
[docs]class DateUtil:
"""Date and time utilities"""
# see also:Talk - Benjamin "Zags" Zagorsky: Handling Timezones in Python
# https://www.youtube.com/watch?v=XZlPXLsSU2U&t=1283s
[docs] @staticmethod
def time_stamp(tz: datetime.timezone | None = None) -> str:
"""
Gets a time stamp string
Args:
tz (timezone | None, optional): TimeZone
Returns:
str: Formatted timestamp such as ``2022-06-19 17:12:38``
"""
dt = datetime.datetime.now(tz) if tz is not None else datetime.datetime.now()
return dt.strftime("%Y-%m-%d %H:%M:%S")
# region --------------- convert methods ---------------------------
[docs] @staticmethod
def date_from_number(value: int | float) -> datetime.datetime:
"""
Converts a float value to corresponding datetime instance.
Args:
value (Number): number to convert to date and time
Raises:
TypeError: If value is not correct type.
Returns:
datetime: Date time instance on success; Otherwise, None
"""
if not isinstance(value, (int, float)):
raise TypeError(f"Incorrect type. Expected int or float got {type(value).__name__}")
delta = datetime.timedelta(days=value)
null_date = cast(datetime.datetime, mLo.Lo.null_date)
return null_date + delta
[docs] @staticmethod
def date_to_number(date: datetime.datetime | datetime.date) -> float:
"""
Converts a date or datetime instance to a corresponding float value.
Args:
date (datetime | date): date or date time to convert to float
Raises:
TypeError: If date is not a correct type.
Returns:
float: date as float on success; Otherwise, None
"""
null_date = cast(datetime.datetime, mLo.Lo.null_date)
if isinstance(date, datetime.datetime):
delta = date - null_date
elif isinstance(date, datetime.date):
delta = date - null_date.date() # pylint: disable=no-member
else:
raise TypeError(f"Incorrect type. Expected 'date' or 'datetime' got {type(date).__name__}")
return delta.days + delta.seconds / (24.0 * 60 * 60)
[docs] @staticmethod
def time_from_number(value: int | float) -> datetime.time | None:
"""
Converts a float value to corresponding time instance.
Only Hours, Minutes and seconds are are used in conversion.
Args:
value (Number): Number such a float or int to convert
Raises:
TypeError: If value is not correct type.
Returns:
time | None: Value as time on success; Otherwise, None
"""
if not isinstance(value, (int, float)):
raise TypeError(f"Incorrect type. Expected int or float got {type(value).__name__}")
delta = datetime.timedelta(days=value)
minutes, second = divmod(delta.seconds, 60)
hour, minute = divmod(minutes, 60)
return datetime.time(hour, minute, second, tzinfo=datetime.timezone.utc)
[docs] @staticmethod
def time_to_number(time: datetime.time) -> float:
"""
Converts a time instance to a corresponding float value.
Only Hours, Minutes and seconds are are used in conversion.
Args:
time (datetime.time): time to convert
Raises:
TypeError: If date is not a correct type.
Returns:
float: time as float on success; Otherwise, None
"""
if not isinstance(time, datetime.time):
raise TypeError(f"Incorrect type. Expected 'Number' got {type(time).__name__}")
return ((time.second / 60.0 + time.minute) / 60.0 + time.hour) / 24.0
[docs] @staticmethod
def date_time_str(dt: datetime.datetime) -> str:
"""
Returns a formatted date and time as string.
|lo_safe|
Args:
dt (datetime): date time
Returns:
str: formatted date string such as ``Jun 05, 2022 20:15``
"""
return dt.strftime("%b %d, %Y %H:%M")
[docs] @staticmethod
def uno_dt_to_dt(uno_dt: UnoDateTime) -> datetime.datetime:
"""
Converts a uno DateTime struct to a datetime instance.
|lo_safe|
Args:
uno_dt (UnoDateTime): uno Datetime struct
Returns:
datetime.datetime: Python DateTime
"""
if uno_dt.Year <= 0 or uno_dt.Month <= 0 or uno_dt.Day <= 0:
return mLo.Lo.null_date
return datetime.datetime(
year=uno_dt.Year,
month=uno_dt.Month,
day=uno_dt.Day,
hour=uno_dt.Hours,
minute=uno_dt.Minutes,
second=uno_dt.Seconds,
microsecond=0 if uno_dt.NanoSeconds == 0 else int(uno_dt.NanoSeconds / 1000),
tzinfo=datetime.timezone.utc if uno_dt.IsUTC else None,
)
[docs] @staticmethod
def uno_date_to_date(uno_date: UnoDate) -> datetime.datetime:
"""
Converts a uno Date struct to a datetime instance
Args:
uno_date (UnoDate): uno Date struct
Returns:
datetime.datetime: Python DateTime
"""
if uno_date.Year <= 0 or uno_date.Month <= 0 or uno_date.Day <= 0:
return mLo.Lo.null_date
return datetime.datetime(
year=uno_date.Year, month=uno_date.Month, day=uno_date.Day, hour=0, minute=0, second=0, microsecond=0
)
[docs] @staticmethod
def uno_time_to_date_time(uno_time: UnoTime) -> datetime.datetime:
"""
Converts a uno Time struct to a datetime instance
Args:
uno_time (UnoTime): uno Time struct
Returns:
datetime.datetime: Python DateTime
"""
# pylint: disable=no-member
null_date = mLo.Lo.null_date
dt = datetime.datetime(
year=null_date.year,
month=null_date.month,
day=null_date.day,
hour=uno_time.Hours,
minute=uno_time.Minutes,
second=uno_time.Seconds,
microsecond=0 if uno_time.NanoSeconds == 0 else int(uno_time.NanoSeconds / 1000),
tzinfo=datetime.timezone.utc if uno_time.IsUTC else None,
)
return dt
[docs] @staticmethod
def uno_time_to_time(uno_time: UnoTime) -> datetime.time:
"""
Converts a uno Time struct to a time instance
Args:
uno_time (UnoTime): uno Time struct
Returns:
datetime.time: Python Time
"""
tm = datetime.time(
hour=uno_time.Hours,
minute=uno_time.Minutes,
second=uno_time.Seconds,
microsecond=0 if uno_time.NanoSeconds == 0 else int(uno_time.NanoSeconds / 1000),
tzinfo=datetime.timezone.utc if uno_time.IsUTC else None,
)
return tm
[docs] @classmethod
def time_to_uno_time(cls, time: datetime.time) -> UnoTime:
"""
Converts a python time to UNO Time struct instance
Args:
time (UnoTime): Python time
Returns:
Time: UNO Time struct
"""
dt = cls.date_to_uno_date_time(time)
return UnoTime(
dt.NanoSeconds,
dt.Seconds,
dt.Minutes,
dt.Hours,
dt.IsUTC,
)
[docs] @staticmethod
def date_to_uno_date_time(date: Any) -> UnoDateTime:
"""
Converts a date representation into the com.sun.star.util.DateTime date format
Acceptable boundaries: ``year >= 1900`` and ``<= 32767``
Args:
date (Any): ``datetime.datetime``, ``datetime.date``, ``datetime.time``, ``float`` (time.time) or ``time.struct_time``
Returns:
DateTime: A ``com.sun.star.util.DateTime`` if conversion was successful; Otherwise, ``date``
"""
uno_date = UnoDateTime()
uno_date.Year = 1899
uno_date.Month = 12
uno_date.Day = 30
uno_date.Hours = 0
uno_date.Minutes = 0
uno_date.Seconds = 0
uno_date.NanoSeconds = 0
uno_date.IsUTC = False
if isinstance(date, float):
date = time.localtime(date)
if isinstance(date, time.struct_time):
if 1900 <= date[0] <= 32767:
(
uno_date.Year,
uno_date.Month,
uno_date.Day,
uno_date.Hours,
uno_date.Minutes,
uno_date.Seconds,
) = date[0:6]
else: # Copy only the time related part
uno_date.Hours, uno_date.Minutes, uno_date.Seconds = cast(Tuple[int, int, int], date[3:3])
elif isinstance(date, (datetime.datetime, datetime.date, datetime.time)):
if isinstance(date, (datetime.datetime, datetime.date)):
if 1900 <= date.year <= 32767:
uno_date.Year, uno_date.Month, uno_date.Day = (
date.year,
date.month,
date.day,
)
if isinstance(date, (datetime.datetime, datetime.time)):
(
uno_date.Hours,
uno_date.Minutes,
uno_date.Seconds,
uno_date.NanoSeconds,
) = (date.hour, date.minute, date.second, date.microsecond * 1000)
else:
return date # Not recognized as a date
return uno_date
[docs] @classmethod
def date_to_uno_date(cls, date: Any) -> UnoDate:
"""
Converts a date representation into the com.sun.star.util.DateTime date format
Acceptable boundaries: ``year >= 1900`` and ``<= 32767``
Args:
date (Any): ``datetime.datetime``, ``datetime.date``, ``datetime.time``, ``float`` (time.time) or ``time.struct_time``
Returns:
Date: A ``com.sun.star.util.Time`` if conversion was successful; Otherwise, ``date``
"""
try:
dt = cls.date_to_uno_date_time(date)
return UnoDate(dt.Day, dt.Month, dt.Year)
except Exception:
return date
[docs] @classmethod
def str_date_time(cls, uno_dt: UnoDateTime) -> str:
"""
Returns a formatted date and time as string.
|lo_safe|
Args:
uno_dt (datetime): date time
Returns:
str: formatted date string such as ``Jun 05, 2022 20:15``
or empty string if ``uno_dt`` is null.
"""
dt = cls.uno_dt_to_dt(uno_dt)
return "" if dt == mLo.Lo.null_date else cls.date_time_str(dt)
[docs] @classmethod
def date(cls, year: int, month: int, day: int) -> datetime.date:
"""Get date from year, month, day
Args:
year (int): Year
month (int): Month
day (int): Day
Returns:
datetime.date: date object
See Also:
`See Python date <https://docs.python.org/3/library/datetime.html#date-objects>`__
"""
d = datetime.date(year, month, day)
return d
[docs] @classmethod
def time(cls, hours: int, minutes: int, seconds: int) -> datetime.time:
"""Get time from hour, minutes, seconds
Args:
hours (int): Hours
minutes (int): Minutes
seconds (int): Seconds
Returns:
datetime.time: Time object
"""
t = datetime.time(hours, minutes, seconds)
return t
[docs] @classmethod
def datetime(cls, year: int, month: int, day: int, hours: int, minutes: int, seconds: int) -> datetime.datetime:
"""Get datetime from year, month, day, hours, minutes and seconds
Args:
year (int): Year
month (int): Month
day (int): Day
hours (int): Hours
minutes (int): Minutes
seconds (int): Seconds
Returns:
datetime.datetime: Datetime object
"""
dt = datetime.datetime(year, month, day, hours, minutes, seconds)
return dt
[docs] @classmethod
def calc_to_date(cls, value: float):
"""
Get date from calc value
Args:
value (float): Float value from cell
Returns:
datetime.date: Date object, the current local date.
See Also:
`See Python fromordinal <https://docs.python.org/3/library/datetime.html#datetime.datetime.fromordinal>`__
"""
from ooodev.utils.info import Info
d = datetime.date.fromordinal(int(value) + Info.date_offset)
return d
[docs] @classmethod
def start(cls):
"""Start counter"""
cls._start = cls.now
info("Start: ", cls._start)
return
[docs] @classmethod
def end(cls, get_seconds: bool = True):
"""End counter
Args:
get_seconds (bool): If return value in total seconds.
Returns:
timedelta | int: Return the timedelta or total seconds.
"""
e = cls.now
td = e - cls._start
result = str(td)
if get_seconds:
result = td.total_seconds()
info("End: ", e)
return result
@classproperty
def now(cls) -> datetime.datetime:
"""
Current local date and time
Returns:
datetime: Return the current local date and time, remove microseconds.
"""
return datetime.datetime.now().replace(microsecond=0)
@classproperty
def now_time(cls) -> datetime.time:
"""Current local time
Returns:
datetime.time: Return the current local time
"""
return cls.now.time()
@classproperty
def today(cls) -> datetime.date:
"""Current local date
Returns:
datetime.date: Return the current local date
"""
return datetime.date.today()
@classproperty
def epoch(cls):
"""Get unix time
Returns:
int: Unix time.
See Also:
`See Unix Time <https://en.wikipedia.org/wiki/Unix_time>`__
"""
n = cls.now
e = int(time.mktime(n.timetuple()))
return e
# endregion ------------ convert methods ---------------------------