CalendarWidget =================== There are no tests for this at all and I want to make a change. This is a scary adventure! >>> from plone.app.form.widgets import datecomponents >>> from zope.publisher.browser import TestRequest >>> request = TestRequest() >>> view = datecomponents.DateComponents(self.portal, request) For historical reasons, the DateTime module parses the timezone differently if a date is created with a "-" instead of a "/". Dates that enter here with a "-" go to "GMT-0" by default whereas "/" goes to the server timezone (in my case this is "US/Pacific"). The bug: Note that this test won't work on certain timezones and DateTime doesn't look at anything obvious to get the timezone info (like environment variables) so I'm going to patch the func that sets the timezone so that this test doesn't break on different timezones. If this test does break then, it means that the actual bug has been fixed. There maybe other signs such as pigs flying, locusts, etc... >>> import DateTime >>> TEST_TIME_ZONE = 'US/Pacific' >>> realLocalZone = DateTime.DateTime.localZone >>> def fakeLocalZone(self, ltm=None): ... return TEST_TIME_ZONE >>> DateTime.DateTime.localZone = fakeLocalZone >>> him = DateTime.DateTime("2011/01/13") >>> her = DateTime.DateTime("2011-01-13") >>> her == him False >>> her._tz 'GMT+0' >>> him._tz == TEST_TIME_ZONE True So this wouldn't be an issue if the DateTime didn't later convert this to the localzone: >>> her.strftime('%d') '12' >>> him.strftime('%d') '13' For the next test case, we want them to work in all timezones so we un-monkey patch. >>> DateTime.DateTime.localZone = realLocalZone It would be sane to fix it in the DateTime module, but I wouldn't wish that task upon my worst enemy. So we can fix it here by replacing all instances of "-" with "/" in the first part of a date string: >>> date = "2011-01-13" >>> parts = view.result(date=date) >>> parts['days'][13]['selected'] 1 >>> date = "2011/01/13" >>> parts = view.result(date=date) >>> parts['days'][13]['selected'] 1 Helper function to parse through the next return values without losing whatever sanity is left >>> def parseDate(parts): ... date = () ... sections = ['years', 'months', 'days', 'hours', 'minutes'] ... for section in sections: ... for item in parts[section]: ... if item['selected'] == 1: ... date += (item['value'],) ... return date Additionally, there was a note that DateTime didn't support GMT and then was stripping it off. I don't think that is still the case and it causes whacky jackiness for people who actually want to pass in time zones. For example, we can pass in GMT-5 here and it should (correctly?) remap US/Eastern to the localzone (which we will again patch to make sure this test case doesn't fail): >>> DateTime.DateTime.localZone = fakeLocalZone >>> date = "2011-01-13 01:30 GMT-5" >>> parts = view.result(date=date) >>> parseDate(parts) (2011, '01', '12', '22', '30') And to be sure, we also can't break other tz representations. I won't test for time here because it will break half of every year due to daylight savings. >>> date = "2011/01/13 01:30 US/Pacific" >>> parts = view.result(date=date) >>> parseDate(parts) (2011, '01', '13', '01', '30') While we are in here, let's fix this edge case as well. The date should always round up to the next interval. There is the edge case of times that are greater than the the last interval. Instead of messing with logic about rounding to the next hour then the next day, etc, etc... let's just round down to the last interval >>> date = "2011/01/13 05:59 US/Pacific" >>> parts = view.result(date=date) >>> parseDate(parts) (2011, '01', '13', '05', '55') And some backwards compatibility checks >>> date = "2011/01/13 05:55 US/Pacific" >>> parts = view.result(date=date) >>> parseDate(parts) (2011, '01', '13', '05', '55') >>> parts = view.result(date="2011/01/13 05:47 US/Pacific") >>> parseDate(parts) (2011, '01', '13', '05', '50') >>> parts = view.result(date="2011/01/13 05:51 US/Pacific") >>> parseDate(parts) (2011, '01', '13', '05', '55') >>> parts = view.result(date="2011/01/13 06:00 US/Pacific") >>> parseDate(parts) (2011, '01', '13', '06', '00') Pre-epoch dates need to work too >>> parts = view.result(date="1961/01/13 06:00 US/Pacific") >>> parseDate(parts) (1961, '01', '13', '06', '00') Test date before 1900 (usual problem with strftime) >>> date = "1899-07-26 22:00 US/Pacific" >>> parts = view.result(date=date) >>> parseDate(parts) (1899, '07', '26', '22', '00') Test date before 2499 >>> date = "2499-12-30 08:00 US/Pacific" >>> parts = view.result(date=date) >>> parseDate(parts) (2499, '12', '30', '08', '00') Test date after 2500. First we need to make sure that PLONE_CEILING is in our fake test timezone, so that it doesn't get converted to a time that will vary depending on the timezone in which the test is run. >>> from plone.app.form.widgets import datecomponents >>> datecomponents.PLONE_CEILING = DateTime.DateTime(2500, 0) >>> date = "2501-01-30 22:00 US/Pacific" >>> parts = view.result(date=date) >>> parseDate(parts) (2499, '12', '31', '00', '00') un-monkey patch >>> DateTime.DateTime.localZone = realLocalZone >>> datecomponents.PLONE_CEILING = DateTime.DateTime(2500, 0)