Python中如何计算日期差值

简单实现

计算两个日期差值,通常使用datetime模块即可方便实现。

简单示例如下:

ts1 = datetime.now() + timedelta(days=-2, hours=23)
ts2 = datetime.now()
ts_delta = ts1 - ts2
print(ts_delta.days)

特殊场景

但在某些特殊场景下,我们可能会需要一些特殊的计算日期差值的方法。

例如:

  • 日期差值四舍五入,满半天按一天计算,不满半天计为0
  • 日期差值向上取整,超出一天的无论多长时间均按一天计算
  • 日期差值向下取整,超出一天但不满一天的全部计为0

在如下代码中,简单试下了一下这些功能。

"""
用于计算两个日期时间(datetime对象或字符串)之间的日期差值
"""
from datetime import datetime
from typing import Union

DateTime = Union[str, datetime]
DELTA_ZERO = 0  # seconds 与microseconds均为0
DELTA_UN_ZERO = 1  # seconds 或 microseconds存在不为0的值
DAYS_POSITIVE = 1  # days为自然数
DAYS_NAGATIVE = -1  # days为负整数

def fmt(ts: str, fmt_str: str) -> datetime:
    if isinstance(ts, datetime):
        return ts
    
    if isinstance(ts, date):
        return datetime(year=date.year, month=date.month,day=date.day)

    if not fmt_str:
        fmt_str = '%Y-%m-%d %H:%M:%S'
    return datetime.strptime(ts, fmt_str)

class DateDelta(object):
    def __init__(self):
        self._ts1 = None
        self._ts2 = None
        self._ts_delta = None
        self._code_days = -2  # days的判断结果
        self._code_ts_delta = -2  # seconds 与microseconds的判断结果

    def delta(self, ts1: DateTime, ts2: DateTime, fmt_str: str = '', mode=''):
        if isinstance(ts1, str) or isinstance(ts2, str):
            ts2 = fmt(ts2, fmt_str)
            ts1 = fmt(ts1, fmt_str)
        if isinstance(ts1, datetime) and isinstance(ts2, datetime):
            self._ts1 = ts1
            self._ts2 = ts2
            self._ts_delta = ts1 - ts2
            self._code_days, self._code_ts_delta = self._cmp()
        else:
            raise ValueError(f'ts1,ts2 must be an instance of datetime or a datetime string:\n{ts1},\n{ts2}\n')
        return self

    def ceil(self) -> int:
        """
        # 向上取整,不足一天按一天计算
        # 如 days为自然数:
        #       seconds或microsecods不为0,向上取整,days+1;
        #       seconds与microsecods均为0,days取原值
        #  如 days为负整数:
        #       seconds或microsecods不为0,向上取整,days取原值;
        #       days+1seconds与microsecods均为0,days取原值
        :return:
        """

        code_days, code_ts_delta = self._code_days, self._code_ts_delta
        if code_days == DAYS_POSITIVE:
            if code_ts_delta == DELTA_ZERO:
                return self._ts_delta.days
            else:
                return self._ts_delta.days + 1
        elif code_days == DAYS_NAGATIVE:
            if code_ts_delta == DELTA_ZERO:
                return self._ts_delta.days
            else:
                return self._ts_delta.days
        else:
            raise ValueError(F'code_days illegal: {code_days}\n')

    def floor(self) -> int:
        """
        # 向下取整,不足一天计为0
        # days为自然数:
        #       seconds或microsecods不为0(即超出days但不满一天),向下取整,不足1天部分舍去,days取原值;
        #       seconds与microsecods均为0(刚好相差{days}天),days取原值
        # days为负整数:
        #       seconds或microsecods不为0,向下取整,days+1;
        #       days+1seconds与microsecods均为0,days取原值
        :return:
        """

        code_days, code_ts_delta = self._code_days, self._code_ts_delta
        if code_days == DAYS_POSITIVE:
            if code_ts_delta == DELTA_ZERO:
                return self._ts_delta.days
            else:
                return self._ts_delta.days
        elif code_days == DAYS_NAGATIVE:
            if code_ts_delta == DELTA_ZERO:
                return self._ts_delta.days
            else:
                return self._ts_delta.days + 1
        else:
            raise ValueError(F'code_days illegal: {code_days}\n')

    def round(self) -> int:
        """
        # 四舍五入,超过半天按一天计算,不足半天计为0
        # days为自然数:
        #       seconds(60*60)>=12(即超出days且满半天),四舍五入,days+1;
        #       seconds(60*60)<12(即超出days但不满半天),四舍五入,days不变;
        # days为负整数:
        #       seconds(60*60)>=12(即超出days且满半天),四舍五入,days+1;
        #       seconds(60*60)<12(即超出days但不满半天),四舍五入,days不变;
        :return:
        """

        if self._ts_delta.seconds / (60 * 60) >= 12:
            return self._ts_delta.days + 1

        elif self._ts_delta.seconds / (60 * 60) < 12:

            return self._ts_delta.days
        else:
            raise ValueError(F'unknown exception happened:\n')

    def _cmp(self):
        days = self._ts_delta.days
        seconds = self._ts_delta.seconds
        microseconds = self._ts_delta.microseconds

        if days >= 0:
            code_days = DAYS_POSITIVE
        else:
            code_days = DAYS_NAGATIVE

        if seconds or microseconds:
            # 若 ts2 + timedela(days=days) < ts1
            code_ts_delta = DELTA_UN_ZERO
        else:
            # 若 ts2 + timedela(days=days) == ts1
            code_ts_delta = DELTA_ZERO
        return code_days, code_ts_delta

最后,为了保证功能的正常可用,必要的单元测试也是必不可少。 这里我使用了pytest来编写单元测试用例。

from date.timedelta import DateDelta
import pytest
from datetime import datetime, timedelta

args = 'ts1,ts2,expect'
ts = datetime.now()
ceil_data = [
    (ts, ts, 0),
    (ts + timedelta(days=2), ts, 2),
    (ts + timedelta(days=-2), ts, -2),
    (ts + timedelta(days=2, hours=1), ts, 3),
    (ts + timedelta(days=-2, hours=1), ts, -2),
    (ts + timedelta(days=2, seconds=1), ts, 3),
    (ts + timedelta(days=-2, seconds=1), ts, -2),
    (ts + timedelta(days=2, microseconds=1), ts, 3),
    (ts + timedelta(days=-2, microseconds=1), ts, -2),
    (ts + timedelta(days=-2, microseconds=-1), ts, -3),

]

floor_data = [
    (ts, ts, 0),
    (ts + timedelta(days=2), ts, 2),
    (ts + timedelta(days=-2), ts, -2),
    (ts + timedelta(days=2, hours=1), ts, 2),
    (ts + timedelta(days=-2, hours=1), ts, -1),
    (ts + timedelta(days=2, seconds=1), ts, 2),
    (ts + timedelta(days=-2, seconds=1), ts, -1),
    (ts + timedelta(days=2, microseconds=1), ts, 2),
    (ts + timedelta(days=-2, microseconds=1), ts, -1),
    (ts + timedelta(days=-2, microseconds=-1), ts, -2),

]
round_data = [
    (ts, ts, 0),
    (ts + timedelta(days=2), ts, 2),
    (ts + timedelta(days=-2), ts, -2),
    (ts + timedelta(days=2, hours=11, minutes=59, seconds=59), ts, 2),
    (ts + timedelta(days=-2, hours=11, minutes=59, seconds=59), ts, -2),
    (ts + timedelta(days=2, hours=12), ts, 3),
    (ts + timedelta(days=-2, hours=12), ts, -1),

]

class TestCase(object):
    def setup_class(self):
        self.td = DateDelta()

    @pytest.mark.parametrize(args, ceil_data)
    def test_ceil(self, ts1, ts2, expect):
        rst = self.td.delta(ts1, ts2).ceil()
        assert rst == expect

    @pytest.mark.parametrize(args, floor_data)
    def test_floor(self, ts1, ts2, expect):
        rst = self.td.delta(ts1, ts2).floor()
        assert rst == expect

    @pytest.mark.parametrize(args, round_data)
    def test_round(self, ts1, ts2, expect):
        rst = self.td.delta(ts1, ts2).round()
        assert rst == expect

---------------------------END---------------------------

题外话

当下这个大数据时代不掌握一门编程语言怎么跟的上脚本呢?当下最火的编程语言Python前景一片光明!如果你也想跟上时代提升自己那么请看一下.

在这里插入图片描述

感兴趣的小伙伴,赠送全套Python学习资料,包含面试题、简历资料等具体看下方。

一、Python所有方向的学习路线

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。

img
img

二、Python必备开发工具

工具都帮大家整理好了,安装就可直接上手!img

三、最新Python学习笔记

当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。

img

四、Python视频合集

观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

img

五、实战案例

纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

img

六、面试宝典

在这里插入图片描述

在这里插入图片描述

简历模板在这里插入图片描述
若有侵权,请联系删除
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值