TryGhost/Ghost · error · Error
Invalid filter date: ${date}
Error message
Invalid filter date: ${date} What it means
getDayBoundsInUtc converts a calendar date plus a timezone into UTC instant bounds (start of day 00:00:00.000, end of day 23:59:59.999). It calls Temporal.PlainDate.from(date) which throws if the string is not a valid ISO 8601 date (YYYY-MM-DD). The catch re-throws with a descriptive message including the offending date value.
Source
Thrown at apps/admin/src/shared/filters/filter-normalization.ts:32
: value;
return Temporal.Instant.from(instantValue).toZonedDateTimeISO(timezone).toPlainDate().toString();
} catch {
return null;
}
}
export function getTodayInTimezone(timezone: string): string {
return Temporal.Now.zonedDateTimeISO(timezone).toPlainDate().toString();
}
export function getDayBoundsInUtc(date: string, timezone: string): {start: string; end: string} {
let plainDate: Temporal.PlainDate;
try {
plainDate = Temporal.PlainDate.from(date);
} catch {
throw new Error(`Invalid filter date: ${date}`);
}
try {
const start = plainDate.toPlainDateTime(Temporal.PlainTime.from('00:00:00')).toZonedDateTime(timezone).toInstant();
const end = plainDate.toPlainDateTime(Temporal.PlainTime.from('23:59:59.999')).toZonedDateTime(timezone).toInstant();
return {
start: start.toString({fractionalSecondDigits: 3}),
end: end.toString({fractionalSecondDigits: 3})
};
} catch {
throw new Error(`Invalid timezone: ${timezone}`);
}
}
View on GitHub (pinned to 47d8b0e2ad)
Solutions
- Normalize the date to ISO 8601 YYYY-MM-DD before calling getDayBoundsInUtc; use Temporal.PlainDate.from(date) in a try/catch upstream or validate with /^\d{4}-\d{2}-\d{2}$/.
- If the input may be a datetime, extract the date portion first: value.slice(0, 10) for ISO datetime strings, or use formatDateInTimezone which accepts both date-only and legacy UTC datetime formats.
- Validate at the form/API boundary and reject non-ISO dates with a user-facing message before they reach this function.
- Use getTodayInTimezone(timezone) when you need 'today' rather than constructing a date string manually.
Example fix
// before (throws — non-padded month)
getDayBoundsInUtc('2024-1-05', 'America/New_York')
// after
getDayBoundsInUtc('2024-01-05', 'America/New_York') Defensive patterns
Strategy: validation
Validate before calling
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
function isValidFilterDate(date: string): boolean {
if (!ISO_DATE.test(date)) return false;
try {
Temporal.PlainDate.from(date);
return true;
} catch {
return false;
}
}
// call: if (!isValidFilterDate(date)) { /* reject */ } Type guard
function isIsoDateString(value: unknown): value is string {
return typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value);
} Try / catch
try {
const bounds = getDayBoundsInUtc(date, timezone);
} catch (e) {
if (e instanceof Error && e.message.startsWith('Invalid filter date')) {
// return a 400 to the caller with the bad date
}
throw e;
} Prevention
- Normalize all date inputs to ISO 8601 YYYY-MM-DD at the API boundary before they reach getDayBoundsInUtc.
- Use getTodayInTimezone(timezone) instead of constructing today's date string manually.
- When accepting datetime input, extract the first 10 chars (the date portion) or use formatDateInTimezone which accepts both formats.
When it happens
Trigger: Calling getDayBoundsInUtc with a date string that is not strict ISO 8601 calendar date format. Examples: '2024-1-5' (non-zero-padded), '2024/01/05' (slashes), '01-05-2024' (date-first), '2024-01-05T00:00:00' (datetime, not date-only), empty string, or a locale-formatted date like 'Jan 5, 2024'.
Common situations: A date picker component emits a locale-specific or non-padded format. A backend returns a datetime string where a bare date was expected. A URL query parameter carries a user-typed date that was not normalized. Date-fns or moment formatting with a non-ISO token produces a string PlainDate.from rejects.
Related errors
- Invalid timezone: ${timezone}
- Your Username is not a valid Bluesky Username
- The URL must be in a format like https://www.threads.net/@yo
- The NQL filter you passed was invalid.
AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13).
Data as JSON: /api/errors/6179381d92826ba3.
Report an issue: GitHub.