///////////////////////////////////////////////////////////////////////////// // // Project ProjectForge Community Edition // www.projectforge.org // // Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de) // // ProjectForge is dual-licensed. // // This community edition 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; version 3 of the License. // // This community edition 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, see http://www.gnu.org/licenses/. // ///////////////////////////////////////////////////////////////////////////// package org.projectforge.calendar; import java.math.BigDecimal; import java.util.Calendar; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.projectforge.core.ConfigXml; /** * * @author Kai Reinhard (k.reinhard@micromata.de) * */ public class Holidays { private static final Logger log = Logger.getLogger(Holidays.class); private static final Holidays instance = new Holidays(); public static Holidays getInstance() { return instance; } /** Contains all holidays of a year. Key is the year. Value is a map of all holidays in the year with the day of the year as key. */ private Map<Integer, Map<Integer, Holiday>> holidaysByYear = new HashMap<Integer, Map<Integer, Holiday>>(); private Map<HolidayDefinition, ConfigureHoliday> reconfiguredHolidays = new HashMap<HolidayDefinition, ConfigureHoliday>(); private ConfigXml xmlConfiguration; private Map<Integer, Holiday> computeHolidays(int year) { log.info("Compute holidays for year: " + year); final Map<Integer, Holiday> holidays = new HashMap<Integer, Holiday>(); final Calendar cal = Calendar.getInstance(); cal.set(Calendar.YEAR, year); for (final HolidayDefinition holiday : HolidayDefinition.values()) { if (holiday.getEasterOffset() == null) { putHoliday(holidays, cal, holiday); } } int g = year % 19; // "Golden Number" of year - 1 int i = 0; // # of days from 3/21 to the Paschal full moon int j = 0; // Weekday (0-based) of Paschal full moon // We're past the Gregorian switchover, so use the Gregorian rules. int c = year / 100; int h = (c - c / 4 - (8 * c + 13) / 25 + 19 * g + 15) % 30; i = h - (h / 28) * (1 - (h / 28) * (29 / (h + 1)) * ((21 - g) / 11)); j = (year + year / 4 + i + 2 - c + c / 4) % 7; /** * Use otherwise the old Julian rules (not really yet needed ;-) i = (19*g + 15) % 30; j = (year + year/4 + i) % 7; } */ int l = i - j; int m = 3 + (l + 40) / 44; // 1-based month in which Easter falls int d = l + 28 - 31 * (m / 4); // Date of Easter within that month cal.set(Calendar.YEAR, year); cal.set(Calendar.MONTH, m - 1); // 0-based cal.set(Calendar.DATE, d); cal.getTime(); // JDK 1.1.2 bug workaround for (final HolidayDefinition holiday : HolidayDefinition.values()) { if (holiday.getEasterOffset() != null) { putEasterHoliday(holidays, cal, holiday); } } if (xmlConfiguration.getHolidays() != null) { for (final ConfigureHoliday cfgHoliday : xmlConfiguration.getHolidays()) { if (cfgHoliday.getId() == null && cfgHoliday.isIgnore() == false) { // New Holiday. if (cfgHoliday.getMonth() == null || cfgHoliday.getDayOfMonth() == null || StringUtils.isBlank(cfgHoliday.getLabel()) == true) { log.error("Holiday not full configured (month, dayOfMonth, label, ...) missed: " + cfgHoliday.toString()); break; } if (cfgHoliday.getYear() != null && cfgHoliday.getYear() != year) { // Holiday affects not the current year. continue; } cal.set(Calendar.MONTH, cfgHoliday.getMonth()); cal.set(Calendar.DAY_OF_MONTH, cfgHoliday.getDayOfMonth()); final Holiday holiday = new Holiday(null, cfgHoliday.getLabel(), cfgHoliday.isWorkingDay(), cfgHoliday.getWorkFraction()); putHoliday(holidays, cal.get(Calendar.DAY_OF_YEAR), holiday); log.info("Configured holiday added: " + holiday); } } } return holidays; } private void putHoliday(final Map<Integer, Holiday> holidays, final Calendar cal, final HolidayDefinition def) { if (def.getEasterOffset() != null) { return; } cal.set(Calendar.MONTH, def.getMonth()); cal.set(Calendar.DAY_OF_MONTH, def.getDayOfMonth()); final Holiday holiday = createHoliday(def); if (holiday != null) { putHoliday(holidays, cal.get(Calendar.DAY_OF_YEAR), holiday); } } private void putEasterHoliday(final Map<Integer, Holiday> holidays, final Calendar cal, final HolidayDefinition def) { if (def.getEasterOffset() != null) { final Holiday holiday = createHoliday(def); if (holiday != null) { putHoliday(holidays, cal.get(Calendar.DAY_OF_YEAR) + def.getEasterOffset(), holiday); } } } private Holiday createHoliday(final HolidayDefinition def) { String i18nKey = def.getI18nKey(); String label = null; BigDecimal workingFraction = null; boolean isWorkingDay = def.isWorkingDay(); if (reconfiguredHolidays.containsKey(def) == true) { final ConfigureHoliday cfgHoliday = reconfiguredHolidays.get(def); if (cfgHoliday.isIgnore() == true) { // Ignore holiday. return null; } if (StringUtils.isNotBlank(cfgHoliday.getLabel()) == true) { i18nKey = null; label = cfgHoliday.getLabel(); } if (cfgHoliday.getWorkFraction() != null) { workingFraction = cfgHoliday.getWorkFraction(); } } return new Holiday(i18nKey, label, isWorkingDay, workingFraction); } private void putHoliday(final Map<Integer, Holiday> holidays, final int dayOfYear, final Holiday holiday) { if (holidays.containsKey(dayOfYear) == true) { log.warn("Holiday does already exist (may-be use ignore in config.xml?): " + holidays.get(dayOfYear) + "! Overwriting it by new one: " + holiday); } holidays.put(dayOfYear, holiday); } private synchronized Map<Integer, Holiday> getHolidays(int year) { if (xmlConfiguration == null) { xmlConfiguration = ConfigXml.getInstance(); if (xmlConfiguration.getHolidays() != null) { for (final ConfigureHoliday holiday : xmlConfiguration.getHolidays()) { if (holiday.getId() != null) { reconfiguredHolidays.put(holiday.getId(), holiday); } } holidaysByYear.clear(); } } Map<Integer, Holiday> holidays = holidaysByYear.get(new Integer(year)); if (holidays == null) { holidays = computeHolidays(year); holidaysByYear.put(year, holidays); } return holidays; } public boolean isHoliday(int year, int dayOfYear) { return (getHolidays(year).containsKey(dayOfYear) == true); } public boolean isWorkingDay(final DayHolder date) { if (date.isWeekend() == true) { return false; } final Holiday day = getHolidays(date.getYear()).get(date.getDayOfYear()); if (day != null && day.isWorkingDay() == false) { return false; } return true; } public BigDecimal getWorkFraction(final DayHolder date) { if (date.isWeekend() == true) { return null; } final Holiday day = getHolidays(date.getYear()).get(date.getDayOfYear()); if (day == null) { return null; } return day.getWorkFraction(); } public String getHolidayInfo(int year, int dayOfYear) { final Holiday day = getHolidays(year).get(dayOfYear); if (day == null) { return ""; } return StringUtils.isNotBlank(day.getLabel()) == true ? day.getLabel() : day.getI18nKey(); } }