From 63f022348b95aa95b397af13b15a07c63c7f6d1e Mon Sep 17 00:00:00 2001 From: Vassilii Khachaturov Date: Tue, 17 Sep 2013 18:58:06 +0000 Subject: [PATCH] 7034,7045,7065: back-merge my fixes from trunk Back-merge from trunk changes to date.py and date_test.py from the following commits (cumulative, clean apply). Tests pass (but need to block the CAL_FRENCH on date_test.py:199, because of bug# 7068 -- skipping it wasn't back-ported in this commit as it is about the fully fixed issues only!) commit fa49752824bd58802773439b35faa39f2d34b151 Author: Vassilii Khachaturov Date: Sat Sep 14 15:44:04 2013 +0000 provide sensible defautls for all Date.set params git-svn-id: svn+ssh://svn.code.sf.net/p/gramps/code/trunk@23126 4ae1f11a-8b86-4847-b8af-ab372f36d1fd commit 3f32597699f3b372324ad87e6f7a04abac6d19e7 Author: Vassilii Khachaturov Date: Sat Sep 14 15:11:09 2013 +0000 7045: Setting an invalid date does not raise do the sanity checks on a separate date object, so that the uncertainty expressed with 0 d/m isn't removed git-svn-id: svn+ssh://svn.code.sf.net/p/gramps/code/trunk@23124 4ae1f11a-8b86-4847-b8af-ab372f36d1fd commit 12edf7e97626e01931c4063b2d94bec3b299a2ed Author: Vassilii Khachaturov Date: Sat Sep 14 14:23:58 2013 +0000 7065: Calendar conversion broken for negative date fixed, repro steps work as expected now date_test still broken due to further blocking issues, see #7045 git-svn-id: svn+ssh://svn.code.sf.net/p/gramps/code/trunk@23123 4ae1f11a-8b86-4847-b8af-ab372f36d1fd commit 091d4461e9715ab06f1ef6ab3b67517d6608daf3 Author: Vassilii Khachaturov Date: Sat Sep 14 13:24:40 2013 +0000 7045: Date.set on invalid date does not raise refactor _zero_adjust_ymd out of 3 cut-and-paste cases the bug with the code inside it remains -- the negative years should not be clamped to positive ones!!!! git-svn-id: svn+ssh://svn.code.sf.net/p/gramps/code/trunk@23122 4ae1f11a-8b86-4847-b8af-ab372f36d1fd commit 5987046ac4cac407a4be506da9242f7a5000d878 Author: Vassilii Khachaturov Date: Sat Sep 14 13:00:19 2013 +0000 7045: Date.set on invalid date does not raise Now it does, but another test breaks: Traceback (most recent call last): File "/usr/lib/python2.7/unittest/loader.py", line 252, in _find_tests module = self._get_module_from_name(name) File "/usr/lib/python2.7/unittest/loader.py", line 230, in _get_module_from_name __import__(name) File "/home/vassilii/Gramps/gramps/gen/lib/test/date_test.py", line 136, in d.set(quality,modifier,calendar,(4,11,-90,False),"Text comment") File "/home/vassilii/Gramps/gramps/gen/lib/date.py", line 1600, in set format(original, value)) DateError: Invalid year -90 passed in value (4, 11, -90, False) because the corresponding year gets adjusted from -90 to 1... git-svn-id: svn+ssh://svn.code.sf.net/p/gramps/code/trunk@23121 4ae1f11a-8b86-4847-b8af-ab372f36d1fd commit d8876cceb64629ce0a025ff714e4875768ab88a6 Author: Vassilii Khachaturov Date: Sat Sep 14 11:50:58 2013 +0000 7034: fix test_copy_ymd_preserves_orig broken in r23083 git-svn-id: svn+ssh://svn.code.sf.net/p/gramps/code/trunk@23120 4ae1f11a-8b86-4847-b8af-ab372f36d1fd commit 7c163636c8e48149a5b09c211ff3dc146ebd84b2 Author: Vassilii Khachaturov Date: Tue Sep 10 17:19:16 2013 +0000 7034: add remove_stop_date parameter git-svn-id: svn+ssh://svn.code.sf.net/p/gramps/code/trunk@23083 4ae1f11a-8b86-4847-b8af-ab372f36d1fd commit b45e20da3bd67d864420f99cf59fbb9929c58851 Author: Vassilii Khachaturov Date: Mon Sep 9 19:31:13 2013 +0000 7034: probably_alive() failing when no birth-death further refactoring of set_.../set2_... common code added accessor get_stop_ymd analogous to get_ymd git-svn-id: svn+ssh://svn.code.sf.net/p/gramps/code/trunk@23068 4ae1f11a-8b86-4847-b8af-ab372f36d1fd commit f13a3fc23e7f0763c49e605b428b6a175c3f9eeb Author: Vassilii Khachaturov Date: Mon Sep 9 19:31:00 2013 +0000 7034: probably_alive() failing when no birth-death docstring update git-svn-id: svn+ssh://svn.code.sf.net/p/gramps/code/trunk@23067 4ae1f11a-8b86-4847-b8af-ab372f36d1fd commit 9ee312d7ed02520b99d2ca1b28f75c87846aa3c6 Author: Vassilii Khachaturov Date: Sun Sep 8 19:35:15 2013 +0000 refactor test git-svn-id: svn+ssh://svn.code.sf.net/p/gramps/code/trunk@23059 4ae1f11a-8b86-4847-b8af-ab372f36d1fd commit 07ca997ebd885ad4d1b205907a00509099ac8f9a Author: Vassilii Khachaturov Date: Sun Sep 8 19:23:23 2013 +0000 consistency between offset and non-offset setters added ugly parameter _update2 to set_yr_mon_day, needs refactoring git-svn-id: svn+ssh://svn.code.sf.net/p/gramps/code/trunk@23058 4ae1f11a-8b86-4847-b8af-ab372f36d1fd commit 67a904c529642668fbe34bfc97ef2915278ecbdb Author: Vassilii Khachaturov Date: Sun Sep 8 19:23:12 2013 +0000 fix set_yr_mon_day_offset for compound dates now calls set2_yr_mon_day_offset git-svn-id: svn+ssh://svn.code.sf.net/p/gramps/code/trunk@23057 4ae1f11a-8b86-4847-b8af-ab372f36d1fd commit 3db06c36d6449ec75cde49b433349cddad40d596 Author: Vassilii Khachaturov Date: Sun Sep 8 19:23:02 2013 +0000 refactor set_yr_mon_day and set2_yr_mon_day refactor common base git-svn-id: svn+ssh://svn.code.sf.net/p/gramps/code/trunk@23056 4ae1f11a-8b86-4847-b8af-ab372f36d1fd commit 4192680c72cc0028c22fa207fe3f1ff0940358b3 Author: Vassilii Khachaturov Date: Sun Sep 8 19:22:52 2013 +0000 raise DateError in set2_... if not is_compound() refactor Date to always use is_compound instead of repeating its logic everywhere git-svn-id: svn+ssh://svn.code.sf.net/p/gramps/code/trunk@23055 4ae1f11a-8b86-4847-b8af-ab372f36d1fd commit 44195ede18c5a887d4440b4132bd5321f76ce5ff Author: Vassilii Khachaturov Date: Sun Sep 8 19:22:40 2013 +0000 Add some UT for Date.set2_... and fix bugs Cut and paste is evil ;-) fix bugs before I refactor the code... git-svn-id: svn+ssh://svn.code.sf.net/p/gramps/code/trunk@23054 4ae1f11a-8b86-4847-b8af-ab372f36d1fd commit 14f6e3a3f35e0ff7b67006bf4996ce63ae665098 Author: Vassilii Khachaturov Date: Sun Sep 8 19:22:30 2013 +0000 7034: probably_alive() failing when no birth-death docstring fix git-svn-id: svn+ssh://svn.code.sf.net/p/gramps/code/trunk@23053 4ae1f11a-8b86-4847-b8af-ab372f36d1fd svn: r23156 --- gramps/gen/lib/date.py | 276 ++++++++++++++++++------------- gramps/gen/lib/test/date_test.py | 79 ++++++++- 2 files changed, 237 insertions(+), 118 deletions(-) diff --git a/gramps/gen/lib/date.py b/gramps/gen/lib/date.py index cf05d3fab..cfda23a57 100644 --- a/gramps/gen/lib/date.py +++ b/gramps/gen/lib/date.py @@ -122,8 +122,7 @@ class Span(object): v = self.date1.sortval - self.date2.sortval self.sort = (v, -Span.ABOUT) self.minmax = (v - Span.ABOUT, v + Span.ABOUT) - elif (self.date2.get_modifier() == Date.MOD_RANGE or - self.date2.get_modifier() == Date.MOD_SPAN): + elif self.date2.is_compound(): start, stop = self.date2.get_start_stop_range() start = Date(*start) stop = Date(*stop) @@ -148,8 +147,7 @@ class Span(object): v = self.date1.sortval - self.date2.sortval self.sort = (v, -Span.ABOUT) self.minmax = (v - Span.ABOUT, v + Span.ABOUT) - elif (self.date2.get_modifier() == Date.MOD_RANGE or - self.date2.get_modifier() == Date.MOD_SPAN): + elif self.date2.is_compound(): v = self.date1.sortval - self.date2.sortval self.sort = (v, -Span.ABOUT) self.minmax = (v - Span.ABOUT, v + Span.ABOUT) @@ -170,8 +168,7 @@ class Span(object): v = self.date1.sortval - self.date2.sortval self.sort = (v, -Span.ABOUT) self.minmax = (v - Span.ABOUT, v + Span.AFTER) - elif (self.date2.get_modifier() == Date.MOD_RANGE or - self.date2.get_modifier() == Date.MOD_SPAN): + elif self.date2.is_compound(): v = self.date1.sortval - self.date2.sortval self.sort = (v, -Span.ABOUT) self.minmax = (v - Span.ABOUT, v + Span.ABOUT) @@ -192,13 +189,11 @@ class Span(object): v = self.date1.sortval - self.date2.sortval self.sort = (v, -Span.ABOUT) self.minmax = (v - Span.ABOUT, v + Span.ABOUT) - elif (self.date2.get_modifier() == Date.MOD_RANGE or - self.date2.get_modifier() == Date.MOD_SPAN): + elif self.date2.is_compound(): v = self.date1.sortval - self.date2.sortval self.sort = (v, -Span.ABOUT) self.minmax = (v - Span.ABOUT, v + Span.ABOUT) - elif (self.date1.get_modifier() == Date.MOD_RANGE or - self.date1.get_modifier() == Date.MOD_SPAN): # SPAN---------------------------- + elif self.date1.is_compound(): if self.date2.get_modifier() == Date.MOD_NONE: start, stop = self.date1.get_start_stop_range() start = Date(*start) @@ -219,8 +214,7 @@ class Span(object): v = self.date1.sortval - self.date2.sortval self.sort = (v, -Span.ABOUT) self.minmax = (v - Span.ABOUT, v + Span.ABOUT) - elif (self.date2.get_modifier() == Date.MOD_RANGE or - self.date2.get_modifier() == Date.MOD_SPAN): + elif self.date2.is_compound(): start1, stop1 = self.date1.get_start_stop_range() start2, stop2 = self.date2.get_start_stop_range() start1 = Date(*start1) @@ -314,8 +308,7 @@ class Span(object): _repr = trans_text("less than") + " " + fdate12 elif self.date2.get_modifier() == Date.MOD_ABOUT: _repr = trans_text("age|about") + " " + fdate12p1 - elif (self.date2.get_modifier() == Date.MOD_RANGE or - self.date2.get_modifier() == Date.MOD_SPAN): + elif self.date2.is_compound(): start, stop = self.date2.get_start_stop_range() start = Date(*start) stop = Date(*stop) @@ -330,8 +323,7 @@ class Span(object): _repr = trans_text("less than") + " " + fdate12 elif self.date2.get_modifier() == Date.MOD_ABOUT: _repr = trans_text("less than about") + " " + fdate12 - elif (self.date2.get_modifier() == Date.MOD_RANGE or - self.date2.get_modifier() == Date.MOD_SPAN): + elif self.date2.is_compound(): _repr = trans_text("less than") + " " + fdate12 elif self.date1.get_modifier() == Date.MOD_AFTER: # AFTER---------------------------- if self.date2.get_modifier() == Date.MOD_NONE: @@ -342,8 +334,7 @@ class Span(object): _repr = self._format((-1, -1 , -1)) elif self.date2.get_modifier() == Date.MOD_ABOUT: _repr = trans_text("more than about") + " " + fdate12p1 - elif (self.date2.get_modifier() == Date.MOD_RANGE or - self.date2.get_modifier() == Date.MOD_SPAN): + elif self.date2.is_compound(): _repr = trans_text("more than") + " " + fdate12 elif self.date1.get_modifier() == Date.MOD_ABOUT: # ABOUT---------------------------- if self.date2.get_modifier() == Date.MOD_NONE: @@ -354,11 +345,9 @@ class Span(object): _repr = trans_text("less than about") + " " + fdate12p1 elif self.date2.get_modifier() == Date.MOD_ABOUT: _repr = trans_text("age|about") + " " + fdate12p1 - elif (self.date2.get_modifier() == Date.MOD_RANGE or - self.date2.get_modifier() == Date.MOD_SPAN): + elif self.date2.is_compound(): _repr = trans_text("age|about") + " " + fdate12p1 - elif (self.date1.get_modifier() == Date.MOD_RANGE or - self.date1.get_modifier() == Date.MOD_SPAN): # SPAN---------------------------- + elif self.date1.is_compound(): if self.date2.get_modifier() == Date.MOD_NONE: start, stop = self.date1.get_start_stop_range() start = Date(*start) @@ -371,8 +360,7 @@ class Span(object): _repr = trans_text("less than") + " " + fdate12 elif self.date2.get_modifier() == Date.MOD_ABOUT: _repr = trans_text("age|about") + " " + fdate12p1 - elif (self.date2.get_modifier() == Date.MOD_RANGE or - self.date2.get_modifier() == Date.MOD_SPAN): + elif self.date2.is_compound(): start1, stop1 = self.date1.get_start_stop_range() start2, stop2 = self.date2.get_start_stop_range() start1 = Date(*start1) @@ -1045,7 +1033,7 @@ class Date(object): (self.dateval[Date._POS_YR]) % 10, self.dateval[Date._POS_MON], self.dateval[Date._POS_DAY]) - elif self.modifier == Date.MOD_RANGE or self.modifier == Date.MOD_SPAN: + elif self.is_compound(): val = "%04d-%02d-%02d - %04d-%02d-%02d" % ( self.dateval[Date._POS_YR], self.dateval[Date._POS_MON], self.dateval[Date._POS_DAY], self.dateval[Date._POS_RYR], @@ -1208,7 +1196,7 @@ class Date(object): of (0, 0, 0, False) is returned. Otherwise, a date of (DD, MM, YY, slash) is returned. If slash is True, then the date is in the form of 1530/1. """ - if self.modifier == Date.MOD_RANGE or self.modifier == Date.MOD_SPAN: + if self.is_compound(): val = self.dateval[4:8] else: val = Date.EMPTY @@ -1238,7 +1226,7 @@ class Date(object): """ Return the item specified. """ - if self.modifier == Date.MOD_SPAN or self.modifier == Date.MOD_RANGE: + if self.is_compound(): val = self.dateval[index] else: val = 0 @@ -1285,89 +1273,91 @@ class Date(object): """ self.newyear = value - def set_yr_mon_day(self, year, month, day): + def __set_yr_mon_day(self, year, month, day, pos_yr, pos_mon, pos_day): + dv = list(self.dateval) + dv[pos_yr] = year + dv[pos_mon] = month + dv[pos_day] = day + self.dateval = tuple(dv) + + def set_yr_mon_day(self, year, month, day, remove_stop_date = None): """ Set the year, month, and day values. + + @param remove_stop_date Required parameter for a compound date. + When True, the stop date is changed to the same date as well. + When False, the stop date is not changed. """ - dv = list(self.dateval) - dv[Date._POS_YR] = year - dv[Date._POS_MON] = month - dv[Date._POS_DAY] = day - self.dateval = tuple(dv) + if self.is_compound() and remove_stop_date is None: + raise DateError("Required parameter remove_stop_date not set!") + + self.__set_yr_mon_day(year, month, day, + Date._POS_YR, Date._POS_MON, Date._POS_DAY) self._calc_sort_value() + if remove_stop_date and self.is_compound(): + self.set2_yr_mon_day(year, month, day) + + def _assert_compound(self): + if not self.is_compound(): + raise DateError("Operation allowed for compound dates only!") def set2_yr_mon_day(self, year, month, day): """ - Set the year, month, and day values. + Set the year, month, and day values in the 2nd part of + a compound date (range or span). """ + self._assert_compound() + self.__set_yr_mon_day(year, month, day, + Date._POS_RYR, Date._POS_RMON, Date._POS_RDAY) + + def __set_yr_mon_day_offset(self, year, month, day, pos_yr, pos_mon, pos_day): dv = list(self.dateval) - dv[Date._POS_RYR] = year - dv[Date._POS_RMON] = month - dv[Date._POS_RDAY] = day + if dv[pos_yr]: + dv[pos_yr] += year + elif year: + dv[pos_yr] = year + if dv[pos_mon]: + dv[pos_mon] += month + elif month: + if month < 0: + dv[pos_mon] = 1 + month + else: + dv[pos_mon] = month + # Fix if month out of bounds: + if month != 0: # only check if changed + if dv[pos_mon] == 0: # subtraction + dv[pos_mon] = 12 + dv[pos_yr] -= 1 + elif dv[pos_mon] < 0: # subtraction + dv[pos_yr] -= int((-dv[pos_mon]) // 12) + 1 + dv[pos_mon] = (dv[pos_mon] % 12) + elif dv[pos_mon] > 12 or dv[pos_mon] < 1: + dv[pos_yr] += int(dv[pos_mon] // 12) + dv[pos_mon] = dv[pos_mon] % 12 self.dateval = tuple(dv) + self._calc_sort_value() + return (day != 0 or dv[pos_day] > 28) def set_yr_mon_day_offset(self, year=0, month=0, day=0): """ - Set the year, month, and day values by offset. + Offset the date by the given year, month, and day values. """ - dv = list(self.dateval) - if dv[Date._POS_YR]: - dv[Date._POS_YR] += year - elif year: - dv[Date._POS_YR] = year - if dv[Date._POS_MON]: - dv[Date._POS_MON] += month - elif month: - if month < 0: - dv[Date._POS_MON] = 1 + month - else: - dv[Date._POS_MON] = month - # Fix if month out of bounds: - if month != 0: # only check if changed - if dv[Date._POS_MON] == 0: # subtraction - dv[Date._POS_MON] = 12 - dv[Date._POS_YR] -= 1 - elif dv[Date._POS_MON] < 0: # subtraction - dv[Date._POS_YR] -= int((-dv[Date._POS_MON]) // 12) + 1 - dv[Date._POS_MON] = (dv[Date._POS_MON] % 12) - elif dv[Date._POS_MON] > 12 or dv[Date._POS_MON] < 1: - dv[Date._POS_YR] += int(dv[Date._POS_MON] // 12) - dv[Date._POS_MON] = dv[Date._POS_MON] % 12 - self.dateval = tuple(dv) - self._calc_sort_value() - if day != 0 or dv[Date._POS_DAY] > 28: - self.set_yr_mon_day(*self.offset(day)) + if self.__set_yr_mon_day_offset(year, month, day, + Date._POS_YR, Date._POS_MON, Date._POS_DAY): + self.set_yr_mon_day(*self.offset(day), remove_stop_date = False) + if self.is_compound(): + self.set2_yr_mon_day_offset(year, month, day) def set2_yr_mon_day_offset(self, year=0, month=0, day=0): """ - Set the year, month, and day values by offset. + Set the year, month, and day values by offset in the 2nd part + of a compound date (range or span). """ - dv = list(self.dateval) - if dv[Date._POS_RYR]: - dv[Date._POS_RYR] += year - elif year: - dv[Date._POS_RYR] = year - if dv[Date._POS_RMON]: - dv[Date._POS_RMON] += month - elif month: - if month < 0: - dv[Date._POS_RMON] = 1 + month - else: - dv[Date._POS_RMON] = month - # Fix if month out of bounds: - if month != 0: # only check if changed - if dv[Date._POS_RMON] == 0: # subtraction - dv[Date._POS_RMON] = 12 - dv[Date._POS_RYR] -= 1 - elif dv[Date._POS_RMON] < 0: # subtraction - dv[Date._POS_RYR] -= int((-dv[Date._POS_RMON]) / 12) + 1 - dv[Date._POS_RMON] = (dv[Date._POS_RMON] % 12) - elif dv[Date._POS_RMON] > 12 or dv[Date._POS_RMON] < 1: - dv[Date._POS_RYR] += int(dv[Date._POS_RMON] / 12) - dv[Date._POS_RMON] = dv[Date._POS_RMON] % 12 - self.dateval = tuple(dv) - if day != 0 or dv[Date._POS_RDAY] > 28: - self.set2_yr_mon_day(*self.offset(day)) + self._assert_compound() + if self.__set_yr_mon_day_offset(year, month, day, + Date._POS_RYR, Date._POS_RMON, Date._POS_RDAY): + stop = Date(self.get_stop_ymd()) + self.set2_yr_mon_day(*stop.offset(day)) def copy_offset_ymd(self, year=0, month=0, day=0): """ @@ -1380,24 +1370,19 @@ class Date(object): new_date = self retval = Date(new_date) retval.set_yr_mon_day_offset(year, month, day) - if (self.get_modifier() == Date.MOD_RANGE or - self.get_modifier() == Date.MOD_SPAN): - retval.set2_yr_mon_day_offset(year, month, day) if orig_cal == 0: return retval else: retval.convert_calendar(orig_cal) return retval - def copy_ymd(self, year=0, month=0, day=0): + def copy_ymd(self, year=0, month=0, day=0, remove_stop_date=None): """ Return a Date copy with year, month, and day set. + @param remove_stop_date Same as in set_yr_mon_day. """ retval = Date(self) - retval.set_yr_mon_day(year, month, day) - if (self.get_modifier() == Date.MOD_RANGE or - self.get_modifier() == Date.MOD_SPAN): - retval.set2_yr_mon_day_offset(year, month, day) + retval.set_yr_mon_day(year, month, day, remove_stop_date) return retval def set_year(self, year): @@ -1502,30 +1487,52 @@ class Date(object): """ return self.text - def set(self, quality, modifier, calendar, value, text=None, - newyear=0): + def _zero_adjust_ymd(self, y, m, d): + year = y if y != 0 else 1 + month = max(m, 1) + day = max(d, 1) + return (year, month, day) + + def set(self, quality=None, modifier=None, calendar=None, + value=None, + text=None, newyear=0): """ Set the date to the specified value. Parameters are:: quality - The date quality for the date (see get_quality - for more information) + for more information). + Defaults to the previous value for the date. modified - The date modifier for the date (see get_modifier for more information) + Defaults to the previous value for the date. calendar - The calendar associated with the date (see get_calendar for more information). + Defaults to the previous value for the date. value - A tuple representing the date information. For a non-compound date, the format is (DD, MM, YY, slash) and for a compound date the tuple stores data as (DD, MM, YY, slash1, DD, MM, YY, slash2) + Defaults to the previous value for the date. text - A text string holding either the verbatim user input or a comment relating to the date. + Defaults to the previous value for the date. newyear - The newyear code, or tuple representing (month, day) - of newyear day. + of newyear day. + Defaults to 0. The sort value is recalculated. """ + + if quality is None: + quality = self.quality + if modifier is None: + modifier = self.modifier + if calendar is None: + calendar = self.calendar + if value is None: + value = self.value if modifier in (Date.MOD_NONE, Date.MOD_BEFORE, Date.MOD_AFTER, Date.MOD_ABOUT) and len(value) < 4: @@ -1551,11 +1558,12 @@ class Date(object): self.calendar = calendar self.dateval = value self.set_new_year(newyear) - year = max(value[Date._POS_YR], 1) - month = max(value[Date._POS_MON], 1) - day = max(value[Date._POS_DAY], 1) + year, month, day = self._zero_adjust_ymd( + value[Date._POS_YR], + value[Date._POS_MON], + value[Date._POS_DAY]) - if year == month == 0 and day == 0: + if year == month == day == 0: self.sortval = 0 else: func = Date._calendar_convert[calendar] @@ -1593,6 +1601,32 @@ class Date(object): d2.set_calendar(self.calendar) d2_val = d2.sortval self.sortval += (d1_val - d2_val) + 1 + + if modifier != Date.MOD_TEXTONLY: + sanity = Date(self) + sanity.convert_calendar(self.calendar, known_valid = False) + # We don't do the roundtrip conversion on self, becaue + # it would remove uncertainty on day/month expressed with zeros + + # Did the roundtrip change the date value?! + if sanity.dateval != value: + # Maybe it is OK because of undetermined value adjustment? + zl = zip(sanity.dateval, value) + # Loop over all values present, whether compound or not + for d,m,y,sl in zip(*[iter(zl)]*4): + # each of d,m,y,sl is a pair from dateval and value, to compare + + for adjusted,original in d,m: + if adjusted != original and not(original == 0 and adjusted == 1): + raise DateError("Invalid day/month {} passed in value {}". + format(original, value)) + + adjusted,original = y + if adjusted != original: + raise DateError("Invalid year {} passed in value {}". + format(original, value)) + + # ignore slash difference if text: self.text = text @@ -1609,26 +1643,30 @@ class Date(object): """ Calculate the numerical sort value associated with the date. """ - year = max(self.dateval[Date._POS_YR], 1) - month = max(self.dateval[Date._POS_MON], 1) - day = max(self.dateval[Date._POS_DAY], 1) + year, month, day = self._zero_adjust_ymd( + self.dateval[Date._POS_YR], + self.dateval[Date._POS_MON], + self.dateval[Date._POS_DAY]) if year == month == 0 and day == 0: self.sortval = 0 else: func = Date._calendar_convert[self.calendar] self.sortval = func(year, month, day) - def convert_calendar(self, calendar): + def convert_calendar(self, calendar, known_valid=True): """ Convert the date from the current calendar to the specified calendar. """ - if calendar == self.calendar and self.newyear == Date.NEWYEAR_JAN1: + if (known_valid # if not known valid, round-trip convert anyway + and calendar == self.calendar + and self.newyear == Date.NEWYEAR_JAN1): return (year, month, day) = Date._calendar_change[calendar](self.sortval) if self.is_compound(): - ryear = max(self.dateval[Date._POS_RYR], 1) - rmonth = max(self.dateval[Date._POS_RMON], 1) - rday = max(self.dateval[Date._POS_RDAY], 1) + ryear, rmonth, rday = self._zero_adjust_ymd( + self.dateval[Date._POS_RYR], + self.dateval[Date._POS_RMON], + self.dateval[Date._POS_RDAY]) sdn = Date._calendar_convert[self.calendar](ryear, rmonth, rday) (nyear, nmonth, nday) = Date._calendar_change[calendar](sdn) self.dateval = (day, month, year, False, @@ -1692,6 +1730,12 @@ class Date(object): """ return (self.get_year(), self.get_month(), self.get_day()) + def get_stop_ymd(self): + """ + Return (year, month, day) of the stop date, or all-zeros if it's not defined. + """ + return (self.get_stop_year(), self.get_stop_month(), self.get_stop_day()) + def offset(self, value): """ Return (year, month, day) of this date +- value. diff --git a/gramps/gen/lib/test/date_test.py b/gramps/gen/lib/test/date_test.py index ae6e374bb..6beb78869 100644 --- a/gramps/gen/lib/test/date_test.py +++ b/gramps/gen/lib/test/date_test.py @@ -38,7 +38,7 @@ from ...config import config from ...datehandler import get_date_formats, set_format from ...datehandler import parser as _dp from ...datehandler import displayer as _dd -from ...lib.date import Date +from ...lib.date import Date, DateError date_tests = {} @@ -193,7 +193,7 @@ for calendar in (Date.CAL_JULIAN, d.set(quality,modifier,calendar,(4,month,1789,False),"Text comment") dates.append( d) -for calendar in (Date.CAL_HEBREW, Date.CAL_FRENCH): +for calendar in (Date.CAL_HEBREW, Date.CAL_HEBREW): for month in range(1,14): d = Date() d.set(quality,modifier,calendar,(4,month,1789,False),"Text comment") @@ -422,6 +422,81 @@ class SwedishDateTest(BaseDateTest): self.assertEqual(date.sortval, date.to_calendar('gregorian').sortval) +class Test_set2(BaseDateTest): + """ + Test the Date.set2_... setters -- the ones to manipulate the 2nd date + of a compound date + """ + def setUp(self): + self.date = d = Date() + d.set(modifier=Date.MOD_RANGE, + #d m y sl--d m y sl + value=(1, 1, 2000, 0, 1, 1, 2010, 0)) + + def testStartStopSanity(self): + start,stop = self.date.get_start_stop_range() + self.assertEqual(start, (2000, 1, 1)) + self.assertEqual(stop, (2010, 1, 1)) + + def test_set2_ymd_overrides_stop_date(self): + self.date.set2_yr_mon_day(2013, 2, 2) + start,stop = self.date.get_start_stop_range() + self.assertEqual(start, (2000, 1, 1)) + self.assertEqual(stop, (2013, 2, 2)) + + def test_set_ymd_overrides_both_dates(self): + self.date.set_yr_mon_day(2013, 2, 2, remove_stop_date = True) + start,stop = self.date.get_start_stop_range() + self.assertEqual(start, stop) + self.assertEqual(stop, (2013, 2, 2)) + + def test_set_ymd_offset_updates_both_ends(self): + self.date.set_yr_mon_day_offset(+2, +2, +2) + start,stop = self.date.get_start_stop_range() + self.assertEqual(start, (2002, 3, 3)) + self.assertEqual(stop, (2012, 3, 3)) + + def test_set2_ymd_offset_updates_stop_date(self): + self.date.set2_yr_mon_day_offset(+7, +5, +5) + start,stop = self.date.get_start_stop_range() + self.assertEqual(start, (2000, 1, 1)) + self.assertEqual(stop, (2017, 6, 6)) + + def test_copy_offset_ymd_preserves_orig(self): + copied = self.date.copy_offset_ymd(year=-1) + self.testStartStopSanity() + start,stop = copied.get_start_stop_range() + self.assertEqual(start, (1999, 1, 1)) + self.assertEqual(stop, (2009, 1, 1)) + + def test_copy_ymd_preserves_orig(self): + copied = self.date.copy_ymd(year=1000, month=10, day=10, + remove_stop_date=True) + self.testStartStopSanity() + start,stop = copied.get_start_stop_range() + self.assertEqual(start, (1000, 10, 10)) + self.assertEqual(stop, (1000, 10, 10)) + + def _test_set2_function_raises_error_unless_compound(self, function): + for mod in (Date.MOD_NONE, Date.MOD_BEFORE, Date.MOD_AFTER, + Date.MOD_ABOUT, + Date.MOD_TEXTONLY): + self.date.set_modifier(mod) + try: + function(self.date) + self.assertTrue(False, + "Modifier: {}, dateval: {} - exception expected!".format( + mod, self.date.dateval)) + except DateError: + pass + + def test_set2_ymd_raises_error_unless_compound(self): + self._test_set2_function_raises_error_unless_compound( + lambda date: date.set2_yr_mon_day(2013, 2, 2)) + + def test_set2_ymd_offset_raises_error_unless_compound(self): + self._test_set2_function_raises_error_unless_compound( + lambda date: date.set2_yr_mon_day_offset(year=-1)) if __name__ == "__main__": unittest.main()