18.0 vanilla

This commit is contained in:
Ernad Husremovic 2025-10-03 18:06:50 +02:00
parent d72e748793
commit 0a7ae8db93
337 changed files with 399651 additions and 232598 deletions

View file

@ -1,18 +1,27 @@
# -*- coding: utf-8 -*-
import math
import calendar
import math
from datetime import date, datetime, time
from typing import Tuple
from typing import Tuple, TypeVar, Literal, Iterator, Type
import babel
import pytz
from dateutil.relativedelta import relativedelta, weekdays
from .func import lazy
from odoo.loglevels import ustr
D = TypeVar('D', date, datetime)
def date_type(value):
__all__ = [
'date_range',
'get_fiscal_year',
'get_month',
'get_quarter',
'get_quarter_number',
'get_timedelta',
]
def date_type(value: D) -> Type[D]:
''' Return either the datetime.datetime class or datetime.date type whether `value` is a datetime or a date.
:param value: A datetime.datetime or datetime.date object.
@ -21,49 +30,39 @@ def date_type(value):
return datetime if isinstance(value, datetime) else date
def get_month(date):
def get_month(date: D) -> Tuple[D, D]:
''' Compute the month dates range on which the 'date' parameter belongs to.
:param date: A datetime.datetime or datetime.date object.
:return: A tuple (date_from, date_to) having the same object type as the 'date' parameter.
'''
date_from = date_type(date)(date.year, date.month, 1)
date_to = date_type(date)(date.year, date.month, calendar.monthrange(date.year, date.month)[1])
return date_from, date_to
return date.replace(day=1), date.replace(day=calendar.monthrange(date.year, date.month)[1])
def get_quarter_number(date):
def get_quarter_number(date: date) -> int:
''' Get the number of the quarter on which the 'date' parameter belongs to.
:param date: A datetime.datetime or datetime.date object.
:return: A [1-4] integer.
'''
return math.ceil(date.month / 3)
def get_quarter(date):
def get_quarter(date: D) -> Tuple[D, D]:
''' Compute the quarter dates range on which the 'date' parameter belongs to.
:param date: A datetime.datetime or datetime.date object.
:return: A tuple (date_from, date_to) having the same object type as the 'date' parameter.
'''
quarter_number = get_quarter_number(date)
month_from = ((quarter_number - 1) * 3) + 1
date_from = date_type(date)(date.year, month_from, 1)
date_to = (date_from + relativedelta(months=2))
date_from = date.replace(month=month_from, day=1)
date_to = date_from + relativedelta(months=2)
date_to = date_to.replace(day=calendar.monthrange(date_to.year, date_to.month)[1])
return date_from, date_to
def get_fiscal_year(date, day=31, month=12):
def get_fiscal_year(date: D, day: int = 31, month: int = 12) -> Tuple[D, D]:
''' Compute the fiscal year dates range on which the 'date' parameter belongs to.
A fiscal year is the period used by governments for accounting purposes and vary between countries.
By default, calling this method with only one parameter gives the calendar year because the ending date of the
fiscal year is set to the YYYY-12-31.
:param date: A datetime.datetime or datetime.date object.
:param date: A date belonging to the fiscal year
:param day: The day of month the fiscal year ends.
:param month: The month of year the fiscal year ends.
:return: A tuple (date_from, date_to) having the same object type as the 'date' parameter.
:return: The start and end dates of the fiscal year.
'''
def fix_day(year, month, day):
@ -72,28 +71,23 @@ def get_fiscal_year(date, day=31, month=12):
return max_day
return min(day, max_day)
day = fix_day(date.year, month, day)
date_to = date_type(date)(date.year, month, day)
date_to = date.replace(month=month, day=fix_day(date.year, month, day))
if date <= date_to:
date_from = date_to - relativedelta(years=1)
day = fix_day(date_from.year, date_from.month, date_from.day)
date_from = date_type(date)(date_from.year, date_from.month, day)
date_from = date_from.replace(day=day)
date_from += relativedelta(days=1)
else:
date_from = date_to + relativedelta(days=1)
date_to = date_to + relativedelta(years=1)
day = fix_day(date_to.year, date_to.month, date_to.day)
date_to = date_type(date)(date_to.year, date_to.month, day)
date_to = date_to.replace(day=day)
return date_from, date_to
def get_timedelta(qty, granularity):
"""
Helper to get a `relativedelta` object for the given quantity and interval unit.
:param qty: the number of unit to apply on the timedelta to return
:param granularity: Type of period in string, can be year, quarter, month, week, day or hour.
def get_timedelta(qty: int, granularity: Literal['hour', 'day', 'week', 'month', 'year']):
""" Helper to get a `relativedelta` object for the given quantity and interval unit.
"""
switch = {
'hour': relativedelta(hours=qty),
@ -105,7 +99,10 @@ def get_timedelta(qty, granularity):
return switch[granularity]
def start_of(value, granularity):
Granularity = Literal['year', 'quarter', 'month', 'week', 'day', 'hour']
def start_of(value: D, granularity: Granularity) -> D:
"""
Get start of a time period from a date or a datetime.
@ -144,7 +141,7 @@ def start_of(value, granularity):
return datetime.combine(result, time.min) if is_datetime else result
def end_of(value, granularity):
def end_of(value: D, granularity: Granularity) -> D:
"""
Get end of a time period from a date or a datetime.
@ -183,7 +180,7 @@ def end_of(value, granularity):
return datetime.combine(result, time.max) if is_datetime else result
def add(value, *args, **kwargs):
def add(value: D, *args, **kwargs) -> D:
"""
Return the sum of ``value`` and a :class:`relativedelta`.
@ -195,7 +192,7 @@ def add(value, *args, **kwargs):
return value + relativedelta(*args, **kwargs)
def subtract(value, *args, **kwargs):
def subtract(value: D, *args, **kwargs) -> D:
"""
Return the difference between ``value`` and a :class:`relativedelta`.
@ -206,29 +203,16 @@ def subtract(value, *args, **kwargs):
"""
return value - relativedelta(*args, **kwargs)
def json_default(obj):
"""
Properly serializes date and datetime objects.
"""
from odoo import fields
if isinstance(obj, datetime):
return fields.Datetime.to_string(obj)
if isinstance(obj, date):
return fields.Date.to_string(obj)
if isinstance(obj, lazy):
return obj._value
return ustr(obj)
def date_range(start, end, step=relativedelta(months=1)):
def date_range(start: D, end: D, step: relativedelta = relativedelta(months=1)) -> Iterator[datetime]:
"""Date range generator with a step interval.
:param date | datetime start: beginning date of the range.
:param date | datetime end: ending date of the range.
:param relativedelta step: interval of the range.
:param start: beginning date of the range.
:param end: ending date of the range.
:param step: interval of the range.
:return: a range of datetime from start to end.
:rtype: Iterator[datetime]
"""
if isinstance(start, datetime) and isinstance(end, datetime):
are_naive = start.tzinfo is None and end.tzinfo is None
are_utc = start.tzinfo == pytz.utc and end.tzinfo == pytz.utc
@ -247,6 +231,9 @@ def date_range(start, end, step=relativedelta(months=1)):
post_process = start.tzinfo.localize if start.tzinfo else lambda dt: dt
elif isinstance(start, date) and isinstance(end, date):
# FIXME: not correctly typed, and will break if the step is a fractional
# day: `relativedelta` will return a datetime, which can't be
# compared with a `date`
dt, end_dt = start, end
post_process = lambda dt: dt