gramps/src/RelLib/_Date.py

593 lines
19 KiB
Python
Raw Normal View History

#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2006 Donald N. Allingham
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
2006-04-08 11:26:31 +05:30
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id$
"Support for dates"
__author__ = "Donald N. Allingham"
2006-04-08 11:26:31 +05:30
__revision__ = "$Revision$"
from gettext import gettext as _
from _CalSdn import *
#-------------------------------------------------------------------------
#
# DateError exception
#
#-------------------------------------------------------------------------
class DateError(Exception):
"""Error used to report Date errors"""
2006-04-08 11:26:31 +05:30
def __init__(self, value=""):
Exception.__init__(self)
self.value = value
def __str__(self):
return self.value
#-------------------------------------------------------------------------
#
# Date class
#
#-------------------------------------------------------------------------
class Date:
"""
2006-04-08 11:26:31 +05:30
The core date handling class for GRAMPs. Supports partial dates,
compound dates and alternate calendars.
"""
MOD_NONE = 0
MOD_BEFORE = 1
MOD_AFTER = 2
MOD_ABOUT = 3
MOD_RANGE = 4
MOD_SPAN = 5
MOD_TEXTONLY = 6
2006-04-08 11:26:31 +05:30
QUAL_NONE = 0
QUAL_ESTIMATED = 1
QUAL_CALCULATED = 2
CAL_GREGORIAN = 0
CAL_JULIAN = 1
CAL_HEBREW = 2
CAL_FRENCH = 3
CAL_PERSIAN = 4
CAL_ISLAMIC = 5
2006-04-08 11:26:31 +05:30
EMPTY = (0, 0, 0, False)
_POS_DAY = 0
_POS_MON = 1
_POS_YR = 2
_POS_SL = 3
_POS_RDAY = 4
_POS_RMON = 5
_POS_RYR = 6
_POS_RSL = 7
_calendar_convert = [
2006-04-08 11:26:31 +05:30
gregorian_sdn,
julian_sdn,
hebrew_sdn,
french_sdn,
persian_sdn,
islamic_sdn,
]
_calendar_change = [
2006-04-08 11:26:31 +05:30
gregorian_ymd,
julian_ymd,
hebrew_ymd,
french_ymd,
persian_ymd,
islamic_ymd,
]
2006-04-08 11:26:31 +05:30
calendar_names = ["Gregorian",
"Julian",
"Hebrew",
"French Republican",
"Persian",
"Islamic"]
2006-04-08 11:26:31 +05:30
ui_calendar_names = [_("Gregorian"),
_("Julian"),
_("Hebrew"),
_("French Republican"),
_("Persian"),
_("Islamic")]
2006-04-08 11:26:31 +05:30
def __init__(self, source=None):
"""
Creates a new Date instance.
"""
if source:
self.calendar = source.calendar
self.modifier = source.modifier
self.quality = source.quality
self.dateval = source.dateval
self.text = source.text
self.sortval = source.sortval
else:
self.calendar = Date.CAL_GREGORIAN
self.modifier = Date.MOD_NONE
self.quality = Date.QUAL_NONE
self.dateval = Date.EMPTY
self.text = u""
self.sortval = 0
def serialize(self):
2006-04-08 11:26:31 +05:30
"""
Convert to a series of tuples for data storage
"""
return (self.calendar, self.modifier, self.quality,
self.dateval, self.text, self.sortval)
2006-04-08 11:26:31 +05:30
def unserialize(self, data):
"""
Load from the format created by serialize
"""
(self.calendar, self.modifier, self.quality,
self.dateval, self.text, self.sortval) = data
return self
2006-04-08 11:26:31 +05:30
def copy(self, source):
"""
Copy all the attributes of the given Date instance
to the present instance, without creating a new object.
"""
self.calendar = source.calendar
self.modifier = source.modifier
self.quality = source.quality
self.dateval = source.dateval
self.text = source.text
self.sortval = source.sortval
2006-04-08 11:26:31 +05:30
def __cmp__(self, other):
"""
Comparison function. Allows the usage of equality tests.
This allows you do run statements like 'date1 <= date2'
"""
2006-04-08 11:26:31 +05:30
if isinstance(other, Date):
return cmp(self.sortval, other.sortval)
else:
return -1
2006-04-08 11:26:31 +05:30
def is_equal(self, other):
"""
Return 1 if the given Date instance is the same as the present
instance IN ALL REGARDS. Needed, because the __cmp__ only looks
at the sorting value, and ignores the modifiers/comments.
"""
if self.modifier == other.modifier \
and self.modifier == Date.MOD_TEXTONLY:
value = self.text == other.text
else:
value = (self.calendar == other.calendar and
self.modifier == other.modifier and
self.quality == other.quality and
self.dateval == other.dateval)
return value
def __str__(self):
"""
Produces a string representation of the Date object. If the
date is not valid, the text representation is displayed. If
the date is a range or a span, a string in the form of
'YYYY-MM-DD - YYYY-MM-DD' is returned. Otherwise, a string in
the form of 'YYYY-MM-DD' is returned.
"""
if self.quality == Date.QUAL_ESTIMATED:
qual = "est "
elif self.quality == Date.QUAL_CALCULATED:
qual = "calc "
else:
qual = ""
if self.modifier == Date.MOD_BEFORE:
pref = "bef "
elif self.modifier == Date.MOD_AFTER:
pref = "aft "
elif self.modifier == Date.MOD_ABOUT:
pref = "abt "
else:
pref = ""
if self.calendar != Date.CAL_GREGORIAN:
cal = " (%s)" % Date.calendar_names[self.calendar]
else:
cal = ""
if self.modifier == Date.MOD_TEXTONLY:
val = self.text
elif self.modifier == Date.MOD_RANGE or self.modifier == Date.MOD_SPAN:
val = "%04d-%02d-%02d - %04d-%02d-%02d" % (
2006-04-08 11:26:31 +05:30
self.dateval[Date._POS_YR], self.dateval[Date._POS_MON],
self.dateval[Date._POS_DAY], self.dateval[Date._POS_RYR],
self.dateval[Date._POS_RMON], self.dateval[Date._POS_RDAY])
else:
val = "%04d-%02d-%02d" % (
2006-04-08 11:26:31 +05:30
self.dateval[Date._POS_YR], self.dateval[Date._POS_MON],
self.dateval[Date._POS_DAY])
2006-04-08 11:26:31 +05:30
return "%s%s%s%s" % (qual, pref, val, cal)
def get_sort_value(self):
"""
Returns the sort value of Date object. If the value is a
text string, 0 is returned. Otherwise, the calculated sort
date is returned. The sort date is rebuilt on every assignment.
The sort value is an integer representing the value. A date of
March 5, 1990 would have the value of 19900305.
"""
return self.sortval
def get_modifier(self):
"""
Returns an integer indicating the calendar selected. The valid
values are::
MOD_NONE = no modifier (default)
MOD_BEFORE = before
MOD_AFTER = after
MOD_ABOUT = about
MOD_RANGE = date range
MOD_SPAN = date span
MOD_TEXTONLY = text only
"""
return self.modifier
2006-04-08 11:26:31 +05:30
def set_modifier(self, val):
"""
Sets the modifier for the date.
"""
2006-04-08 11:26:31 +05:30
if val not in (Date.MOD_NONE, Date.MOD_BEFORE, Date.MOD_AFTER,
Date.MOD_ABOUT, Date.MOD_RANGE, Date.MOD_SPAN,
Date.MOD_TEXTONLY):
raise DateError("Invalid modifier")
self.modifier = val
def get_quality(self):
"""
Returns an integer indicating the calendar selected. The valid
values are::
QUAL_NONE = normal (default)
QUAL_ESTIMATED = estimated
QUAL_CALCULATED = calculated
"""
return self.quality
2006-04-08 11:26:31 +05:30
def set_quality(self, val):
"""
Sets the quality selected for the date.
"""
2006-04-08 11:26:31 +05:30
if val not in (Date.QUAL_NONE, Date.QUAL_ESTIMATED,
Date.QUAL_CALCULATED):
raise DateError("Invalid quality")
self.quality = val
def get_calendar(self):
"""
Returns an integer indicating the calendar selected. The valid
values are::
CAL_GREGORIAN - Gregorian calendar
CAL_JULIAN - Julian calendar
CAL_HEBREW - Hebrew (Jewish) calendar
CAL_FRENCH - French Republican calendar
CAL_PERSIAN - Persian calendar
CAL_ISLAMIC - Islamic calendar
"""
return self.calendar
2006-04-08 11:26:31 +05:30
def set_calendar(self, val):
"""
Sets the calendar selected for the date.
"""
2006-04-08 11:26:31 +05:30
if val not in (Date.CAL_GREGORIAN, Date.CAL_JULIAN, Date.CAL_HEBREW,
Date.CAL_FRENCH, Date.CAL_PERSIAN, Date.CAL_ISLAMIC):
raise DateError("Invalid calendar")
self.calendar = val
def get_start_date(self):
"""
Returns a tuple representing the start date. If the date is a
compound date (range or a span), it is the first part of the
compound date. If the date is a text string, a tuple of
2006-04-08 11:26:31 +05:30
(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_TEXTONLY:
val = Date.EMPTY
else:
val = self.dateval[0:4]
return val
def get_stop_date(self):
"""
Returns a tuple representing the second half of a compound date.
If the date is not a compound date, (including text strings) a tuple
2006-04-08 11:26:31 +05:30
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:
val = self.dateval[4:8]
else:
val = Date.EMPTY
return val
2006-04-08 11:26:31 +05:30
def _get_low_item(self, index):
"""
Returns the item specified
"""
if self.modifier == Date.MOD_TEXTONLY:
val = 0
else:
val = self.dateval[index]
return val
2006-04-08 11:26:31 +05:30
def _get_low_item_valid(self, index):
"""
Determines if the item specified is valid
"""
if self.modifier == Date.MOD_TEXTONLY:
val = False
else:
val = self.dateval[index] != 0
return val
2006-04-08 11:26:31 +05:30
def _get_high_item(self, index):
"""
Returns the item specified
"""
if self.modifier == Date.MOD_SPAN or self.modifier == Date.MOD_RANGE:
val = self.dateval[index]
else:
val = 0
return val
def get_year(self):
"""
Returns the year associated with the date. If the year is
not defined, a zero is returned. If the date is a compound
date, the lower date year is returned.
"""
return self._get_low_item(Date._POS_YR)
2006-04-08 11:26:31 +05:30
def set_year(self, year):
"""
2006-04-08 11:26:31 +05:30
Sets the year value
"""
2006-04-08 11:26:31 +05:30
self.dateval = self.dateval[0:2] + (year, ) + self.dateval[3:]
self._calc_sort_value()
def get_year_valid(self):
2006-04-08 11:26:31 +05:30
"""
Returns true if the year is valid
"""
return self._get_low_item_valid(Date._POS_YR)
def get_month(self):
"""
Returns the month associated with the date. If the month is
not defined, a zero is returned. If the date is a compound
date, the lower date month is returned.
"""
return self._get_low_item(Date._POS_MON)
def get_month_valid(self):
2006-04-08 11:26:31 +05:30
"""
Returns true if the month is valid
"""
return self._get_low_item_valid(Date._POS_MON)
def get_day(self):
"""
Returns the day of the month associated with the date. If
the day is not defined, a zero is returned. If the date is
a compound date, the lower date day is returned.
"""
return self._get_low_item(Date._POS_DAY)
def get_day_valid(self):
2006-04-08 11:26:31 +05:30
"""
Returns true if the day is valid
"""
return self._get_low_item_valid(Date._POS_DAY)
def get_valid(self):
""" Returns true if any part of the date is valid"""
return self.modifier != Date.MOD_TEXTONLY
def get_stop_year(self):
"""
Returns the day of the year associated with the second
part of a compound date. If the year is not defined, a zero
is returned.
"""
return self._get_high_item(Date._POS_RYR)
def get_stop_month(self):
"""
Returns the month of the month associated with the second
part of a compound date. If the month is not defined, a zero
is returned.
"""
return self._get_high_item(Date._POS_RMON)
def get_stop_day(self):
"""
Returns the day of the month associated with the second
part of a compound date. If the day is not defined, a zero
is returned.
"""
return self._get_high_item(Date._POS_RDAY)
def get_high_year(self):
"""
Returns the high year estimate. For compound dates with non-zero
stop year, the stop year is returned. Otherwise, the start year
is returned.
"""
if self.is_compound():
ret = self.get_stop_year()
if ret:
return ret
else:
return self.get_year()
def get_text(self):
"""
Returns the text value associated with an invalid date.
"""
return self.text
2006-04-08 11:26:31 +05:30
def set(self, quality, modifier, calendar, value, text=None):
"""
Sets the date to the specified value. Parameters are::
quality - The date quality for the date (see get_quality
for more information)
modified - The date modifier for the date (see get_modifier
for more information)
calendar - The calendar associated with the date (see
get_calendar for more information).
value - A tuple representing the date information. For a
2006-04-08 11:26:31 +05:30
non-compound date, the format is (DD, MM, YY, slash)
and for a compound date the tuple stores data as
2006-04-08 11:26:31 +05:30
(DD, MM, YY, slash1, DD, MM, YY, slash2)
text - A text string holding either the verbatim user input
or a comment relating to the date.
The sort value is recalculated.
"""
2006-04-08 11:26:31 +05:30
if modifier in (Date.MOD_NONE, Date.MOD_BEFORE,
Date.MOD_AFTER, Date.MOD_ABOUT) and len(value) < 4:
raise DateError("Invalid value. Should be: (DD, MM, YY, slash)")
if modifier in (Date.MOD_RANGE, Date.MOD_SPAN) and len(value) < 8:
raise DateError("Invalid value. Should be: (DD, MM, "
"YY, slash1, DD, MM, YY, slash2)")
if modifier not in (Date.MOD_NONE, Date.MOD_BEFORE, Date.MOD_AFTER,
Date.MOD_ABOUT, Date.MOD_RANGE, Date.MOD_SPAN,
Date.MOD_TEXTONLY):
raise DateError("Invalid modifier")
2006-04-08 11:26:31 +05:30
if quality not in (Date.QUAL_NONE, Date.QUAL_ESTIMATED,
Date.QUAL_CALCULATED):
raise DateError("Invalid quality")
2006-04-08 11:26:31 +05:30
if calendar not in (Date.CAL_GREGORIAN, Date.CAL_JULIAN,
Date.CAL_HEBREW, Date.CAL_FRENCH,
Date.CAL_PERSIAN, Date.CAL_ISLAMIC):
raise DateError("Invalid calendar")
self.quality = quality
self.modifier = modifier
self.calendar = calendar
self.dateval = value
2006-04-08 11:26:31 +05:30
year = max(value[Date._POS_YR], 1)
month = max(value[Date._POS_MON], 1)
day = max(value[Date._POS_DAY], 1)
if year == 0 and month == 0 and day == 0:
self.sortval = 0
else:
2006-04-08 11:26:31 +05:30
func = Date._calendar_convert[calendar]
self.sortval = func(year, month, day)
if text:
self.text = text
2006-04-08 11:26:31 +05:30
def _calc_sort_value(self):
"""
Calculates 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)
if year == 0 and month == 0 and day == 0:
self.sortval = 0
else:
2006-04-08 11:26:31 +05:30
func = Date._calendar_convert[self.calendar]
self.sortval = func(year, month, day)
2006-04-08 11:26:31 +05:30
def convert_calendar(self, calendar):
"""
Converts the date from the current calendar to the specified
calendar.
"""
if calendar == self.calendar:
return
2006-04-08 11:26:31 +05:30
(year, month, day) = Date._calendar_change[calendar](self.sortval)
if self.is_compound():
2006-04-08 11:26:31 +05:30
ryear = max(self.dateval[Date._POS_RYR], 1)
rmonth = max(self.dateval[Date._POS_RMON], 1)
rday = max(self.dateval[Date._POS_RDAY], 1)
sdn = Date._calendar_convert[self.calendar](ryear, rmonth, rday)
(nyear, nmonth, nday) = Date._calendar_change[calendar](sdn)
self.dateval = (day, month, year, self.dateval[Date._POS_SL],
nday, nmonth, nyear, self.dateval[Date._POS_RSL])
else:
2006-04-08 11:26:31 +05:30
self.dateval = (day, month, year, self.dateval[Date._POS_SL])
self.calendar = calendar
2006-04-08 11:26:31 +05:30
def set_as_text(self, text):
"""
Sets the day to a text string, and assigns the sort value
to zero.
"""
self.modifier = Date.MOD_TEXTONLY
self.text = text
self.sortval = 0
2006-04-08 11:26:31 +05:30
def set_text_value(self, text):
"""
Sets the text string to a given text.
"""
self.text = text
def is_empty(self):
"""
Returns True if the date contains no information (empty text).
"""
return (self.modifier == Date.MOD_TEXTONLY and not self.text) or \
(self.get_start_date()==Date.EMPTY
and self.get_stop_date()==Date.EMPTY)
def is_compound(self):
"""
Returns True if the date is a date range or a date span.
"""
return self.modifier == Date.MOD_RANGE \
or self.modifier == Date.MOD_SPAN
def is_regular(self):
"""
Returns True if the date is a regular date.
The regular date is a single exact date, i.e. not text-only, not
a range or a span, not estimated/calculated, not about/before/after
date, and having year, month, and day all non-zero.
"""
return self.modifier == Date.MOD_NONE \
and self.quality == Date.QUAL_NONE \
and self.get_year_valid() and self.get_month_valid() \
and self.get_day_valid()