Time differences: subtracting times and formatting durations

3 min read

“How long is 9:00 to 17:30?” “What time is 3 days 4 hours from now?” — time arithmetic is everyday, but day boundaries and negative values trip people up. This article walks through the operations.

Basic subtraction

“9:00 to 17:30”:

17:30 - 09:00 = 8:30 (8 hours 30 minutes)

Convert times to minutes for clean math:

17:30 → 17 × 60 + 30 = 1050 min
09:00 →  9 × 60 +  0 =  540 min
diff = 1050 - 540 = 510 min = 8h 30m

Crossing midnight

“23:00 to 7:00 next day”:

7:00 - 23:00 = -16:00 (negative)

Wrong. Add 24 hours when the wrap is detected:

(7 + 24):00 - 23:00 = 31:00 - 23:00 = 8:00

Or include the date:

2024-01-02 07:00 - 2024-01-01 23:00 = 8 hours

Detecting wrap

Simple rule:

let diff = end - start; // minutes
if (diff < 0) diff += 24 * 60; // crossed midnight, add a day

Doesn’t generalize past one day — for ranges spanning multiple dates, use full date arithmetic.

DST and “summer time”

DST start day is 23 hours; end day is 25 hours:

  • Spring — 3:00 jumps to 4:00, the day is 23 hours.
  • Fall — 2:00 falls back to 1:00, the day is 25 hours.

“24h = 1 day” breaks. Compute in UTC, or use a library (date-fns, Luxon, etc.).

Japan has no DST (so it doesn’t matter locally) but global products must handle it.

ISO 8601 duration notation

Standard format for a “duration”:

P3Y2M5D     — 3 years 2 months 5 days
PT2H30M     — 2 hours 30 minutes
P1DT12H     — 1 day and 12 hours
P1W         — 1 week
  • Starts with P.
  • Before T — date components; after — time components.
  • Letters — Y (year), M (month, before T), W (week), D (day), H (hour), M (minute, after T), S (second).

Used in API responses and e.g., YouTube video lengths:

PT4M13S → 4 minutes 13 seconds

Human-readable relative times

“3 hours 25 minutes ago”, “tomorrow morning”:

const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
rtf.format(-1, 'day'); // "yesterday"
rtf.format(2, 'hour'); // "in 2 hours"
rtf.format(-30, 'minute'); // "30 minutes ago"

Japanese:

const rtf = new Intl.RelativeTimeFormat('ja', { numeric: 'auto' });
rtf.format(-1, 'day'); // "昨日"
rtf.format(2, 'hour'); // "2 時間後"

Elapsed-time formatting

“1:23:45” elapsed:

function formatDuration(seconds) {
	const h = Math.floor(seconds / 3600);
	const m = Math.floor((seconds % 3600) / 60);
	const s = seconds % 60;
	return `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
}

formatDuration(5025); // "1:23:45"

Same shape as YouTube timestamps and stopwatches.

Adding to a timestamp

const start = new Date('2024-01-15T10:00:00');
const end = new Date(start.getTime() + 90 * 60 * 1000); // +90 minutes
end.toISOString(); // "2024-01-15T11:30:00.000Z"

Date.getTime() is milliseconds:

  • 1 second = 1,000.
  • 1 minute = 60,000.
  • 1 hour = 3,600,000.
  • 1 day = 86,400,000.

Working hours with breaks

“9:00–18:00 with 12:00–13:00 lunch”:

  • Compute total span.
  • Subtract overlap with break.
function workHoursInRange(start, end, breakStart, breakEnd) {
	let total = end - start;
	if (start < breakEnd && end > breakStart) {
		const overlap = Math.min(end, breakEnd) - Math.max(start, breakStart);
		total -= overlap;
	}
	return total;
}

Months and years

“1 year 3 months from now”:

  • Months aren’t fixed length (28, 29, 30, 31 days).
  • Years differ by leap status (365 / 366).
  • “Same day next month” needs explicit handling.
function addMonths(date, months) {
	const d = new Date(date);
	const targetMonth = d.getMonth() + months;
	d.setDate(1);
	d.setMonth(targetMonth);
	const lastDay = new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate();
	d.setDate(Math.min(date.getDate(), lastDay));
	return d;
}

“March 31 + 1 month = April 30” — month-end clamping is required.

Unit conversions

UnitSeconds
sec1
min60
hour3,600
day86,400
week604,800
month (30 days)2,592,000
year (365 days)31,536,000

“What’s 1,000,000 seconds in days?” → 1,000,000 / 86,400 ≒ 11.57 days.

Across time zones

“Tokyo 10:00 vs NY 21:00 (previous day)“:

  • Tokyo = UTC+9.
  • NY = UTC-5 (UTC-4 during DST).
  • Difference is 14 hours (13 during DST).

Convert to UTC and subtract:

const tokyo = new Date('2024-01-15T10:00:00+09:00');
const ny = new Date('2024-01-14T21:00:00-05:00');
const diff = (tokyo - ny) / (1000 * 60 * 60); // 0 hours (same instant)

24-hour vs 12-hour clock

  • 24h — 00:00 to 23:59.
  • 12h — 12:00 AM to 11:59 PM.

12-hour pitfalls:

  • 12:00 AM = midnight (0:00).
  • 12:00 PM = noon (12:00).
  • 12:01 AM ≠ 12:01 PM (off by 12 hours).

Backends and databases should standardize on 24-hour.

Summary

  • Convert to minutes, subtract, convert back.
  • Add 24 hours (or use full dates) to handle midnight crossing.
  • ISO 8601 duration — P3Y2M5D, PT2H30M.
  • Months and years aren’t constant in seconds — handle them explicitly.
  • For cross-zone differences, normalize to UTC.

For computing time differences and adding offsets, the time-duration calculator on this site handles common cases.