koala73/worldmonitor · warning · ValidationError

Invalid imagery datetime

Error message

Invalid imagery datetime

What it means

searchImagery normalizes req.datetime (or a default last-7-days window) through normalizeDatetime(); if the value cannot be parsed into a valid STAC-style datetime or range, it throws ValidationError on field 'datetime' with 'Invalid imagery datetime'.

Solutions

  1. Send an ISO-8601 instant ('2026-01-01T00:00:00Z') or a start/end range joined with '/'
  2. If you want recent imagery, omit datetime entirely to use the default last-7-days window
  3. Catch ValidationError and map the field 'datetime' back to the date input for correction

Example fix

// before
await searchImagery(ctx, { datetime: '01/02/2026' });
// after
await searchImagery(ctx, { datetime: '2026-01-01T00:00:00Z/2026-01-08T00:00:00Z' });
Defensive patterns

Strategy: validation

Validate before calling

const iso = /^\d{4}-\d{2}-\d{2}(T[\d:.]+Z)?(\/\d{4}-\d{2}-\d{2}(T[\d:.]+Z)?)?$/;
if (req.datetime && !iso.test(req.datetime.trim())) throw new Error('datetime must be ISO-8601 instant or start/end range');

Type guard

const isIsoDatetime = (v: unknown): v is string =>
  typeof v === 'string' && !Number.isNaN(Date.parse(v.includes('/') ? v.split('/')[0] : v));

Try / catch

try {
  return await searchImagery(ctx, { datetime });
} catch (e) {
  if (e instanceof ValidationError && e.issues.some(i => i.field === 'datetime')) {
    return await searchImagery(ctx, {}); // default last-7-days window
  }
  throw e;
}

Prevention

When it happens

Trigger: datetime values like '2024-13-45', 'last week', '2024/01/01' (wrong separator), a reversed range where start > end, or open ranges in a form normalizeDatetime does not accept.

Common situations: Free-text date inputs passed straight through; date components reordered per locale (DD/MM vs MM/DD); timestamps with local timezone offsets that fail the expected ISO-8601 shape; forgetting the '/'-separated range format.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/6a82a1d15e8671c0. Report an issue: GitHub.

Appendix: source

Thrown at server/worldmonitor/imagery/v1/search-imagery.ts:138

  _ctx: ServerContext,
  req: SearchImageryRequest,
): Promise<SearchImageryResponse> {
  if (!req.bbox) {
    return { scenes: [], totalResults: 0, cacheHit: false };
  }

  const parsedBbox = validateBbox(req.bbox);
  if (!parsedBbox) {
    return { scenes: [], totalResults: 0, cacheHit: false };
  }

  const limit = Math.max(1, Math.min(50, req.limit || 10));
  const nowHour = new Date();
  nowHour.setMinutes(0, 0, 0);
  const weekAgo = new Date(nowHour.getTime() - 7 * 24 * 60 * 60 * 1000);
  const defaultDatetime = `${weekAgo.toISOString().split('.')[0]}Z/${nowHour.toISOString().split('.')[0]}Z`;
  const datetime = normalizeDatetime(req.datetime || defaultDatetime);
  if (datetime === null) throw new ValidationError([{ field: 'datetime', description: 'Invalid imagery datetime' }]);
  const source = (req.source ?? '').trim().toLowerCase();
  const matchedCollections = COLLECTIONS.filter(collection => collection.includes(source));
  const collections = matchedCollections.length > 0 ? matchedCollections : COLLECTIONS;
  const body = JSON.stringify({
    bbox: parsedBbox,
    datetime,
    collections,
    limit,
    sortby: [{ field: 'properties.datetime', direction: 'desc' }],
  });

  try {
    const key = `imagery:search:v2:${await sha256Hex(body)}`;
    const result = await cachedFetchJsonWithMeta<{ scenes: ImageryScene[]; totalResults: number }>(
      key,
      CACHE_TTL,
      async () => {

View on GitHub (pinned to 7d06c8633d)