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\$
  • \$\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 '14 at 9:52
  • \$\begingroup\$ Will that make the code faster? \$\endgroup\$ – Martijn Burger Oct 8 '14 at 10:15
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\$
  • \$\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\$ – Martijn Burger Oct 8 '14 at 18:25
  • \$\begingroup\$ Your assumption about the payments_per_year being a set of 1,2,3,4,6,12 is correct! \$\endgroup\$ – Martijn Burger Oct 8 '14 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 '14 at 19:40
  • \$\begingroup\$ It actually needs to be Feb28th or Feb29th when a date is over the month \$\endgroup\$ – Martijn Burger Oct 9 '14 at 12:17
  • \$\begingroup\$ Ah, silly business requirements, I will leave that part as an exercise to you ;) \$\endgroup\$ – konijn Oct 9 '14 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\$
  • \$\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\$ – Martijn Burger Oct 8 '14 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\$ – Minsheng Liu Oct 8 '14 at 12:34
  • \$\begingroup\$ Ah, that would be nice if we had those old start dates. They are all in 2012, 2013, 2014. \$\endgroup\$ – Martijn Burger Oct 8 '14 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 '14 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\$ – Minsheng Liu Oct 8 '14 at 12:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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