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
This commit is contained in:
Vassilii Khachaturov 2013-09-18 09:55:54 +00:00
parent 03e800d808
commit 3f3fc5a84d
3 changed files with 283 additions and 151 deletions

View File

@ -23,7 +23,7 @@
""" """
Provide calendar to sdn (serial date number) conversion. Provide calendar to sdn (serial date number) conversion.
""" """
from __future__ import division from __future__ import division, print_function
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# #
# Python modules # Python modules
@ -45,14 +45,15 @@ _JLN_SDN_OFFSET = 32083
_JLN_DAYS_PER_5_MONTHS = 153 _JLN_DAYS_PER_5_MONTHS = 153
_JLN_DAYS_PER_4_YEARS = 1461 _JLN_DAYS_PER_4_YEARS = 1461
_HBR_HALAKIM_PER_HOUR = 1080
_HBR_HALAKIM_PER_DAY = 25920 _HBR_HALAKIM_PER_DAY = 25920
_HBR_HALAKIM_PER_LUNAR_CYCLE = 765433 _HBR_HALAKIM_PER_LUNAR_CYCLE = 29 * _HBR_HALAKIM_PER_DAY + 13753
_HBR_HALAKIM_PER_METONIC_CYCLE = 179876755 _HBR_HALAKIM_PER_METONIC_CYCLE = _HBR_HALAKIM_PER_LUNAR_CYCLE * (12 * 19 + 7)
_HBR_SDN_OFFSET = 347997 _HBR_SDN_OFFSET = 347997
_HBR_NEW_MOON_OF_CREATION = 31524 _HBR_NEW_MOON_OF_CREATION = 31524
_HBR_NOON = 19440 _HBR_NOON = 18 * _HBR_HALAKIM_PER_HOUR
_HBR_AM3_11_20 = 9924 _HBR_AM3_11_20 = (9 * _HBR_HALAKIM_PER_HOUR) + 204
_HBR_AM9_32_43 = 16789 _HBR_AM9_32_43 = (15 * _HBR_HALAKIM_PER_HOUR) + 589
_HBR_SUNDAY = 0 _HBR_SUNDAY = 0
_HBR_MONDAY = 1 _HBR_MONDAY = 1
@ -122,9 +123,9 @@ def _tishri_molad(input_day):
# really quite close. # really quite close.
while molad_day < (input_day - 6940 + 310): while molad_day < (input_day - 6940 + 310):
metonic_cycle = metonic_cycle + 1 metonic_cycle += 1
molad_halakim = molad_halakim + _HBR_HALAKIM_PER_METONIC_CYCLE molad_halakim += _HBR_HALAKIM_PER_METONIC_CYCLE
molad_day = molad_day + ( molad_halakim // _HBR_HALAKIM_PER_DAY) molad_day += molad_halakim // _HBR_HALAKIM_PER_DAY
molad_halakim = molad_halakim % _HBR_HALAKIM_PER_DAY molad_halakim = molad_halakim % _HBR_HALAKIM_PER_DAY
# Find the molad of Tishri closest to this date. # Find the molad of Tishri closest to this date.
@ -133,12 +134,11 @@ def _tishri_molad(input_day):
if molad_day > input_day - 74: if molad_day > input_day - 74:
break break
molad_halakim = molad_halakim + (_HBR_HALAKIM_PER_LUNAR_CYCLE molad_halakim += (_HBR_HALAKIM_PER_LUNAR_CYCLE
* _HBR_MONTHS_PER_YEAR[metonic_year]) * _HBR_MONTHS_PER_YEAR[metonic_year])
molad_day = molad_day + (molad_halakim // _HBR_HALAKIM_PER_DAY) molad_day += molad_halakim // _HBR_HALAKIM_PER_DAY
molad_halakim = 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) return (metonic_cycle, metonic_year, molad_day, molad_halakim)
def _molad_of_metonic_cycle(metonic_cycle): def _molad_of_metonic_cycle(metonic_cycle):
@ -161,10 +161,10 @@ def _molad_of_metonic_cycle(metonic_cycle):
# will be in d1. # will be in d1.
d2 = r2 // _HBR_HALAKIM_PER_DAY d2 = r2 // _HBR_HALAKIM_PER_DAY
r2 = r2 - (d2 * _HBR_HALAKIM_PER_DAY) r2 -= d2 * _HBR_HALAKIM_PER_DAY
r1 = (r2 << 16) | (r1 & 0xFFFF) r1 = (r2 << 16) | (r1 & 0xFFFF)
d1 = r1 // _HBR_HALAKIM_PER_DAY 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_day = (d2 << 16) | d1
molad_halakim = r1 molad_halakim = r1
@ -261,9 +261,10 @@ def hebrew_sdn(year, month, day):
return sdn + _HBR_SDN_OFFSET return sdn + _HBR_SDN_OFFSET
def hebrew_ymd(sdn): 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 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) (metonic_cycle, metonic_year, day, halakim) = _tishri_molad(input_day)
tishri1 = _tishri1(metonic_year, day, halakim) 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 # We need the length of the year to figure this out, so find
# Tishri 1 of the next year. # Tishri 1 of the next year.
halakim = halakim + (_HBR_HALAKIM_PER_LUNAR_CYCLE halakim += (_HBR_HALAKIM_PER_LUNAR_CYCLE
* _HBR_MONTHS_PER_YEAR[metonic_year]) * _HBR_MONTHS_PER_YEAR[metonic_year])
day = day + (halakim // _HBR_HALAKIM_PER_DAY) day += halakim // _HBR_HALAKIM_PER_DAY
halakim = halakim % _HBR_HALAKIM_PER_DAY halakim = halakim % _HBR_HALAKIM_PER_DAY
tishri1_after = _tishri1((metonic_year + 1) % 19, day, halakim) tishri1_after = _tishri1((metonic_year + 1) % 19, day, halakim)
else: else:
@ -320,24 +321,24 @@ def hebrew_ymd(sdn):
day = input_day - tishri1 + 207 day = input_day - tishri1 + 207
if day > 0: if day > 0:
return (year, month, day) return (year, month, day)
month = month - 1 month -= 1
day = day + 30 day += 30
if day > 0: if day > 0:
return (year, month, day) return (year, month, day)
month = month - 1 month -= 1
day = day + 30 day += 30
else: else:
month = 6 month = 6
day = input_day - tishri1 + 207 day = input_day - tishri1 + 207
if day > 0: if day > 0:
return (year, month, day) return (year, month, day)
month = month - 1 month -= 1
day = day + 30 day += 30
if day > 0: if day > 0:
return (year, month, day) return (year, month, day)
month = month - 1 month -= 1
day = day + 29 day += 29
if day > 0: if day > 0:
return (year, month, day) return (year, month, day)
@ -348,25 +349,23 @@ def hebrew_ymd(sdn):
tishri1 = _tishri1(metonic_year, day, halakim) tishri1 = _tishri1(metonic_year, day, halakim)
year_length = tishri1_after - tishri1 year_length = tishri1_after - tishri1
cday = input_day - tishri1 - 29 day = input_day - tishri1 - 29
if year_length == 355 or year_length == 385 : if year_length == 355 or year_length == 385 :
# Heshvan has 30 days # Heshvan has 30 days
if day <= 30: if day <= 30:
month = 2 month = 2
day = cday
return (year, month, day) return (year, month, day)
day = day - 30 day -= 30
else: else:
# Heshvan has 29 days # Heshvan has 29 days
if day <= 29: if day <= 29:
month = 2 month = 2
day = cday
return (year, month, day) return (year, month, day)
cday = cday - 29 day -= 29
# It has to be Kislev # It has to be Kislev
return (year, 3, cday) return (year, 3, day)
def julian_sdn(year, month, day): def julian_sdn(year, month, day):
"""Convert a Julian calendar date to an SDN number.""" """Convert a Julian calendar date to an SDN number."""
@ -573,3 +572,16 @@ def swedish_ymd(sdn):
return gregorian_ymd(sdn) return gregorian_ymd(sdn)
else: else:
return julian_ymd(sdn) 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.")

View File

@ -125,8 +125,7 @@ class Span(object):
self.sort = (v, -Span.ABOUT) self.sort = (v, -Span.ABOUT)
self.minmax = (v - Span.ABOUT, v + Span.ABOUT) self.minmax = (v - Span.ABOUT, v + Span.ABOUT)
#self.repr = "about " + self._format(self._diff(self.date1, self.date2)) #self.repr = "about " + self._format(self._diff(self.date1, self.date2))
elif (self.date2.get_modifier() == Date.MOD_RANGE or elif self.date2.is_compound():
self.date2.get_modifier() == Date.MOD_SPAN):
start, stop = self.date2.get_start_stop_range() start, stop = self.date2.get_start_stop_range()
start = Date(*start) start = Date(*start)
stop = Date(*stop) stop = Date(*stop)
@ -157,8 +156,7 @@ class Span(object):
self.sort = (v, -Span.ABOUT) self.sort = (v, -Span.ABOUT)
self.minmax = (v - Span.ABOUT, v + Span.ABOUT) self.minmax = (v - Span.ABOUT, v + Span.ABOUT)
#self.repr = "about " + self._format(self._diff(self.date1, self.date2)) #self.repr = "about " + self._format(self._diff(self.date1, self.date2))
elif (self.date2.get_modifier() == Date.MOD_RANGE or elif self.date2.is_compound():
self.date2.get_modifier() == Date.MOD_SPAN):
v = self.date1.sortval - self.date2.sortval v = self.date1.sortval - self.date2.sortval
self.sort = (v, -Span.ABOUT) self.sort = (v, -Span.ABOUT)
self.minmax = (v - Span.ABOUT, v + Span.ABOUT) self.minmax = (v - Span.ABOUT, v + Span.ABOUT)
@ -184,8 +182,7 @@ class Span(object):
self.sort = (v, -Span.ABOUT) self.sort = (v, -Span.ABOUT)
self.minmax = (v - Span.ABOUT, v + Span.AFTER) self.minmax = (v - Span.ABOUT, v + Span.AFTER)
#self.repr = "more than about " + self._format(self._diff(self.date1, self.date2)) #self.repr = "more than about " + self._format(self._diff(self.date1, self.date2))
elif (self.date2.get_modifier() == Date.MOD_RANGE or elif self.date2.is_compound():
self.date2.get_modifier() == Date.MOD_SPAN):
v = self.date1.sortval - self.date2.sortval v = self.date1.sortval - self.date2.sortval
self.sort = (v, -Span.ABOUT) self.sort = (v, -Span.ABOUT)
self.minmax = (v - Span.ABOUT, v + Span.ABOUT) self.minmax = (v - Span.ABOUT, v + Span.ABOUT)
@ -211,14 +208,12 @@ class Span(object):
self.sort = (v, -Span.ABOUT) self.sort = (v, -Span.ABOUT)
self.minmax = (v - Span.ABOUT, v + Span.ABOUT) self.minmax = (v - Span.ABOUT, v + Span.ABOUT)
#self.repr = "about " + self._format(self._diff(self.date1, self.date2)) #self.repr = "about " + self._format(self._diff(self.date1, self.date2))
elif (self.date2.get_modifier() == Date.MOD_RANGE or elif self.date2.is_compound():
self.date2.get_modifier() == Date.MOD_SPAN):
v = self.date1.sortval - self.date2.sortval v = self.date1.sortval - self.date2.sortval
self.sort = (v, -Span.ABOUT) self.sort = (v, -Span.ABOUT)
self.minmax = (v - Span.ABOUT, v + Span.ABOUT) self.minmax = (v - Span.ABOUT, v + Span.ABOUT)
#self.repr = "about " + self._format(self._diff(self.date1, self.date2)) #self.repr = "about " + self._format(self._diff(self.date1, self.date2))
elif (self.date1.get_modifier() == Date.MOD_RANGE or elif self.date1.is_compound():
self.date1.get_modifier() == Date.MOD_SPAN): # SPAN----------------------------
if self.date2.get_modifier() == Date.MOD_NONE: if self.date2.get_modifier() == Date.MOD_NONE:
start, stop = self.date1.get_start_stop_range() start, stop = self.date1.get_start_stop_range()
start = Date(*start) start = Date(*start)
@ -244,8 +239,7 @@ class Span(object):
self.sort = (v, -Span.ABOUT) self.sort = (v, -Span.ABOUT)
self.minmax = (v - Span.ABOUT, v + Span.ABOUT) self.minmax = (v - Span.ABOUT, v + Span.ABOUT)
#self.repr = "about " + self._format(self._diff(self.date1, self.date2)) #self.repr = "about " + self._format(self._diff(self.date1, self.date2))
elif (self.date2.get_modifier() == Date.MOD_RANGE or elif self.date2.is_compound():
self.date2.get_modifier() == Date.MOD_SPAN):
start1, stop1 = self.date1.get_start_stop_range() start1, stop1 = self.date1.get_start_stop_range()
start2, stop2 = self.date2.get_start_stop_range() start2, stop2 = self.date2.get_start_stop_range()
start1 = Date(*start1) start1 = Date(*start1)
@ -319,8 +313,7 @@ class Span(object):
#self.minmax = (v - Span.ABOUT, v + Span.ABOUT) #self.minmax = (v - Span.ABOUT, v + Span.ABOUT)
# TO_FIX: bug #5293 ! # TO_FIX: bug #5293 !
self.repr = _("age|about") + " " + self._format(self._diff(self.date1, self.date2)).format(precision=1) self.repr = _("age|about") + " " + self._format(self._diff(self.date1, self.date2)).format(precision=1)
elif (self.date2.get_modifier() == Date.MOD_RANGE or elif self.date2.is_compound():
self.date2.get_modifier() == Date.MOD_SPAN):
start, stop = self.date2.get_start_stop_range() start, stop = self.date2.get_start_stop_range()
start = Date(*start) start = Date(*start)
stop = Date(*stop) stop = Date(*stop)
@ -351,8 +344,7 @@ class Span(object):
#self.sort = (v, -Span.ABOUT) #self.sort = (v, -Span.ABOUT)
#self.minmax = (v - Span.ABOUT, v + Span.ABOUT) #self.minmax = (v - Span.ABOUT, v + Span.ABOUT)
self.repr = _("less than about") + " " + self._format(self._diff(self.date1, self.date2)) self.repr = _("less than about") + " " + self._format(self._diff(self.date1, self.date2))
elif (self.date2.get_modifier() == Date.MOD_RANGE or elif self.date2.is_compound():
self.date2.get_modifier() == Date.MOD_SPAN):
#v = self.date1.sortval - self.date2.sortval #v = self.date1.sortval - self.date2.sortval
#self.sort = (v, -Span.ABOUT) #self.sort = (v, -Span.ABOUT)
#self.minmax = (v - Span.ABOUT, v + Span.ABOUT) #self.minmax = (v - Span.ABOUT, v + Span.ABOUT)
@ -378,8 +370,7 @@ class Span(object):
#self.sort = (v, -Span.ABOUT) #self.sort = (v, -Span.ABOUT)
#self.minmax = (v - Span.ABOUT, v + Span.AFTER) #self.minmax = (v - Span.ABOUT, v + Span.AFTER)
self.repr = _("more than about") + " " + self._format(self._diff(self.date1, self.date2)).format(precision=1) 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 elif self.date2.is_compound():
self.date2.get_modifier() == Date.MOD_SPAN):
#v = self.date1.sortval - self.date2.sortval #v = self.date1.sortval - self.date2.sortval
#self.sort = (v, -Span.ABOUT) #self.sort = (v, -Span.ABOUT)
#self.minmax = (v - Span.ABOUT, v + Span.ABOUT) #self.minmax = (v - Span.ABOUT, v + Span.ABOUT)
@ -405,14 +396,12 @@ class Span(object):
#self.sort = (v, -Span.ABOUT) #self.sort = (v, -Span.ABOUT)
#self.minmax = (v - Span.ABOUT, 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) self.repr = _("age|about") + " " + self._format(self._diff(self.date1, self.date2)).format(precision=1)
elif (self.date2.get_modifier() == Date.MOD_RANGE or elif self.date2.is_compound():
self.date2.get_modifier() == Date.MOD_SPAN):
#v = self.date1.sortval - self.date2.sortval #v = self.date1.sortval - self.date2.sortval
#self.sort = (v, -Span.ABOUT) #self.sort = (v, -Span.ABOUT)
#self.minmax = (v - Span.ABOUT, 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) self.repr = _("age|about") + " " + self._format(self._diff(self.date1, self.date2)).format(precision=1)
elif (self.date1.get_modifier() == Date.MOD_RANGE or elif self.date1.is_compound():
self.date1.get_modifier() == Date.MOD_SPAN): # SPAN----------------------------
if self.date2.get_modifier() == Date.MOD_NONE: if self.date2.get_modifier() == Date.MOD_NONE:
start, stop = self.date1.get_start_stop_range() start, stop = self.date1.get_start_stop_range()
start = Date(*start) start = Date(*start)
@ -438,8 +427,7 @@ class Span(object):
#self.sort = (v, -Span.ABOUT) #self.sort = (v, -Span.ABOUT)
#self.minmax = (v - Span.ABOUT, 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) self.repr = _("age|about") + " " + self._format(self._diff(self.date1, self.date2)).format(precision=1)
elif (self.date2.get_modifier() == Date.MOD_RANGE or elif self.date2.is_compound():
self.date2.get_modifier() == Date.MOD_SPAN):
start1, stop1 = self.date1.get_start_stop_range() start1, stop1 = self.date1.get_start_stop_range()
start2, stop2 = self.date2.get_start_stop_range() start2, stop2 = self.date2.get_start_stop_range()
start1 = Date(*start1) start1 = Date(*start1)
@ -1039,7 +1027,7 @@ class Date(object):
(self.dateval[Date._POS_YR]) % 10, (self.dateval[Date._POS_YR]) % 10,
self.dateval[Date._POS_MON], self.dateval[Date._POS_MON],
self.dateval[Date._POS_DAY]) 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" % ( val = "%04d-%02d-%02d - %04d-%02d-%02d" % (
self.dateval[Date._POS_YR], self.dateval[Date._POS_MON], self.dateval[Date._POS_YR], self.dateval[Date._POS_MON],
self.dateval[Date._POS_DAY], self.dateval[Date._POS_RYR], 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) 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. 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] val = self.dateval[4:8]
else: else:
val = Date.EMPTY val = Date.EMPTY
@ -1232,7 +1220,7 @@ class Date(object):
""" """
Return the item specified. Return the item specified.
""" """
if self.modifier == Date.MOD_SPAN or self.modifier == Date.MOD_RANGE: if self.is_compound():
val = self.dateval[index] val = self.dateval[index]
else: else:
val = 0 val = 0
@ -1279,89 +1267,91 @@ class Date(object):
""" """
self.newyear = value 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. 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) if self.is_compound() and remove_stop_date is None:
dv[Date._POS_YR] = year raise DateError("Required parameter remove_stop_date not set!")
dv[Date._POS_MON] = month
dv[Date._POS_DAY] = day self.__set_yr_mon_day(year, month, day,
self.dateval = tuple(dv) Date._POS_YR, Date._POS_MON, Date._POS_DAY)
self._calc_sort_value() 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): 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 = list(self.dateval)
dv[Date._POS_RYR] = year if dv[pos_yr]:
dv[Date._POS_RMON] = month dv[pos_yr] += year
dv[Date._POS_RDAY] = day 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.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): 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 self.__set_yr_mon_day_offset(year, month, day,
if dv[Date._POS_YR]: Date._POS_YR, Date._POS_MON, Date._POS_DAY):
dv[Date._POS_YR] += year self.set_yr_mon_day(*self.offset(day), remove_stop_date = False)
elif year: if self.is_compound():
dv[Date._POS_YR] = year self.set2_yr_mon_day_offset(year, month, day)
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))
def set2_yr_mon_day_offset(self, year=0, month=0, day=0): 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) self._assert_compound()
if dv[Date._POS_RYR]: if self.__set_yr_mon_day_offset(year, month, day,
dv[Date._POS_RYR] += year Date._POS_RYR, Date._POS_RMON, Date._POS_RDAY):
elif year: stop = Date(self.get_stop_ymd())
dv[Date._POS_RYR] = year self.set2_yr_mon_day(*stop.offset(day))
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))
def copy_offset_ymd(self, year=0, month=0, day=0): def copy_offset_ymd(self, year=0, month=0, day=0):
""" """
@ -1374,24 +1364,19 @@ class Date(object):
new_date = self new_date = self
retval = Date(new_date) retval = Date(new_date)
retval.set_yr_mon_day_offset(year, month, day) 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: if orig_cal == 0:
return retval return retval
else: else:
retval.convert_calendar(orig_cal) retval.convert_calendar(orig_cal)
return retval 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. 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 = Date(self)
retval.set_yr_mon_day(year, month, day) retval.set_yr_mon_day(year, month, day, remove_stop_date)
if (self.get_modifier() == Date.MOD_RANGE or
self.get_modifier() == Date.MOD_SPAN):
retval.set2_yr_mon_day_offset(year, month, day)
return retval return retval
def set_year(self, year): def set_year(self, year):
@ -1496,30 +1481,52 @@ class Date(object):
""" """
return self.text return self.text
def set(self, quality, modifier, calendar, value, text=None, def _zero_adjust_ymd(self, y, m, d):
newyear=0): 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. Set the date to the specified value.
Parameters are:: Parameters are::
quality - The date quality for the date (see get_quality 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 modified - The date modifier for the date (see get_modifier
for more information) for more information)
Defaults to the previous value for the date.
calendar - The calendar associated with the date (see calendar - The calendar associated with the date (see
get_calendar for more information). get_calendar for more information).
Defaults to the previous value for the date.
value - A tuple representing the date information. For a value - A tuple representing the date information. For a
non-compound date, the format is (DD, MM, YY, slash) non-compound date, the format is (DD, MM, YY, slash)
and for a compound date the tuple stores data as and for a compound date the tuple stores data as
(DD, MM, YY, slash1, DD, MM, YY, slash2) (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 text - A text string holding either the verbatim user input
or a comment relating to the date. or a comment relating to the date.
Defaults to the previous value for the date.
newyear - The newyear code, or tuple representing (month, day) newyear - The newyear code, or tuple representing (month, day)
of newyear day. of newyear day.
Defaults to 0.
The sort value is recalculated. 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, if modifier in (Date.MOD_NONE, Date.MOD_BEFORE,
Date.MOD_AFTER, Date.MOD_ABOUT) and len(value) < 4: Date.MOD_AFTER, Date.MOD_ABOUT) and len(value) < 4:
@ -1545,11 +1552,12 @@ class Date(object):
self.calendar = calendar self.calendar = calendar
self.dateval = value self.dateval = value
self.set_new_year(newyear) self.set_new_year(newyear)
year = max(value[Date._POS_YR], 1) year, month, day = self._zero_adjust_ymd(
month = max(value[Date._POS_MON], 1) value[Date._POS_YR],
day = max(value[Date._POS_DAY], 1) value[Date._POS_MON],
value[Date._POS_DAY])
if year == month == 0 and day == 0: if year == month == day == 0:
self.sortval = 0 self.sortval = 0
else: else:
func = Date._calendar_convert[calendar] func = Date._calendar_convert[calendar]
@ -1587,6 +1595,32 @@ class Date(object):
d2.set_calendar(self.calendar) d2.set_calendar(self.calendar)
d2_val = d2.sortval d2_val = d2.sortval
self.sortval += (d1_val - d2_val) + 1 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: if text:
self.text = text self.text = text
@ -1603,26 +1637,30 @@ class Date(object):
""" """
Calculate the numerical sort value associated with the date. Calculate the numerical sort value associated with the date.
""" """
year = max(self.dateval[Date._POS_YR], 1) year, month, day = self._zero_adjust_ymd(
month = max(self.dateval[Date._POS_MON], 1) self.dateval[Date._POS_YR],
day = max(self.dateval[Date._POS_DAY], 1) self.dateval[Date._POS_MON],
self.dateval[Date._POS_DAY])
if year == month == 0 and day == 0: if year == month == 0 and day == 0:
self.sortval = 0 self.sortval = 0
else: else:
func = Date._calendar_convert[self.calendar] func = Date._calendar_convert[self.calendar]
self.sortval = func(year, month, day) 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. 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 return
(year, month, day) = Date._calendar_change[calendar](self.sortval) (year, month, day) = Date._calendar_change[calendar](self.sortval)
if self.is_compound(): if self.is_compound():
ryear = max(self.dateval[Date._POS_RYR], 1) ryear, rmonth, rday = self._zero_adjust_ymd(
rmonth = max(self.dateval[Date._POS_RMON], 1) self.dateval[Date._POS_RYR],
rday = max(self.dateval[Date._POS_RDAY], 1) self.dateval[Date._POS_RMON],
self.dateval[Date._POS_RDAY])
sdn = Date._calendar_convert[self.calendar](ryear, rmonth, rday) sdn = Date._calendar_convert[self.calendar](ryear, rmonth, rday)
(nyear, nmonth, nday) = Date._calendar_change[calendar](sdn) (nyear, nmonth, nday) = Date._calendar_change[calendar](sdn)
self.dateval = (day, month, year, False, self.dateval = (day, month, year, False,
@ -1686,6 +1724,12 @@ class Date(object):
""" """
return (self.get_year(), self.get_month(), self.get_day()) 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): def offset(self, value):
""" """
Return (year, month, day) of this date +- value. Return (year, month, day) of this date +- value.

View File

@ -49,7 +49,7 @@ import config
import DateHandler import DateHandler
from DateHandler import parser as _dp from DateHandler import parser as _dp
from DateHandler import displayer as _dd 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.textdomain("gramps")
gettext.install("gramps",loc,unicode=1) gettext.install("gramps",loc,unicode=1)
@ -444,6 +444,82 @@ def suite4():
count += 1 count += 1
return suite 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__": if __name__ == "__main__":
unittest.TextTestRunner().run(suite()) unittest.TextTestRunner().run(suite())
unittest.TextTestRunner().run(suite2()) unittest.TextTestRunner().run(suite2())