Как иметь дело с “частичными” датами (2010-00-00) от MySQL в Django?

В одном из моих проектов Django, которые используют MySQL в качестве базы данных, у меня должны быть поля даты, которые принимают также "частичные" даты только как год (YYYY) и год и месяц (YYYY-MM) плюс нормальная дата (YYYY-MM-DD).

Поле даты в MySQL может иметь дело с этим путем принятия 00 в течение месяца и дня. Так 2010-00-00 допустимо в MySQL, и он представляет 2010. То же самое для 2010-05-00, которые представляют май 2010.

Таким образом, я начал создавать a PartialDateField поддерживать эту функцию. Но я врезался в стену, потому что, по умолчанию, и Django используют значение по умолчанию, MySQLdb, драйвер Python к MySQL, возвращают a datetime.date объект для поля даты И datetime.date() поддерживайте только реальную дату. Таким образом, возможно изменить преобразователь для поля даты, используемого MySQLdb и возвратить только строку в этом формате 'YYYY-MM-DD'. К сожалению, использование преобразователя MySQLdb установлено на уровне соединения, таким образом, это - использование для всех полей даты MySQL. Но Django DateField полагайтесь на то, что возврат базы данных a datetime.date объект, поэтому если я изменяю преобразователь для возврата строки, Django, не счастлив вообще.

У кого-то есть идея или совет решить эту проблему? Как создать a PartialDateField в Django?

Править

Также я должен добавить, что уже думал о 2 решениях, создайте 3 целочисленных поля в течение года, месяца и дня (как упоминание Alison R.) или используйте varchar поле для хранения даты как строки в этом формате YYYY-MM-DD.

Но в обоих решениях, если я не ошибаюсь, я буду освобождать специальные свойства поля даты как выполнение запроса этого вида на них: Получите все записи после этой даты. Я могу, вероятно, повторно реализовать эту функциональность на стороне клиента, но это не будет допустимым решением в моем случае, потому что база данных может быть запросом от других систем (mysql клиент, Доступ MS, и т.д.)

8
задан Etienne 4 June 2010 в 13:26
поделиться

4 ответа

Во-первых, спасибо за все ваши ответы. Ни один из них, как есть, не был хорошим решением для моей проблемы, но, для вашей защиты, я должен добавить, что я не указал все требования. Но каждый из них помог мне задуматься над моей проблемой, и некоторые из ваших идей вошли в мое окончательное решение.

Итак, мое окончательное решение на стороне БД заключается в использовании поля varchar (ограниченного 10 символами) и хранении в нем даты в виде строки в формате ISO (YYYY-MM-DD) с 00 для месяца и дня, когда нет месяца и/или дня (как поле date в MySQL). Таким образом, это поле может работать с любыми базами данных, данные могут быть прочитаны, поняты и отредактированы непосредственно и легко человеком с помощью простого клиента (например, mysql client, phpmyadmin и т.д.). Это было одним из требований. Также данные могут быть экспортированы в Excel/CSV без каких-либо преобразований и т.д. Недостатком является то, что формат не является принудительным (кроме как в Django). Кто-то может написать 'not a date' или сделать ошибку в формате, и БД примет это (если у вас есть идеи по поводу этой проблемы...).

Таким образом можно также относительно легко делать все специальные запросы поля дата. Для запросов с WHERE: <, >, <=, >= и = работают напрямую. Запросы IN и BETWEEN также работают напрямую. Для запроса по дню или месяцу достаточно сделать это с помощью EXTRACT (DAY|MONTH ...). Упорядочивание также работает напрямую. Таким образом, я думаю, что это покрывает все потребности в запросах и в основном без каких-либо сложностей.

На стороне Django я сделал две вещи. Во-первых, я создал объект PartialDate, который выглядит в основном как datetime.date, но поддерживает дату без месяца и/или дня. Внутри этого объекта я использую объект datetime.datetime для хранения даты. Я использую часы и минуты как флаг, который определяет, действительны ли месяц и день, если они установлены в 1. Это та же идея, которую предлагает steveha, но с другой реализацией (и только на стороне клиента). Использование объекта datetime.datetime дает мне много приятных возможностей для работы с датами (валидация, сравнение и т.д.).

Во-вторых, я создал PartialDateField, который в основном занимается преобразованием между объектом PartialDate и базой данных.

Пока что он работает довольно хорошо (я в основном закончил свои обширные модульные тесты).

6
ответ дан 5 December 2019 в 14:00
поделиться

Можно сохранить частичную дату в виде целого числа (предпочтительно в поле, названном для части даты, которую вы храните, например год, месяц или день) и выполнить проверку и преобразование в объект даты в модели.

EDIT

Если вам нужна функция реальной даты, вам, вероятно, понадобятся реальные, а не частичные даты. Например, даты возвращения «получить все после 2010-0-0», включая 2010 год, или только даты в 2011 году и далее? То же самое касается и другого вашего примера мая 2010 года. Способы, которыми разные языки / клиенты имеют дело с частичными датами (если они вообще поддерживают их), вероятно, будут очень своеобразными, и они вряд ли будут соответствовать реализации MySQL.

С другой стороны, если вы храните целое число year, такое как 2010, легко запросить в базе данных «все записи с годом > 2010» и точно понять, каким должен быть результат, от любого клиента, на любой платформе.Вы даже можете комбинировать этот подход для более сложных дат / запросов, таких как «все записи с годом > 2010 года И месяцем > 5».

ВТОРОЕ РЕДАКТИРОВАНИЕ

Ваш единственный другой (и, возможно, лучший) вариант - хранить действительно действительные даты и придумывать соглашение в вашем приложении о том, что они означают. Поле DATETIME с именем date_month может иметь значение 2010-05-01, но вы будете рассматривать его как представляющее все даты в мае 2010 года. Вам нужно будет учитывать это при программировании. Если бы в Python в качестве объекта datetime был date_month, вам нужно было бы вызвать функцию типа date_month.end_of_month() для запроса дат после этого месяца. (Это псевдокод, но может быть легко реализован с помощью чего-то вроде модуля calendar.)

2
ответ дан 5 December 2019 в 14:00
поделиться

Можете ли вы хранить дату вместе с флагом, который говорит, какая часть даты действительна?

Что-то вроде этого:

YEAR_VALID = 0x04
MONTH_VALID = 0x02
DAY_VALID = 0x01

Y_VALID = YEAR_VALID
YM_VALID = YEAR_VALID | MONTH_VALID
YMD_VALID = YEAR_VALID | MONTH_VALID | DAY_VALID

Тогда, если у вас есть дата 2010-00-00, преобразуйте ее в 2010-01-01 и установите флаг Y_VALID. Если у вас есть дата 2010-06-00, преобразуйте ее в 2010-06-01 и установите флаг YM_VALID.

Итак, PartialDateField будет классом, который объединяет дату и флаг date-valid, описанный выше.

P.S. На самом деле вам не обязательно использовать флаги так, как я показал; это старый программист на языке Си во мне выходит на поверхность. Вы можете использовать Y_VALID, YM_VALID, YMD_VALID = range(3), и это будет работать примерно также. Главное, чтобы был какой-то флаг, который говорит, какой части даты доверять.

1
ответ дан 5 December 2019 в 14:00
поделиться

Похоже, вы хотите сохранить интервал дат. В Python это (насколько я понимаю, все еще немного нуб) легче всего реализовать, сохранив два объекта datetime.datetime, один из которых указывает начало диапазона дат, а другой - конец. Аналогично тому, как это используется для указания фрагментов списка, конечная точка сама не будет включена в диапазон дат.

Например, этот код будет реализовывать диапазон дат как именованный кортеж:

>>> from datetime import datetime
>>> from collections import namedtuple
>>> DateRange = namedtuple('DateRange', 'start end')
>>> the_year_2010 = DateRange(datetime(2010, 1, 1), datetime(2011, 1, 1))
>>> the_year_2010.start <= datetime(2010, 4, 20) < the_year_2010.end
True
>>> the_year_2010.start <= datetime(2009, 12, 31) < the_year_2010.end
False
>>> the_year_2010.start <= datetime(2011, 1, 1) < the_year_2010.end
False

Или даже добавить немного магии:

>>> DateRange.__contains__ = lambda self, x: self.start <= x < self.end
>>> datetime(2010, 4, 20) in the_year_2010
True
>>> datetime(2011, 4, 20) in the_year_2010
False

Это настолько полезная концепция, что я почти уверен, что кто-то уже сделал доступной реализацию . Например, беглый взгляд подсказывает, что класс relativedate из пакета dateutil сделает это, и более выразительно, позволив аргументу ключевого слова 'years' передаваться в конструктор.

Однако сопоставление такого объекта с полями базы данных несколько сложнее, поэтому вам может быть лучше реализовать его, просто извлекая оба поля по отдельности, а затем объединяя их. Я предполагаю, что это зависит от структуры БД; Я еще не очень хорошо знаком с этим аспектом Python.

В любом случае, я думаю, что главное - думать о «частичной дате» как о диапазоне, а не как о простом значении.

править

Заманчиво, но я считаю неуместным добавить больше магических методов, которые будут обрабатывать использование операторов > и <.Здесь есть некоторая двусмысленность: наступает ли дата, которая «больше» заданного диапазона, после конца диапазона или после его начала? Первоначально кажется уместным использовать <= , чтобы указать, что дата в правой части уравнения находится после начала диапазона, и <, чтобы указать, что это после конец.

Однако это подразумевает равенство между диапазоном и датой в пределах диапазона, что неверно, поскольку подразумевает, что месяц май 2010 года равен 2010 году, потому что 4 мая 2010 года приравнивается к обоим из их. То есть вы получите фальсификации вроде 2010-04-20 == 2010 == 2010-05-04 , что это правда.

Поэтому, вероятно, было бы лучше реализовать такой метод, как isafterstart , чтобы явно проверять, находится ли дата после начала диапазона. Но опять же, кто-то, вероятно, уже сделал это, поэтому, вероятно, стоит взглянуть на pypi , чтобы узнать, что считается готовым к производству. На это указывает наличие «Статус разработки :: 5 - Производство / стабильная» в разделе «Категории» на странице pypi данного модуля. Обратите внимание, что не всем модулям присвоен статус разработки.

Или вы можете просто упростить задачу и, используя базовую реализацию namedtuple, явно проверить

>>> datetime(2012, 12, 21) >= the_year_2010.start
True
2
ответ дан 5 December 2019 в 14:00
поделиться
Другие вопросы по тегам:

Похожие вопросы: