Skip to content

duly_duty.timezone accepts any string — a typo'd IANA zone validates clean and fails at dispatch #24

Description

@os-warren

Found while implementing the period engine (#1). Not fixed there: the fix belongs on src/objects/duty.object.ts, outside that card's file surface.

What happens now

timezone: Field.text({
  label: 'Timezone',
  defaultValue: 'UTC',
  maxLength: 64,
  description: 'IANA name (e.g. Europe/Berlin). Period boundaries and due dates are computed here.',
}),

Nothing checks the value is a zone the host knows. Europe/Munich, CET+1, Asia/Shanghai with a trailing space, or an empty string all save, all pass pnpm validate, and all look correct in the duty form.

The failure surfaces later and somewhere else: periodKeyFor / periodBounds / dueDateFor in src/functions/period.ts throw Unknown IANA timezone: "Europe/Munich" the first time the dispatcher touches that duty. So one bad character on one duty record becomes a run-time throw inside a batch job, attributed to the job rather than to the record — and every other duty in the same run is collateral depending on how #2 handles a partial failure.

Why it is worth a validation rather than tolerance in the engine

The engine deliberately refuses instead of falling back to UTC: a duty silently resolving "the 5th of the month" in the wrong zone is a wrong due date that nobody can see is wrong, which is strictly worse than a loud failure. That is the right call for the consumer — but it means the author never hears about the typo until dispatch night. The check wants to live at author time, where the person who made the typo is still looking at the field.

Options

  • A script validation on duly_duty (and duly_catalog_item, which carries the same field) rejecting a value the host's Intl cannot resolve, in the shape of the two validations already on the object.
  • Or a select of the zones the deployment actually serves, if the product would rather not offer all 418.

Same field, same gap on duly_catalog_item. Filing unassigned for triage — the choice between the two options is a product call, not a mechanical fix.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions