74 lines
2.1 KiB
Python
74 lines
2.1 KiB
Python
"""分期缴费:应缴日计算(整数月按日历递增,小数月按 30 天/月换算)"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import calendar
|
|
from decimal import Decimal, ROUND_DOWN
|
|
from typing import Union
|
|
|
|
Number = Union[int, float, Decimal, str]
|
|
|
|
DAYS_PER_MONTH = 30
|
|
FQ_ADVANCE_DAYS = 10
|
|
|
|
|
|
def add_days_yyyymmdd(date_str: str, days: int) -> str:
|
|
raw = (date_str or "").strip().replace("-", "")
|
|
if len(raw) != 8:
|
|
return ""
|
|
from datetime import date, timedelta
|
|
|
|
y = int(raw[0:4])
|
|
m = int(raw[4:6])
|
|
d = int(raw[6:8])
|
|
dt = date(y, m, d) + timedelta(days=days)
|
|
return f"{dt.year:04d}{dt.month:02d}{dt.day:02d}"
|
|
|
|
|
|
def add_months_yyyymmdd(date_str: str, months: int) -> str:
|
|
raw = (date_str or "").strip().replace("-", "")
|
|
if len(raw) != 8:
|
|
return ""
|
|
y = int(raw[0:4])
|
|
m = int(raw[4:6])
|
|
d = int(raw[6:8])
|
|
month_index = m - 1 + months
|
|
ny = y + month_index // 12
|
|
nm = month_index % 12 + 1
|
|
last_day = calendar.monthrange(ny, nm)[1]
|
|
nd = min(d, last_day)
|
|
return f"{ny:04d}{nm:02d}{nd:02d}"
|
|
|
|
|
|
def add_interval_months_yyyymmdd(date_str: str, total_months: Number) -> str:
|
|
"""整数部分按日历加月,小数部分按 30 天/月换算为天数。"""
|
|
raw = (date_str or "").strip().replace("-", "")
|
|
if len(raw) != 8:
|
|
return ""
|
|
total = Decimal(str(total_months))
|
|
if total == 0:
|
|
return raw
|
|
whole = int(total.to_integral_value(rounding=ROUND_DOWN))
|
|
frac = total - Decimal(whole)
|
|
result = raw
|
|
if whole:
|
|
result = add_months_yyyymmdd(result, whole)
|
|
if frac:
|
|
days = int(round(float(frac) * DAYS_PER_MONTH))
|
|
if days:
|
|
result = add_days_yyyymmdd(result, days)
|
|
return result
|
|
|
|
|
|
def calc_fq_installment_due_date(
|
|
start_date: str,
|
|
index: int,
|
|
interval_months: Number,
|
|
) -> str:
|
|
"""第 1 期=开始时间;第 2 期起在间隔递增基础上提前 10 天。"""
|
|
total_months = Decimal(str(index)) * Decimal(str(interval_months))
|
|
base = add_interval_months_yyyymmdd(start_date, total_months)
|
|
if index >= 1:
|
|
return add_days_yyyymmdd(base, -FQ_ADVANCE_DAYS)
|
|
return base
|