В одном из моих проектов 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, и т.д.)
Во-первых, спасибо за все ваши ответы. Ни один из них, как есть, не был хорошим решением для моей проблемы, но, для вашей защиты, я должен добавить, что я не указал все требования. Но каждый из них помог мне задуматься над моей проблемой, и некоторые из ваших идей вошли в мое окончательное решение.
Итак, мое окончательное решение на стороне БД заключается в использовании поля 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
и базой данных.
Пока что он работает довольно хорошо (я в основном закончил свои обширные модульные тесты).
Можно сохранить частичную дату в виде целого числа (предпочтительно в поле, названном для части даты, которую вы храните, например год,
месяц
или день
) и выполнить проверку и преобразование в объект даты в модели.
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.)
Можете ли вы хранить дату вместе с флагом, который говорит, какая часть даты действительна?
Что-то вроде этого:
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), и это будет работать примерно также. Главное, чтобы был какой-то флаг, который говорит, какой части даты доверять.
Похоже, вы хотите сохранить интервал дат. В 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