odoo tools 源码系列 date_utils.py (一)

发布于 2020-08-28 12:29:16

前情: 代码中总是会有时间日期转换的需求, 每次都是度娘, 后者自己写函数再调用。殊不知, 官方给我们提供了常见功能的函数, odoo.tools (源码位置 odoo/odoo/tools)给我们提供了一些现成的方法,下面开始这个系列的第一次学习,看了之后发现也是翻译, 借花献佛送给大家。
目的: 主要也是为了自己能更好的学习 odoo 这个系统, 深入的了解它。

1. 导入模块

import math
import calendar
from datetime import date, datetime, time
import pytz
from dateutil.relativedelta import relativedelta
from . import ustr

ps: 大部分都是常用的内置函数, 必须掌握的, 眼生的不知道怎么用的, 速度去补起来。有些我不熟的会在相应的方法下面补充知识点。
_ustr 这个还没找到, 知道的朋友望留言告知, 提前拜谢。
_

2. 方法概览

def get_month(date):
def get_quarter_number(date):
def get_quarter(date):
def get_fiscal_year(date, day=31, month=12):
def get_timedelta(qty, granularity):
def start_of(value, granularity):
def end_of(value, granularity):
def add(value, *args, **kwargs):
def subtract(value, *args, **kwargs):
def json_default(obj):
def date_range(start, end, step=relativedelta(months=1)):
这次先看前五个, 后面的下次接着分享。

3. 方法详解

3.1 计算“date”参数所属的月份日期范围

def get_month(date):
date_from = type(date)(date.year, date.month, 1)
date_to = type(date)(date.year, date.month, calendar.monthrange(date.year, date.month)[1])
return date_from, date_to

参数: 一个 datetime.datetime 或者是 datetime.date 对象。
返回: 与“date”参数具有相同对象类型的元组(date_from,date_to)。
测试: 返回 date 或 datetime 类型的元组, 也可以转换成 str 类型的

print(get_month(date(2020, 8, 8)))  
print(str(get_month(date(2020, 8, 5))[0]))  
print(get_month(datetime(2020, 8, 8)))  
print(str(get_month(datetime(2020, 8, 5))[0]))  

# (datetime.date(2020, 8, 1), datetime.date(2020, 8, 31))
# 2020-08-01
# (datetime.datetime(2020, 8, 1, 0, 0), datetime.datetime(2020, 8, 31, 0, 0))
# 2020-08-01 00:00:00

知识点:
回顾下 type(object) 的神奇用法:

d = date(2020, 8, 1)
print(type(d)(d.year, d.month, 10)) 
l = [2]
print(type(l)(l)) 

# 2020-08-10
# [2]

# 上述的写法可以理解为类型转换或 date 数据初始化
d = date(2020, 8, 1)
a = (d.year, d.month, 10)
b = type(d)(d.year, d.month, 10)
print(type(a)) 
print(type(b))  

# <class 'tuple'>
# <class 'datetime.date'>

复习 calendar.monthrange() 用法:

# 该方法两个参数: 第一个是年份(int),第二个是月份(int)

d = date(2020, 8, 10)
print(calendar.monthrange(d.year, d.month)) 
# (5, 31) 第一个值 5 是该月的第一天是周几, 周一到周日用 0-6 表示;第二个值表示该月最后一天的日期,也相当于这个月有多少天
补充:
calendar.weekday(year, month, day)
# 回年(1970-…)、月(1-12)、日(1-31)的工作日(0-6~周一至日)。

3.2 获取“date”参数所属季度的编号
def get_quarter_number(date):

return math.ceil(date.month / 3)

参数: 一个 datetime.datetime 或者是 datetime.date 对象。
返回: 一个[1-4]整数。

测试:

d = date(2020, 4, 1)
dt = datetime(2020, 8, 5)
print(get_quarter_number(d))  
print(get_quarter_number(dt)) 

# 2
# 3

理解: 全年四个季度,每个季度是 3 个月, 就是看每个季度的这三个月属于哪个季度,如下:

常用参数:
月份季度编号---
12311/3=0 2/3=0 3/3=1
45624/3=1.X 5/3=1.X 6/3=2
78937/3=2.X 8/3=2.X 9/3=3
101112410/3=3.X 11/3=3.X 12/3=4

3.3 计算“date”参数所属的季度日期范围

def get_quarter(date):
quarter_number = get_quarter_number(date)
month_from = ((quarter_number - 1) * 3) + 1
date_from = type(date)(date.year, month_from, 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

参数: 一个 datetime.datetime 或者是 datetime.date 对象。
返回: 与“date”参数具有相同对象类型的元组(date_from,date_to)。
测试:

d = date(2020, 8, 4)
dt = datetime(2020, 12, 25)
print(get_quarter(d))
print(get_quarter(dt))

# (datetime.date(2020, 7, 1), datetime.date(2020, 9, 30))
# (datetime.datetime(2020, 10, 1, 0, 0), datetime.datetime(2020, 12, 31, 0, 0))

学习 relativedelta(object) 用法:

now = datetime.datetime.now()
print(now)  # 现在时间
print(now + relativedelta(months=2))  # 当前日期加上两个月
print(now + relativedelta(month=2))  # 替换当前日期的月份修改成2月
print(now + relativedelta(years=2))  # 当前日期加上两年
print(now + relativedelta(year=2000))  # 替换当前日期的年份修改成2000
print(now + relativedelta(weeks=2))  # 当前日期加上两周
print(relativedelta(now, datetime.datetime.now()))  # 比较两个日期时间相差多少
print(relativedelta(now, datetime.date(2019, 10, 8)))  # 比较日期时间相差多少

# 2020-08-28 11:51:18.393913
# 2020-10-28 11:51:18.393913
# 2020-02-28 11:51:18.393913
# 2022-08-28 11:51:18.393913
# 2000-08-28 11:51:18.393913
# 2020-09-11 11:51:18.393913
# relativedelta(seconds=-1, microseconds=+999805)
# relativedelta(months=+10, days=+20, hours=+11, minutes=+51, seconds=+18, microsecon

timedelta() 和 relativedelta() 区别:
timedelta()函数仅支持days和weeks参数。
而relativedelta()函数可以支持年 、月、日、周、时、分、秒的参数,功能更加强大。

3.4 计算“date”参数所属的会计年度日期范围(财政年度是政府为会计目的而使用的期间,因国家而异)

def get_fiscal_year(date, day=31, month=12):
max_day = calendar.monthrange(date.year, month)[1]
date_to = type(date)(date.year, month, min(day, max_day))

# Force at 29 February instead of 28 in case of leap year.
if date_to.month == 2 and date_to.day == 28 and max_day == 29:
    date_to = type(date)(date.year, 2, 29)

if date <= date_to:
    date_from = date_to - relativedelta(years=1)
    max_day = calendar.monthrange(date_from.year, date_from.month)[1]

    # Force at 29 February instead of 28 in case of leap year.
    if date_from.month == 2 and date_from.day == 28 and max_day == 29:
        date_from = type(date)(date_from.year, 2, 29)

    date_from += relativedelta(days=1)
else:
    date_from = date_to + relativedelta(days=1)
    max_day = calendar.monthrange(date_to.year + 1, date_to.month)[1]
    date_to = type(date)(date.year + 1, month, min(day, max_day))

    # Force at 29 February instead of 28 in case of leap year.
    if date_to.month == 2 and date_to.day == 28 and max_day == 29:
        date_to += relativedelta(days=1)
return date_from, date_to

参数:

param date: 一个 datetime.datetime 或者是 datetime.date 对象。
param day: 会计年度结束月的日。
param moth: 会计年度结束的月份。
返回: 与“date”参数具有相同对象类型的元组(date_from,date_to)。

测试:

d = date(2019, 2, 1)
dt = datetime(2019, 2, 28)
print(get_fiscal_year(d))
print(get_fiscal_year(dt))

# (datetime.date(2019, 1, 1), datetime.date(2019, 12, 31))
# (datetime.datetime(2019, 1, 1, 0, 0), datetime.datetime(2019, 12, 31, 0, 0))

3.5 获取给定数量和间隔单位的“relativedelta”对象

def get_timedelta(qty, granularity):
switch = {
    'hour': relativedelta(hours=qty),
    'day': relativedelta(days=qty),
    'week': relativedelta(weeks=qty),
    'month': relativedelta(months=qty),
    'year': relativedelta(years=qty),
}
return switch[granularity]

参数:
param qty: 要在要返回的timedelta上应用的单位数。
param granularity: 字符串中的期间类型,可以是年、季度、月、周、日或小时。
返回: 返回 relativedelta 对象
测试:

print(get_timedelta(-10, 'hour'))
print(get_timedelta(-1, 'day'))
print(get_timedelta(10, 'week'))
print(get_timedelta(5, 'month'))
print(get_timedelta(2, 'year'))

# relativedelta(hours=-10)
# relativedelta(days=-1)
# relativedelta(days=+70)
# relativedelta(months=+5)
# relativedelta(years=+2)
0 条评论

发布
问题