1

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.

Joseph Astrahan
  • 8,659
  • 12
  • 83
  • 154
  • Does this answer your question? [How many novembers (or any particular month) are there between a start date and end date?](https://stackoverflow.com/questions/75103871/how-many-novembers-or-any-particular-month-are-there-between-a-start-date-and) – pilchard Jan 13 '23 at 01:31
  • you can't do simple math here. Because adding a unit of time like a 'month' is subjective, sometimes it's 28 days and sometimes its 30 or 31. Therefore knowing the unix epoch does not help. If a month was always 30 days it would be easy – Joseph Astrahan Jan 13 '23 at 01:35
  • also a duplicate [Difference in Months between two dates in JavaScript](https://stackoverflow.com/questions/2536379/difference-in-months-between-two-dates-in-javascript) – pilchard Jan 13 '23 at 01:37
  • If I wanted to get how many months were between 2 dates I can simply use moment library to do this. It simply tells you that, but that is not good enough for my use case. I'm trying to see how much a certain iteration unit of time exists between two dates, which may very well not be possible without looping manually do to the vagueness of what a 'month' means – Joseph Astrahan Jan 13 '23 at 01:39
  • `month` is the only non-standard value in your list and they have been addressed in both of the flagged duplicates. The rest can be converted to seconds or ms and computed with simple arithmetic (accounting for leap years as required). [How can I calculate the number of years between two dates?](https://stackoverflow.com/questions/8152426/how-can-i-calculate-the-number-of-years-between-two-dates) – pilchard Jan 13 '23 at 01:52
  • So you want to know how many minutes passed from 12 jan 1834 to 4 oct 2043? for example – Medet Tleukabiluly Jan 13 '23 at 01:53
  • This is not a duplicate of Difference in Months between two dates in JavaScript because it's asking how many units of a complex duration of time exists between two dates not simply months – Joseph Astrahan Jan 13 '23 at 01:53
  • @pilchard complex duration means simply not a month, something like 1 month and 2 days – Joseph Astrahan Jan 13 '23 at 01:54
  • @medet tieukabiluly, what I want to know is for example, how many units of (X amount of months + X days + X amount of minutes ) exists between the start and end dates. I already know how many minutes exist, but this does not help for iterations of the unit of complex time where months can be different. – Joseph Astrahan Jan 13 '23 at 02:00
  • Am I misunderstanding? It seems like OP realizes that certain intervals (ones with lunar components, unmoored from an exact start date) can't be converted into numerical values, and that we can only perform math operations on numerical values. If you can convert the inputs to milliseconds, you can divide them. If you can't, you can't. Right? – danh Jan 13 '23 at 02:45
  • 1
    I'm going to add a JSFiddle to demonstrate the issue with my current solution in a bit @danh (that said, the issue here is how many units of a complex unit of time can fit between two dates), I have a solution which is to manually add that time to the start time until its greater than the endtime and count how many times that occured to get the iterations, but this is extremely time consuming. There is no math way to do this (I think). That is really my question, is there a mathematical way to solve this? (or something I'm missing if there is a non math way) – Joseph Astrahan Jan 13 '23 at 02:49
  • Not an exact duplicate, but [*How can I determine if multiples of a duration fit evenly between two dates?*](https://stackoverflow.com/questions/74945560/how-can-i-determine-if-multiples-of-a-duration-fit-evenly-between-two-dates) provides sufficient information to answer your question. The point is that any duration that includes years, months or days is problematic as all three have different lengths depending on various factors (leap years, uneven length months, daylight saving, etc.). Also places change their standard timezone offset, which might be rare since 1900 but still occurs. – RobG Jan 13 '23 at 04:27
  • So sequentially adding the duration to the start point is really the only way to go, and you'll still need business rules to sort out things like 31 March plus 1 month, or 29 Feb plus 1 year, and so on. – RobG Jan 13 '23 at 04:31
  • If the duration is in hours or smaller units, then simply getting the difference in time values and dividing by the duration in milliseconds should be fine. However, as soon as you have days, months or years you're back to iterative adding or subtracting. – RobG Jan 13 '23 at 04:37
  • @RobG thanks I had a suspicion that was the only way to do it but wasn't sure – Joseph Astrahan Jan 13 '23 at 05:06

1 Answers1

1

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-1
  } else {
    let intervalLength = object => {
      let i = moment.duration()
      Object.entries(object).forEach(([unit, qty]) => i.add(qty, unit))
      return i
    }
    return Math.floor(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>
danh
  • 62,181
  • 10
  • 95
  • 136
  • I added an example function that does it through brute force, with my example and a 2 year given date range, your function returns 20, while mine returns 19. Which is correct? – Joseph Astrahan Jan 14 '23 at 19:18
  • 1
    It's a floor vs ceiling question. My loop increments the counter on the iteration where the current date is pushed over the limit, so it returns the integer ceiling. Yours subtracts 1 after the loop is complete, producing the integer floor. Up to you: `return dividend-1` might be what you want in mine – danh Jan 15 '23 at 01:43