moment
semver
>=2.29.0 <3.0.0postconditions16functions15last verified2026-04-16coverage score83%Postconditions — what we check
- moment · moment-invalid-dateerrorWheninput string is not a valid date formatReturnsInvalid moment object (moment.isValid() returns false)Required handlingCaller MUST check isValid() before using the moment object. Invalid moment objects can cause incorrect date calculations, display issues, or NaN values propagating through the application. Use pattern: const m = moment(input); if (!m.isValid()) { /* handle error */ }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1]
- utc · utc-invalid-dateerrorWheninput string is not a valid date formatReturnsInvalid moment object (moment.isValid() returns false)Required handlingCaller MUST check isValid() after parsing. Invalid UTC moments can cause timezone calculation errors and data corruption. Use pattern: const m = moment.utc(input); if (!m.isValid()) { /* handle error */ }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[2]
- locale · locale-path-traversalerrorWhenuser-provided locale string passed without validationThrows
Path traversal vulnerability — unvalidated locale string may traverse to arbitrary filesRequired handlingWhen using user input to set locale, MUST validate against an allowlist. Vulnerable versions (1.0.1-2.29.1) allow path traversal via locale strings containing dot-dot sequences (/../). Patched in version 2.29.2. Use pattern: const allowed = ['en','fr','de']; if (allowed.includes(userLocale)) moment.locale(userLocale);costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[3] - parseZone · parsezone-invalid-dateerrorWheninput string is not a valid date formatReturnsInvalid moment object (moment.isValid() returns false, utcOffset() returns NaN)Required handlingCaller MUST check isValid() before using the moment object. moment.parseZone() is often used to preserve timezone offset from API responses (e.g., "2013-01-01T00:00:00-13:00"). If the input string is malformed, the returned moment is invalid AND utcOffset() returns NaN — both the date and the offset are silently broken. Use pattern: const m = moment.parseZone(input); if (!m.isValid()) { /* handle */ }costmediumin prodsilent failureusers seelost datavisibilitysilentSources[4]
- unix · unix-nan-timestampwarningWheninput is NaN, null, undefined, or a non-numeric stringReturnsInvalid moment object (moment.isValid() returns false)Required handlingCaller MUST validate that the Unix timestamp is a finite number before calling moment.unix(). Common sources of invalid timestamps: API responses where a field is null/undefined (e.g., user.lastLoginAt), database values that are 0 or negative (treated as valid by moment — epoch-relative), or string values not cast to number. moment.unix(NaN) silently returns an invalid moment object. Use pattern: const ts = getTimestamp(); if (typeof ts === 'number' && isFinite(ts)) { moment.unix(ts) }costlowin prodsilent failureusers seelost datavisibilitysilentSources[5]
- duration · duration-nan-propagationwarningWheninput to duration-based arithmetic is NaN or invalidReturnsNaN values in all numeric outputs (asMilliseconds(), asSeconds(), etc.)Required handlingmoment.duration() does not throw when given NaN inputs. Instead all numeric extraction methods (asMilliseconds(), asSeconds(), asMinutes(), etc.) return NaN, which propagates silently through calculations. Common pattern causing issues: computing duration from two moment objects where one is invalid — moment.duration(invalid.diff(valid)) produces NaN duration. Check isValid() on both moments before computing diffs: if (start.isValid() && end.isValid()) { const d = moment.duration(end.diff(start)); }costlowin prodsilent failureusers seelost datavisibilitysilentSources[6]
- defineLocale · definelocale-path-traversalerrorWhenlocale name is user-provided and contains path traversal sequencesThrows
Path traversal vulnerability — the locale name is later used as a file path in require('./locale/' + name)Required handlingWhen the locale name comes from user input, MUST validate against an allowlist of known safe locale codes (e.g., ['en', 'fr', 'de', 'es', 'zh']). The name is passed through isLocaleNameSane() in loadLocale() (checks for '/' and '\'), but this check is insufficient against other path traversal vectors or locale names used in string interpolation elsewhere in the application. Patched in moment 2.29.2. If on older version, validate before calling.costhighin prodsilent failureusers seesecurity breachvisibilitysilent - updateLocale · updatelocale-path-traversalerrorWhenlocale name is user-provided and contains path traversal sequencesThrows
Path traversal vulnerability — the locale name is later used as a file pathRequired handlingSame risk as moment.locale() and moment.defineLocale(). When updating locales dynamically (e.g., in multi-tenant SaaS where tenant sets their locale preference), MUST validate against an allowlist of known locale codes. Use pattern: const ALLOWED_LOCALES = ['en', 'fr', 'de']; if (ALLOWED_LOCALES.includes(tenantLocale)) { moment.updateLocale(tenantLocale, config); }costhighin prodsilent failureusers seesecurity breachvisibilitysilent - format · format-invalid-date-stringerrorWhenmoment object is invalid (parsed from malformed input)ReturnsThe string 'Invalid date' (or locale-specific equivalent) instead of a formatted dateRequired handlingCaller MUST check isValid() before calling format(). The string 'Invalid date' is silently returned and easily gets stored in databases, API responses, or displayed to users without error. This is the #1 moment.js footgun in production: moment(userInput).format('YYYY-MM-DD') returns "Invalid date" instead of throwing. Use pattern: const m = moment(input); if (!m.isValid()) { throw new Error('invalid date'); } return m.format('YYYY-MM-DD');costmediumin prodsilent failureusers seelost datavisibilitysilent
- format · format-redos-unvalidated-inputerrorWhenformat string or input string from user-provided data is excessively long (>10,000 chars)Throws
ReDoS (Regular Expression Denial of Service) — quadratic time complexity in RFC2822 date parsingRequired handlingMUST validate input string length before parsing untrusted strings with moment(). The RFC2822 date format regex has quadratic (N²) complexity for strings exceeding 10,000 characters. A 50,000-char input can cause 25+ second blocking on the event loop. Use pattern: if (typeof input === 'string' && input.length > 1000) { throw new Error('date input too long'); } Patched in moment 2.29.4. If on vulnerable version, add length validation.costhighin proddegraded serviceusers seeservice unavailablevisibilityvisibleSources[11] - toISOString · toisostring-null-for-invaliderrorWhenmoment object is invalid (parsed from malformed input)Returnsnull instead of an ISO stringRequired handlingCaller MUST check isValid() before calling toISOString(). The null return value is a common source of TypeErrors: code that chains .split('T') or .substring(0, 10) on the toISOString() result crashes at runtime. Also affects JSON.stringify(obj) when obj contains a moment in toJSON() call — toJSON() calls toISOString() and returns null for invalid moments. Use pattern: const m = moment(input); if (!m.isValid()) { return null; } return m.toISOString();costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
- add · add-invalid-moment-propagationwarningWhenadd() is called on a moment object that is already invalid (isValid() === false)ReturnsInvalid moment object — NaN propagates through all subsequent operationsRequired handlingCaller MUST check isValid() on the source moment BEFORE calling add(). When add() is called on an invalid moment, it silently returns another invalid moment. This is particularly dangerous when the moment comes from a parsed user input or API response: the subsequent add() call succeeds without error, but every downstream operation (format(), diff(), toISOString()) continues silently returning "Invalid date" / null / NaN. Pattern: const m = moment(input); if (!m.isValid()) { throw new Error('invalid date'); } m.add(1, 'day');costlowin prodsilent failureusers seelost datavisibilitysilent
- subtract · subtract-invalid-moment-propagationwarningWhensubtract() is called on a moment object that is already invalid (isValid() === false)ReturnsInvalid moment object — NaN propagates through all subsequent operationsRequired handlingCaller MUST check isValid() on the source moment BEFORE calling subtract(). When subtract() is called on an invalid moment, it silently returns another invalid moment. Common dangerous pattern: computing expiry dates (moment(createdAt).subtract(30, 'days')) where createdAt may be null/undefined from a DB column — the invalid moment silently propagates, producing incorrect expiry timestamps that get stored in the database. Pattern: const m = moment(input); if (!m.isValid()) { throw new Error('invalid date'); } m.subtract(30, 'days');costlowin prodsilent failureusers seelost datavisibilitysilent
- min · min-invalid-moment-propagationerrorWhenany argument to moment.min() is an invalid moment (isValid() === false)ReturnsInvalid moment object, regardless of the validity of other argumentsRequired handlingCaller MUST ensure all moments passed to moment.min() are valid before calling. Even if only one of many moments is invalid, the entire min() result is invalid. This is counterintuitive — developers often expect min() to return the best valid value, but instead it propagates invalidity from any single bad argument. Common dangerous pattern: moment.min(userInputDates.map(d => moment(d))) where any one unparseable date in the array causes the entire result to be invalid. Pattern: const validDates = dates.map(d => moment(d)).filter(m => m.isValid()); moment.min(validDates);costmediumin prodsilent failureusers seelost datavisibilitysilentSources[15]
- max · max-invalid-moment-propagationerrorWhenany argument to moment.max() is an invalid moment (isValid() === false)ReturnsInvalid moment object, regardless of the validity of other argumentsRequired handlingCaller MUST ensure all moments passed to moment.max() are valid before calling. Even if only one of many moments is invalid, the entire max() result is invalid. This is the same counterintuitive propagation as moment.min(). Common dangerous pattern: moment.max(events.map(e => moment(e.endAt))) where any one null/missing endAt in the dataset causes the maximum to be invalid — leading to "Invalid date" being stored as the computed deadline or expiry date. Pattern: const validDates = dates.map(d => moment(d)).filter(m => m.isValid()); moment.max(validDates);costmediumin prodsilent failureusers seelost datavisibilitysilentSources[16]
- fromNow · fromnow-invalid-date-stringwarningWhenmoment object is invalid (isValid() === false)ReturnsThe localized string 'Invalid date' instead of a relative time stringRequired handlingCaller MUST check isValid() before calling fromNow(). The string "Invalid date" is silently returned and easily gets displayed to users in UI components like "Posted Invalid date ago" or "Expires Invalid date". This is a common UX bug in production React/Next.js apps that use moment for relative timestamps. Use pattern: const m = moment(input); return m.isValid() ? m.fromNow() : 'Unknown date';costlowin prodsilent failureusers seedegraded performancevisibilityvisible
Sources
Every postcondition cites at least one of these. Numbered to match the footnotes above.
- [1]momentjs.com/docshttps://momentjs.com/docs/#/parsing/string/
- [2]momentjs.com/docshttps://momentjs.com/docs/#/parsing/utc/
- [3]nvd.nist.gov/vuln/detailhttps://nvd.nist.gov/vuln/detail/CVE-2022-24785
- [4]momentjs.com/docshttps://momentjs.com/docs/#/parsing/parse-zone/
- [5]momentjs.com/docshttps://momentjs.com/docs/#/parsing/unix-timestamp/
- [6]momentjs.com/docshttps://momentjs.com/docs/#/durations/
- [7]momentjs.com/docshttps://momentjs.com/docs/#/i18n/defining-locale/
- [8]momentjs.com/docshttps://momentjs.com/docs/#/i18n/changing-locale/
- [9]momentjs.com/docshttps://momentjs.com/docs/#/displaying/format/
- [10]momentjs.com/docshttps://momentjs.com/docs/#/parsing/is-valid/
- [11]nvd.nist.gov/vuln/detailhttps://nvd.nist.gov/vuln/detail/CVE-2022-31129
- [12]momentjs.com/docshttps://momentjs.com/docs/#/displaying/as-iso-string/
- [13]momentjs.com/docshttps://momentjs.com/docs/#/manipulating/add/
- [14]momentjs.com/docshttps://momentjs.com/docs/#/manipulating/subtract/
- [15]momentjs.com/docshttps://momentjs.com/docs/#/get-set/min/
- [16]momentjs.com/docshttps://momentjs.com/docs/#/get-set/max/
- [17]momentjs.com/docshttps://momentjs.com/docs/#/displaying/fromnow/
Need a different package?
Request a profile