Подтвердить что ты не робот

Почему дата + timedelta - дата, а не дата?

В Python, в операции чисел смешанного типа, более узкий тип расширен больше, чем у другого, например 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)

4b9b3361

Ответ 1

Поведение задокументировано:

date2 перемещается вперед во времени, если timedelta.days > 0, или назад, если timedelta.days < 0. Затем date2 - date1 == timedelta.days. timedelta.seconds и timedelta.microseconds игнорируются.

(Мой акцент. Это поведение осталось неизменным, поскольку

2.3 >

Я не смог найти никаких доказательств того, почему модуль разработан таким образом. Конечно, есть примеры использования, такие как ваши, где вы хотите представить момент времени, соответствующий полуночи в начале дня. В этих случаях раздражает необходимость конвертировать туда и обратно. Но есть и другие варианты использования, в которых вы хотите представить целый день (а не только некоторый момент времени в этот день), и в этом случае вы не хотите, чтобы случайно закончились частичные дни, когда вы добавляете timedeltas.

Крис Уизерс предложил изменить поведение в вопрос 3249, но Тим Петерс отметил, что:

вряд ли будет принято несовместимое изменение документально подтвержденного поведения, которое всегда срабатывало.

Если вам нужен объект, который ведет себя как 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)

(Это не проверено, но отсюда не должно быть трудно заставить его работать.)

Ответ 2

Объект timedelta не хранит никакой информации о том, касается ли он только даты, а также раз. (Тот факт, что количество часов/минут/секунд/микросов равно 0, может быть просто совпадением!)

Следовательно, предположим, что у нас есть кто-то, кто просто хочет манипулировать датами, игнорируя время, она сделает что-то вроде my_new_date = my_old_date + timedelta(days=1). Она была бы очень удивлена ​​и, возможно, раздражена, обнаружив, что my_new_date теперь является объектом datetime, а не объектом date.

Ответ 3

Дата не является подклассом даты и времени; datetime - составной тип, объединяющий объект date и time в один. Вы не можете решить создать сложный тип из операций на date здесь; компонент time не является частью даты.

С другой стороны, числовая иерархия Python определяет целые числа как тип специализированного float (numbers.Integral является косвенным подклассом numbers.Real), и поскольку такие операции смешивания между целыми числами и поплавками приводят к получению базового типа. А float не является составным типом, для десятичной части значения нет отдельного типа.

Если вы хотите создать составной тип из операций с датами, вы должны быть явными (и явное лучше, чем неявное). Добавьте компонент времени самостоятельно:

datetime.combine(yourdate, time.min) + yourdelta

где yourdate можно было бы получить из анализа date.strptime('%Y %j') вашего года и входа в день года.

Альтернативы (создающие объект datetime, иногда основанный на значении timedelta.seconds или всегда) требуют от программиста развернуть компонент даты еще раз, если это все, что они ожидали.

Ответ 4

Вероятная техническая причина заключается в том, что встроенные типы в Python не возвращают подклассы из своих операторов, как правило, т.е. date.__add__ не вернет datetime. И для последовательного поведения требуется, чтобы date и datetime были взаимозаменяемыми (они не являются).

date + timedelta поведение документировано и не изменится. Если вы хотите datetime в результате; создайте datetime с даты d:

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

Технически, date.__add__ мог делегировать работу timedelta.__radd__. timedelta хранит days отдельно и поэтому просто и эффективно выяснить, представляет ли оно целое число дней, т.е. мы могли бы, если бы хотели получить date или datetime из date + timedelta (это не значит мы должны).

Проблема в том, что 1 и 1.0 в этом случае одинаковы timedelta(1), т.е. если мы должны позволить date + timedelta возвращать datetime, тогда он должен возвращать datetime для всех значений, если мы рассмотрим только типы.

Был прецедент, когда int + int возвращал либо int, либо long в зависимости от результата, т.е. операция с теми же типами может возвращать значения разных типов, зависящие только от входных значений. Хотя date и datetime не так много взаимозаменяемы, как int и long были.

date + timedelta возврат date для некоторых значений timedelta и datetime для других создаст путаницу, если мы не представим date(y,m,d) == datetime(y,m,d) тоже (например, 1 == 1.0) или date.today() < datetime.now() из связанная проблема Python, упомянутая @Gareth Rees (например, 1 < 1.1). datetime, являющийся подклассом, предлагает этот маршрут, хотя я слышал аргумент о том, что было ошибкой сделать подкласс datetime a date.

Желаемое поведение реализовано в пакете 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.0 и 1; type(timedelta(1.0)) == type(timedelta(1)) (тот же тип в дополнение к тому, чтобы быть равным).

Я пишу процедуру чтения для формата файла, где одно поле - год, одно поле - в день года, одно поле - миллисекунды - с полуночи.

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

Ответ 5

Когда вы суммируете timedelta на дату, python будет искать атрибут days timedelta. Поэтому, если добавить 42 секунды, день будет 0, и это не повлияет на вашу дату.