From 3f3fc5a84dd596515236f8cd0eb6433549041f12 Mon Sep 17 00:00:00 2001 From: Vassilii Khachaturov Date: Wed, 18 Sep 2013 09:55:54 +0000 Subject: [PATCH] 7034,7045,7065,7066: back merge from trunk Note: need to disable CAL.FRENCH in gramps_test.py, otherwise they fail due to #7068 svn: r23159 --- src/gen/lib/calendar.py | 80 +++++----- src/gen/lib/date.py | 276 ++++++++++++++++++++-------------- src/gen/lib/test/date_test.py | 78 +++++++++- 3 files changed, 283 insertions(+), 151 deletions(-) diff --git a/src/gen/lib/calendar.py b/src/gen/lib/calendar.py index 7d963fa2d..bd25d4b77 100644 --- a/src/gen/lib/calendar.py +++ b/src/gen/lib/calendar.py @@ -23,7 +23,7 @@ """ Provide calendar to sdn (serial date number) conversion. """ -from __future__ import division +from __future__ import division, print_function #------------------------------------------------------------------------- # # Python modules @@ -45,14 +45,15 @@ _JLN_SDN_OFFSET = 32083 _JLN_DAYS_PER_5_MONTHS = 153 _JLN_DAYS_PER_4_YEARS = 1461 +_HBR_HALAKIM_PER_HOUR = 1080 _HBR_HALAKIM_PER_DAY = 25920 -_HBR_HALAKIM_PER_LUNAR_CYCLE = 765433 -_HBR_HALAKIM_PER_METONIC_CYCLE = 179876755 +_HBR_HALAKIM_PER_LUNAR_CYCLE = 29 * _HBR_HALAKIM_PER_DAY + 13753 +_HBR_HALAKIM_PER_METONIC_CYCLE = _HBR_HALAKIM_PER_LUNAR_CYCLE * (12 * 19 + 7) _HBR_SDN_OFFSET = 347997 _HBR_NEW_MOON_OF_CREATION = 31524 -_HBR_NOON = 19440 -_HBR_AM3_11_20 = 9924 -_HBR_AM9_32_43 = 16789 +_HBR_NOON = 18 * _HBR_HALAKIM_PER_HOUR +_HBR_AM3_11_20 = (9 * _HBR_HALAKIM_PER_HOUR) + 204 +_HBR_AM9_32_43 = (15 * _HBR_HALAKIM_PER_HOUR) + 589 _HBR_SUNDAY = 0 _HBR_MONDAY = 1 @@ -122,9 +123,9 @@ def _tishri_molad(input_day): # really quite close. while molad_day < (input_day - 6940 + 310): - metonic_cycle = metonic_cycle + 1 - molad_halakim = molad_halakim + _HBR_HALAKIM_PER_METONIC_CYCLE - molad_day = molad_day + ( molad_halakim // _HBR_HALAKIM_PER_DAY) + metonic_cycle += 1 + molad_halakim += _HBR_HALAKIM_PER_METONIC_CYCLE + molad_day += molad_halakim // _HBR_HALAKIM_PER_DAY molad_halakim = molad_halakim % _HBR_HALAKIM_PER_DAY # Find the molad of Tishri closest to this date. @@ -133,12 +134,11 @@ def _tishri_molad(input_day): if molad_day > input_day - 74: break - molad_halakim = molad_halakim + (_HBR_HALAKIM_PER_LUNAR_CYCLE - * _HBR_MONTHS_PER_YEAR[metonic_year]) - molad_day = molad_day + (molad_halakim // _HBR_HALAKIM_PER_DAY) + molad_halakim += (_HBR_HALAKIM_PER_LUNAR_CYCLE + * _HBR_MONTHS_PER_YEAR[metonic_year]) + molad_day += molad_halakim // _HBR_HALAKIM_PER_DAY molad_halakim = molad_halakim % _HBR_HALAKIM_PER_DAY - else: - metonic_year += 1 + return (metonic_cycle, metonic_year, molad_day, molad_halakim) def _molad_of_metonic_cycle(metonic_cycle): @@ -161,10 +161,10 @@ def _molad_of_metonic_cycle(metonic_cycle): # will be in d1. d2 = r2 // _HBR_HALAKIM_PER_DAY - r2 = r2 - (d2 * _HBR_HALAKIM_PER_DAY) + r2 -= d2 * _HBR_HALAKIM_PER_DAY r1 = (r2 << 16) | (r1 & 0xFFFF) d1 = r1 // _HBR_HALAKIM_PER_DAY - r1 = r1 - ( d1 * _HBR_HALAKIM_PER_DAY) + r1 -= d1 * _HBR_HALAKIM_PER_DAY molad_day = (d2 << 16) | d1 molad_halakim = r1 @@ -261,9 +261,10 @@ def hebrew_sdn(year, month, day): return sdn + _HBR_SDN_OFFSET def hebrew_ymd(sdn): - """Convert an SDN number to a Julian calendar date.""" + """Convert an SDN number to a Hebrew calendar date.""" input_day = sdn - _HBR_SDN_OFFSET + # TODO if input_day <= 0, the result is a date invalid in Hebrew calendar! (metonic_cycle, metonic_year, day, halakim) = _tishri_molad(input_day) tishri1 = _tishri1(metonic_year, day, halakim) @@ -284,9 +285,9 @@ def hebrew_ymd(sdn): # We need the length of the year to figure this out, so find # Tishri 1 of the next year. - halakim = halakim + (_HBR_HALAKIM_PER_LUNAR_CYCLE - * _HBR_MONTHS_PER_YEAR[metonic_year]) - day = day + (halakim // _HBR_HALAKIM_PER_DAY) + halakim += (_HBR_HALAKIM_PER_LUNAR_CYCLE + * _HBR_MONTHS_PER_YEAR[metonic_year]) + day += halakim // _HBR_HALAKIM_PER_DAY halakim = halakim % _HBR_HALAKIM_PER_DAY tishri1_after = _tishri1((metonic_year + 1) % 19, day, halakim) else: @@ -320,24 +321,24 @@ def hebrew_ymd(sdn): day = input_day - tishri1 + 207 if day > 0: return (year, month, day) - month = month - 1 - day = day + 30 + month -= 1 + day += 30 if day > 0: return (year, month, day) - month = month - 1 - day = day + 30 + month -= 1 + day += 30 else: month = 6 day = input_day - tishri1 + 207 if day > 0: return (year, month, day) - month = month - 1 - day = day + 30 + month -= 1 + day += 30 if day > 0: return (year, month, day) - month = month - 1 - day = day + 29 + month -= 1 + day += 29 if day > 0: return (year, month, day) @@ -348,25 +349,23 @@ def hebrew_ymd(sdn): tishri1 = _tishri1(metonic_year, day, halakim) year_length = tishri1_after - tishri1 - cday = input_day - tishri1 - 29 + day = input_day - tishri1 - 29 if year_length == 355 or year_length == 385 : # Heshvan has 30 days if day <= 30: month = 2 - day = cday return (year, month, day) - day = day - 30 + day -= 30 else: # Heshvan has 29 days if day <= 29: month = 2 - day = cday return (year, month, day) - cday = cday - 29 + day -= 29 # It has to be Kislev - return (year, 3, cday) + return (year, 3, day) def julian_sdn(year, month, day): """Convert a Julian calendar date to an SDN number.""" @@ -573,3 +572,16 @@ def swedish_ymd(sdn): return gregorian_ymd(sdn) else: return julian_ymd(sdn) + +try: + import sdn + + hebrew_ymd = sdn.SdnToJewish # Fix bug# 7066 + hebrew_sdn = sdn.JewishToSdn + #TODO maybe alias the other local invented wheels to Calendar convertors + +except ImportError: + import logging + LOG = logging.getLogger(".calendar") + LOG.warn("sdn not available. " + "Install Calendar with pypi for native Hebrew calendar calculations.") diff --git a/src/gen/lib/date.py b/src/gen/lib/date.py index 852e491d8..ec3467238 100644 --- a/src/gen/lib/date.py +++ b/src/gen/lib/date.py @@ -125,8 +125,7 @@ class Span(object): self.sort = (v, -Span.ABOUT) self.minmax = (v - Span.ABOUT, v + Span.ABOUT) #self.repr = "about " + self._format(self._diff(self.date1, self.date2)) - 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) @@ -157,8 +156,7 @@ class Span(object): self.sort = (v, -Span.ABOUT) self.minmax = (v - Span.ABOUT, v + Span.ABOUT) #self.repr = "about " + self._format(self._diff(self.date1, self.date2)) - 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) @@ -184,8 +182,7 @@ class Span(object): self.sort = (v, -Span.ABOUT) self.minmax = (v - Span.ABOUT, v + Span.AFTER) #self.repr = "more than about " + self._format(self._diff(self.date1, self.date2)) - 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) @@ -211,14 +208,12 @@ class Span(object): self.sort = (v, -Span.ABOUT) self.minmax = (v - Span.ABOUT, v + Span.ABOUT) #self.repr = "about " + self._format(self._diff(self.date1, self.date2)) - 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) #self.repr = "about " + self._format(self._diff(self.date1, self.date2)) - 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) @@ -244,8 +239,7 @@ class Span(object): self.sort = (v, -Span.ABOUT) self.minmax = (v - Span.ABOUT, v + Span.ABOUT) #self.repr = "about " + self._format(self._diff(self.date1, self.date2)) - 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) @@ -319,8 +313,7 @@ class Span(object): #self.minmax = (v - Span.ABOUT, v + Span.ABOUT) # TO_FIX: bug #5293 ! self.repr = _("age|about") + " " + self._format(self._diff(self.date1, self.date2)).format(precision=1) - 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) @@ -351,8 +344,7 @@ class Span(object): #self.sort = (v, -Span.ABOUT) #self.minmax = (v - Span.ABOUT, v + Span.ABOUT) self.repr = _("less than about") + " " + self._format(self._diff(self.date1, self.date2)) - 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) @@ -378,8 +370,7 @@ class Span(object): #self.sort = (v, -Span.ABOUT) #self.minmax = (v - Span.ABOUT, v + Span.AFTER) self.repr = _("more than about") + " " + self._format(self._diff(self.date1, self.date2)).format(precision=1) - 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) @@ -405,14 +396,12 @@ class Span(object): #self.sort = (v, -Span.ABOUT) #self.minmax = (v - Span.ABOUT, v + Span.ABOUT) self.repr = _("age|about") + " " + self._format(self._diff(self.date1, self.date2)).format(precision=1) - 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) self.repr = _("age|about") + " " + self._format(self._diff(self.date1, self.date2)).format(precision=1) - 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) @@ -438,8 +427,7 @@ class Span(object): #self.sort = (v, -Span.ABOUT) #self.minmax = (v - Span.ABOUT, v + Span.ABOUT) self.repr = _("age|about") + " " + self._format(self._diff(self.date1, self.date2)).format(precision=1) - 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) @@ -1039,7 +1027,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], @@ -1202,7 +1190,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 @@ -1232,7 +1220,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 @@ -1279,89 +1267,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): """ @@ -1374,24 +1364,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): @@ -1496,30 +1481,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: @@ -1545,11 +1552,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] @@ -1587,6 +1595,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 @@ -1603,26 +1637,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, @@ -1686,6 +1724,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/src/gen/lib/test/date_test.py b/src/gen/lib/test/date_test.py index 838094438..f81d986e2 100644 --- a/src/gen/lib/test/date_test.py +++ b/src/gen/lib/test/date_test.py @@ -49,7 +49,7 @@ import config import DateHandler from DateHandler import parser as _dp from DateHandler import displayer as _dd -from gen.lib.date import Date, Span +from gen.lib.date import Date, Span, DateError gettext.textdomain("gramps") gettext.install("gramps",loc,unicode=1) @@ -444,6 +444,82 @@ def suite4(): count += 1 return suite +class Test_set2(unittest.TestCase): + """ + 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.TextTestRunner().run(suite()) unittest.TextTestRunner().run(suite2())