/* ==================================================================== * Limited Evaluation License: * * This software is open source, but licensed. The license with this package * is an evaluation license, which may not be used for productive systems. If * you want a full license, please contact us. * * The exclusive owner of this work is the OpenRate project. * This work, including all associated documents and components * is Copyright of the OpenRate project 2006-2015. * * The following restrictions apply unless they are expressly relaxed in a * contractual agreement between the license holder or one of its officially * assigned agents and you or your organisation: * * 1) This work may not be disclosed, either in full or in part, in any form * electronic or physical, to any third party. This includes both in the * form of source code and compiled modules. * 2) This work contains trade secrets in the form of architecture, algorithms * methods and technologies. These trade secrets may not be disclosed to * third parties in any form, either directly or in summary or paraphrased * form, nor may these trade secrets be used to construct products of a * similar or competing nature either by you or third parties. * 3) This work may not be included in full or in part in any application. * 4) You may not remove or alter any proprietary legends or notices contained * in or on this work. * 5) This software may not be reverse-engineered or otherwise decompiled, if * you received this work in a compiled form. * 6) This work is licensed, not sold. Possession of this software does not * imply or grant any right to you. * 7) You agree to disclose any changes to this work to the copyright holder * and that the copyright holder may include any such changes at its own * discretion into the work * 8) You agree not to derive other works from the trade secrets in this work, * and that any such derivation may make you liable to pay damages to the * copyright holder * 9) You agree to use this software exclusively for evaluation purposes, and * that you shall not use this software to derive commercial profit or * support your business or personal activities. * * This software is provided "as is" and any expressed or impled warranties, * including, but not limited to, the impled warranties of merchantability * and fitness for a particular purpose are disclaimed. In no event shall * The OpenRate Project or its officially assigned agents be liable to any * direct, indirect, incidental, special, exemplary, or consequential damages * (including but not limited to, procurement of substitute goods or services; * Loss of use, data, or profits; or any business interruption) however caused * and on theory of liability, whether in contract, strict liability, or tort * (including negligence or otherwise) arising in any way out of the use of * this software, even if advised of the possibility of such damage. * This software contains portions by The Apache Software Foundation, Robert * Half International. * ==================================================================== */ package OpenRate.process; import OpenRate.CommonConfig; import OpenRate.lang.ProRatingResult; import OpenRate.utils.ConversionUtils; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; /** * This module works on the start and end dates given in the event, and * calculates the pro-ration that should be applied to the event. It calculates * the number of whole months and days outside the whole months that should * be charged for, and then calculates the pricing factor based on these. * * Note that if the start date is equal to the end date, exactly one day will * be charged. If the start date is after the end date, this will have the * effect of calculating a refund. * * If you want to skip the proration (for example for the case of rating one * off events), set the end date to 2000-01-01, which will cause the processing * to always return a pro-ration factor of 1. * * @author ian */ public abstract class AbstractProRation extends AbstractStubPlugIn { private static Date FirstJan2000; private Calendar workingCalendar; private Calendar helperCalendar; private static final long MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; /** * Constructor - Create the class */ public AbstractProRation() { super(); // get the calendar object for working with the dates workingCalendar = new GregorianCalendar(); workingCalendar.setLenient(false); // get the calendar helper object helperCalendar = new GregorianCalendar(); SimpleDateFormat sdfIn = new SimpleDateFormat("dd/MM/yyyy"); String firstJanDate = "01/01/2000"; try { FirstJan2000 = sdfIn.parse(firstJanDate); } catch (ParseException ex) { message = "Error getting date in <" + getSymbolicName() + ">"; getPipeLog().fatal(message); } } /** * This calculates the total period to be considered for this event. Given an * input start and end date, the multiplier factor is returned for the total * multipler to a simple monthly fee. * * For example, given a monthly fee of EUR10, the total factor calculated for * the period "20120103000000" to "20120303000000" will be 2 months and 1 day. * * The factor that is returned can be calculated either by using the real * number of days in the month, (useCalendarDays = true), or a standard 30 days * (useCalendarDays = false). * * For calculating a refund, put the end date before the start date * (i.e. invert the parameters). This method works on the basis of days * (not hours, minutes or seconds). * * @param StartDate the start of the period to calculate for * @param EndDate the end of the period to calculate for * @param useCalendarDays True = use the real number of days in the month, otherwise assume 30 * @return The processed record */ public ProRatingResult calculateProRatedMonth(Date StartDate, Date EndDate, boolean useCalendarDays) { int DaysInMonth; int TotalPeriodDays; int WholeMonths = 0; int TotalDays = 0; double ProRationOffset = 0; boolean refunding = false; ProRatingResult result = new ProRatingResult(); // if we find the end date 01JAN2000, get out of here - it is a purchase if (EndDate.compareTo(FirstJan2000) == 0) { result.setProRationFactor(1); // Get the component parts result.setDaysInPeriod(0); result.setMonthsInPeriod(0); // no need to work anything else out - finished return result; } else { // see if we have a refund case if (StartDate.after(EndDate)) { Date tmpDate = StartDate; StartDate = EndDate; EndDate = tmpDate; refunding = true; } // get out starting point workingCalendar.setTime(StartDate); // See how many days short of the first month we are helperCalendar.setTime(EndDate); // get the total number of days in the period TotalPeriodDays = getDaysBetweenDates(workingCalendar.getTime(), helperCalendar.getTime()) + 1; while (TotalPeriodDays > 0) { // Get the real number of days in the month DaysInMonth = workingCalendar.getActualMaximum(Calendar.DAY_OF_MONTH); if (TotalPeriodDays >= DaysInMonth) { // move on a whole month WholeMonths++; // reduce the days to work on TotalPeriodDays -= DaysInMonth; // move the date on and deal with the rolling month int Month = workingCalendar.get(Calendar.MONTH) + 1; if (Month == 12) { // Bump up the year workingCalendar.roll(Calendar.YEAR, true); Month = 0; } workingCalendar.set(Calendar.MONTH, Month); } else { // get the fractional part TotalDays = TotalPeriodDays; if (useCalendarDays) { // Make the calculation based on the real number of days in the month ProRationOffset = (double)TotalDays/(double)DaysInMonth; } else { // Assume 30 for the calculation part ProRationOffset = ((double)TotalDays/(double)DaysInMonth) * ((double)DaysInMonth/(double)30); } TotalPeriodDays = 0; } } } if (refunding) { result.setProRationFactor(-(WholeMonths + ProRationOffset)); result.setDaysInPeriod(-TotalDays); result.setMonthsInPeriod(-WholeMonths); } else { // Get the final prorated sum result.setProRationFactor(WholeMonths + ProRationOffset); // Get the component parts result.setDaysInPeriod(TotalDays); result.setMonthsInPeriod(WholeMonths); } // set the period dates result.setPeriodStartDate(StartDate); result.setPeriodEndDate(EndDate); // return the updated record return result; } /** * This calculates the pro-ration factor to be considered for this event. This * takes the validity start and end date into the algorithm, and then * returns the total pro-ration factor for the month of the EventDate. Thus the * result is bounded between 0 and 1. 0 means that there were no days of * validity, 1 means that all days were covered by the validity. * * @param StartDate the start of the period to calculate for * @param EndDate the end of the period to calculate for * @param EventDate The Date to calculate the pro-ration factor for * @param useCalendarDays True = use the real number of days in the month, otherwise assume 30 * @return The pro rating result */ public ProRatingResult calculateProRationFactor(Date StartDate, Date EndDate, Date EventDate, boolean useCalendarDays) { int DaysInMonth; int daysInPeriod; Date periodStart; Date periodEnd; ProRatingResult result = new ProRatingResult(); // Deal with the easy cases first: no validity if (EventDate.before(StartDate) || EventDate.after(EndDate)) { // No validity coverage result.setProRationFactor(0); // Get the component parts result.setDaysInPeriod(0); result.setMonthsInPeriod(0); result.setPeriodStartDate(new Date(CommonConfig.LOW_DATE)); result.setPeriodStartDate(new Date(CommonConfig.HIGH_DATE)); // no need to work anything else out - finished return result; } else { // See if we have a pro-ration to do: if the Event Date is not in the // Start month or end month, then we are home and dry workingCalendar.setTime(EventDate); int EventMonth = workingCalendar.get(Calendar.MONTH) + 1 + workingCalendar.get(Calendar.YEAR)*12; DaysInMonth = workingCalendar.getActualMaximum(Calendar.DAY_OF_MONTH); // Default the period start and end to the start and end of the month // we will only change these in the code later if they are wrong // (often they will be right) periodStart = ConversionUtils.getConversionUtilsObject().getMonthStart(EventDate); periodEnd = ConversionUtils.getConversionUtilsObject().getMonthEnd(EventDate); // get the months of the validity start and end workingCalendar.setTime(StartDate); int StartMonth = workingCalendar.get(Calendar.MONTH) + 1 + workingCalendar.get(Calendar.YEAR)*12; workingCalendar.setTime(EndDate); int EndMonth = workingCalendar.get(Calendar.MONTH) + 1 + workingCalendar.get(Calendar.YEAR)*12; // Check the cases based on the month identifiers if (EventMonth == StartMonth) { // check if we can just look at the start month if (StartMonth == EndMonth) { // both start and end in the same month, just return the number of // days divided by the days in the month daysInPeriod = getDaysBetweenDates(StartDate,EndDate); // The period dates are just the original validity dates periodStart = StartDate; periodEnd = EndDate; } else { // We need to calculate up to the end of the month workingCalendar.setTime(ConversionUtils.getConversionUtilsObject().getMonthEnd(StartDate)); daysInPeriod = getDaysBetweenDates(StartDate,workingCalendar.getTime()); // The period dates are the original start date to the end of the month periodStart = StartDate; } } else if (EventMonth == EndMonth) { // check if we can just look at the start month if (StartMonth == EndMonth) { // both start and end in the same month, just return the number of // days divided by the days in the month daysInPeriod = getDaysBetweenDates(StartDate,EndDate); } else { // We need to calculate from the start of the month workingCalendar.setTime(ConversionUtils.getConversionUtilsObject().getMonthStart(EndDate)); daysInPeriod = getDaysBetweenDates(workingCalendar.getTime(),EndDate); // The period dates are the start of the month to the original end date periodEnd = EndDate; } } else { // full month in the middle daysInPeriod = DaysInMonth; } // Set the default values result.setMonthsInPeriod(0); // Now work out the factor if (useCalendarDays) { if (daysInPeriod > 30) { daysInPeriod = 30; } if (DaysInMonth > 30) { DaysInMonth = 30; } } // set up the output result.setDaysInPeriod(daysInPeriod); result.setProRationFactor((double)daysInPeriod / (double) DaysInMonth); result.setPeriodStartDate(periodStart); result.setPeriodEndDate(periodEnd); } // return the updated record return result; } // ----------------------------------------------------------------------------- // ----------------------- Start of utility functions -------------------------- // ----------------------------------------------------------------------------- /** * Calculate the number of days between two dates * * @param startDate The start date of the period * @param endDate The end date of the period * @return The number of days difference */ public int getDaysBetweenDates(Date startDate, Date endDate) { long diff = endDate.getTime() - startDate.getTime(); double daysdiff = diff / (double) MILLISECONDS_PER_DAY; int days = (int) Math.ceil(daysdiff); return Math.abs(days); } }