Quartz.NET "?" vs "*"

A cron expression that would be perfectly valid in Hangfire or plain Unix cron throws Support for specifying both a day-of-week AND a day-of-month parameter is not implemented in Quartz.NET, for an expression that doesn't look like it's specifying both of anything. The rule is narrower and stricter than it first appears.

reference guide Quartz.NET

// the actual rule

Quartz.NET expressions have both a day-of-month field and a day-of-week field, and the rule is exact: exactly one of the two must be ?. It doesn't matter what the other field contains — *, a specific value, a list, a range, L, # — as long as one field is ?, the expression is valid. The error only fires when neither field is ?.

valid vs invalid
0 0 12 * * ?Valid. Day-of-month is *, day-of-week is ? — fires every day at noon.
0 0 6 ? * MONValid. Day-of-month is ?, day-of-week is MON — fires every Monday at 6 AM.
0 0 12 * * *Invalid. Neither field is ? — throws the "not implemented" error, even though both fields are just wildcards.
0 0 6 15 * MONInvalid, for a different reason: this looks like it's asking for "the 15th, and also every Monday," which cron's AND/OR semantics don't actually resolve the way most people expect even in dialects that allow it. Quartz.NET refuses to guess and requires you to pick one field to leave unconstrained.
The most common trigger isn't a deliberate attempt to combine both fields — it's translating a 5-field Unix or Hangfire expression into Quartz.NET's 6/7-field format by adding a leading seconds field and leaving both day fields as *, the way they were in the original. That translation is invalid; one of the two has to become ?.

// why the restriction exists at all

Standard cron resolves day-of-month and day-of-week with OR logic when both are constrained to specific values — "the 15th, OR any Friday" — which is rarely what anyone actually wants and is a well-documented source of schedules that fire far more often than intended. Quartz.NET sidesteps the ambiguity entirely by making it a parse error instead of a silent surprise: you're forced to explicitly say which of the two fields you're not using, via ?, rather than have the parser guess at AND-vs-OR semantics on your behalf.

// build it without guessing

The Cron Builder & Explainer generates valid Quartz.NET expressions field-by-field, so the ? placement is handled automatically instead of being something to remember while translating between dialects.