An expiration date such as 2026-11-12 is a calendar date. A reminder sent at 2026-11-12T00:30:00Z is an instant. Treating those as the same data type creates bugs in training trackers, membership systems, and renewal workflows.

Disclosure: this article is published by TrainingTrackerHQ, which builds employee training and certification tracking software. The examples below use synthetic dates and contain no employee data. AI assisted preparation of this article; the included fixture was executed locally.

The bug: the date changes when you display it

Consider a certificate that displays November 12 as its expiration date:

const expiry = new Date('2026-11-12'); console.log(new Intl.DateTimeFormat('en-US', { timeZone: 'America/Los_Angeles', dateStyle: 'long', }).format(expiry)); // November 11, 2026 

JavaScript parses that ISO date string at UTC midnight. Displaying the instant in Los Angeles moves it to the preceding local day. The employee's certificate did not change; the representation did.

Store the original YYYY-MM-DD as a date-only value. If your database has a date type, use it. If it uses text, validate the full date and keep the format consistent. Keep an issued-at or approved-at timestamp in a separate field.

Decide which business calendar defines today

At 2026-11-12T00:30:00Z, Los Angeles is still on November 11, while Tokyo is on November 12. A server's local clock is not necessarily the organization's calendar.

For a multi-site product, decide whether status follows the organization's timezone or each location's timezone. Document the choice and pass an explicit date into your status function. Do not let a browser silently choose the business timezone.

function businessDate(now, timeZone) { const parts = new Intl.DateTimeFormat('en-CA', { timeZone, year: 'numeric', month: '2-digit', day: '2-digit', }).formatToParts(now); const part = type => parts.find(item => item.type === type).value; return `${part('year')}-${part('month')}-${part('day')}`; } 

In production, validate the timezone against your supported IANA identifiers. A bad timezone should produce a visible configuration error rather than silently fall back to the server timezone.

Compare calendar days after validating input

UTC is useful as an arithmetic coordinate system after you have selected the correct business date. It is not a substitute for selecting that date.

function isoDay(value) { if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) throw new Error('Expected YYYY-MM-DD'); const parsed = new Date(`${value}T00:00:00.000Z`); if (!Number.isFinite(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== value) { throw new Error('Invalid calendar date'); } return parsed.getTime() / 86_400_000; } function remainingDays(expiresOn, today) { return isoDay(expiresOn) - isoDay(today); } 

The round-trip validation rejects February 29 in a non-leap year. A regular expression alone would accept it. This intentionally narrow helper supports four-digit ISO dates used by the example; use a well-maintained date library if your domain needs historical calendars or other formats.

Fix boundary behavior before adding reminders

For today = 2026-11-12, the locally executed fixture produced these results:

Expiration Remaining days Example interpretation
2026-11-11 -1 Expired
2026-11-12 0 Due today
2026-11-19 7 Within seven days
2026-12-12 30 Within thirty days
2027-01-11 60 Within sixty days
2027-02-10 90 Within ninety days

The arithmetic does not determine policy. Here, a certificate remains unexpired through its printed expiration date and becomes expired on the next business date. Some credentials specify an exact expiry timestamp or different legal conditions; those need their own rule.

The fixture also checks the leap-year boundary from February 28 to March 1, 2028: two calendar days. It rejects 2026-02-29, 2026-13-01, an ambiguous slash-formatted date, and an empty value. Run these checks with a fixed today, not the actual clock, so your CI results remain stable.

Missing dates need an exception workflow

Do not convert a missing expiry into zero days or January 1, 1970. Distinguish a missing value, a non-expiring requirement, an invalid import, and a real overdue record. Ask an authorized reviewer to resolve ambiguous spreadsheet dates instead of guessing their locale.

When displaying a matrix, also keep evidence availability separate from expiration status. A record can have a future date and still lack readable certificate evidence. A renewed document can be waiting for approval while the older certificate remains the active record.

These distinctions matter when implementing a certification expiration tracker or a role-based training matrix. The useful invariant is simple: a display timezone must never rewrite the date printed on the underlying evidence.