DEV Community

BeGoodTool.com
BeGoodTool.com

Posted on

Why "the date exactly halfway between these two dates" doesn't always have one answer

A friend asked me to help her find the exact midpoint between her and her partner's birthdays — apparently it's a whole thing people do, so they get a "shared" anniversary to celebrate every year. I figured it'd be a five-minute (date1 + date2) / 2 kind of problem. It's not. Building the date calculator that eventually answered her question, I ran into a genuinely annoying edge case: sometimes there just isn't a single day exactly in the middle.

Counting days between two dates without native Date math

The tool doesn't do new Date(a) - new Date(b) and divide by 86400000. It uses Luxon, parses both inputs as calendar dates, and asks Luxon for a calendar-aware diff:

let start = DateTime.fromISO(twoDate.date1);
let end = DateTime.fromISO(twoDate.date2);
let diffInMonths = end.diff(start, "days");
betweenRes.value = diffInMonths.toObject().days;
Enter fullscreen mode Exit fullscreen mode

That variable name is not a typo I'm inventing for the article — it's sitting in the actual source, and I only noticed it while re-reading the file for this post. It's clearly a leftover from an earlier version of the function that computed something else, and it's now permanently doing a days diff while being named diffInMonths. Harmless, since .diff(start, "days") is unambiguous regardless of variable naming, but it's a good reminder that variable names lie the moment you stop actively maintaining them.

The more deliberate design choice is letting Luxon own the calendar math instead of hand-rolling it. Raw millisecond subtraction on native Date objects gets you into trouble the moment DST or timezone offsets are involved; a calendar-aware diff("days") over ISO date strings just counts calendar days, which is what a "how many days between these two dates" tool actually needs.

The fencepost question nobody asks out loud: is today "day 0" or "day 1"?

Say two dates are 3 days apart. Is the second date "the 3rd day after" the first, or "the 4th day" if you count the first date itself as day 1? This is the classic fencepost ambiguity, and the tool resolves it by spelling out its own convention in the result text instead of leaving it implicit:

between_res_detail:
  "以隔天為第1天來說,{date1}過後的第{betweenRes}天,就是{data2}。"
// "Counting the day after {date1} as day 1, the {betweenRes}th day after that is {data2}."
Enter fullscreen mode Exit fullscreen mode

So a 3-day gap is explicitly defined as: the day after date1 is day 1, and date2 is day 3. It's a plain diff under the hood, but the UI text exists specifically so nobody has to guess whether the tool is counting inclusively or exclusively — which is exactly the kind of ambiguity that makes people distrust a date calculator's output even when the math is correct.

Finding the midpoint: what happens when the gap is an odd number of days

This is the part that actually stopped me. If two dates are, say, 6 days apart, the midpoint is clean: 3 days from either end. But if they're 7 days apart, dividing by 2 gives 3.5 — there is no calendar day at "3.5 days from the start." The tool handles this by branching on parity and returning a range instead of a single date when the gap is odd:

let days = diffInMonths.toObject().days;
if (days % 2 == 0) {
  // even gap: one exact midpoint
  let middle = Math.abs(days) / 2;
  middleRes.value =
    days < 0
      ? end.plus({ days: middle }).toFormat("yyyy-MM-dd (ccc)")
      : start.plus({ days: middle }).toFormat("yyyy-MM-dd (ccc)");
} else {
  // odd gap: midpoint falls between two candidate days
  let middle1 = (Math.abs(days) - 1) / 2;
  let middle2 = middle1 + 1;
  middleRes.value =
    days < 0
      ? `${end.plus({ days: middle1 }).toFormat("yyyy-MM-dd (ccc)")}~${end.plus({ days: middle2 }).toFormat("yyyy-MM-dd (ccc)")}`
      : `${start.plus({ days: middle1 }).toFormat("yyyy-MM-dd (ccc)")}~${start.plus({ days: middle2 }).toFormat("yyyy-MM-dd (ccc)")}`;
}
Enter fullscreen mode Exit fullscreen mode

Even gap → one exact answer. Odd gap → two adjacent dates shown as a range, because both are equally "the middle" and picking one over the other would just be arbitrary. The days < 0 branch also handles a detail that's easy to overlook: the user isn't required to type the earlier date into the first field, so the function figures out which of start/end is actually earlier before it adds the offset, rather than assuming input order. The same input-order tolerance shows up in the days-between result too — before display, the component swaps which date is shown first based on the sign of the raw diff, so date1 and date2 in the output text always resolve to the chronologically earlier and later date, regardless of which one the user typed first.

Where this falls short

A few honest limitations, since none of this is magic:

  • The single-date calculator only adds or subtracts a plain count of days — there's no "add 1 month" or "add 1 year" option. If you want a month from now, you have to already know how many days that is (28–31, depending), the tool won't do that translation for you.
  • The date inputs allow anything up to year 9999 with no realistic lower bound. Luxon will happily compute a diff against a date in year 1, but that's applying the proleptic Gregorian calendar backwards past 1582, when the Gregorian calendar didn't actually exist yet — the math is internally consistent, it just isn't "historically real" that far back.
  • The "at least 1" rule on the day-count input for the single-date calculator is enforced by a UI hint and a truthy check (!inferNumber.value), not a strict numeric range check — it stops empty/zero input but isn't the same as validating that the value is a genuine positive integer.

None of these are bugs exactly, just the edges you hit once you stop treating "days between two dates" as a solved problem.

I cleaned up the version I built for my friend's shared-birthday math into a small free tool: Online Date Calculator. No sign-up, and yes, it shows the range when the midpoint lands between two days instead of quietly rounding.


Available in other languages

Top comments (0)