Skip to content

Read day of month, month and year as prose - #718

Open
austek wants to merge 4 commits into
jmrozanec:masterfrom
austek:fix/issue_3_phrasing
Open

Read day of month, month and year as prose#718
austek wants to merge 4 commits into
jmrozanec:masterfrom
austek:fix/issue_3_phrasing

Conversation

@austek

@austek austek commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Description of your changes

Follows #717 and #719, and is stacked on both — this branch contains those commits too, so please merge #719 and #717 first and this will reduce to its own two commits. Together the three cover the concrete cases reported on #3.

Three reported descriptions, all still reproducing on master:

reported in expression before after
issue body (@francisdb, 2014) 0 0 0 1 1 ? at 00:00 at 1 day at January month at 00:00 on day 1 in January
issue body (@francisdb, 2014) 0/1 * * * * ? every second from second 0 every second
comment (@Naxos84, 2017) 3/4 5/6 7/8 9/2 10/2 ? 2017/2 … every 2 months from month 10 every 2 years from year 2017 … every 2 months from October every 2 years from 2017

Three changes in descriptor/ produce that:

  • Day of month, month and year get their own On phrasing instead of the generic "at <value> <unit>" template.
  • describe(Every) drops the start clause when the period is 1 and the start is 0, since "every 1 unit from 0" is just "every unit". A meaningful start is kept — 5/1 * * * * ? still reads "every second from second 5", and 0 0/5 14 * * ? still reads "every 5 minutes from minute 0 at 14 hour".
  • The unit word is omitted where the value already names it, so "from October" rather than "from month October". monthsInstance also no longer substitutes Object::toString when the expression is an Every, which is what produced "month 10" in the first place.

Day of week and the list forms follow, so the whole vocabulary agrees rather than only the three reported expressions:

field single list
day of month on day 1 on days 1, 5 and 26
day of week on Monday on Monday, Wednesday and Friday
month in March in January, May and December
year in 2005 in 2005 and 2006

A list reuses the bundle key of its single value form, passing the joined values as the argument, so a translator fixes one string rather than two. Only day of month needs a plural key of its own. Ranges and Every expressions are untouched — MON-FRI still reads "every day between Monday and Friday". The L and # day of week forms are untouched by this PR; they read "on the last Friday of the month" and "on the first Monday of the month" because #719 sits below it in the stack.

Impact Analysis

Output changes for expressions with an explicit day of month, day of week, month or year, whether single or a list, and for x/1 starting at 0. Callers asserting on those exact strings will need updating.

I diffed 50 Quartz and Unix expressions between this branch and the commit it stacks on: 21 lines change, all of them the cases above, nothing incidental. Seven pre-existing expectations in this repo are updated across the two commits here — three in CronDescriptorQuartzIntegrationTest, two rows of Issue126Test (the month clause of the #719 expression moves from "from month 1" to "from January"), one in CronDescriptorTest and one in ExecutionTimeQuartzIntegrationTest.

Two things reviewers should weigh:

  • Day of week and list forms are included. Fixing months first left 0 10,44 14 ? 3 WED reading "… in March at Wednesday day", and fixing that in turn exposed lists still reading "at Monday, Wednesday and Friday days". Both are now consistent with the single value forms; see the table above. Hours and minutes keep their existing at 1, 2, 3 and 4 hours, since numeric units read fine as a list and are not part of Descriptions could be better #3.
  • The five new keys are in the base bundle only, so the other seventeen locales fall back to English for these phrases — German reads "um 00:00 on day 1 in Januar". No locale throws; I checked all eighteen. This is the same fallback the bundles already rely on elsewhere (ro has no at today). Months and years take separate keys, in_month_x and in_year_x, even though English formats both as in {0}, precisely so a translator can decline them differently rather than being forced into one template. Translations need native speakers and I did not want to guess them.

One rough edge left in place: an expression restricting both day of month and day of week repeats the preposition, so 30 4 1,15 * 5 reads "at 04:30 on days 1 and 15 on Friday". That is the field concatenation in CronDescriptor.describe, not the phrasing changed here, and it was equally repetitive before ("at 1 and 15 days at Friday day"). Joining the fields properly means deciding an order for the whole sentence, which is the larger change #126 also asks for.

I am aware of the 2018 discussion where the template approach was set aside in favour of neural translation. This is not an attempt to reopen that — it is the minimum needed to make the specific expressions in #3 grammatical, and it leaves the per-language phrasing decisions to the bundles. Happy to close it if you would rather keep that direction clear.

TestDescriptor is left disabled; its expectations describe a different output shape (comma separated clauses, ordinals, am/pm) and reaching them is a larger change than this.

Checklist

austek added 4 commits July 28, 2026 15:33
"0 59 10 ? 1/2 MON#1 *" read "at 10:59 every 2 months from month 1 Monday 1 of
every month". The nth day of week printed its position as a bare number, and
claimed "of every month" right after the month field had restricted it to every
second month.

Give the # and L forms their own phrasing, so they read "on the first Monday of
the month" and "on the last Friday of the month" and no longer contradict a
restricted month. Positions past the fifth, outside the Quartz range but
reachable through the generic descriptor, fall back to the plain number.

Field order is untouched, so the day of week still follows the month rather
than leading the sentence as the report suggests.

Fixes jmrozanec#126
Minute 0 was always suppressed as an implied default, so an expression
firing on a sub-minute schedule lost the minute bounding it. "* 0 9-23 * * ?"
described as "every second every hour between 9 and 23", roughly sixty times
the firings it actually produces.

Only suppress minute 0 when the seconds are implicit too. Also fixes
"5 0 9-23 * * ?", "0/5 0 * * * ?" and "* 0 */4 * * ?", and covers the
deliberate suppression cases so they stay collapsed.

Makes the strategy set insertion ordered and hoists the duplicated
predicate call while here.

Refs jmrozanec#3
"0 0 0 1 1 ?" described as "at 00:00 at 1 day at January month" and
"0 15 10 * * ? 2005" as "at 10:15 at 2005 year". Give day of month, month
and year their own phrasing so they read "on day 1 in January" and "in 2005".

Drop the start of an every expression when the period is 1 and the start is
0, since "every 1 unit from 0" is just "every unit", so "0/1 * * * * ?" is
now "every second" rather than "every second from second 0". A meaningful
start is kept: "5/1 * * * * ?" still reads "every second from second 5".

Omit the unit word where the value already names it, so months, years and
days of week read "from October" rather than "from month 10". Months no
longer fall back to the raw number inside an every expression, which is what
produced "month 10".

Months and years take separate bundle keys even though English formats both
as "in {0}", so a translator can decline them differently. The keys are only
in the base bundle for now, so other locales fall back to English for these
phrases.

Refs jmrozanec#3
Day of week kept the generic template, so "0 10,44 14 ? 3 WED" read
"... in March at Wednesday day" once months had been fixed. Give it the same
treatment, so it reads "in March on Wednesday".

Fixing the single value exposed the list form, which still read
"at Monday, Wednesday and Friday days". Match lists to the single value they
generalise across day of month, day of week, month and year, so a list and a
single value do not use two different vocabularies in one sentence.

A list reuses the key of its single value form, passing the joined values as
the argument, so only day of month needs a plural key of its own. Ranges,
every expressions and the L and # forms are untouched.

Refs jmrozanec#3
@austek
austek force-pushed the fix/issue_3_phrasing branch from 0077ebe to 2093161 Compare July 28, 2026 14:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant