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

时间:2016-05-11 14:38:58

标签: python datetime

在Python中,在混合类型数的操作中,较窄的类型为widened to that of the other,例如int + floatfloat

In [57]: 3 + 0.1
Out[57]: 3.1

但对于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)

为什么扩大推理应用于数字,而不适用于date / datetime / timedelta

(背景:我正在编写一个文件格式的阅读例程,其中一个字段是年份,一个字段是一年中的一个字段,一个字段是毫秒 - 从午夜开始。当然,简单明了解决方案是datetime.datetime(2013, 1, 1, 0, 0, 0) + datetime.timedelta(seconds=42),但人们可以同样地认为应该将3 + 0.1重写为3.0 + 0.1

5 个答案:

答案 0 :(得分:7)

行为是documented

  

date2 timedelta.days > 0时向前移动,如果在timedelta.days < 0则向后移动。之后date2 - date1 == timedelta.days timedelta.secondstimedelta.microseconds会被忽略。

(我的重点。由于在Python 2.3中添加了date个对象,因此此行为有remained unchanged。)

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

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

  

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

如果你想要一个行为类似于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)

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

答案 1 :(得分:6)

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

因此,假设我们有一个人只想操纵日期,忽略时间,她会做my_new_date = my_old_date + timedelta(days=1)之类的事情。她发现my_new_date现在是datetime对象而不是date对象,我感到非常惊讶并且可能很恼火。

答案 2 :(得分:5)

日期不是datetime的子类; datetime是复合类型,将<VirtualHost *:80> DocumentRoot /var/www/html/public # ... <Directory /var/www/html/public> # ... </Directory> # ... </VirtualHost> date对象合并为一个。你不能决定从这里time的操作中产生一个复合类型; date组件不是日期的一小部分。

另一方面,Python numeric hierarchy将整数定义为一种专用浮点数(timenumbers.Integral的间接子类),因此整数和浮点数之间的混合操作结果在正在生产的基本类型中。并且numbers.Real 不是复合类型,值的小数部分没有单独的类型。

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

float

其中datetime.combine(yourdate, time.min) + yourdelta 可以通过yourdate解析您的年份以及每年的日期输入来生成。

备选方案(根据date.strptime('%Y %j')值生成datetime对象有时,或始终)需要程序员解包日期如果这是他们所期望的那样,那就再次组成。

答案 3 :(得分:3)

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

记录

date + timedelta行为并且不会发生变化。如果您希望datetime作为结果;从日期datetime创建d

dt = datetime(d.year, d.month, d.day)

从技术上讲,date.__add__可以将工作委托给timedelta.__radd__timedelta分别存储days因此,它是否简单有效地确定它是否代表整数天,即如果我们想从datedatetime获取date + timedelta {1}}(这并不意味着我们应该这样做。)

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

根据结果,int + int返回intlong时有一个先例,即具有相同类型的操作可能会返回不同类型的值,具体取决于输入值。虽然datedatetime的互换性不如intlong那么多。

date + timedelta为其他人datetimedelta返回datetime会造成混淆,除非我们也引入date(y,m,d) == datetime(y,m,d)(例如1 == 1.0 )或来自the related Python issue mentioned by @Gareth Reesdate.today() < datetime.now()(如1 < 1.1)。 datetime作为一个子类建议这条路线虽然我听到一个论点,认为datetimedate子类是错误的。

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)
  

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.1重写为3.0 + 0.1

int + float始终为float

>>> 3 + 1.0
4.0
>>> 3 + 1
4

1.01不同; type(timedelta(1.0)) == type(timedelta(1))(相同类型除外)。

  

我正在为一个文件格式编写一个阅读例程,其中一个字段是年份,一个字段是一年中的一个字段,一个字段是毫秒 - 从午夜开始。

dt = datetime(year, 1, 1) + timedelta(days=day_of_year - 1, milliseconds=ms)

答案 4 :(得分:-3)

当你将timedelta与日期相加时,python只会查找timedelta的days属性。因此,如果添加42秒,那么这一天将为0并且不会影响您的日期。