Move "holiday" functions into its own module named libholiday. libholiday is used by all three calendar based reports.
svn: r11759
This commit is contained in:
parent
497e8d8233
commit
1adf052e55
@ -10,6 +10,7 @@ SUBDIRS = \
|
||||
gramplet \
|
||||
graph \
|
||||
import \
|
||||
lib \
|
||||
quickview \
|
||||
rel \
|
||||
textreport \
|
||||
@ -20,7 +21,6 @@ pkgdatadir = $(datadir)/@PACKAGE@/plugins
|
||||
|
||||
pkgdata_PYTHON = \
|
||||
BookReport.py\
|
||||
Calendar.py \
|
||||
ExportXml.py\
|
||||
ImportGpkg.py\
|
||||
ImportXml.py\
|
||||
|
@ -2,8 +2,10 @@
|
||||
# Gramps - a GTK+/GNOME based genealogy program
|
||||
#
|
||||
# Copyright (C) 2007 Thom Sturgill
|
||||
# Copyright (C) 2007-2008 Brian G. Matherly
|
||||
# Copyright (C) 2007-2009 Brian G. Matherly
|
||||
# Copyright (C) 2008 Rob G. Healey <robhealey1@gmail.com>
|
||||
# Copyright (C) 2008 Jason Simanek
|
||||
# Copyright (C) 2008 Kees Bakker
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Pubilc License as published by
|
||||
@ -25,21 +27,6 @@
|
||||
"""
|
||||
Web Calendar generator.
|
||||
|
||||
Menu selection: Reports -> Web Page -> Web Calendar
|
||||
|
||||
Created 4/22/07 by Thom Sturgill based on Calendar.py (with patches)
|
||||
by Doug Blank with input dialog based on NarrativeWeb.py by Don Allingham.
|
||||
|
||||
2008-05-11 Jason Simanek
|
||||
Improving markup for optimal separation of content and presentation.
|
||||
|
||||
2008-June-22 Rob G. Healey
|
||||
*** Remove StyleEditor, make it css based as is NarrativeWeb,
|
||||
move title to first tab, re-word note tabs, complete re-write of
|
||||
calendar build, added year glance, and blank year, added easter and
|
||||
dst start/stop from Calendar.py, etc.
|
||||
|
||||
2008 Kees Bakker
|
||||
Refactoring. This is an ongoing job until this plugin is in a better shape.
|
||||
TODO list:
|
||||
- change filename for one_day pages to yyyy/mm/dd.html (just numbers)
|
||||
@ -60,12 +47,9 @@ import os
|
||||
import time
|
||||
import datetime
|
||||
import calendar
|
||||
import math
|
||||
import codecs
|
||||
import shutil
|
||||
from gettext import gettext as _
|
||||
from xml.parsers import expat
|
||||
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
#
|
||||
@ -90,11 +74,13 @@ from gen.plug.menu import BooleanOption, NumberOption, StringOption, \
|
||||
DestinationOption
|
||||
import Utils
|
||||
import GrampsLocale
|
||||
from QuestionDialog import ErrorDialog, WarningDialog
|
||||
from QuestionDialog import WarningDialog
|
||||
from Utils import probably_alive
|
||||
from DateHandler import displayer as _dd
|
||||
from DateHandler import parser as _dp
|
||||
|
||||
import libholiday
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
#
|
||||
# constants
|
||||
@ -256,8 +242,8 @@ class WebCalReport(Report):
|
||||
self.author = self.author.replace(',,,', '')
|
||||
self.email = researcher.email
|
||||
|
||||
self.start_month = 1 # set to January, and it can change
|
||||
self.end_month = 12 # set to December, this value never changes
|
||||
self.start_month = 1 # set to January, and it can change
|
||||
self.end_month = 12 # set to December, this value never changes
|
||||
|
||||
today = time.localtime() # set to today's date
|
||||
self.today = datetime.date(today[0], today[1], today[2])
|
||||
@ -316,68 +302,22 @@ class WebCalReport(Report):
|
||||
except ValueError:
|
||||
my_date = '...'
|
||||
else:
|
||||
my_date = '...' #Incomplete date as in about, circa, etc.
|
||||
my_date = '...' #Incomplete date as in about, circa, etc.
|
||||
|
||||
day_list.append((text, event, my_date))
|
||||
month_dict[day] = day_list
|
||||
self.calendar[month] = month_dict
|
||||
|
||||
def get_holidays(self, year, country="United States"):
|
||||
"""
|
||||
Looks in multiple places for holidays.xml file.
|
||||
the holidays file will be used first if it exists in user's plugins, otherwise,
|
||||
the GRAMPS plugins will be checked.
|
||||
"""
|
||||
|
||||
holiday_file = 'holidays.xml'
|
||||
holiday_full_path = ""
|
||||
fname1 = os.path.join(const.USER_PLUGINS, holiday_file)
|
||||
fname2 = os.path.join(const.PLUGINS_DIR, holiday_file)
|
||||
if os.path.exists(fname1):
|
||||
holiday_full_path = fname1
|
||||
elif os.path.exists(fname2):
|
||||
holiday_full_path = fname2
|
||||
if holiday_full_path != "":
|
||||
self.process_holiday_file(year, holiday_full_path, country)
|
||||
|
||||
def process_holiday_file(self, year, filename, country):
|
||||
"""
|
||||
This will process the holidays file for the selected country.
|
||||
|
||||
All holidays, except Easter, and Daylight Saving start/ stop, will be
|
||||
processed from the holidays.xml file. Easter and DST will be handled by
|
||||
specific mathematical formulas within this plugin ...
|
||||
|
||||
"Easter" -- _easter()
|
||||
"Daylight Saving Time" -- _get_dst_start_stop()
|
||||
0 = year, 1 = month, 2 = day
|
||||
"""
|
||||
|
||||
parser = Xml2Obj()
|
||||
element = parser.Parse(filename)
|
||||
holidays_calendar = Holidays(element, country)
|
||||
date = datetime.date(year, 1, 1)
|
||||
while date.year == year:
|
||||
holidays = holidays_calendar.check_date(date)
|
||||
for text in holidays:
|
||||
if text == "Easter": # TODO. Verify if this needs translation, and how
|
||||
easter = _easter(year)
|
||||
self.add_holiday_item(_("Easter"), easter.get_year(), easter.get_month(), easter.get_day())
|
||||
elif text == "Daylight Saving begins":
|
||||
# TODO. There is more than USA and Europe.
|
||||
if Utils.xml_lang() == "en-US": # DST for United States of America
|
||||
dst_start, dst_stop = _get_dst_start_stop(year)
|
||||
else: # DST for Europe
|
||||
dst_start, dst_stop = _get_dst_start_stop(year, "eu")
|
||||
self.add_holiday_item(_("Daylight Saving begins"), \
|
||||
dst_start[0], dst_start[1], dst_start[2])
|
||||
self.add_holiday_item(_("Daylight Saving ends"), \
|
||||
dst_stop[0], dst_stop[1], dst_stop[2])
|
||||
elif text == "Daylight Saving ends":
|
||||
pass # end is already done above
|
||||
else: # not easter, or Daylight Saving Time
|
||||
self.add_holiday_item(text, date.year, date.month, date.day)
|
||||
date = date.fromordinal( date.toordinal() + 1)
|
||||
def __get_holidays(self, year):
|
||||
""" Get the holidays for the specified country and year """
|
||||
holiday_table = libholiday.HolidayTable()
|
||||
country = holiday_table.get_countries()[self.country]
|
||||
holiday_table.load_holidays(year, country)
|
||||
for month in range(1, 13):
|
||||
for day in range(1, 32):
|
||||
holiday_names = holiday_table.get_holidays(month, day)
|
||||
for holiday_name in holiday_names:
|
||||
self.add_holiday_item(holiday_name, year, month, day)
|
||||
|
||||
def add_holiday_item(self, text, year, month, day):
|
||||
if day == 0:
|
||||
@ -481,7 +421,8 @@ class WebCalReport(Report):
|
||||
of.write('\t<ul>\n')
|
||||
cols = 0
|
||||
cal_year = self.start_year
|
||||
while ((0 <= cols < 25) and (self.start_year <= cal_year <= self.end_year)):
|
||||
while ((0 <= cols < 25) and
|
||||
(self.start_year <= cal_year <= self.end_year)):
|
||||
url = ''
|
||||
cs = False
|
||||
|
||||
@ -497,7 +438,8 @@ class WebCalReport(Report):
|
||||
if cal_year == self.today.year:
|
||||
lng_month = _get_long_month_name(self.today.month)
|
||||
|
||||
# Note. We use '/' here because it is a URL, not a OS dependent pathname
|
||||
# Note. We use '/' here because it is a URL, not a OS dependent
|
||||
# pathname.
|
||||
url = '/'.join(subdirs + [lng_month]) + self.ext
|
||||
|
||||
# determine if we need to highlight???
|
||||
@ -518,7 +460,8 @@ class WebCalReport(Report):
|
||||
of.write('\t</ul>\n')
|
||||
of.write('</div>\n\n')
|
||||
|
||||
def calendar_common(self, of, nr_up, year, currsec1, title, body_id, use_home=False, add_print=True):
|
||||
def calendar_common(self, of, nr_up, year, currsec1, title, body_id,
|
||||
use_home=False, add_print=True):
|
||||
"""
|
||||
Will create the common information for each calendar being created
|
||||
"""
|
||||
@ -591,13 +534,13 @@ class WebCalReport(Report):
|
||||
day = col2day[col]
|
||||
return day_names[day]
|
||||
|
||||
# monthinfo is filled using standard Python library calendar.monthcalendar
|
||||
# It fills a list of 7-day-lists. The first day of the 7-day-list is
|
||||
# determined by calendar.firstweekday
|
||||
# monthinfo is filled using standard Python library
|
||||
# calendar.monthcalendar. It fills a list of 7-day-lists. The first day
|
||||
# of the 7-day-list is determined by calendar.firstweekday.
|
||||
monthinfo = calendar.monthcalendar(year, month)
|
||||
|
||||
# Begin calendar head. We'll use the capitalized name, because here it seems
|
||||
# appropriate for most countries.
|
||||
# Begin calendar head. We'll use the capitalized name, because here it
|
||||
# seems appropriate for most countries.
|
||||
month_name = lng_month.capitalize()
|
||||
th_txt = month_name
|
||||
if cal == 'wc': # normal_cal()
|
||||
@ -653,11 +596,11 @@ class WebCalReport(Report):
|
||||
dayclass = get_class_for_daycol(day_col)
|
||||
|
||||
day = week[day_col]
|
||||
if day == 0: # a day in the previous or next month
|
||||
if week_row == 0: # a day in the previous month
|
||||
if day == 0: # a day in the previous or next month
|
||||
if week_row == 0: # a day in the previous month
|
||||
specday = lastweek_prevmonth[day_col]
|
||||
specclass = "previous " + dayclass
|
||||
elif week_row == nweeks-1: # a day in the next month
|
||||
elif week_row == nweeks-1: # a day in the next month
|
||||
specday = firstweek_nextmonth[day_col]
|
||||
specclass = "next " + dayclass
|
||||
|
||||
@ -665,7 +608,7 @@ class WebCalReport(Report):
|
||||
of.write('\t\t\t\t<div class="date">%d</div>\n' % specday)
|
||||
of.write('\t\t\t</td>\n')
|
||||
|
||||
else: # normal day number in current month
|
||||
else: # normal day number in current month
|
||||
thisday = datetime.date.fromordinal(current_ord)
|
||||
of.write('\t\t\t<td id="%s%02d" ' % (shrt_month, day))
|
||||
if thisday.month == month: # Something this month
|
||||
@ -799,7 +742,6 @@ class WebCalReport(Report):
|
||||
# Note. We use '/' here because it is a URL, not a OS dependent pathname
|
||||
fname = '/'.join(subdirs + ['images'] + ['somerights20.gif'])
|
||||
text = _CC[copy_nr] % {'gif_fname' : fname}
|
||||
self.use_copyright = True
|
||||
else:
|
||||
text = "© %s %s" % (self.today.year, self.author)
|
||||
of.write('\t<p id="copyright">%s</p>\n' % text)
|
||||
@ -981,9 +923,9 @@ class WebCalReport(Report):
|
||||
# initialize the holidays dict to fill:
|
||||
self.holidays = {}
|
||||
|
||||
# get the information from holidays for every year being created
|
||||
if self.country != 0: # Don't include holidays
|
||||
self.get_holidays(cal_year, _COUNTRIES[self.country]) # _country is currently global
|
||||
# get the information, first from holidays:
|
||||
if self.country != 0:
|
||||
self.__get_holidays(cal_year)
|
||||
|
||||
# adjust the months being created if self.partyear is True,
|
||||
# and if the year is the current year, then start month is current month
|
||||
@ -1004,10 +946,10 @@ class WebCalReport(Report):
|
||||
cal_year = self.start_year
|
||||
|
||||
self.holidays = {}
|
||||
|
||||
# get the information from holidays for each year being created
|
||||
if self.country != 0: # Don't include holidays
|
||||
self.get_holidays(cal_year, _COUNTRIES[self.country]) # _COUNTRIES is currently global
|
||||
|
||||
# get the information, first from holidays:
|
||||
if self.country != 0:
|
||||
self.__get_holidays(cal_year)
|
||||
|
||||
# generate progress pass for single year
|
||||
#self.progress.set_pass(_('Creating calendars'), self.end_month - self.start_month)
|
||||
@ -1263,7 +1205,7 @@ class WebCalOptions(MenuReportOptions):
|
||||
# set to today's date for use in menu, etc.
|
||||
# 0 = year, 1 = month, 2 = day
|
||||
today = time.localtime()
|
||||
self.today = datetime.date(today[0], today[1], today[2])
|
||||
today = datetime.date(today[0], today[1], today[2])
|
||||
|
||||
partyear = BooleanOption(_('Create Partial Year calendar'), False)
|
||||
partyear.set_help(_('Create a partial year calendar. The start month will be'
|
||||
@ -1275,11 +1217,11 @@ class WebCalOptions(MenuReportOptions):
|
||||
menu.add_option(category_name, 'multiyear', self.__multiyear)
|
||||
self.__multiyear.connect('value-changed', self.__multiyear_changed)
|
||||
|
||||
self.__start_year = NumberOption(_('Start Year for the Calendar(s)'), self.today.year, 1900, 3000)
|
||||
self.__start_year = NumberOption(_('Start Year for the Calendar(s)'), today.year, 1900, 3000)
|
||||
self.__start_year.set_help(_('Enter the starting year for the calendars between 1900 - 3000'))
|
||||
menu.add_option(category_name, 'start_year', self.__start_year)
|
||||
|
||||
self.__end_year = NumberOption(_('End Year for the Calendar(s)'), self.today.year, 1900, 3000)
|
||||
self.__end_year = NumberOption(_('End Year for the Calendar(s)'), today.year, 1900, 3000)
|
||||
self.__end_year.set_help(_('Enter the ending year for the calendars between 1900 - 3000.'
|
||||
' if multiple years is selected, then only twenty years at any given time'))
|
||||
menu.add_option(category_name, 'end_year', self.__end_year)
|
||||
@ -1291,7 +1233,8 @@ class WebCalOptions(MenuReportOptions):
|
||||
menu.add_option(category_name, 'fullyear', fullyear)
|
||||
|
||||
country = EnumeratedListOption(_('Country for holidays'), 0 )
|
||||
for index, item in enumerate(_COUNTRIES):
|
||||
holiday_table = libholiday.HolidayTable()
|
||||
for index, item in enumerate(holiday_table.get_countries()):
|
||||
country.add_item(index, item)
|
||||
country.set_help(_("Holidays will be included for the selected "
|
||||
"country"))
|
||||
@ -1417,292 +1360,6 @@ class WebCalOptions(MenuReportOptions):
|
||||
else:
|
||||
self.__end_year.set_available(False)
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
#
|
||||
#
|
||||
#
|
||||
#------------------------------------------------------------------------
|
||||
class Element:
|
||||
""" A parsed XML element """
|
||||
def __init__(self, name, attributes):
|
||||
'Element constructor'
|
||||
# The element's tag name
|
||||
self.name = name
|
||||
# The element's attribute dictionary
|
||||
self.attributes = attributes
|
||||
# The element's cdata
|
||||
self.cdata = ''
|
||||
# The element's child element list (sequence)
|
||||
self.children = []
|
||||
|
||||
def addChild(self, element):
|
||||
'Add a reference to a child element'
|
||||
self.children.append(element)
|
||||
|
||||
def getAttribute(self, key):
|
||||
'Get an attribute value'
|
||||
return self.attributes.get(key)
|
||||
|
||||
def getData(self):
|
||||
'Get the cdata'
|
||||
return self.cdata
|
||||
|
||||
def getElements(self, name=''):
|
||||
'Get a list of child elements'
|
||||
#If no tag name is specified, return the all children
|
||||
if not name:
|
||||
return self.children
|
||||
else:
|
||||
# else return only those children with a matching tag name
|
||||
elements = []
|
||||
for element in self.children:
|
||||
if element.name == name:
|
||||
elements.append(element)
|
||||
return elements
|
||||
|
||||
def toString(self, level=0):
|
||||
retval = " " * level
|
||||
retval += "<%s" % self.name
|
||||
for attribute in self.attributes:
|
||||
retval += " %s=\"%s\"" % (attribute, self.attributes[attribute])
|
||||
c = ""
|
||||
for child in self.children:
|
||||
c += child.toString(level+1)
|
||||
if c == "":
|
||||
retval += "/>\n"
|
||||
else:
|
||||
retval += ">\n" + c + ("</%s>\n" % self.name)
|
||||
return retval
|
||||
|
||||
|
||||
class Xml2Obj:
|
||||
""" XML to Object """
|
||||
def __init__(self):
|
||||
self.root = None
|
||||
self.nodeStack = []
|
||||
|
||||
def StartElement(self, name, attributes):
|
||||
'SAX start element even handler'
|
||||
# Instantiate an Element object
|
||||
element = Element(name.encode(), attributes)
|
||||
# Push element onto the stack and make it a child of parent
|
||||
if len(self.nodeStack) > 0:
|
||||
parent = self.nodeStack[-1]
|
||||
parent.addChild(element)
|
||||
else:
|
||||
self.root = element
|
||||
self.nodeStack.append(element)
|
||||
|
||||
def EndElement(self, name):
|
||||
'SAX end element event handler'
|
||||
self.nodeStack = self.nodeStack[:-1]
|
||||
|
||||
def CharacterData(self, data):
|
||||
'SAX character data event handler'
|
||||
if data.strip():
|
||||
data = data.encode()
|
||||
element = self.nodeStack[-1]
|
||||
element.cdata += data
|
||||
return
|
||||
|
||||
def Parse(self, filename):
|
||||
# Create a SAX parser
|
||||
Parser = expat.ParserCreate()
|
||||
# SAX event handlers
|
||||
Parser.StartElementHandler = self.StartElement
|
||||
Parser.EndElementHandler = self.EndElement
|
||||
Parser.CharacterDataHandler = self.CharacterData
|
||||
# Parse the XML File
|
||||
ParserStatus = Parser.Parse(open(filename, 'r').read(), 1)
|
||||
return self.root
|
||||
|
||||
class Holidays:
|
||||
""" Class used to read XML holidays to add to calendar. """
|
||||
def __init__(self, elements, country="US"):
|
||||
self.debug = 0
|
||||
self.elements = elements
|
||||
self.country = country
|
||||
self.dates = []
|
||||
self.initialize()
|
||||
|
||||
def set_country(self, country):
|
||||
self.country = country
|
||||
self.dates = []
|
||||
self.initialize()
|
||||
|
||||
def initialize(self):
|
||||
# parse the date objects
|
||||
for country_set in self.elements.children:
|
||||
if country_set.name == "country" and country_set.attributes["name"] == self.country:
|
||||
for date in country_set.children:
|
||||
if date.name == "date":
|
||||
data = {"value" : "",
|
||||
"name" : "",
|
||||
"offset": "",
|
||||
"type": "",
|
||||
"if": "",
|
||||
} # defaults
|
||||
for attr in date.attributes:
|
||||
data[attr] = date.attributes[attr]
|
||||
self.dates.append(data)
|
||||
|
||||
def get_daynames(self, y, m, dayname):
|
||||
if self.debug:
|
||||
print "%s's in %d %d..." % (dayname, m, y)
|
||||
retval = [0]
|
||||
dow = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'].index(dayname)
|
||||
for d in range(1, 32):
|
||||
try:
|
||||
date = datetime.date(y, m, d)
|
||||
except ValueError:
|
||||
continue
|
||||
if date.weekday() == dow:
|
||||
retval.append( d )
|
||||
if self.debug:
|
||||
print "dow=", dow, "days=", retval
|
||||
return retval
|
||||
|
||||
def check_date(self, date):
|
||||
retval = []
|
||||
for rule in self.dates:
|
||||
if self.debug:
|
||||
print "Checking ", rule["name"], "..."
|
||||
offset = 0
|
||||
if rule["offset"] != "":
|
||||
if rule["offset"].isdigit():
|
||||
offset = int(rule["offset"])
|
||||
elif rule["offset"][0] in ["-", "+"] and rule["offset"][1:].isdigit():
|
||||
offset = int(rule["offset"])
|
||||
else:
|
||||
# must be a dayname
|
||||
offset = rule["offset"]
|
||||
if rule["value"].count("/") == 3: # year/num/day/month, "3rd wednesday in april"
|
||||
y, num, dayname, mon = rule["value"].split("/")
|
||||
if y == "*":
|
||||
y = date.year
|
||||
else:
|
||||
y = int(y)
|
||||
if mon.isdigit():
|
||||
m = int(mon)
|
||||
elif mon == "*":
|
||||
m = date.month
|
||||
else:
|
||||
m = ['jan', 'feb', 'mar', 'apr', 'may', 'jun',
|
||||
'jul', 'aug', 'sep', 'oct', 'nov', 'dec'].index(mon) + 1
|
||||
dates_of_dayname = self.get_daynames(y, m, dayname)
|
||||
if self.debug:
|
||||
print "num =", num
|
||||
d = dates_of_dayname[int(num)]
|
||||
elif rule["value"].count("/") == 2: # year/month/day
|
||||
y, m, d = rule["value"].split("/")
|
||||
if y == "*":
|
||||
y = date.year
|
||||
else:
|
||||
y = int(y)
|
||||
if m == "*":
|
||||
m = date.month
|
||||
else:
|
||||
m = int(m)
|
||||
if d == "*":
|
||||
d = date.day
|
||||
else:
|
||||
d = int(d)
|
||||
ndate = datetime.date(y, m, d)
|
||||
if self.debug:
|
||||
print ndate, offset, type(offset)
|
||||
if isinstance(offset, int):
|
||||
if offset != 0:
|
||||
ndate = ndate.fromordinal(ndate.toordinal() + offset)
|
||||
elif isinstance(offset, basestring):
|
||||
dir_ = 1
|
||||
if offset[0] == "-":
|
||||
dir_ = -1
|
||||
offset = offset[1:]
|
||||
if offset in ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']:
|
||||
# next tuesday you come to, including this one
|
||||
dow = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'].index(offset)
|
||||
ord_ = ndate.toordinal()
|
||||
while ndate.fromordinal(ord_).weekday() != dow:
|
||||
ord_ += dir_
|
||||
ndate = ndate.fromordinal(ord_)
|
||||
if self.debug:
|
||||
print "ndate:", ndate, "date:", date
|
||||
if ndate == date:
|
||||
if rule["if"] != "":
|
||||
if not eval(rule["if"]):
|
||||
continue
|
||||
retval.append(rule["name"])
|
||||
return retval
|
||||
|
||||
def _get_countries_from_holiday_file(filename):
|
||||
""" This will process a holiday file for country names """
|
||||
parser = Xml2Obj()
|
||||
element = parser.Parse(filename)
|
||||
country_list = []
|
||||
for country_set in element.children:
|
||||
if country_set.name == "country":
|
||||
if country_set.attributes["name"] not in country_list:
|
||||
country_list.append(country_set.attributes["name"])
|
||||
return country_list
|
||||
|
||||
def _get_countries():
|
||||
""" Looks in multiple places for holidays.xml files """
|
||||
locations = [const.PLUGINS_DIR, const.USER_PLUGINS]
|
||||
holiday_file = 'holidays.xml'
|
||||
country_list = []
|
||||
for dir_ in locations:
|
||||
holiday_full_path = os.path.join(dir_, holiday_file)
|
||||
if os.path.exists(holiday_full_path):
|
||||
cs = _get_countries_from_holiday_file(holiday_full_path)
|
||||
for c in cs:
|
||||
if c not in country_list:
|
||||
country_list.append(c)
|
||||
country_list.sort()
|
||||
country_list.insert(0, _("Don't include holidays"))
|
||||
return country_list
|
||||
|
||||
# TODO: Only load this once the first time it is actually needed so Gramps
|
||||
# doesn't take so long to start up.
|
||||
_COUNTRIES = _get_countries()
|
||||
|
||||
# code snippets for Easter and Daylight saving start/ stop
|
||||
# are borrowed from Calendar.py
|
||||
def _easter(year):
|
||||
"""
|
||||
Computes the year/month/day of easter. Based on work by
|
||||
J.-M. Oudin (1940) and is reprinted in the "Explanatory Supplement
|
||||
to the Astronomical Almanac", ed. P. K. Seidelmann (1992). Note:
|
||||
Ash Wednesday is 46 days before Easter Sunday.
|
||||
"""
|
||||
c = year / 100
|
||||
n = year - 19 * (year / 19)
|
||||
k = (c - 17) / 25
|
||||
i = c - c / 4 - (c - k) / 3 + 19 * n + 15
|
||||
i = i - 30 * (i / 30)
|
||||
i = i - (i / 28) * (1 - (i / 28) * (29 / (i + 1)) * ((21 - n) / 11))
|
||||
j = year + year / 4 + i + 2 - c + c / 4
|
||||
j = j - 7 * (j / 7)
|
||||
l = i - j
|
||||
month = 3 + (l + 40) / 44
|
||||
day = l + 28 - 31 * (month / 4)
|
||||
return _make_date(year, month, day)
|
||||
|
||||
def _get_dst_start_stop(year, area="us"):
|
||||
"""
|
||||
Return Daylight Saving Time start/stop in a given area ("us", "eu").
|
||||
US calculation valid 1976-2099; EU 1996-2099
|
||||
"""
|
||||
if area == "us":
|
||||
if year > 2006:
|
||||
start = "%d/%d/%d" % (year, 3, 14 - (math.floor(1 + year * 5 / 4) % 7)) # March
|
||||
stop = "%d/%d/%d" % (year, 11, 7 - (math.floor(1 + year * 5 / 4) % 7)) # November
|
||||
else:
|
||||
start = "%d/%d/%d" % (year, 4, (2 + 6 * year - math.floor(year / 4)) % 7 + 1) # April
|
||||
stop = "%d/%d/%d" % (year, 10, (31 - (math.floor(year * 5 / 4) + 1) % 7)) # October
|
||||
elif area == "eu":
|
||||
start = "%d/%d/%d" % (year, 3, (31 - (math.floor(year * 5 / 4) + 4) % 7)) # March
|
||||
stop = "%d/%d/%d" % (year, 10, (31 - (math.floor(year * 5 / 4) + 1) % 7)) # Oct
|
||||
return (start, stop)
|
||||
|
||||
def _get_regular_surname(sex, name):
|
||||
"""
|
||||
|
File diff suppressed because it is too large
Load Diff
21
src/plugins/lib/Makefile.am
Normal file
21
src/plugins/lib/Makefile.am
Normal file
@ -0,0 +1,21 @@
|
||||
# This is the src/plugins/lib level Makefile for Gramps
|
||||
# We could use GNU make's ':=' syntax for nice wildcard use,
|
||||
# but that is not necessarily portable.
|
||||
# If not using GNU make, then list all .py files individually
|
||||
|
||||
pkgdatadir = $(datadir)/@PACKAGE@/plugins/lib
|
||||
|
||||
pkgdata_PYTHON = \
|
||||
libholiday.py
|
||||
|
||||
pkgpyexecdir = @pkgpyexecdir@/plugins/lib
|
||||
pkgpythondir = @pkgpythondir@/plugins/lib
|
||||
|
||||
# Clean up all the byte-compiled files
|
||||
MOSTLYCLEANFILES = *pyc *pyo
|
||||
|
||||
GRAMPS_PY_MODPATH = "../../"
|
||||
|
||||
pycheck:
|
||||
(export PYTHONPATH=$(GRAMPS_PY_MODPATH); \
|
||||
pychecker $(pkgdata_PYTHON));
|
441
src/plugins/lib/libholiday.py
Normal file
441
src/plugins/lib/libholiday.py
Normal file
@ -0,0 +1,441 @@
|
||||
# Gramps - a GTK+/GNOME based genealogy program
|
||||
#
|
||||
# Copyright (C) 2000-2007 Donald N. Allingham
|
||||
# Copyright (C) 2008-2009 Brian G. Matherly
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# 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
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
#
|
||||
# python modules
|
||||
#
|
||||
#------------------------------------------------------------------------
|
||||
from gettext import gettext as _
|
||||
from xml.parsers import expat
|
||||
import datetime
|
||||
import math
|
||||
import const
|
||||
import os
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
#
|
||||
# GRAMPS modules
|
||||
#
|
||||
#------------------------------------------------------------------------
|
||||
from gen.plug import PluginManager, Plugin
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
#
|
||||
# Support functions
|
||||
#
|
||||
#------------------------------------------------------------------------
|
||||
def easter(year):
|
||||
"""
|
||||
Computes the year/month/day of easter. Based on work by
|
||||
J.-M. Oudin (1940) and is reprinted in the "Explanatory Supplement
|
||||
to the Astronomical Almanac", ed. P. K. Seidelmann (1992). Note:
|
||||
Ash Wednesday is 46 days before Easter Sunday.
|
||||
"""
|
||||
c = year / 100
|
||||
n = year - 19 * (year / 19)
|
||||
k = (c - 17) / 25
|
||||
i = c - c / 4 - (c - k) / 3 + 19 * n + 15
|
||||
i = i - 30 * (i / 30)
|
||||
i = i - (i / 28) * (1 - (i / 28) * (29 / (i + 1))
|
||||
* ((21 - n) / 11))
|
||||
j = year + year / 4 + i + 2 - c + c / 4
|
||||
j = j - 7 * (j / 7)
|
||||
l = i - j
|
||||
month = 3 + (l + 40) / 44
|
||||
day = l + 28 - 31 * (month / 4)
|
||||
return "%d/%d/%d" % (year, month, day)
|
||||
|
||||
def g2iso(dow):
|
||||
""" Converst GRAMPS day of week to ISO day of week """
|
||||
# GRAMPS: SUN = 1
|
||||
# ISO: MON = 1
|
||||
return (dow + 5) % 7 + 1
|
||||
|
||||
def dst(year, area="us"):
|
||||
"""
|
||||
Return Daylight Saving Time start/stop in a given area ("us", "eu").
|
||||
US calculation valid 1976-2099; EU 1996-2099
|
||||
"""
|
||||
if area == "us":
|
||||
if year > 2006:
|
||||
start = "%d/%d/%d" % (year, 3, 14 - (math.floor(1 + year * 5 / 4) % 7)) # March
|
||||
stop = "%d/%d/%d" % (year, 11, 7 - (math.floor(1 + year * 5 / 4) % 7)) # November
|
||||
else:
|
||||
start = "%d/%d/%d" % (year, 4, (2 + 6 * year - math.floor(year / 4)) % 7 + 1) # April
|
||||
stop = "%d/%d/%d" % (year, 10, (31 - (math.floor(year * 5 / 4) + 1) % 7)) # October
|
||||
elif area == "eu":
|
||||
start = "%d/%d/%d" % (year, 3, (31 - (math.floor(year * 5 / 4) + 4) % 7)) # March
|
||||
stop = "%d/%d/%d" % (year, 10, (31 - (math.floor(year * 5 / 4) + 1) % 7)) # Oct
|
||||
return (start, stop)
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
#
|
||||
# HolidayTable
|
||||
#
|
||||
#------------------------------------------------------------------------
|
||||
class HolidayTable:
|
||||
"""
|
||||
HolidayTable is a class which provides holidays for various
|
||||
countries and years.
|
||||
"""
|
||||
__holiday_files = []
|
||||
__countries = []
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Find the holiday files and load the countries if it has not already
|
||||
been done.
|
||||
"""
|
||||
if( not HolidayTable.__holiday_files ):
|
||||
self.__find_holiday_files()
|
||||
|
||||
if( not HolidayTable.__countries ):
|
||||
self.__build_country_list()
|
||||
|
||||
# Initialize the holiday table to be empty
|
||||
self.__holidays = {}
|
||||
self.__init_table()
|
||||
|
||||
def __find_holiday_files(self):
|
||||
""" Looks in multiple places for holidays.xml files """
|
||||
holiday_file = 'holidays.xml'
|
||||
|
||||
# Look for holiday files in the installation plugins directory and all
|
||||
# subdirectories.
|
||||
holiday_full_path = os.path.join(const.PLUGINS_DIR, holiday_file)
|
||||
if os.path.exists(holiday_full_path):
|
||||
HolidayTable.__holiday_files.append(holiday_full_path)
|
||||
|
||||
for (dirpath, dirnames, filenames) in os.walk(const.PLUGINS_DIR):
|
||||
for directory in dirnames:
|
||||
holiday_full_path = os.path.join(directory, holiday_file)
|
||||
if os.path.exists(holiday_full_path):
|
||||
HolidayTable.__holiday_files.append(holiday_full_path)
|
||||
|
||||
# Look for holiday files in the user plugins directory and all
|
||||
# subdirectories.
|
||||
holiday_full_path = os.path.join(const.USER_PLUGINS, holiday_file)
|
||||
if os.path.exists(holiday_full_path):
|
||||
HolidayTable.__holiday_files.append(holiday_full_path)
|
||||
|
||||
for (dirpath, dirnames, filenames) in os.walk(const.USER_PLUGINS):
|
||||
for directory in dirnames:
|
||||
holiday_full_path = os.path.join(directory, holiday_file)
|
||||
if os.path.exists(holiday_full_path):
|
||||
HolidayTable.__holiday_files.append(holiday_full_path)
|
||||
|
||||
def __build_country_list(self):
|
||||
""" Generate the list of countries that have holiday information. """
|
||||
for holiday_file_path in HolidayTable.__holiday_files:
|
||||
parser = _Xml2Obj()
|
||||
root_element = parser.parse(holiday_file_path)
|
||||
for country_element in root_element.get_children():
|
||||
if country_element.get_name() == "country":
|
||||
country_name = country_element.get_attribute("name")
|
||||
if country_name not in HolidayTable.__countries:
|
||||
HolidayTable.__countries.append(country_name)
|
||||
|
||||
def __init_table(self):
|
||||
""" Initialize the holiday table structure. """
|
||||
for month in range(1, 13):
|
||||
self.__holidays[month] = {}
|
||||
for day in range(1, 32):
|
||||
self.__holidays[month][day] = []
|
||||
|
||||
def get_countries(self):
|
||||
"""
|
||||
Get all the country names that holidays are available for.
|
||||
|
||||
@return: nothing
|
||||
"""
|
||||
return HolidayTable.__countries
|
||||
|
||||
def load_holidays(self, year, country):
|
||||
"""
|
||||
Load the holiday table for the specified year and country.
|
||||
This must be called before get_holidays().
|
||||
|
||||
@param year: The year for which the holidays should be loaded.
|
||||
Example: 2010
|
||||
@type year: int
|
||||
@param country: The country for which the holidays should be loaded.
|
||||
Example: "United States"
|
||||
@type country: str
|
||||
@return: nothing
|
||||
"""
|
||||
self.__init_table()
|
||||
for holiday_file_path in HolidayTable.__holiday_files:
|
||||
parser = _Xml2Obj()
|
||||
element = parser.parse(holiday_file_path)
|
||||
calendar = _Holidays(element, country)
|
||||
date = datetime.date(year, 1, 1)
|
||||
while date.year == year:
|
||||
holidays = calendar.check_date(date)
|
||||
for text in holidays:
|
||||
self.__holidays[date.month][date.day].append(text)
|
||||
date = date.fromordinal(date.toordinal() + 1)
|
||||
|
||||
def get_holidays(self, month, day):
|
||||
"""
|
||||
Get the holidays for the given day of the year.
|
||||
|
||||
@param month: The month for the requested holidays.
|
||||
Example: 1
|
||||
@type month: int
|
||||
@param month: The day for the requested holidays.
|
||||
Example: 1
|
||||
@type month: int
|
||||
@return: An array of strings with holiday names.
|
||||
@return type: [str]
|
||||
"""
|
||||
return self.__holidays[month][day]
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
#
|
||||
# _Element
|
||||
#
|
||||
#------------------------------------------------------------------------
|
||||
class _Element:
|
||||
""" A parsed XML element """
|
||||
def __init__(self, name, attributes):
|
||||
'Element constructor'
|
||||
# The element's tag name
|
||||
self.__name = name
|
||||
# The element's attribute dictionary
|
||||
self.__attributes = attributes
|
||||
# The element's child element list (sequence)
|
||||
self.__children = []
|
||||
|
||||
def add_child(self, element):
|
||||
'Add a reference to a child element'
|
||||
self.__children.append(element)
|
||||
|
||||
def get_attribute(self, key):
|
||||
'Get an attribute value'
|
||||
return self.__attributes.get(key)
|
||||
|
||||
def get_attributes(self):
|
||||
'Get all the attributes'
|
||||
return self.__attributes
|
||||
|
||||
def get_name(self):
|
||||
""" Get the name of this element. """
|
||||
return self.__name
|
||||
|
||||
def get_children(self):
|
||||
""" Get the children elements for this element. """
|
||||
return self.__children
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
#
|
||||
# _Xml2Obj
|
||||
#
|
||||
#------------------------------------------------------------------------
|
||||
class _Xml2Obj:
|
||||
""" XML to Object """
|
||||
def __init__(self):
|
||||
self.root = None
|
||||
self.nodeStack = []
|
||||
|
||||
def start_element(self, name, attributes):
|
||||
'SAX start element even handler'
|
||||
# Instantiate an Element object
|
||||
element = _Element(name.encode(), attributes)
|
||||
# Push element onto the stack and make it a child of parent
|
||||
if len(self.nodeStack) > 0:
|
||||
parent = self.nodeStack[-1]
|
||||
parent.add_child(element)
|
||||
else:
|
||||
self.root = element
|
||||
self.nodeStack.append(element)
|
||||
|
||||
def end_element(self, name):
|
||||
'SAX end element event handler'
|
||||
self.nodeStack = self.nodeStack[:-1]
|
||||
|
||||
def parse(self, filename):
|
||||
'Create a SAX parser and parse filename '
|
||||
parser = expat.ParserCreate()
|
||||
# SAX event handlers
|
||||
parser.StartElementHandler = self.start_element
|
||||
parser.EndElementHandler = self.end_element
|
||||
# Parse the XML File
|
||||
parser.Parse(open(filename, 'r').read(), 1)
|
||||
return self.root
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
#
|
||||
# _Holidays
|
||||
#
|
||||
#------------------------------------------------------------------------
|
||||
class _Holidays:
|
||||
""" Class used to read XML holidays to add to calendar. """
|
||||
def __init__(self, elements, country="US"):
|
||||
self.debug = 0
|
||||
self.elements = elements
|
||||
self.country = country
|
||||
self.dates = []
|
||||
self.initialize()
|
||||
|
||||
def set_country(self, country):
|
||||
""" Set the contry of holidays to read """
|
||||
self.country = country
|
||||
self.dates = []
|
||||
self.initialize()
|
||||
|
||||
def initialize(self):
|
||||
""" Parse the holiday date XML items """
|
||||
for country_set in self.elements.get_children():
|
||||
if country_set.get_name() == "country" and \
|
||||
country_set.get_attribute("name") == self.country:
|
||||
for date in country_set.get_children():
|
||||
if date.get_name() == "date":
|
||||
data = {"value" : "",
|
||||
"name" : "",
|
||||
"offset": "",
|
||||
"type": "",
|
||||
"if": "",
|
||||
} # defaults
|
||||
for attr in date.get_attributes():
|
||||
data[attr] = date.get_attribute(attr)
|
||||
self.dates.append(data)
|
||||
|
||||
def get_daynames(self, year, month, dayname):
|
||||
""" Get the items for a particular year/month and day of week """
|
||||
if self.debug:
|
||||
print "%s's in %d %d..." % (dayname, month, year)
|
||||
|
||||
retval = [0]
|
||||
dow = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'].index(dayname)
|
||||
for day in range(1, 32):
|
||||
try:
|
||||
date = datetime.date(year, month, day)
|
||||
except ValueError:
|
||||
continue
|
||||
if date.weekday() == dow:
|
||||
retval.append(day)
|
||||
|
||||
if self.debug:
|
||||
print "dow=", dow, "days=", retval
|
||||
|
||||
return retval
|
||||
|
||||
def check_date(self, date):
|
||||
""" Return items that match rules """
|
||||
retval = []
|
||||
for rule in self.dates:
|
||||
|
||||
if self.debug:
|
||||
print "Checking ", rule["name"], "..."
|
||||
|
||||
offset = 0
|
||||
if rule["offset"] != "":
|
||||
if rule["offset"].isdigit():
|
||||
offset = int(rule["offset"])
|
||||
elif rule["offset"][0] in ["-", "+"] and \
|
||||
rule["offset"][1:].isdigit():
|
||||
offset = int(rule["offset"])
|
||||
else:
|
||||
# must be a dayname
|
||||
offset = rule["offset"]
|
||||
if len(rule["value"]) > 0 and rule["value"][0] == '>':
|
||||
# eval exp -> year/num[/day[/month]]
|
||||
y, m, d = date.year, date.month, date.day
|
||||
rule["value"] = eval(rule["value"][1:])
|
||||
|
||||
if self.debug:
|
||||
print "rule['value']:", rule["value"]
|
||||
|
||||
if rule["value"].count("/") == 3: # year/num/day/month, "3rd wednesday in april"
|
||||
y, num, dayname, mon = rule["value"].split("/")
|
||||
if y == "*":
|
||||
y = date.year
|
||||
else:
|
||||
y = int(y)
|
||||
if mon.isdigit():
|
||||
m = int(mon)
|
||||
elif mon == "*":
|
||||
m = date.month
|
||||
else:
|
||||
m = ['jan', 'feb', 'mar', 'apr', 'may', 'jun',
|
||||
'jul', 'aug', 'sep', 'oct', 'nov', 'dec'].index(mon) + 1
|
||||
dates_of_dayname = self.get_daynames(y, m, dayname)
|
||||
|
||||
if self.debug:
|
||||
print "num =", num
|
||||
|
||||
d = dates_of_dayname[int(num)]
|
||||
elif rule["value"].count("/") == 2: # year/month/day
|
||||
y, m, d = rule["value"].split("/")
|
||||
if y == "*":
|
||||
y = date.year
|
||||
else:
|
||||
y = int(y)
|
||||
if m == "*":
|
||||
m = date.month
|
||||
else:
|
||||
m = int(m)
|
||||
if d == "*":
|
||||
d = date.day
|
||||
else:
|
||||
d = int(d)
|
||||
ndate = datetime.date(y, m, d)
|
||||
|
||||
if self.debug:
|
||||
print ndate, offset, type(offset)
|
||||
|
||||
if isinstance(offset, int):
|
||||
if offset != 0:
|
||||
ndate = ndate.fromordinal(ndate.toordinal() + offset)
|
||||
elif isinstance(offset, basestring):
|
||||
direction = 1
|
||||
if offset[0] == "-":
|
||||
direction = -1
|
||||
offset = offset[1:]
|
||||
if offset in ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']:
|
||||
# next tuesday you come to, including this one
|
||||
dow = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'].index(offset)
|
||||
ordinal = ndate.toordinal()
|
||||
while ndate.fromordinal(ordinal).weekday() != dow:
|
||||
ordinal += direction
|
||||
ndate = ndate.fromordinal(ordinal)
|
||||
|
||||
if self.debug:
|
||||
print "ndate:", ndate, "date:", date
|
||||
|
||||
if ndate == date:
|
||||
if rule["if"] != "":
|
||||
if not eval(rule["if"]):
|
||||
continue
|
||||
retval.append(rule["name"])
|
||||
return retval
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
#
|
||||
# Register the plugins
|
||||
#
|
||||
#------------------------------------------------------------------------
|
||||
PluginManager.get_instance().register_plugin(
|
||||
Plugin(
|
||||
name = __name__,
|
||||
description = _("Provides holiday information for different countries."),
|
||||
module_name = __name__
|
||||
)
|
||||
)
|
488
src/plugins/textreport/BirthdayReport.py
Normal file
488
src/plugins/textreport/BirthdayReport.py
Normal file
@ -0,0 +1,488 @@
|
||||
# Gramps - a GTK+/GNOME based genealogy program
|
||||
#
|
||||
# Copyright (C) 2000-2007 Donald N. Allingham
|
||||
# Copyright (C) 2008-2009 Brian G. Matherly
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# 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
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
#
|
||||
# python modules
|
||||
#
|
||||
#------------------------------------------------------------------------
|
||||
from gettext import gettext as _
|
||||
import datetime
|
||||
import time
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
#
|
||||
# GRAMPS modules
|
||||
#
|
||||
#------------------------------------------------------------------------
|
||||
import BaseDoc
|
||||
from BasicUtils import name_displayer
|
||||
from gen.plug import PluginManager
|
||||
from ReportBase import Report, ReportUtils, MenuReportOptions, CATEGORY_TEXT
|
||||
from gen.plug.menu import BooleanOption, StringOption, NumberOption, \
|
||||
EnumeratedListOption, FilterOption, PersonOption
|
||||
import GrampsLocale
|
||||
import gen.lib
|
||||
from Utils import probably_alive, ProgressMeter
|
||||
|
||||
import libholiday
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
#
|
||||
# Support functions
|
||||
#
|
||||
#------------------------------------------------------------------------
|
||||
def make_date(year, month, day):
|
||||
"""
|
||||
Return a Date object of the particular year/month/day.
|
||||
"""
|
||||
retval = gen.lib.Date()
|
||||
retval.set_yr_mon_day(year, month, day)
|
||||
return retval
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
#
|
||||
# Calendar
|
||||
#
|
||||
#------------------------------------------------------------------------
|
||||
class CalendarReport(Report):
|
||||
"""
|
||||
Create the Calendar object that produces the report.
|
||||
"""
|
||||
def __init__(self, database, options_class):
|
||||
Report.__init__(self, database, options_class)
|
||||
menu = options_class.menu
|
||||
|
||||
self.titletext = menu.get_option_by_name('titletext').get_value()
|
||||
self.relationships = \
|
||||
menu.get_option_by_name('relationships').get_value()
|
||||
self.year = menu.get_option_by_name('year').get_value()
|
||||
self.name_format = menu.get_option_by_name('name_format').get_value()
|
||||
self.country = menu.get_option_by_name('country').get_value()
|
||||
self.anniversaries = menu.get_option_by_name('anniversaries').get_value()
|
||||
self.start_dow = menu.get_option_by_name('start_dow').get_value()
|
||||
self.maiden_name = menu.get_option_by_name('maiden_name').get_value()
|
||||
self.alive = menu.get_option_by_name('alive').get_value()
|
||||
self.birthdays = menu.get_option_by_name('birthdays').get_value()
|
||||
self.text1 = menu.get_option_by_name('text1').get_value()
|
||||
self.text2 = menu.get_option_by_name('text2').get_value()
|
||||
self.text3 = menu.get_option_by_name('text3').get_value()
|
||||
self.filter_option = menu.get_option_by_name('filter')
|
||||
self.filter = self.filter_option.get_filter()
|
||||
pid = menu.get_option_by_name('pid').get_value()
|
||||
self.center_person = database.get_person_from_gramps_id(pid)
|
||||
|
||||
def get_name(self, person, maiden_name = None):
|
||||
"""
|
||||
Return person's name, unless maiden_name given, unless married_name
|
||||
listed.
|
||||
"""
|
||||
# Get all of a person's names:
|
||||
primary_name = person.get_primary_name()
|
||||
married_name = None
|
||||
names = [primary_name] + person.get_alternate_names()
|
||||
for name in names:
|
||||
if int(name.get_type()) == gen.lib.NameType.MARRIED:
|
||||
married_name = name
|
||||
break # use first
|
||||
# Now, decide which to use:
|
||||
if maiden_name is not None:
|
||||
if married_name is not None:
|
||||
name = gen.lib.Name(married_name)
|
||||
else:
|
||||
name = gen.lib.Name(primary_name)
|
||||
name.set_surname(maiden_name)
|
||||
else:
|
||||
name = gen.lib.Name(primary_name)
|
||||
name.set_display_as(self.name_format)
|
||||
return name_displayer.display_name(name)
|
||||
|
||||
def add_day_item(self, text, month, day):
|
||||
""" Add an item to a day. """
|
||||
month_dict = self.calendar.get(month, {})
|
||||
day_list = month_dict.get(day, [])
|
||||
day_list.append(text)
|
||||
month_dict[day] = day_list
|
||||
self.calendar[month] = month_dict
|
||||
|
||||
def __get_holidays(self):
|
||||
""" Get the holidays for the specified country and year """
|
||||
holiday_table = libholiday.HolidayTable()
|
||||
country = holiday_table.get_countries()[self.country]
|
||||
holiday_table.load_holidays(self.year, country)
|
||||
for month in range(1, 13):
|
||||
for day in range(1, 32):
|
||||
holiday_names = holiday_table.get_holidays(month, day)
|
||||
for holiday_name in holiday_names:
|
||||
self.add_day_item(holiday_name, month, day)
|
||||
|
||||
def write_report(self):
|
||||
""" The short method that runs through each month and creates a page. """
|
||||
# initialize the dict to fill:
|
||||
self.progress = ProgressMeter(_('Birthday and Anniversary Report'))
|
||||
self.calendar = {}
|
||||
# get the information, first from holidays:
|
||||
if self.country != 0:
|
||||
self.__get_holidays()
|
||||
# get data from database:
|
||||
self.collect_data()
|
||||
# generate the report:
|
||||
self.doc.start_paragraph('BIR-Title')
|
||||
self.doc.write_text(str(self.titletext) + ": " + str(self.year))
|
||||
self.doc.end_paragraph()
|
||||
if self.text1.strip() != "":
|
||||
self.doc.start_paragraph('BIR-Text1style')
|
||||
self.doc.write_text(str(self.text1))
|
||||
self.doc.end_paragraph()
|
||||
if self.text2.strip() != "":
|
||||
self.doc.start_paragraph('BIR-Text2style')
|
||||
self.doc.write_text(str(self.text2))
|
||||
self.doc.end_paragraph()
|
||||
if self.text3.strip() != "":
|
||||
self.doc.start_paragraph('BIR-Text3style')
|
||||
self.doc.write_text(str(self.text3))
|
||||
self.doc.end_paragraph()
|
||||
if self.relationships:
|
||||
name = self.center_person.get_primary_name()
|
||||
self.doc.start_paragraph('BIR-Text3style')
|
||||
self.doc.write_text(_("Relationships shown are to %s") % name_displayer.display_name(name))
|
||||
self.doc.end_paragraph()
|
||||
self.progress.set_pass(_('Formatting months...'), 12)
|
||||
for month in range(1, 13):
|
||||
self.progress.step()
|
||||
self.print_page(month)
|
||||
self.progress.close()
|
||||
|
||||
def print_page(self, month):
|
||||
""" Prints a month as a page """
|
||||
year = self.year
|
||||
self.doc.start_paragraph('BIR-Monthstyle')
|
||||
self.doc.write_text(GrampsLocale.long_months[month].capitalize())
|
||||
self.doc.end_paragraph()
|
||||
current_date = datetime.date(year, month, 1)
|
||||
current_ord = current_date.toordinal()
|
||||
started_day = {}
|
||||
for i in range(31):
|
||||
thisday = current_date.fromordinal(current_ord)
|
||||
if thisday.month == month:
|
||||
list = self.calendar.get(month, {}).get(thisday.day, [])
|
||||
for p in list:
|
||||
p = p.replace("\n", " ")
|
||||
if thisday not in started_day:
|
||||
self.doc.start_paragraph("BIR-Daystyle")
|
||||
self.doc.write_text(str(thisday.day))
|
||||
self.doc.end_paragraph()
|
||||
started_day[thisday] = 1
|
||||
self.doc.start_paragraph("BIR-Datastyle")
|
||||
self.doc.write_text(p)
|
||||
self.doc.end_paragraph()
|
||||
current_ord += 1
|
||||
|
||||
def collect_data(self):
|
||||
"""
|
||||
This method runs through the data, and collects the relevant dates
|
||||
and text.
|
||||
"""
|
||||
people = self.database.get_person_handles(sort_handles=False)
|
||||
self.progress.set_pass(_('Applying Filter...'), len(people))
|
||||
people = self.filter.apply(self.database, people, self.progress)
|
||||
pmgr = PluginManager.get_instance()
|
||||
rel_calc = pmgr.get_relationship_calculator()
|
||||
|
||||
self.progress.set_pass(_('Reading database...'), len(people))
|
||||
for person_handle in people:
|
||||
self.progress.step()
|
||||
person = self.database.get_person_from_handle(person_handle)
|
||||
birth_ref = person.get_birth_ref()
|
||||
birth_date = None
|
||||
if birth_ref:
|
||||
birth_event = self.database.get_event_from_handle(birth_ref.ref)
|
||||
birth_date = birth_event.get_date_object()
|
||||
if self.birthdays and birth_date is not None:
|
||||
year = birth_date.get_year()
|
||||
month = birth_date.get_month()
|
||||
day = birth_date.get_day()
|
||||
age = self.year - year
|
||||
# add some things to handle maiden name:
|
||||
father_lastname = None # husband, actually
|
||||
if self.maiden_name in ['spouse_first', 'spouse_last']: # get husband's last name:
|
||||
if person.get_gender() == gen.lib.Person.FEMALE:
|
||||
family_list = person.get_family_handle_list()
|
||||
if len(family_list) > 0:
|
||||
if self.maiden_name == 'spouse_first':
|
||||
fhandle = family_list[0]
|
||||
else:
|
||||
fhandle = family_list[-1]
|
||||
fam = self.database.get_family_from_handle(fhandle)
|
||||
father_handle = fam.get_father_handle()
|
||||
mother_handle = fam.get_mother_handle()
|
||||
if mother_handle == person_handle:
|
||||
if father_handle:
|
||||
father = self.database.get_person_from_handle(father_handle)
|
||||
if father is not None:
|
||||
father_lastname = father.get_primary_name().get_surname()
|
||||
short_name = self.get_name(person, father_lastname)
|
||||
if age >= 0:
|
||||
alive = probably_alive(person, self.database, make_date(self.year, month, day))
|
||||
if ((self.alive and alive) or not self.alive):
|
||||
comment = ""
|
||||
if self.relationships:
|
||||
relation = rel_calc.get_one_relationship(
|
||||
self.database,
|
||||
self.center_person,
|
||||
person)
|
||||
if relation:
|
||||
comment = " --- %s" % relation
|
||||
self.add_day_item("%s, %d%s" % (short_name, age, comment), month, day)
|
||||
if self.anniversaries:
|
||||
family_list = person.get_family_handle_list()
|
||||
for fhandle in family_list:
|
||||
fam = self.database.get_family_from_handle(fhandle)
|
||||
father_handle = fam.get_father_handle()
|
||||
mother_handle = fam.get_mother_handle()
|
||||
if father_handle == person.get_handle():
|
||||
spouse_handle = mother_handle
|
||||
else:
|
||||
continue # with next person if the father is not "person"
|
||||
# this will keep from duplicating the anniversary
|
||||
if spouse_handle:
|
||||
spouse = self.database.get_person_from_handle(spouse_handle)
|
||||
if spouse:
|
||||
spouse_name = self.get_name(spouse)
|
||||
short_name = self.get_name(person)
|
||||
# TEMP: this will hanlde ordered events
|
||||
# GRAMPS 3.0 will have a new mechanism for start/stop events
|
||||
are_married = None
|
||||
for event_ref in fam.get_event_ref_list():
|
||||
event = self.database.get_event_from_handle(event_ref.ref)
|
||||
if event.type in [gen.lib.EventType.MARRIAGE,
|
||||
gen.lib.EventType.MARR_ALT]:
|
||||
are_married = event
|
||||
elif event.type in [gen.lib.EventType.DIVORCE,
|
||||
gen.lib.EventType.ANNULMENT,
|
||||
gen.lib.EventType.DIV_FILING]:
|
||||
are_married = None
|
||||
if are_married is not None:
|
||||
for event_ref in fam.get_event_ref_list():
|
||||
event = self.database.get_event_from_handle(event_ref.ref)
|
||||
event_obj = event.get_date_object()
|
||||
year = event_obj.get_year()
|
||||
month = event_obj.get_month()
|
||||
day = event_obj.get_day()
|
||||
years = self.year - year
|
||||
if years >= 0:
|
||||
text = _("%(spouse)s and\n %(person)s, %(nyears)d") % {
|
||||
'spouse' : spouse_name,
|
||||
'person' : short_name,
|
||||
'nyears' : years,
|
||||
}
|
||||
alive1 = probably_alive(person, self.database, make_date(self.year, month, day))
|
||||
alive2 = probably_alive(spouse, self.database, make_date(self.year, month, day))
|
||||
if ((self.alive and alive1 and alive2) or not self.alive):
|
||||
self.add_day_item(text, month, day)
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
#
|
||||
# CalendarOptions
|
||||
#
|
||||
#------------------------------------------------------------------------
|
||||
class CalendarOptions(MenuReportOptions):
|
||||
""" Options for the Birthday/Anniversary Report """
|
||||
def __init__(self, name, dbase):
|
||||
self.__db = dbase
|
||||
self.__pid = None
|
||||
self.__filter = None
|
||||
MenuReportOptions.__init__(self, name, dbase)
|
||||
|
||||
def add_menu_options(self, menu):
|
||||
""" Add the options for the graphical calendar """
|
||||
category_name = _("Report Options")
|
||||
|
||||
year = NumberOption(_("Year of calendar"), time.localtime()[0],
|
||||
1000, 3000)
|
||||
year.set_help(_("Year of calendar"))
|
||||
menu.add_option(category_name, "year", year)
|
||||
|
||||
self.__filter = FilterOption(_("Filter"), 0)
|
||||
self.__filter.set_help(
|
||||
_("Select filter to restrict people that appear on calendar"))
|
||||
menu.add_option(category_name, "filter", self.__filter)
|
||||
|
||||
self.__pid = PersonOption(_("Center Person"))
|
||||
self.__pid.set_help(_("The center person for the report"))
|
||||
menu.add_option(category_name, "pid", self.__pid)
|
||||
self.__pid.connect('value-changed', self.__update_filters)
|
||||
|
||||
self.__update_filters()
|
||||
|
||||
# We must figure out the value of the first option before we can
|
||||
# create the EnumeratedListOption
|
||||
fmt_list = name_displayer.get_name_format()
|
||||
name_format = EnumeratedListOption(_("Name format"), fmt_list[0][0])
|
||||
for num, name, fmt_str, act in fmt_list:
|
||||
name_format.add_item(num, name)
|
||||
name_format.set_help(_("Select the format to display names"))
|
||||
menu.add_option(category_name, "name_format", name_format)
|
||||
|
||||
country = EnumeratedListOption(_("Country for holidays"), 0)
|
||||
holiday_table = libholiday.HolidayTable()
|
||||
count = 0
|
||||
for c in holiday_table.get_countries():
|
||||
country.add_item(count, c)
|
||||
count += 1
|
||||
country.set_help(_("Select the country to see associated holidays"))
|
||||
menu.add_option(category_name, "country", country)
|
||||
|
||||
start_dow = EnumeratedListOption(_("First day of week"), 1)
|
||||
for count in range(1, 8):
|
||||
# conversion between gramps numbering (sun=1) and iso numbering (mon=1) of weekdays below
|
||||
start_dow.add_item((count+5) % 7 + 1, GrampsLocale.long_days[count].capitalize())
|
||||
start_dow.set_help(_("Select the first day of the week for the calendar"))
|
||||
menu.add_option(category_name, "start_dow", start_dow)
|
||||
|
||||
maiden_name = EnumeratedListOption(_("Birthday surname"), "own")
|
||||
maiden_name.add_item("spouse_first", _("Wives use husband's surname (from first family listed)"))
|
||||
maiden_name.add_item("spouse_last", _("Wives use husband's surname (from last family listed)"))
|
||||
maiden_name.add_item("own", _("Wives use their own surname"))
|
||||
maiden_name.set_help(_("Select married women's displayed surname"))
|
||||
menu.add_option(category_name, "maiden_name", maiden_name)
|
||||
|
||||
alive = BooleanOption(_("Include only living people"), True)
|
||||
alive.set_help(_("Include only living people in the calendar"))
|
||||
menu.add_option(category_name, "alive", alive)
|
||||
|
||||
birthdays = BooleanOption(_("Include birthdays"), True)
|
||||
birthdays.set_help(_("Include birthdays in the calendar"))
|
||||
menu.add_option(category_name, "birthdays", birthdays)
|
||||
|
||||
anniversaries = BooleanOption(_("Include anniversaries"), True)
|
||||
anniversaries.set_help(_("Include anniversaries in the calendar"))
|
||||
menu.add_option(category_name, "anniversaries", anniversaries)
|
||||
|
||||
option = BooleanOption(_("Include relationships to center person"),
|
||||
False)
|
||||
option.set_help(_("Include relationships to center person (slower)"))
|
||||
menu.add_option(category_name, "relationships", option)
|
||||
|
||||
category_name = _("Text Options")
|
||||
|
||||
titletext = StringOption(_("Title text"),
|
||||
_("Birthday and Anniversary Report"))
|
||||
titletext.set_help(_("Title of calendar"))
|
||||
menu.add_option(category_name, "titletext", titletext)
|
||||
|
||||
text1 = StringOption(_("Text Area 1"), _("My Calendar"))
|
||||
text1.set_help(_("First line of text at bottom of calendar"))
|
||||
menu.add_option(category_name, "text1", text1)
|
||||
|
||||
text2 = StringOption(_("Text Area 2"), _("Produced with GRAMPS"))
|
||||
text2.set_help(_("Second line of text at bottom of calendar"))
|
||||
menu.add_option(category_name, "text2", text2)
|
||||
|
||||
text3 = StringOption(_("Text Area 3"), "http://gramps-project.org/",)
|
||||
text3.set_help(_("Third line of text at bottom of calendar"))
|
||||
menu.add_option(category_name, "text3", text3)
|
||||
|
||||
def __update_filters(self):
|
||||
"""
|
||||
Update the filter list based on the selected person
|
||||
"""
|
||||
gid = self.__pid.get_value()
|
||||
person = self.__db.get_person_from_gramps_id(gid)
|
||||
filter_list = ReportUtils.get_person_filters(person, False)
|
||||
self.__filter.set_filters(filter_list)
|
||||
|
||||
def make_my_style(self, default_style, name, description,
|
||||
size=9, font=BaseDoc.FONT_SERIF, justified ="left",
|
||||
color=None, align=BaseDoc.PARA_ALIGN_CENTER,
|
||||
shadow = None, italic=0, bold=0, borders=0, indent=None):
|
||||
""" Create paragraph and graphic styles of the same name """
|
||||
# Paragraph:
|
||||
f = BaseDoc.FontStyle()
|
||||
f.set_size(size)
|
||||
f.set_type_face(font)
|
||||
f.set_italic(italic)
|
||||
f.set_bold(bold)
|
||||
p = BaseDoc.ParagraphStyle()
|
||||
p.set_font(f)
|
||||
p.set_alignment(align)
|
||||
p.set_description(description)
|
||||
p.set_top_border(borders)
|
||||
p.set_left_border(borders)
|
||||
p.set_bottom_border(borders)
|
||||
p.set_right_border(borders)
|
||||
if indent:
|
||||
p.set(first_indent=indent)
|
||||
if justified == "left":
|
||||
p.set_alignment(BaseDoc.PARA_ALIGN_LEFT)
|
||||
elif justified == "right":
|
||||
p.set_alignment(BaseDoc.PARA_ALIGN_RIGHT)
|
||||
elif justified == "center":
|
||||
p.set_alignment(BaseDoc.PARA_ALIGN_CENTER)
|
||||
default_style.add_paragraph_style(name, p)
|
||||
# Graphics:
|
||||
g = BaseDoc.GraphicsStyle()
|
||||
g.set_paragraph_style(name)
|
||||
if shadow:
|
||||
g.set_shadow(*shadow)
|
||||
if color is not None:
|
||||
g.set_fill_color(color)
|
||||
if not borders:
|
||||
g.set_line_width(0)
|
||||
default_style.add_draw_style(name, g)
|
||||
|
||||
def make_default_style(self, default_style):
|
||||
""" Add the styles used in this report """
|
||||
self.make_my_style(default_style, "BIR-Title",
|
||||
_('Title text style'), 14,
|
||||
bold=1, justified="center")
|
||||
self.make_my_style(default_style, "BIR-Datastyle",
|
||||
_('Data text display'), 12, indent=1.0)
|
||||
self.make_my_style(default_style, "BIR-Daystyle",
|
||||
_('Day text style'), 12, indent=.5,
|
||||
italic=1, bold=1)
|
||||
self.make_my_style(default_style, "BIR-Monthstyle",
|
||||
_('Month text style'), 14, bold=1)
|
||||
self.make_my_style(default_style, "BIR-Text1style",
|
||||
_('Text at bottom, line 1'), 12, justified="center")
|
||||
self.make_my_style(default_style, "BIR-Text2style",
|
||||
_('Text at bottom, line 2'), 12, justified="center")
|
||||
self.make_my_style(default_style, "BIR-Text3style",
|
||||
_('Text at bottom, line 3'), 12, justified="center")
|
||||
|
||||
#------------------------------------------------------------------------
|
||||
#
|
||||
# Register the plugins
|
||||
#
|
||||
#------------------------------------------------------------------------
|
||||
pmgr = PluginManager.get_instance()
|
||||
pmgr.register_report(
|
||||
name = 'birthday_report',
|
||||
category = CATEGORY_TEXT,
|
||||
report_class = CalendarReport,
|
||||
options_class = CalendarOptions,
|
||||
modes = PluginManager.REPORT_MODE_GUI | \
|
||||
PluginManager.REPORT_MODE_BKI | \
|
||||
PluginManager.REPORT_MODE_CLI,
|
||||
translated_name = _("Birthday and Anniversary Report"),
|
||||
status = _("Stable"),
|
||||
author_name = "Douglas S. Blank",
|
||||
author_email = "dblank@cs.brynmawr.edu",
|
||||
description = _("Produces a report of birthdays and anniversaries"),
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user