From 844918e814c5bf8d0aef9fb1445f66ed4ec2f944 Mon Sep 17 00:00:00 2001 From: Julien Muchembled <jm@nexedi.com> Date: Fri, 8 Jan 2010 03:25:17 +0000 Subject: [PATCH] Fix DateUtils.atTheEndOfPeriod and an infinite loop in some timezones Contrary to Europe/Paris, Zope performs automatic DST calculation in US/Eastern timezone, which caused infinite loop in Alarm.getNextPeriodicalDate. Reenable testOpenOrder.testPeriodicityDateList (still failing though), and create a testOpenOrder.testPeriodicityDateListUniversal to show that it works with UTC times. git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@31657 20353a03-c40f-0410-a6d1-a30d3c3de9de --- product/ERP5/Document/Alarm.py | 45 +++++--------- product/ERP5/tests/testOpenOrder.py | 83 +++++++++++++------------ product/ERP5Type/DateUtils.py | 11 +--- product/ERP5Type/tests/testDateUtils.py | 18 ++++++ 4 files changed, 79 insertions(+), 78 deletions(-) diff --git a/product/ERP5/Document/Alarm.py b/product/ERP5/Document/Alarm.py index ca60739173..6d3002882e 100644 --- a/product/ERP5/Document/Alarm.py +++ b/product/ERP5/Document/Alarm.py @@ -35,7 +35,7 @@ from Products.ERP5Type.XMLObject import XMLObject from Acquisition import aq_base from DateTime import DateTime from Products.ERP5Type.Message import Message -from Products.ERP5Type.DateUtils import addToDate +from Products.ERP5Type.DateUtils import addToDate, atTheEndOfPeriod from Products.ERP5Security.ERP5UserManager import SUPER_USER from AccessControl.SecurityManagement import getSecurityManager, \ setSecurityManager, newSecurityManager @@ -158,40 +158,23 @@ class PeriodicityMixin: or (periodicity_stop_date is not None \ and next_start_date >= periodicity_stop_date): return None - else: - # Make sure the old date is not too far away - day_count = int(current_date - next_start_date) - next_start_date = next_start_date + day_count previous_date = next_start_date - next_start_date = addToDate(next_start_date, minute=1) + next_start_date = max(addToDate(next_start_date, minute=1), current_date) while 1: - validate_minute = self._validateMinute(next_start_date, previous_date) - validate_hour = self._validateHour(next_start_date) - validate_day = self._validateDay(next_start_date) - validate_week = self._validateWeek(next_start_date) - validate_month = self._validateMonth(next_start_date) - if (next_start_date >= current_date \ - and validate_minute and validate_hour and validate_day \ - and validate_week and validate_month): - break + if not self._validateMonth(next_start_date): + next_start_date = atTheEndOfPeriod(next_start_date, 'month') + elif not (self._validateDay(next_start_date) and + self._validateWeek(next_start_date)): + next_start_date = atTheEndOfPeriod(next_start_date, 'day') + elif not self._validateMinute(next_start_date, previous_date): + next_start_date = addToDate(next_start_date, minute=1) + elif not self._validateHour(next_start_date): + next_start_date = addToDate(next_start_date, hour=1) else: - if not(validate_minute): - next_start_date = addToDate(next_start_date, minute=1) - else: - if not(validate_hour): - next_start_date = addToDate(next_start_date, hour=1) - else: - if not(validate_day and validate_week and validate_month): - # We have to reset hours and minutes in order to make sure - # we will start at the beginning of the next day - next_start_date = DateTime(next_start_date.Date() + ' 00:00:00 %s' % next_start_date.timezone()) - next_start_date = addToDate(next_start_date, day=1) - else: - # Everything is right, but the date is still not bigger - # than the current date, so we must continue - next_start_date = addToDate(next_start_date, minute=1) - return next_start_date + parts = list(next_start_date.parts()) + parts[5] = previous_date.second() # XXX keep old behaviour + return DateTime(*parts) # XXX May be we should create a Date class for following methods ??? security.declareProtected(Permissions.AccessContentsInformation, 'getWeekDayList') diff --git a/product/ERP5/tests/testOpenOrder.py b/product/ERP5/tests/testOpenOrder.py index 3370c62ced..a5ad8259a6 100644 --- a/product/ERP5/tests/testOpenOrder.py +++ b/product/ERP5/tests/testOpenOrder.py @@ -172,59 +172,64 @@ class TestOpenOrder(ERP5TypeTestCase): transaction.commit() self.tic() - def testPeriodicityDateList(self): + def testPeriodicityDateList(self, timezone=None): """ Make sure that periodicity line can generate correct schedule. """ - self.fail('Test disabled because it freezes') + #self.fail('Test disabled because it freezes') + def D(yr, mo, dy, hr=0, mn=0, sc=0): + return DateTime(yr, mo, dy, hr, mn, sc, timezone) # This across Summer time period, if server's timezone uses it. self.assertEqual(self.portal.sale_trade_condition_module.main_trade_condition.internet_connection_periodicity_line.getDatePeriodList( - DateTime(2008,1,15), DateTime(2008,12,1)), - [(DateTime(2008,2,1,0,1), DateTime(2008,2,29)), - (DateTime(2008,3,1,0,1), DateTime(2008,3,31)), - (DateTime(2008,4,1,0,1), DateTime(2008,4,30)), - (DateTime(2008,5,1,0,1), DateTime(2008,5,31)), - (DateTime(2008,6,1,0,1), DateTime(2008,6,30)), - (DateTime(2008,7,1,0,1), DateTime(2008,7,31)), - (DateTime(2008,8,1,0,1), DateTime(2008,8,31)), - (DateTime(2008,9,1,0,1), DateTime(2008,9,30)), - (DateTime(2008,10,1,0,1), DateTime(2008,10,31)), - (DateTime(2008,11,1,0,1), DateTime(2008,11,30)), + D(2008,1,15), D(2008,12,1)), + [(D(2008,2,1,0,1), DateTime(2008,2,29)), + (D(2008,3,1,0,1), DateTime(2008,3,31)), + (D(2008,4,1,0,1), DateTime(2008,4,30)), + (D(2008,5,1,0,1), DateTime(2008,5,31)), + (D(2008,6,1,0,1), DateTime(2008,6,30)), + (D(2008,7,1,0,1), DateTime(2008,7,31)), + (D(2008,8,1,0,1), DateTime(2008,8,31)), + (D(2008,9,1,0,1), DateTime(2008,9,30)), + (D(2008,10,1,0,1), DateTime(2008,10,31)), + (D(2008,11,1,0,1), DateTime(2008,11,30)), ]) - + self.assertEqual(self.portal.sale_trade_condition_module.main_trade_condition.bread_periodicity_line.getDatePeriodList( - DateTime(2008,2,26), DateTime(2008,3,5)), - [(DateTime(2008,2,26,6,0), DateTime(2008,2,26,6,0)), - (DateTime(2008,2,26,12,0), DateTime(2008,2,26,12,0)), - (DateTime(2008,2,27,6,0), DateTime(2008,2,27,6,0)), - (DateTime(2008,2,27,12,0), DateTime(2008,2,27,12,0)), - (DateTime(2008,2,28,6,0), DateTime(2008,2,28,6,0)), - (DateTime(2008,2,28,12,0), DateTime(2008,2,28,12,0)), - (DateTime(2008,2,29,6,0), DateTime(2008,2,29,6,0)), - (DateTime(2008,2,29,12,0), DateTime(2008,2,29,12,0)), - (DateTime(2008,3,1,6,0), DateTime(2008,3,1,6,0)), - (DateTime(2008,3,1,12,0), DateTime(2008,3,1,12,0)), - (DateTime(2008,3,3,6,0), DateTime(2008,3,3,6,0)), - (DateTime(2008,3,3,12,0), DateTime(2008,3,3,12,0)), - (DateTime(2008,3,4,6,0), DateTime(2008,3,4,6,0)), - (DateTime(2008,3,4,12,0), DateTime(2008,3,4,12,0)), + D(2008,2,26), D(2008,3,5)), + [(D(2008,2,26,6,0), D(2008,2,26,6,0)), + (D(2008,2,26,12,0), D(2008,2,26,12,0)), + (D(2008,2,27,6,0), D(2008,2,27,6,0)), + (D(2008,2,27,12,0), D(2008,2,27,12,0)), + (D(2008,2,28,6,0), D(2008,2,28,6,0)), + (D(2008,2,28,12,0), D(2008,2,28,12,0)), + (D(2008,2,29,6,0), D(2008,2,29,6,0)), + (D(2008,2,29,12,0), D(2008,2,29,12,0)), + (D(2008,3,1,6,0), D(2008,3,1,6,0)), + (D(2008,3,1,12,0), D(2008,3,1,12,0)), + (D(2008,3,3,6,0), D(2008,3,3,6,0)), + (D(2008,3,3,12,0), D(2008,3,3,12,0)), + (D(2008,3,4,6,0), D(2008,3,4,6,0)), + (D(2008,3,4,12,0), D(2008,3,4,12,0)), ]) self.assertEqual(self.portal.sale_trade_condition_module.main_trade_condition.water_periodicity_line.getDatePeriodList( - DateTime(2008,2,16), DateTime(2008,4,15)), - [(DateTime(2008,2,18,10,0), DateTime(2008,3,3,10,0)), - (DateTime(2008,3,3,10,0), DateTime(2008,3,17,10,0)), - (DateTime(2008,3,17,10,0), DateTime(2008,3,31,10,0)), - (DateTime(2008,3,31,10,0), DateTime(2008,4,14,10,0)), - (DateTime(2008,4,14,10,0), DateTime(2008,4,28,10,0)), + D(2008,2,16), D(2008,4,15)), + [(D(2008,2,18,10,0), D(2008,3,3,10,0)), + (D(2008,3,3,10,0), D(2008,3,17,10,0)), + (D(2008,3,17,10,0), D(2008,3,31,10,0)), + (D(2008,3,31,10,0), D(2008,4,14,10,0)), + (D(2008,4,14,10,0), D(2008,4,28,10,0)), ]) self.assertEqual(self.portal.sale_trade_condition_module.main_trade_condition.training_periodicity_line.getDatePeriodList( - DateTime(2008,2,16), DateTime(2008,3,6)), - [(DateTime(2008,2,18,10,0), DateTime(2008,2,19,10,0)), - (DateTime(2008,2,25,10,0), DateTime(2008,2,26,10,0)), - (DateTime(2008,3,3,10,0), DateTime(2008,3,4,10,0)), + D(2008,2,16), D(2008,3,6)), + [(D(2008,2,18,10,0), D(2008,2,19,10,0)), + (D(2008,2,25,10,0), D(2008,2,26,10,0)), + (D(2008,3,3,10,0), D(2008,3,4,10,0)), ]) + def testPeriodicityDateListUniversal(self): + self.testPeriodicityDateList('Universal') + def testOpenOrderRule(self): """ Make sure that Open Order Rule can generate simulation movements by diff --git a/product/ERP5Type/DateUtils.py b/product/ERP5Type/DateUtils.py index 22f6d4ec29..3d07262d26 100644 --- a/product/ERP5Type/DateUtils.py +++ b/product/ERP5Type/DateUtils.py @@ -506,21 +506,16 @@ def atTheEndOfPeriod(date, period): If timezone is Universal, strftime('%Z') return empty string and TimeZone is replaced by local zone, so date formating is manualy rendered. - XXXSunday is hardcoded """ if period == 'year': end = addToDate(DateTime('%s/01/01 00:00:00 %s' % (date.year(), date.timezone())), **{period:1}) elif period == 'month': end = addToDate(DateTime('%s/%s/01 00:00:00 %s' % (date.year(), zfill(date.month(), 2), date.timezone())), **{period:1}) elif period == 'day': - end = addToDate(DateTime('%s/%s/%s 00:00:00 %s' % (date.year(), zfill(date.month(), 2), zfill(date.day(), 2), date.timezone())), **{period:1}) + end = addToDate(date.earliestTime(), hour=36).earliestTime() elif period == 'week': - day_of_week = date.strftime('%A') - end = DateTime('%s/%s/%s 00:00:00 %s' % (date.year(), zfill(date.month(), 2), zfill(date.day(), 2), date.timezone())) - while day_of_week != 'Sunday': - end = addToDate(end, day=1) - day_of_week = end.strftime('%A') - end = addToDate(end, day=1) + end = atTheEndOfPeriod(date, 'day') + end = addToDate(end, day=(1-end.dow()) % 7) else: raise NotImplementedError, 'Period "%s" not Handled yet' % period return end diff --git a/product/ERP5Type/tests/testDateUtils.py b/product/ERP5Type/tests/testDateUtils.py index 55a47d24f2..72baadb258 100644 --- a/product/ERP5Type/tests/testDateUtils.py +++ b/product/ERP5Type/tests/testDateUtils.py @@ -167,6 +167,24 @@ class TestDateUtils(unittest.TestCase): self.assertEqual(atTheEndOfPeriod(date, 'month').pCommonZ(), 'Feb. 1, 2008 12:00 am Universal') self.assertEqual(atTheEndOfPeriod(date, 'week').pCommonZ(), 'Jan. 7, 2008 12:00 am Universal') self.assertEqual(atTheEndOfPeriod(date, 'day').pCommonZ(), 'Jan. 2, 2008 12:00 am Universal') + # Switch to summer time + self.assertEqual('Apr. 6, 2008 12:00 am US/Eastern', + atTheEndOfPeriod(DateTime('2008/04/05 23:59:59 US/Eastern'), 'day').pCommonZ()) + self.assertEqual('Apr. 7, 2008 12:00 am US/Eastern', + atTheEndOfPeriod(DateTime('2008/04/06 00:00:00 US/Eastern'), 'day').pCommonZ()) + self.assertEqual('Apr. 7, 2008 12:00 am US/Eastern', + atTheEndOfPeriod(DateTime('2008/04/06 23:59:59 US/Eastern'), 'day').pCommonZ()) + self.assertEqual('May 1, 2008 12:00 am US/Eastern', + atTheEndOfPeriod(DateTime('2008/04/01 US/Eastern'), 'month').pCommonZ()) + # Switch to winter time + self.assertEqual('Oct. 26, 2008 12:00 am US/Eastern', + atTheEndOfPeriod(DateTime('2008/10/25 23:59:59 US/Eastern'), 'day').pCommonZ()) + self.assertEqual('Oct. 27, 2008 12:00 am US/Eastern', + atTheEndOfPeriod(DateTime('2008/10/26 00:00:00 US/Eastern'), 'day').pCommonZ()) + self.assertEqual('Oct. 27, 2008 12:00 am US/Eastern', + atTheEndOfPeriod(DateTime('2008/10/26 23:59:59 US/Eastern'), 'day').pCommonZ()) + self.assertEqual('Nov. 1, 2008 12:00 am US/Eastern', + atTheEndOfPeriod(DateTime('2008/10/01 US/Eastern'), 'month').pCommonZ()) def test_suite(): suite = unittest.TestSuite() -- 2.30.9