web-utilities-date-fns-be47f0 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited web-utilities-date-fns-be47f0 (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Quick Guide: date-fns is a tree-shakeable set of pure functions — each returns a newDateand nothing mutates. Parse withparseISOfor ISO strings orparsefor custom formats, then checkisValid, because v4 returns Invalid Date where v3 threw. Format tokens are Unicode TR35 (yyyy,dd,EEEE) and differ from Moment's. Timezone support lives in a companion package whose name depends on the major version.
Detailed Resources:
in option, transpose, DST detection, UTC classes@date-fns/tz supplies TZDate and tz(), @date-fns/utc supplies UTCDate, and transpose ships in the core package. Follow examples/timezone.md.date-fns-tz supplies formatInTimeZone, toZonedTime and fromZonedTime. Same file, second half.parseISO, normalise date-only values with startOfDay, and skip the timezone file.<critical_requirements>
Parse ISO 8601 strings with `parseISO`. new Date(string) parsing is engine-dependent, so new Date("01/15/2026") can succeed, fail, or mean a different day depending on the runtime.
Check `isValid()` on anything you parsed. v4 returns Invalid Date and NaN where v3 threw, so an unchecked parse stays silent until the first format call downstream.
Import each function by name. import * as dateFns defeats tree-shaking and pulls the whole library; a named format import costs roughly 2 KB.
*Build new dates with `add / sub` / `startOf rather than the Date setters.** date.setDate(...)` mutates the object every holder of that reference shares; every date-fns function returns a new one.
</critical_requirements>
Auto-detection: date-fns, format, parseISO, addDays, subMonths, differenceInDays, formatDistance, formatDistanceToNow, isAfter, isBefore, eachDayOfInterval, startOfWeek, weekStartsOn, date-fns-tz, @date-fns/tz, @date-fns/utc, TZDate, TZDateMini, UTCDate, UTCDateMini, tz(), transpose, tzName, tzScan, tzOffset, withTimeZone
Applies to:
Handled elsewhere:
Intl.DateTimeFormat covers it at zero bundle cost, and a runtime that ships the Temporal standard makes most of this library optional<philosophy>
Functions, not methods. There is no chainable wrapper object: format(date, "PP") takes a Date and returns a string, addDays(date, 7) takes a Date and returns a new Date. That is what makes the library tree-shakeable — a bundler can drop the 200 functions you did not import — and it is what makes every operation testable in isolation.
Two consequences worth holding: composition reads inside-out (format(startOfMonth(addDays(d, 7)), F)), and there is no ambient configuration, so a locale or a timezone reaches a call only as an option you pass.
</philosophy>
<patterns>
Tokens follow Unicode Technical Standard #35, which differs from Moment: yyyy not YYYY, dd not DD, EEEE not dddd. A Moment token is not an error — it produces a different value.
import { format } from "date-fns";
const date = new Date(2026, 0, 15, 14, 30, 0);
format(date, "yyyy-MM-dd"); // "2026-01-15"
format(date, "MMMM d, yyyy 'at' h:mm a"); // "January 15, 2026 at 2:30 PM"
format(date, "EEEE"); // "Thursday"Full token table: reference.md. Full code: examples/core.md
P, PP, PPP, PPPP (and p, pp for time) render the locale's own conventions, so the same call reads correctly in every region. A hardcoded MM/dd/yyyy is US-only.
import { format } from "date-fns";
import { enUS, de, ja } from "date-fns/locale";
format(date, "P", { locale: enUS }); // "01/15/2026"
format(date, "P", { locale: de }); // "15.01.2026"
format(date, "P", { locale: ja }); // "2026/01/15"
format(date, "PPPP", { locale: de }); // "Donnerstag, 15. Januar 2026"Full code: examples/i18n.md
parseISO for ISO 8601, parse for a custom format with a reference date as the third argument. Neither throws — validate before use. Round-tripping through format additionally rejects dates that parsed but do not exist, such as Feb 30.
import { parseISO, parse, isValid, format } from "date-fns";
const fromApi = parseISO("2026-01-15T14:30:00Z");
const fromUser = parse("15/01/2026", "dd/MM/yyyy", new Date());
if (!isValid(fromUser)) return null;
if (format(fromUser, "dd/MM/yyyy") !== "15/01/2026") return null; // Feb 30 etc.Full code: examples/core.md
Every add* and sub* function leaves its input untouched, so a date held in state or passed as a prop is safe to compute from.
import { addDays, addMonths, subDays } from "date-fns";
const trialEnd = addDays(signupDate, 14);
const nextBilling = addMonths(signupDate, 1);
const warnAt = subDays(trialEnd, 7);
// signupDate is unchangedFull code: examples/core.md
startOf* / endOf* handle month lengths, leap years and week-start conventions, and give the inclusive ranges a >= start AND <= end filter needs. endOfDay is 23:59:59.999, not midnight.
import {
startOfDay,
endOfDay,
startOfWeek,
startOfMonth,
endOfMonth,
} from "date-fns";
const today = { start: startOfDay(date), end: endOfDay(date) };
const thisMonth = { start: startOfMonth(date), end: endOfMonth(date) };
const isoWeekStart = startOfWeek(date, { weekStartsOn: 1 }); // MondayFull code: examples/core.md
isSameDay, isWithinInterval and friends express the intent and handle the unit-truncation the manual version gets wrong.
import { isAfter, isSameDay, isWithinInterval, isWeekend } from "date-fns";
isAfter(end, start);
isSameDay(a, b); // ignores time-of-day
isWithinInterval(candidate, { start, end }); // endpoints sorted, so order is not checked
isWeekend(date);Full code: examples/comparison.md
</patterns>
<red_flags>
Breaks at runtime:
format on an Invalid Date throws, and in v4 the parse itself gave no warning. Gate on isValid.parse called with two arguments — the reference date is required, and its year/month supply anything the format string omits.format(date, "z") on a UTCDate — the z token needs a native Date.Surprising behaviour:
start later than end is not rejected — since v3 isWithinInterval and areIntervalsOverlapping sort the endpoints and eachDayOfInterval returns the days reversed, so a range the user got backwards answers plausibly instead of failing. Order the interval yourself.new Date("2026-01-15") is UTC midnight, because a date-only ISO string is specified as UTC; every other string shape is implementation-defined, so the same input can shift a whole day between runtimes.addMonths(jan31, 1) is Feb 28, and addYears(feb29, 1) is Feb 28 — the day clamps to the target month's length.differenceIn* truncates: 45 days is differenceInMonths 1, not 1.5. v4 truncates toward zero where v3 floored, which changes negative results.weekStartsOn or a locale for anything user-facing.daysInYear import from date-fns/constants, not from the package root.</red_flags>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.