在我的一个使用MySQL作为数据库的Django项目中,我需要有一个日期字段,这些日期字段也接受“部分”日期,比如只有年份(YYYY)和年份和月份(YYYY)加上正常日期(YYYY DD)。
MySQL中的date字段可以通过接受月份和日期的00来处理这个问题。所以2010-00-00在MySQL中是有效的,它代表了2010年.2010年5月的2010-05-00年也是一样。
所以我开始创建一个PartialDateField来支持这个特性。但我遇到了麻烦,因为默认情况下,Django使用默认值,MySQLdb是MySQL的python驱动程序,它为date字段返回datetime.date对象,而datetime.date()只支持真正的日期。因此,可以修改MySQLdb使用的日期字段的转换器,并且只返回这种格式的字符串‘YYYY DD’。不幸的是,MySQLdb使用的转换器设置在连接级别,因此它用于所有MySQL日期字段。但是Django DateField依赖于数据库返回一个datetime.date对象这一事实,因此如果我将转换器更改为返回一个字符串,Django一点也不高兴。
有人有解决这个问题的主意或建议吗?如何在Django中创建PartialDateField?
编辑
此外,我还应该补充说,我已经想出了两个解决方案,为年份、月和日创建了3个整数字段(如Alison R.)或者使用varchar字段将日期保持为这种格式的字符串YYYY DD。
但是在这两种解决方案中,如果我没有错,我将放松日期字段的特殊属性,比如对它们执行此类查询:在此日期之后获取所有条目。我可能可以在客户端重新实现这个功能,但在我的情况下,这不是一个有效的解决方案,因为数据库可以从其他系统(mysql客户端、MS Access等)查询。
发布于 2010-06-12 03:50:38
首先,谢谢你的回答。事实上,它们都不是解决我问题的好办法,但对于你的辩护,我应该补充一点,我没有给出所有的要求。但是每一个都帮助我思考我的问题,你的一些想法是我最后解决方案的一部分。
因此,在DB端,我的最后解决方案是使用varchar字段(限制为10个字符),并以字符串的形式将日期存储在其中,在没有月份和/或日的月份和日(如MySQL中的日期字段)中,以ISO格式(YYYY DD)形式存储00。通过这种方式,该字段可以与任何数据库一起工作,数据可以由人使用简单的客户机(如mysql客户端、phpmyadmin等)直接读取、理解和编辑。这是一项要求。它也可以导出到Excel/CSV,不需要任何转换等等。缺点是格式没有强制执行(Django除外)。有人可以写“not a date”或者在格式上做错误,DB会接受它(如果你对这个问题有想法的话.)。
通过这种方式,还可以相对轻松地执行date字段的所有特殊查询。对于使用WHERE:<,>、<=、>=和=的查询,可以直接工作。IN和中间查询也直接工作。对于按日或月进行查询,您只需使用提取程序进行查询即可( day _月.)。订购工作也是直接的。因此,我认为它涵盖了所有的查询需求,而且基本上没有复杂。
在Django一边,我做了两件事。首先,我创建了一个PartialDate对象,它看起来很像datetime.date,但支持没有月和/或日的日期。在这个对象中,我使用一个datetime.datetime对象来保存日期。我使用“小时”和“分钟”作为标志,指示当月和日设置为1时是否有效。这与steveha提议的想法相同,但具有不同的实现(仅在客户端)。使用datetime.datetime对象为我提供了许多处理日期(验证、比较等)的好功能。
其次,我创建了一个PartialDateField,它主要处理PartialDate对象和数据库之间的转换。
到目前为止,它运行得很好(我已经完成了广泛的单元测试)。
发布于 2010-06-04 02:54:56
您可以将部分日期存储为整数(最好是在为存储日期的部分(如year, month或day)命名的字段中),并在模型中对日期对象进行验证和转换。
编辑
如果您需要真正的日期功能,那么您可能需要真正的日期,而不是部分的日期。例如,“在2010 -0-0之后得到一切”返回日期包括2010年还是仅包括2011年及以后的日期?2010年5月的另一个例子也是如此。不同的语言/客户端处理部分日期的方式(如果他们支持的话)可能非常特殊,而且不太可能与MySQL的实现相匹配。
另一方面,如果存储像2010年这样的year整数,那么很容易要求数据库提供“所有年>2010年的记录”,并且很容易从任何平台上的任何客户端了解结果。您甚至可以将这种方法结合到更复杂的日期/查询中,例如“所有记录的年份> 2010和月份> 5”。
二次编辑
您唯一的其他(也许也是最好的)选择是存储真正有效的日期,并在您的应用程序中为它们的含义制定一个约定。名为date_month的DATETIME字段的值可能为2010-05-01,但您可以将其视为表示2010年5月的所有日期。在编程时,您需要适应这一点。如果将Python中的date_month作为日期时间对象,则需要调用像date_month.end_of_month()这样的函数来查询那个月之后的日期。(这是伪代码,但可以很容易地用类似于日历模块的方式实现。)
发布于 2010-06-04 23:00:34
听起来你想要存储一个日期间隔。在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包的丁香醇类将通过允许将一个'years‘关键字参数传递给构造函数来做到这一点,而且更有表现力。
但是,将这样的对象映射到数据库字段要复杂得多,因此您最好只是将两个字段分开,然后将它们组合起来,从而实现它。我想这取决于DB框架;我还不太熟悉Python的这个方面。
无论如何,我认为关键是把“部分日期”看作一个范围,而不是一个简单的值。
编辑
添加更多神奇的方法来处理>和<运算符的使用是很有诱惑力的,但我认为这是不合适的。有一点模棱两可:一个“大于”给定范围的日期是在范围结束后还是在开始之后发生?最初,使用<=表示方程右侧的日期是范围开始后的日期,使用<表示在结束之后。
然而,这意味着幅度与幅度内日期之间的平等,这是不正确的,因为这意味着2010年5月等于2010年,因为2010年5月4日等于两者。你最终会出现像2010-04-20 == 2010 == 2010-05-04这样的谎言。
因此,可能最好实现像isafterstart这样的方法,显式地检查日期是否在范围开始之后。但同样,有人可能已经做了,所以它可能值得一看皮皮,看看什么被认为是生产就绪。这表现在给定模块的pypi页面的“类别”部分中存在“开发状态::5-生产/稳定”。注意,并非所有模块都获得了开发状态。
或者,您可以简单地使用基本的namedtuple实现,显式检查
>>> datetime(2012, 12, 21) >= the_year_2010.start
Truehttps://stackoverflow.com/questions/2971198
复制相似问题