为什么date + timedelta成为日期,而不是日期时间?

ger*_*rit 18 python datetime

在Python中,在混合类型的数字操作中,较窄的类型被扩展为另一个的类型,例如int+ floatfloat:

In [57]: 3 + 0.1
Out[57]: 3.1
Run Code Online (Sandbox Code Playgroud)

但是datetime.date,我们有datetime.date+ datetime.timedeltadatetime.date,而不是 datetime.datetime:

In [58]: datetime.date(2013, 1, 1) + datetime.timedelta(seconds=42)
Out[58]: datetime.date(2013, 1, 1)
Run Code Online (Sandbox Code Playgroud)

为什么拓宽推理应用于数字,而不是date/ datetime/ timedelta

(背景:我正在写一读例程,其中一个领域是一年的文件格式中,一个场中某一天的一年,场以来的毫秒数午夜当然,简单而明确的解决方案.datetime.datetime(2013, 1, 1, 0, 0, 0) + datetime.timedelta(seconds=42)但一个同样可以说一个人应该改写3 + 0.13.0 + 0.1)

Gar*_*ees 7

行为记录在案:

如果timedelta.days > 0,则向后移动date2,如果向后移动,则向前移动timedelta.days < 0.之后date2 - date1 == timedelta.days.timedelta.seconds并被timedelta.microseconds忽略.

(我的重点.由于在Python 2.3中添加了对象,因此这种行为保持不变date.)

我无法找到任何关于为什么模块设计如此的证据.当然,像你这样的用例你想要代表与一天开始的午夜相对应的时间点.在这些情况下,必须来回转换是令人讨厌的.但是还有其他一些用例需要代表一整天(而不仅仅是那天的某个时间点),在这种情况下,您不希望在添加timedeltas时意外地结束部分日期.

Chris Withers建议在第3249期更改行为,但Tim Peters指出:

对记录的始终工作的这种行为的不兼容的改变不太可能被接受.

如果你想要一个行为类似a的对象datetime.date,但算术操作返回datetime.datetime对象,那么编写一个对象应该不会太难:

from datetime import date, datetime, time, timedelta

def _part_day(t):
    """Return True if t is a timedelta object that does not consist of
    whole days.

    """
    return isinstance(t, timedelta) and (t.seconds or t.microseconds)

class mydate(date):
    """Subclass of datetime.date where arithmetic operations with a
    timedelta object return a datetime.datetime object unless the
    timedelta object consists of whole days.

    """
    def datetime(self):
        """Return datetime corresponding to the midnight at start of this
        date.

        """
        return datetime.combine(self, time())

    def __add__(self, other):
        if _part_day(other):
            return self.datetime() + other
        else:
            return super().__add__(other)

    __radd__ = __add__

    def __sub__(self, other):
        if _part_day(other):
            return self.datetime() - other
        else:
            return super().__sub__(other)
Run Code Online (Sandbox Code Playgroud)

(这是未经测试的,但从这里开始工作应该不难.)


acd*_*cdr 6

timedelta对象不存储任何关于它是否只涉及日期或时间的信息.(小时/分钟/秒/微秒数为0的事实可能只是巧合!)

因此,假设我们有一个人只想操纵约会,无视时间,她会做类似的事情my_new_date = my_old_date + timedelta(days=1).她会非常惊讶并且可能会发现它my_new_date现在是一个datetime物体而不是一个date物体.


jfs*_*jfs 6

可能的技术原因是 Python 中的内置类型通常不会从其运算符返回子类,即date.__add__不会返回datetime。并且一致的行为需要datedatetime是可以互换的(它们不是)。

date + timedelta行为已记录并且不会改变。如果你想要datetime这样的结果;创建datetime日期d

dt = datetime(d.year, d.month, d.day)
Run Code Online (Sandbox Code Playgroud)

从技术上讲,date.__add__可以将工作委托给timedelta.__radd__. timedelta单独存储days,因此可以简单有效地找出它是否代表整数天数,即,如果我们想要获取datedatetime来自date + timedelta(这并不意味着我们应该),我们就可以。

问题是1和在这种情况下1.0是相同的timedelta(1),即,如果我们允许date + timedelta返回datetime,那么如果我们只考虑类型,它应该返回datetime所有值。

int + int在返回intlong取决于结果时有一个先例,即相同类型的操作可能仅取决于输入值而返回不同类型的值。虽然datedatetime不像以前那样可以int互换long

date + timedelta返回和date的某些值会造成混乱,除非我们也引入(如)或@Gareth Rees 提到的相关 Python 问题(如)。作为一个子类建议这条路线,尽管我听到一个论点说创建子类是一个错误。timedeltadatetimedate(y,m,d) == datetime(y,m,d)1 == 1.0date.today() < datetime.now()1 < 1.1datetimedatetimedate

所需的行为在包中实现dateutil

>>> from datetime import date
>>> from dateutil.relativedelta import relativedelta
>>> date.today() + relativedelta(days=1)
datetime.date(2016, 5, 12)
>>> date.today() + relativedelta(days=1, seconds=1)
datetime.datetime(2016, 5, 12, 0, 0, 1)
Run Code Online (Sandbox Code Playgroud)

time() + timedelta无效,所以combine()在这里对我没有帮助。

combine()作品datetime.combine(d, time()) + timedelta_obj。不过,您可以将其写为:datetime(d.year, d.month, d.day) + timedelta_obj

当然,简单而明确的解决方案是datetime(2013, 1, 1, 0, 0, 0) + timedelta(seconds=42),但也可以重写3 + 0.13.0 + 0.1)

int+float始终是float

>>> 3 + 1.0
4.0
>>> 3 + 1
4
Run Code Online (Sandbox Code Playgroud)

1.0与 和不同1type(timedelta(1.0)) == type(timedelta(1))(除了相等之外,类型相同)。

我正在为一种文件格式编写一个读取例程,其中一个字段是年份,一个字段是一年中的某一天,一个字段是自午夜以来的毫秒数。

dt = datetime(year, 1, 1) + timedelta(days=day_of_year - 1, milliseconds=ms)
Run Code Online (Sandbox Code Playgroud)


Mar*_*ers 5

日期不是datetime的子类; datetime是一种复合类型,将a date和一个time对象合二为一.你不能决定从date这里的操作中产生复合类型; 该time组件不是日期的一小部分.

另一方面,Python数字层次结构将整数定义为专用float类型(numbers.Integral是其间接子类numbers.Real),因此整数和浮点数之间的混合操作会导致生成基类型.并且float化合物类型,存在的值的小数部分没有单独的类型.

如果要从日期操作生成复合类型,则必须是显式的(并且显式优于隐式).自己添加时间组件:

datetime.combine(yourdate, time.min) + yourdelta
Run Code Online (Sandbox Code Playgroud)

其中,yourdate可以从生产date.strptime('%Y %j')的这一年加一天的最年输入的解析.

替代方案(有时基于值生成datetime对象,或者总是)需要程序员再次打开日期组件,如果这是他们期望的那样.timedelta.seconds