2
\$\begingroup\$

I am not very good a JavaScript, surely not date manipulation, but I hacked together the script below with the purpose to determine the next payment renewal date based on a start date and payment term. The script does what it is supposed to do. The script is part of a data transformation, which runs for every row in the data transformation (Pentaho). It has 1.1 million rows, so performance is very important. Who has ideas on increasing performance (currently only 18 rows / sec on my i5).

var renewaldate = new Date(START_DATE.getTime());
var renewalday = START_DATE.getDate();
var renewalmonth = START_DATE.getMonth();
var renewalyear = START_DATE.getYear();
var paymentterm= 12 / PAYMENTS_PER_YEAR;
var today = new Date();

while (renewaldate - today < 0) {
    renewalmonth = renewalmonth + paymentterm;
    if (renewalmonth > 12) {
        renewalmonth = renewalmonth - 12;
        renewalyear++;
    }
    renewaldate.setFullYear(renewalyear, renewalmonth, renewalday);
}
\$\endgroup\$
2
  • \$\begingroup\$ If you had asked this on SO, I'd say to take a look at Moment: while (renewalDate < today) { renewalDate.add(paymentTerm, "months"); } \$\endgroup\$
    – Schism
    Oct 8, 2014 at 9:52
  • \$\begingroup\$ Will that make the code faster? \$\endgroup\$ Oct 8, 2014 at 10:15

2 Answers 2

3
\$\begingroup\$

An interesting question..

I am assuming that the values of PAYMENTS_PER_YEAR can only be these:

var paymentFrequencies = [ 1 , 2 , 3 , 4 , 6 , 12 ];

If that is the case, then the renewalyear can only be todays year or tomorrows year.

I would create a cache that pre-calculates for each month and each payment frequency what the next payment month is, and whether a year will be skipped.

Then you can simply use a look up to determine when the next payment month is, there should be no faster way than that.

I am currently looking into building just that, it is definitely possible, but slightly harder than expected ;)

This is what I have, I tested it a bit, the principal should be clear:

function createCache( monthsOffset ){
  var cache = {},
      paymentFrequencies = [ 1 , 2 , 3 , 4 , 6 , 12 ],
      todaysMonth = (new Date ()).getMonth() + monthsOffset,
      renewalYear, renewalMonth, targetMonth, frequency, monthsShort, nextYear, term;
  for( var month = 0 ; month < 12 ; month++ ){
    cache[month] = {};
    for( var i = 0 ; i < paymentFrequencies.length ; i++ ){
      frequency = paymentFrequencies[i];
      term = 12 / frequency;
      targetMonth = todaysMonth < month ? todaysMonth + 12 : todaysMonth;
      monthsShort = ( targetMonth - month ) % term;
      cache[month][frequency] = todaysMonth + (monthsShort ? term - monthsShort : 0);
      
    }
  }
  return cache;
}

var stillThisMonth = createCache( 0 ),
    nextMonth = createCache(1);

function determineRenewalDate( startDate , paymentsPerYear  ){

    var today = new Date(),
        todaysYear =  today.getFullYear();
        startMonth =  startDate.getMonth();
  /*Compare the days, if we renew say monthly, and we start on the 15th, 
    but today is the 20, then we need to renew for next month, if we are the
    the 8th then you can stick to this month */    
  var todaysDayOfTheMonth = today.getDate(),
      startDayOfTheMonth = startDate.getDate();
  //One more cool thing, `setFullYear` and `new Date` will roll over the year if you set the month too high
  if( todaysDayOfTheMonth > startDayOfTheMonth  ){
     return new Date( todaysYear, nextMonth[startMonth][paymentsPerYear], startDayOfTheMonth );
  } else {
     return new Date( todaysYear, stillThisMonth[startMonth][paymentsPerYear], startDayOfTheMonth );
  }    
}


document.write(  determineRenewalDate( new Date(2010 , 1 , 1 ) , 12 ) + '<br>' );
document.write(  determineRenewalDate( new Date(2010 , 1 , 2 ) , 2 ) + '<br>' );

\$\endgroup\$
6
  • \$\begingroup\$ Wow! Great code. Thanks a lot. I did not execute the code up till now due to another problem, but I think the 'February problem' is still persistent in this code. Presume startdate Jan31. Payments_per_year 12 and currentdate Feb15. It will calculate a paymentdate of Feb31, which does not exist! \$\endgroup\$ Oct 8, 2014 at 18:25
  • \$\begingroup\$ Your assumption about the payments_per_year being a set of 1,2,3,4,6,12 is correct! \$\endgroup\$ Oct 8, 2014 at 18:26
  • \$\begingroup\$ It will actually calculate March 2, it automatically rolls over to correct date. Same as for the months. Will add an example with feb 28. \$\endgroup\$
    – konijn
    Oct 8, 2014 at 19:40
  • \$\begingroup\$ It actually needs to be Feb28th or Feb29th when a date is over the month \$\endgroup\$ Oct 9, 2014 at 12:17
  • \$\begingroup\$ Ah, silly business requirements, I will leave that part as an exercise to you ;) \$\endgroup\$
    – konijn
    Oct 9, 2014 at 15:56
0
\$\begingroup\$

The first problem is: what about February?

> var a = new Date()
undefined
> a.setFullYear(2014, 2, 30)
1396175283793
> a
Sun Mar 30 2014 18:28:03 GMT+0800 (CST)

And similarly, if the last payment is the 31th of May and the number of payments per year is 12, what will happen?

Second, does 18 rows per second means 18 transformations per second? Are you sure the bottleneck is this transformation? Or is the start date a long long time ago?

One way to optimize: you could simply set the renewal year to this year since the number of payments per year is divisible by 12.

\$\endgroup\$
7
  • \$\begingroup\$ Great comment about february. I think I cannot set the renewal year to this year. I.e.: current date is Oct 15th 2014, payments_per_year is 4 and start_date is Oct 10th 2014. Renewal date will be Jan 10th, 2015 \$\endgroup\$ Oct 8, 2014 at 11:50
  • \$\begingroup\$ Oh, you misunderstood it: I meant situation like the start_date is Oct 10th 1954, you could set it to Oct 10th 2014 and start loop then, saving "lots of" computations. \$\endgroup\$ Oct 8, 2014 at 12:34
  • \$\begingroup\$ Ah, that would be nice if we had those old start dates. They are all in 2012, 2013, 2014. \$\endgroup\$ Oct 8, 2014 at 12:49
  • \$\begingroup\$ The month is 0 based, so you would try for a.setFullYear(2014, 1, 30) which evaluates to Sun March 2, 2014 which is 'correct' \$\endgroup\$
    – konijn
    Oct 8, 2014 at 12:55
  • \$\begingroup\$ Does 18 rows / sec mean that 18 transformations per second? I think a single i3 could do this calculation a million times. Are you sure this is the bottleneck? \$\endgroup\$ Oct 8, 2014 at 12:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.