Home > Enterprise >  How many iterations of a complex duration of time exists between a start date and end date?
How many iterations of a complex duration of time exists between a start date and end date?

Time:01-16

For example, I'm using the moment.js library. You can specify a duration as follows : {years:1,months:1,weeks:1,days:1,hours:1,minutes:1,seconds:1}. This duration of time is subjective based on the context of the start and end time.

For example certain months may be 28 days and others 31. So if you were to add a month to the end of january 31st, it will say that 1 month from then is the 28th of february.

So you can see that simple math will not be able to calculate how many iterations there will be.

The solution I have come up with so far is to loop the unit of time manually until it is less than the end time and that is how many iterations of that unit of time would fit.

However, if the unit of time is 1 minute and I am calculating between years, this would be extremely slow to figure out how many iterations there would be.

I can't simply say 'there is this many minutes in a year', because certain years have more minutes than others because some years (because of leap years) have 366 days instead of 365 and therefore a different amount of total minutes for that possible duration.

Hence, you can see my dilemma. Is there a way to efficiently figure out how many iterations of a unit of time exists between a start and end date time? (without manually adding the unit of time to the start time like I am doing now until it is greater than the end time and manually counting the iterations )

https://jsfiddle.net/gentleman_goat66/ye6bsd1v/1/

let start = "2021-01-01 00:00";
let end = "2023-01-01 00:00";
let unit = {years: 1, months: 1, weeks: 1, days: 1, hours: 1, minutes: 1};

let startMoment = moment(start, 'YYYY-MM-DD HH:mm');
let endMoment = moment(end, 'YYYY-MM-DD HH:mm');

let startEndDurationSeconds = endMoment.diff(startMoment, "seconds");
let startEndDurationMinutes = endMoment.diff(startMoment, "minutes");
let startEndDurationHours = endMoment.diff(startMoment, "hours");
let startEndDurationDays = endMoment.diff(startMoment, "days");
let startEndDurationWeeks = endMoment.diff(startMoment, "weeks");
let startEndDurationMonths = endMoment.diff(startMoment, "months");
let startEndDurationYears = endMoment.diff(startMoment, "years");

console.log(`seconds=${startEndDurationSeconds}`);
console.log(`minutes=${startEndDurationMinutes}`);
console.log(`hours=${startEndDurationHours}`);
console.log(`days=${startEndDurationDays}`);
console.log(`weeks=${startEndDurationWeeks}`);
console.log(`months=${startEndDurationMonths}`);
console.log(`years=${startEndDurationYears}`);

Attached is a JSFiddle demonstrating an easy way to test how the moment library breaks down the times between a start and end. The question would be how to know how many of the 'unit' goes into this.

If it were just months it would be easy to look at the difference and use that for the iterations, but when you are using a complex set of times that is a different story.

The moment library is smart about accounting for leap years if you set the start date to one that is 2020, it will say 366 days correctly just as a note and 365 for the other days.

 function countIterations(start, end, duration) {
    let remainingStart = moment(start);
    let remainingEnd = moment(end);
    let i = 0;
    while (remainingStart <= remainingEnd) {
        i  ;
        remainingStart.add(duration);
    }
    return i - 1;
 }
let duration = {years: 0, months: 1, weeks: 1, days: 0, hours: 0, minutes: 0};
let iterations = countIterations(moment(start), moment(end), duration);
console.log(iterations);

The code above is the brute force method to count iterations and seems to work but is slow.

CodePudding user response:

Here's an idea: intervals not containing months or years are unambiguous, and we can just do millisecond math on them. Intervals with lunar components must be repeatedly added to find a dividend, but we can just add the interval to the start date until it exceeds the end date, so the loop only runs dividend times.

The function below has two branches corresponding to those two cases. Both contain a little adder function inline that runs moment add() on the Object.entries() of the duration object.

It passed a few cursory tests I gave it.

let start = "2021-01-01 00:00";
let end = "2023-01-01 00:00";
let unit = { years: 1, months: 1, weeks: 1, days: 1, hours: 1, minutes: 1, seconds: 1 };

let smallUnit = { weeks: 1, days: 1, hours: 1, minutes: 1, seconds: 1 };
let tinyUnit = { seconds: 1 };

function unitsBetweenDates(start, end, unit) {
  let m = moment(start)
  let mEnd = moment(end)

  if (unit.years || unit.months) {
    let dividend = 0
    let add = (date, object) => {
      Object.entries(object).forEach(([unit, qty]) => date.add(qty, unit))
    }
    // notice that we don't try to count more than a million years or months
    while (m.isBefore(mEnd) &&    dividend < 100000) add(m, unit);
    return dividend
  } else {
    let intervalLength = object => {
      let i = moment.duration()
      Object.entries(object).forEach(([unit, qty]) => i.add(qty, unit))
      return i
    }
    return Math.round(mEnd.diff(m) / intervalLength(unit))
  }
}

console.log("a year and a month, etc, divided into 2 years =", unitsBetweenDates(start, end, unit))
console.log("a week and a day, etc, divided into 2 years =", unitsBetweenDates(start, end, smallUnit))
console.log("one second divided into 2 years =", unitsBetweenDates(start, end, tinyUnit))
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script>

  • Related