Clamp a Date to a Min/Max Range in JavaScript

To clamp a date into [min, max], compare it to each bound and return the bound it passes — a type-safe alternative to Math.min/Math.max. Part of Working with Date Ranges and Intervals.

Why this scenario is tricky

Clamping a date into [min, max] looks like Math.min(Math.max(...)), but Math only works on numbers — pass it Date objects and it coerces them through valueOf(), which happens to work for Date but silently fails for Temporal values, which throw rather than coerce. The robust pattern is to clamp with explicit compare calls so the logic is identical for instants and civil dates, and so an inverted [min, max] is caught instead of producing a nonsense result.

Clamp with compare, not Math.min/maxMath coerces Dates and throws on TemporalClamp with compare, not Math.min/maxMath.min(Math.max(d,min),max)throws on Temporal valuescompare-based clampworks for any comparableExplicit comparison keeps clamping correct for both Date and Temporal types.

Minimal working solution

Two comparisons pick the bound.

import { Temporal } from '@js-temporal/polyfill';
const cmp = Temporal.PlainDate.compare;
function clamp(d, min, max: Temporal.PlainDate) {
  if (cmp(d, min) < 0) return min; // below the floor
  if (cmp(d, max) > 0) return max; // above the ceiling
  return d;
}

Clamp into [min, max]Return the nearest bound when outsideClamp into [min, max]d, min, maxd<min? mind>max? maxelse d

Full production version

Validate the bounds and keep the function generic over any Temporal type via its static compare.

import { Temporal } from '@js-temporal/polyfill';
function clampDate<T extends { }>(d: T, min: T, max: T, compare: (a: T, b: T) => number): T {
  if (compare(min, max) > 0) throw new RangeError('min after max');
  if (compare(d, min) < 0) return min;
  if (compare(d, max) > 0) return max;
  return d;
}
// clampDate(d, min, max, Temporal.Instant.compare)

A generic clampPass the type's compare; reject inverted boundsA generic clampd,min,maxmin>max?throwcompare dclamped

Verification snippet

Clamp assertionsBelow, inside, and above assert correctlyAssertions that prove the edge cased < minreturns minmin <= d <= maxreturns dd > maxreturns maxmin > maxthrows

Common pitfalls

Clamp pitfallsNumeric coercion and inverted boundsWrongRightMath.min/max on datesthrows or coercescompare-based clamptype-safeignore min>maxsilent wrong boundvalidate bounds firstfail loud

Frequently Asked Questions

How do I clamp a date between a minimum and maximum?

Compare the date to each bound and return the bound it exceeds: if it is before min return min, if after max return max, otherwise return it unchanged. Using Temporal.compare instead of Math.min/max keeps the logic working for both instants and civil PlainDate values.

Why not use Math.min and Math.max on dates?

Math operates on numbers, so it coerces Date via valueOf() — which works by accident — but throws on Temporal values, which do not coerce to numbers. A compare-based clamp is explicit, type-safe, and reads the same for Date, Instant, and PlainDate.