Как бороться с «частичными» датами (2010-00-00) из MySQL в Джанго? - PullRequest
8 голосов
/ 04 июня 2010

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

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

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

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

EDIT

Также я должен добавить, что я уже подумал о 2 решениях, создать 3 целочисленных поля для года, месяца и дня (как упомянуто Алисон Р. ) или использовать поле varchar сохранить дату в виде строки в этом формате ГГГГ-ММ-ДД .

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

Ответы [ 5 ]

6 голосов
/ 12 июня 2010

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

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

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

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

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

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

2 голосов
/ 05 июня 2010

Звучит так, как будто вы хотите сохранить интервал дат. В 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 голосов
/ 04 июня 2010

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

EDIT

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

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

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

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

1 голос
/ 28 мая 2012

Хотя не в Python - вот пример того, как та же проблема была решена в Ruby - с использованием одного целочисленного значения - и побитовых операторов для хранения года, месяца и дня - с необязательным месяцем и днем.

https://github.com/58bits/partial-date

Посмотрите на источник в lib для date.rb и bits.rb.

Я уверен, что подобное решение может быть написано на Python.

Чтобы сохранить дату (сортируемую), вы просто сохраняете Integer в базе данных.

1 голос
/ 04 июня 2010

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

Примерно так:

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 будет классом, который объединяет дату и флаг действительной даты, описанные выше.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...