cronli5 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,32 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.1.1]
10
+
11
+ ### Fixed
12
+
13
+ - A minute of `0` is no longer dropped when a sub-minute second makes the
14
+ cadence sub-minute. `* 0 * * * *` now reads "every second, zero minutes past
15
+ the hour, every hour" (was "every second, every hour", which described every
16
+ second of *every* minute, losing the minute-0 restriction). The bug affected
17
+ the wildcard-second + minute-0 combination over the every-hour, hour-range,
18
+ and hour-step idioms, in all five languages; fuzz coverage was added for the
19
+ shape so it cannot silently regress.
20
+
21
+ ### Changed
22
+
23
+ - A minute **list or range** within an **hour range** now closes on the bare
24
+ hour, with the minutes stated separately: `2,3,4 9-17 * * *` reads "at 2, 3,
25
+ and 4 minutes past the hour from 9 a.m. through 5 p.m." (was "through 5:04
26
+ p.m.", which glued the last fire's minute onto the bound and read as a
27
+ misleading continuous span). Single-minute (`30 9-17 * * *` → "through 5:30
28
+ p.m.") and wildcard (`* 9-17 * * *` → "through 5:59 p.m.") bounds are
29
+ unchanged. All five languages.
30
+
31
+ ## [0.1.0]
32
+
33
+ First non-beta release.
34
+
9
35
  ### Added
10
36
 
11
37
  - **Per-language documentation** under `docs/lang/` (`en.md`, `es.md`,
@@ -34,7 +60,6 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
34
60
  `ampm` option is ignored. Ships with a reviewed corpus, minimal
35
61
  pairs, language notes, and a review log under `test/lang/fi/`.
36
62
  Like Spanish, Finnish required zero core changes.
37
-
38
63
  - Descriptions for **lists containing range or step segments** (e.g.
39
64
  `0-30,45` or `9,17-19`) in every field. Minute and second lists read their
40
65
  spans discretely (`at five through ten and 20 minutes past the hour`), hour
@@ -87,6 +112,18 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
87
112
  - An explicitly supplied year is now always described: object input with a
88
113
  `year` property (e.g. `{hour: 9, year: 2030}`) previously validated the
89
114
  year and then silently dropped it from the description.
115
+ - Idiomatic descriptions for **lists** (`,`), **ranges** (`-`), and **compound**
116
+ patterns that combine multiple non-trivial fields (e.g.
117
+ `at 30 minutes past the hour from 9 a.m. through 5 p.m.`).
118
+ - Trailing day qualifiers for bare frequencies (e.g. `every minute on Monday`,
119
+ `every hour on January 13`).
120
+ - Dual **ESM** and **CommonJS** builds plus a minified **browser** global, an
121
+ `exports` map, and bundled **TypeScript** type definitions (`cronli5.d.ts`).
122
+ - Continuous integration (GitHub Actions) across Node 18/20/22, with a
123
+ coverage gate.
124
+ - Code coverage via **c8** with enforced thresholds (`npm run coverage`).
125
+ - Property-based tests (**fast-check**), smoke tests against the built
126
+ ESM/CJS artifacts, and type tests (**tsd**, `npm run test:types`).
90
127
 
91
128
  ### Changed
92
129
 
@@ -94,7 +131,6 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
94
131
  `uk` for the Ukrainian language, so the British-English style now uses the
95
132
  ISO-3166 country code `'gb'`. `{ dialect: 'uk' }` still works as a
96
133
  deprecated alias for `'gb'` and will be removed in a future release.
97
-
98
134
  - **Spanish now defaults to the 24-hour clock** (`a las 09:30`,
99
135
  `a las 17:00`), matching RAE convention for written Spanish. Pass
100
136
  `{ampm: true}` for the previous 12-hour behavior with day periods
@@ -166,6 +202,11 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
166
202
  `0-30` in minute lists, and clock-time windows like `9 a.m.-5:45 p.m.`).
167
203
  Previously short mode abbreviated names but left "through" in place for
168
204
  every field except weekdays.
205
+ - Source is now authored as an ES module in `src/` and bundled with esbuild.
206
+ - Date descriptions always use suffixed numeric ordinals (`1st`, `2nd`, ...).
207
+ - Modernized the toolchain: ESLint 9 (flat config), Mocha 11, Chai 4.
208
+ - Enforced explicit ESLint budgets for cyclomatic `complexity`, `max-depth`,
209
+ and `max-params` as regression guards.
169
210
 
170
211
  ### Fixed
171
212
 
@@ -236,36 +277,6 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
236
277
  own clause instead of cross-multiplying into a wall of times
237
278
  (`0,30 8-18/2 * * *` reads "at 0 and 30 minutes past the hour, at 8
238
279
  a.m., 10 a.m., ..." — six times, not twelve).
239
-
240
- ## [0.1.0]
241
-
242
- First non-beta release.
243
-
244
- ### Added
245
-
246
- - Idiomatic descriptions for **lists** (`,`), **ranges** (`-`), and **compound**
247
- patterns that combine multiple non-trivial fields (e.g.
248
- `at 30 minutes past the hour from 9 a.m. through 5 p.m.`).
249
- - Trailing day qualifiers for bare frequencies (e.g. `every minute on Monday`,
250
- `every hour on January 13`).
251
- - Dual **ESM** and **CommonJS** builds plus a minified **browser** global, an
252
- `exports` map, and bundled **TypeScript** type definitions (`cronli5.d.ts`).
253
- - Continuous integration (GitHub Actions) across Node 18/20/22, with a
254
- coverage gate.
255
- - Code coverage via **c8** with enforced thresholds (`npm run coverage`).
256
- - Property-based tests (**fast-check**), smoke tests against the built
257
- ESM/CJS artifacts, and type tests (**tsd**, `npm run test:types`).
258
-
259
- ### Changed
260
-
261
- - Source is now authored as an ES module in `src/` and bundled with esbuild.
262
- - Date descriptions always use suffixed numeric ordinals (`1st`, `2nd`, ...).
263
- - Modernized the toolchain: ESLint 9 (flat config), Mocha 11, Chai 4.
264
- - Enforced explicit ESLint budgets for cyclomatic `complexity`, `max-depth`,
265
- and `max-params` as regression guards.
266
-
267
- ### Fixed
268
-
269
280
  - Weekday/date/month-only patterns no longer drop their qualifier.
270
281
  - A specific minute within an hour range is no longer dropped.
271
282
 
package/cronli5.min.js CHANGED
@@ -1,3 +1,3 @@
1
- "use strict";(()=>{var qn={SUN:0,MON:1,TUE:2,WED:3,THU:4,FRI:5,SAT:6},Jn={JAN:1,FEB:2,MAR:3,APR:4,MAY:5,JUN:6,JUL:7,AUG:8,SEP:9,OCT:10,NOV:11,DEC:12},h={second:{cyclic:!0,max:59,min:0,top:59},minute:{cyclic:!0,max:59,min:0,top:59},hour:{cyclic:!0,max:23,min:0,top:23},date:{aliases:{"?":"*"},cyclic:!0,max:31,min:1,top:31},month:{cyclic:!0,max:12,min:1,numbers:Jn,top:12},weekday:{aliases:{"?":"*",L:"6"},cyclic:!0,max:7,min:0,numbers:qn,top:6},year:{max:9999,min:1970}},k=["second","minute","hour","date","month","weekday","year"],j={"@annually":"0 0 1 1 *","@yearly":"0 0 1 1 *","@monthly":"0 0 1 * *","@weekly":"0 0 * * 0","@daily":"0 0 * * *","@midnight":"0 0 * * *","@hourly":"0 * * * *"},U=6;function s(n,e){return(""+n).indexOf(e)!==-1}function F(n){return Array.from(new Set(n))}function I(n){return/^\d+$/.test(n)}function d(n,e){return I(n)?+n:e[n.toUpperCase()]}function sn(n){return k.forEach(function(r){Vn(n[r],h[r],r)}),n}function Vn(n,e,r){typeof n!="string"&&typeof n!="number"&&un(n,r);let t=""+n;t!=="*"&&(r==="date"&&C(t)||r==="weekday"&&w(t,e)||t.split(",").forEach(function(o){$n(o,e)||un(o,r)}))}function C(n){if(n==="L"||n==="LW"||n==="WL")return!0;let e=/^L-(\d{1,2})$/.exec(n);if(e)return+e[1]>=1&&+e[1]<=30;let r=/^(\d{1,2})W$|^W(\d{1,2})$/.exec(n);if(r){let t=+(r[1]||r[2]);return t>=1&&t<=31}return!1}function w(n,e){if(/L$/.test(n))return v(n.slice(0,-1),e);let r=n.split("#");return r.length===2?v(r[0],e)&&/^[1-5]$/.test(r[1]):!1}function $n(n,e){return s(n,"/")?Yn(n,e):s(n,"-")?an(n,e):v(n,e)}function Yn(n,e){let r=n.split("/");return r.length!==2||!I(r[1])||+r[1]<1?!1:r[0]==="*"||v(r[0],e)||an(r[0],e,!0)}function an(n,e,r){let t=n.split("-");return t.length!==2||!v(t[0],e)||!v(t[1],e)?!1:e.cyclic&&!r?!0:d(t[0],e.numbers)<=d(t[1],e.numbers)}function v(n,e){return n==="*"?!1:I(n)?+n>=e.min&&+n<=e.max:e.numbers?n.toUpperCase()in e.numbers:!1}function un(n,e){throw new Error('`cronli5` was passed an invalid field value "'+n+'" for the '+e+" field.")}function cn(n){k.forEach(function(r){let t=h[r].aliases,i=t&&t[""+n[r]];i&&(n[r]=i)})}function mn(n){return k.forEach(function(r){let t=""+n[r];if(r==="date"&&C(t)||r==="weekday"&&w(t,h[r])){n[r]=t;return}n[r]=Bn(t,h[r])}),n}function Bn(n,e){let r=""+n;if(r==="*")return r;let t=r.split(",").map(function(o){return Xn(Kn(Gn(o,e),e),e)});return t.indexOf("*")!==-1?"*":F(t).sort(function(o,u){return ln(o,e)-ln(u,e)}).join(",")}function Gn(n,e){let r=n.split("/");if(!e.cyclic||r.length!==2||+r[1]!=1)return n;let t=r[0];return s(t,"-")?t:t==="*"||d(t,e.numbers)===e.min?"*":t+"-"+e.top}function Kn(n,e){let r=n.split("/");if(!e.cyclic||typeof e.top!="number"||r.length!==2||s(r[0],"-"))return n;let t=r[0];return(t==="*"?e.min:d(t,e.numbers))+ +r[1]<=e.top?n:t==="*"?""+e.min:t}function Xn(n,e){let r=n.split("/")[0];if(!s(r,"-"))return n;let t=r.split("-");return d(t[0],e.numbers)!==d(t[1],e.numbers)?n:t[0]}function ln(n,e){let r=n.split("/")[0].split("-")[0];return r==="*"?e.min:d(r,e.numbers)}function fn(n,e){let r=n instanceof Array;if(n===null||typeof n>"u"||n===""||r&&n.length===0)throw new Error("`cronli5` expects a non-empty cron pattern as the first argument.");if(r)return dn(n,e);if(typeof n=="object")return Zn(n);if(typeof n=="string")return _n(n,e);throw new Error("`cronli5` was passed an unexpected type.")}function dn(n,e){if(n.length>7)throw new Error("`cronli5` was passed a cron pattern with more than seven fields.");return!e.seconds&&n.length<(e.years?7:6)&&n.unshift("0"),{second:n[0]||"0",minute:n[1]||"*",hour:n[2]||"*",date:n[3]||"*",month:n[4]||"*",weekday:n[5]||"*",year:n[6]||"*"}}function Zn(n){if(!n.second&&!n.minute&&!n.hour)throw new Error("`cronli5` expects that any object being interpreted as a cron pattern have at least one of the following properties: `second`, `minute`, or `hour`");let e=typeof n.second<"u",r=typeof n.minute<"u",t=e?"*":"0",i=e||r?"*":"0";return{second:N(n.second,"0"),minute:N(n.minute,t),hour:N(n.hour,i),date:N(n.date,"*"),month:N(n.month,"*"),weekday:N(n.weekday,"*"),year:N(n.year,"*")}}function N(n,e){return typeof n>"u"?e:n}function _n(n,e){let r=ne(n).split(/\s+/);return dn(r,e)}function ne(n){let e=n.trim();if(e.charAt(0)!=="@")return n;let r=e.toLowerCase();if(Object.hasOwn(j,r))return j[r];throw new Error("`cronli5` does not recognize the macro `"+e+"`.")}function gn(n){return n!=="*"&&!s(n,",")&&!s(n,"-")&&!s(n,"/")}function Q(n){return s(n,"-")&&!s(n,",")&&!s(n,"/")}function ee(n){return s(n,"/")&&!s(n,",")}function q(n){return n!=="*"&&!s(n,"-")&&!s(n,"/")}function J(n){return n!=="*"&&!Q(n)&&!ee(n)}function R(n,e,r){let t=[],i=n;for(;i<=r;)t.push(i),i+=e;return t}function hn(n,e,r,t){let i=n.split("/"),o=+i[1];if(s(i[0],"-")){let f=i[0].split("-");return R(d(f[0],t),o,d(f[1],t))}let u=i[0]==="*"?e:d(i[0],t);return R(u,o,r)}function V(n,e,r){let t=[];return n.split(",").forEach(function(o){if(s(o,"/"))t.push(...hn(o,e,r));else if(s(o,"-")){let u=o.split("-");+u[0]<=+u[1]?t.push(...R(+u[0],1,+u[1])):(t.push(...R(+u[0],1,r)),t.push(...R(e,1,+u[1])))}else t.push(+o)}),F(t)}function re(n){return q(n)?n.split(",").map(Number):[0]}function te(n){if(n==="*")return[0,59];if(Q(n)){let e=n.split("-");if(+e[0]<=+e[1])return[+e[0],+e[1]]}return null}function ie(n){return n==="*"?59:Math.max(...V(n,0,59))}function oe(n){if(gn(n)&&n!=="0")return+n}function ue(n,e){return n==="*"?"wildcard":e==="date"&&C(n)||e==="weekday"&&w(n,h.weekday)?"quartz":s(n,",")?"list":s(n,"/")?"step":s(n,"-")?"range":"single"}function se(n,e,r){return e==="wildcard"||e==="quartz"?null:n.split(",").map(function(i){if(s(i,"/")){let o=i.split("/");return{fires:hn(i,r.min,r.top,r.numbers),interval:+o[1],kind:"step",startToken:o[0]}}return s(i,"-")?{bounds:i.split("-"),kind:"range"}:{kind:"single",value:i}})}function pn(n){let e={},r={};k.forEach(function(u){e[u]=ue(n[u],u),r[u]=se(n[u],e[u],h[u])});let i={analyses:{clockSecond:oe(n.second),lastMinuteFire:ie(n.minute),minuteSpan:te(n.minute),segments:r},pattern:n,shapes:e};return{...i,plan:ae(i)}}function ae(n){let{analyses:e,pattern:r,shapes:t}=n;if(r.second!=="0"){let i=le(r,t,e);if(i)return i}return yn(r,t,e)||Sn(r,t,e)}function le(n,e,r){let t=ce(n,e);return t||(n.hour==="*"&&e.minute==="single"&&n.second!=="*"?{kind:"secondsWithinMinute",singleSecond:e.second==="single"}:e.second==="single"&&q(n.minute)&&J(n.hour)?null:{kind:"composeSeconds",rest:yn(n,e,r)||Sn(n,e,r)})}function ce(n,e){return n.minute!=="*"||n.hour!=="*"?null:n.second==="*"?{kind:"everySecond"}:e.second==="single"?{kind:"secondPastMinute"}:{kind:"standaloneSeconds"}}function yn(n,e,r){if(e.minute==="step")return{hours:fe(n,e,r),kind:"minuteFrequency"};if(e.hour==="single"&&r.minuteSpan)return{hour:+n.hour,kind:"minuteSpanInHour",span:r.minuteSpan};let t=de(n,e);if(t)return t;let i=me(n,e);if(i)return i;if(n.hour==="*")return ge(n,e)}function bn(n){let[e,r]=n.split("/"),t=e==="*"?0:+e;return e.indexOf("-")===-1&&24%+r===0&&t<+r}function me(n,e){return e.hour!=="step"?null:n.minute==="*"?bn(n.hour)?{form:"wildcard",kind:"minuteSpanAcrossHourStep"}:{form:"wildcard",kind:"minutesAcrossHours",times:x(n.hour)}:e.minute==="range"?{form:"range",kind:"minuteSpanAcrossHourStep"}:null}function fe(n,e,r){if(e.hour==="list")return{kind:"during",times:x(n.hour)};if(e.hour==="range"){let t=n.hour.split("-");return{from:+t[0],kind:"window",last:r.lastMinuteFire,to:+t[1]}}return e.hour==="single"?{from:+n.hour,kind:"window",last:r.lastMinuteFire,to:+n.hour}:e.hour==="step"?bn(n.hour)?{kind:"step"}:{kind:"during",times:x(n.hour)}:{kind:"none"}}function de(n,e){return J(n.hour)?n.minute==="*"?{form:"wildcard",kind:"minutesAcrossHours",times:x(n.hour)}:e.minute==="range"||e.minute==="list"&&s(n.minute,"-")&&!s(n.minute,"/")?{form:e.minute==="range"?"range":"list",kind:"minutesAcrossHours",times:x(n.hour)}:null:null}function ge(n,e){if(e.minute==="range")return{kind:"rangeOfMinutes"};if(e.minute==="list")return{kind:"multipleMinutes"};if(n.minute==="*")return{kind:"everyMinute"};if(n.minute!=="0")return{kind:"singleMinute"}}function Sn(n,e,r){if(e.hour==="range"){let t=n.hour.split("-"),i="lead";return n.minute==="*"?i="wildcard":e.minute==="range"&&(i="range"),{from:+t[0],kind:"hourRange",last:r.lastMinuteFire,minuteForm:i,to:+t[1]}}return e.hour==="step"&&n.minute==="0"?{kind:"hourStep"}:n.hour==="*"?{kind:"everyHour"}:he(n,r)}function he(n,e){let r=V(n.hour,0,23),t=re(n.minute);if(r.length*t.length>U)return{fold:t.length===1,kind:"compactClockTimes",minute:t[0]};let i=[];return r.forEach(function(u){t.forEach(function(l){i.push({hour:u,minute:l,second:e.clockSecond})})}),{kind:"clockTimes",times:i}}function x(n){let e=V(n,0,23);return e.length<=U?{fires:e,kind:"fires"}:{kind:"segments"}}function On(n,e){let r=fn(n,e);return cn(r),sn(r),mn(r)}function $(n){return n=""+n,n.length<2?"0"+n:n}function kn(n,e,r){return r.short?n:e[n]||n}function Y(n,{sep:e,pad:r,lean:t}){let i=r?$(n.hour):""+n.hour;return t&&!n.minute&&!n.second?i:i+e+$(n.minute)+(n.second?e+$(n.second):"")}var B={gb:{am:"am",closeUp:!0,dayFirst:!0,midday:"midday",midnight:"midnight",ordinals:!1,pm:"pm",sep:".",serialComma:!1,through:" to "},us:{am:"a.m.",closeUp:!1,dayFirst:!1,midday:"noon",midnight:"midnight",ordinals:!1,pm:"p.m.",sep:":",serialComma:!0,through:" through "},house:{am:"AM",closeUp:!1,dayFirst:!1,midday:"noon",midnight:"midnight",ordinals:!0,pm:"PM",sep:":",serialComma:!0,through:" - "}};function Nn(n){return typeof n=="object"&&n!==null?{...B.us,...n}:B[n==="uk"?"gb":n]||B.us}var pe=["zero","one","two","three","four","five","six","seven","eight","nine","ten"],G=["th","st","nd","rd"],g=[null,["January","Jan"],["February","Feb"],["March","Mar"],["April","Apr"],["May","May"],["June","Jun"],["July","Jul"],["August","Aug"],["September","Sep"],["October","Oct"],["November","Nov"],["December","Dec"]],y=[["Sunday","Sun"],["Monday","Mon"],["Tuesday","Tue"],["Wednesday","Wed"],["Thursday","Thu"],["Friday","Fri"],["Saturday","Sat"]],ye={JAN:g[1],FEB:g[2],MAR:g[3],APR:g[4],MAY:g[5],JUN:g[6],JUL:g[7],AUG:g[8],SEP:g[9],OCT:g[10],NOV:g[11],DEC:g[12]},be={SUN:y[0],MON:y[1],TUE:y[2],WED:y[3],THU:y[4],FRI:y[5],SAT:y[6]},Se=[null,"first","second","third","fourth","fifth"];function Oe(n){return n=n||{},{ampm:typeof n.ampm=="boolean"?n.ampm:!0,lenient:!!n.lenient,seconds:!!n.seconds,short:!!n.short,style:Nn(n.dialect),years:!!n.years}}function ke(n,e){return Xe(zn(n,n.plan,e),n,e)}function zn(n,e,r){let t=Qe[e.kind];return t(n,e,r)}function Ne(n,e,r){return"every second"+a(n,r)}function ze(n,e,r){return H(n,r)+a(n,r)}function Pe(n,e,r){let t=n.pattern.second;return m(t,r)+" "+P(t,"second")+" past the minute, every minute"+a(n,r)}function ve(n,e,r){let t=n.pattern.minute,i=m(t,r),o=P(t,"minute");if(e.singleSecond){let u=n.pattern.second;return i+" "+o+" and "+m(u,r)+" "+P(u,"second")+" past the hour, every hour"+a(n,r)}return H(n,r)+", "+i+" "+o+" past the hour, every hour"+a(n,r)}function Ce(n,e,r){return H(n,r)+", "+zn(n,e.rest,r)}function H(n,e){let r=n.pattern.second,t=n.shapes.second;if(r==="*")return"every second";if(t==="step")return wn(n.analyses.segments.second[0],"second","minute",e);if(t==="range"){let i=r.split("-"),o=E(i,e);return"every second from "+o(i[0])+O(e)+o(i[1])+" past the minute"}return t==="single"?"at "+m(r,e)+" "+P(r,"second")+" past the minute":b(T(n.analyses.segments.second,e),"second","minute",e)}function we(n,e,r){return"every minute"+a(n,r)}function Re(n,e,r){let t=n.pattern.minute;return m(t,r)+" "+P(t,"minute")+" past the hour, every hour"+a(n,r)}function xe(n,e,r){return L(n.pattern.minute,r)+a(n,r)}function Me(n,e,r){return b(T(n.analyses.segments.minute,r),"minute","hour",r)+a(n,r)}function Te(n,e,r){let t=wn(n.analyses.segments.minute[0],"minute","hour",r);return e.hours.kind==="during"?t+=" during the "+Z(n,e.hours.times,!1,r)+" hours":e.hours.kind==="window"?t+=" "+Cn(e.hours,r):e.hours.kind==="step"&&(t+=" "+Pn(n.analyses.segments.hour[0],r)),t+a(n,r)}function Fe(n,e,r){return"every minute from "+c({hour:e.hour,minute:e.span[0]},r)+O(r)+c({hour:e.hour,minute:e.span[1]},r)+a(n,r)}function Ie(n,e,r){if(e.form==="wildcard")return"every minute during the "+Z(n,e.times,!1,r)+" hours"+a(n,r);let t=Z(n,e.times,!0,r);return(e.form==="range"?L(n.pattern.minute,r):b(T(n.analyses.segments.minute,r),"minute","hour",r))+", at "+t+a(n,r)}var He={2:"other",3:"third",4:"fourth",6:"sixth",8:"eighth",12:"twelfth"};function Pn(n,e){let r="during every "+He[n.interval]+" hour",t=n.startToken==="*"?0:+n.startToken;return t===0?r:r+" starting at "+c({hour:t,minute:0},e)}function Le(n,e,r){let t=n.analyses.segments.hour[0];return e.form==="wildcard"?"every minute "+Pn(t,r)+a(n,r):L(n.pattern.minute,r)+", "+Rn(t,r)+a(n,r)}function L(n,e){let r=n.split("-"),t=E(r,e);return"every minute from "+t(r[0])+O(e)+t(r[1])+" past the hour"}function Ee(n,e,r){return"every hour"+a(n,r)}function Ae(n,e,r){let t=Cn(e,r);return e.minuteForm==="wildcard"?"every minute "+t+a(n,r):e.minuteForm==="range"?L(n.pattern.minute,r)+", "+t+a(n,r):vn(n,r)+" "+t+a(n,r)}function vn(n,e){return n.pattern.minute==="0"?"every hour":b(T(n.analyses.segments.minute,e),"minute","hour",e)}function We(n,e,r){return Rn(n.analyses.segments.hour[0],r)+a(n,r)}function Cn(n,e){return"from "+c({hour:n.from,minute:0},e)+O(e)+c({hour:n.to,minute:n.last},e)}function De(n,e,r){let t=rn(e.times),i=e.times.map(function(u){return c({hour:u.hour,minute:u.minute,second:u.second,plain:t},r)});return xn(n,r)+"at "+S(i,r)}function je(n,e,r){if(e.fold){if(n.analyses.segments.hour.some(function(f){return f.kind==="range"})&&!n.analyses.clockSecond)return Ue(n,e,r)+a(n,r);let o={minute:e.minute,second:n.analyses.clockSecond};return xn(n,r)+"at "+_(n,o,!0,r)}let t=b(T(n.analyses.segments.minute,r),"minute","hour",r)+", at "+_(n,{minute:0,second:null},!0,r)+a(n,r);return n.analyses.clockSecond?H(n,r)+", "+t:t}function Ue(n,e,r){let t=e.minute,i=[],o=[];n.analyses.segments.hour.forEach(function(l){l.kind==="range"?i.push("from "+c({hour:l.bounds[0],minute:0},r)+O(r)+c({hour:l.bounds[1],minute:t},r)):l.kind==="step"?o.push(...l.fires):o.push(+l.value)});let u=vn(n,r)+" "+S(i,r);return o.length&&(u+=" and at "+S(o.map(function(l){return c({hour:l,minute:t},r)}),r)),u}var Qe={clockTimes:De,compactClockTimes:je,composeSeconds:Ce,everyHour:Ee,everyMinute:we,everySecond:Ne,hourRange:Ae,hourStep:We,minuteFrequency:Te,minuteSpanAcrossHourStep:Le,minuteSpanInHour:Fe,minutesAcrossHours:Ie,multipleMinutes:Me,rangeOfMinutes:xe,secondPastMinute:Pe,secondsWithinMinute:ve,singleMinute:Re,standaloneSeconds:ze};function wn(n,e,r,t){if(n.startToken.indexOf("-")!==-1)return b(K(n.fires,t),e,r,t);let i=n.startToken==="*"?0:+n.startToken,o=n.interval;return i!==0?n.fires.length<=3?b(K(n.fires,t),e,r,t):"every "+m(o,t)+" "+e+"s from "+m(i,t)+" "+P(i,e)+" past the "+r:60%o===0?"every "+m(o,t)+" "+e+"s":n.fires.length<=2?b(K(n.fires,t),e,r,t):"every "+m(o,t)+" "+e+"s past the "+r}function Rn(n,e){if(n.startToken.indexOf("-")!==-1)return"at "+X(n.fires,e);let r=n.startToken==="*"?0:+n.startToken,t=n.interval;return r===0&&24%t===0?"every "+m(t,e)+" hours":n.fires.length<=3?"at "+X(n.fires,e):r===0?"every "+m(t,e)+" hours from midnight":"every "+m(t,e)+" hours from "+c({hour:r,minute:0},e)}function E(n,e){let r=n.some(function(i){return+i>10});return function(i){return r?""+i:m(i,e)}}function K(n,e){return n.map(E(n,e))}function T(n,e){let r=n.flatMap(function(o){return o.kind==="range"?o.bounds:o.kind==="step"?o.fires:[o.value]}),t=E(r,e);return n.flatMap(function(o){return o.kind==="range"?[t(o.bounds[0])+O(e)+t(o.bounds[1])]:o.kind==="step"?o.fires.map(t):[t(o.value)]})}function b(n,e,r,t){return"at "+S(n,t)+" "+e+"s past the "+r}function qe(n,e,r){return(+n==0||+n==12)&&+e==0&&!(typeof r=="number"&&r>0)}function rn(n){let e=n.filter(function(t){return qe(t.hour,t.minute,t.second)});return e.length>0&&e.length<n.length}function X(n,e){let r=rn(n.map(function(o){return{hour:o,minute:0}})),t=n.map(function(o){return c({hour:o,minute:0,plain:r},e)});return S(t,e)}function Z(n,e,r,t){return e.kind==="fires"?X(e.fires,t):_(n,{minute:0,second:null},r,t)}function Je(n){return n.kind==="range"?n.bounds:n.kind==="step"?n.fires:[n.value]}function _(n,e,r,t){let{minute:i,second:o}=e,u=n.analyses.segments.hour,f=rn(u.flatMap(function(p){return Je(p).map(function(D){return{hour:+D,minute:i,second:o}})})),l=[];return u.forEach(function(p){p.kind==="step"?l.push(...p.fires.map(function(D){return c({hour:D,minute:i,second:o,plain:f},t)})):p.kind==="range"?l.push(c({hour:p.bounds[0],minute:i,second:o,plain:f},t)+O(t)+c({hour:p.bounds[1],minute:i,second:o,plain:f},t)):l.push(c({hour:p.value,minute:i,second:o,plain:f},t))}),S(Ve(l,u,r),t)}function Ve(n,e,r){let t=e.some(function(o){return o.kind==="range"});return!r||!t?n:n.map(function(o,u){return u===0?o:"at "+o})}function S(n,e){if(n.length<=1)return n.join("");if(n.length===2)return n[0]+" and "+n[1];let r=e.style.serialComma?", and ":" and ";return n.slice(0,-1).join(", ")+r+n[n.length-1]}var $e={all:"",month:"in ",stepDate:"on ",weekday:"on "},Ye={all:"every day",month:"every day in ",stepDate:"",weekday:"every "};function a(n,e){let r=Mn(n,$e,e);return r&&" "+r}function xn(n,e){return Mn(n,Ye,e)+" "}function Mn(n,e,r){let t=n.pattern;return t.date!=="*"&&t.weekday!=="*"?Ge(n,r):t.date!=="*"?Be(n,e,r):t.weekday!=="*"?(In(t.weekday,r)||e.weekday+An(n,r))+z(n,r):t.month!=="*"?e.month+A(n,r):e.all}function Be(n,e,r){let t=n.pattern,i=Fn(t.date,r);return i?i+z(n,r):tn(t.date)?e.stepDate+Ln(t.date)+z(n,r):t.month!=="*"&&!Tn(n)?"on the "+nn(n,r)+z(n,r):t.month!=="*"?"on "+Hn(n,r):"on the "+nn(n,r)}function Tn(n){return!En(n.pattern.month)&&n.analyses.segments.month.every(function(r){return r.kind!=="range"})}function Ge(n,e){let r=n.pattern,t=In(r.weekday,e)||"on "+An(n,e),i=Fn(r.date,e);return i?i+z(n,e)+" or "+t:tn(r.date)?Ln(r.date)+z(n,e)+" or "+t:r.month!=="*"&&Tn(n)?"on "+Hn(n,e)+" or "+t+" in "+A(n,e):"on the "+nn(n,e)+" or "+t+z(n,e)}function Fn(n,e){if(n==="L")return"on the last day of the month";if(n==="LW"||n==="WL")return"on the last weekday of the month";let r=/^L-(\d{1,2})$/.exec(n);if(r)return m(+r[1],e)+" "+P(r[1],"day")+" before the last day of the month";let t=/^(\d{1,2})W$|^W(\d{1,2})$/.exec(n);if(t)return"on the weekday nearest the "+M(t[1]||t[2])}function In(n,e){let r=n.split("#");if(r.length===2)return"on the "+Se[+r[1]]+" "+en(r[0],e)+" of the month";if(/L$/.test(n))return"on the last "+en(n.slice(0,-1),e)+" of the month"}function Hn(n,e){let r=A(n,e),t=W(n.analyses.segments.date,e.style.ordinals?M:Ke,e);return e.style.dayFirst?t+" "+r:r+" "+t}function Ke(n){return""+n}function z(n,e){return n.pattern.month==="*"?"":" in "+A(n,e)}function Ln(n){let e=n.split("/"),r=+e[1],t=e[0],o=(r===2?"every other":"every "+M(r))+" day of the month";return t!=="*"&&t!=="1"&&(o+=" from the "+M(t)),o}function nn(n,e){return W(n.analyses.segments.date,M,e)}function A(n,e){let r=En(n.pattern.month);return r||W(n.analyses.segments.month,function(i){return er(i,e)},e)}function En(n){if(!tn(n))return null;let[e,r]=n.split("/");return+r!=2?null:e==="*"||e==="1"?"every odd-numbered month":e==="2"?"every even-numbered month":null}function An(n,e){return W(n.analyses.segments.weekday,function(t){return en(t,e)},e)}function W(n,e,r){let t=[];return n.forEach(function(o){o.kind==="step"?t.push(...o.fires.map(e)):o.kind==="range"?t.push(o.bounds.map(e).join(O(r))):t.push(e(o.value))}),S(t,r)}function tn(n){return n.indexOf("/")!==-1&&n.indexOf("-")===-1&&n.indexOf(",")===-1}function Xe(n,e,r){let t=e.pattern.year;if(t==="*")return n;if(t.indexOf("/")!==-1)return n+" "+_e(t,r);let i=Ze(t,r);if(t.indexOf("-")===-1&&t.indexOf(",")===-1&&e.pattern.date!=="*"&&n.indexOf(" at ")!==-1){let o=r.style.dayFirst?" ":", ";return n.replace(" at ",o+i+" at ")}return n+" in "+i}function Ze(n,e){return n.indexOf(",")!==-1?S(n.split(","),e):n}function _e(n,e){let r=n.split("/"),t=+r[1],i=r[0];if(t<=1)return"every year";let o="every "+m(t,e)+" years";return i!=="*"&&i!=="0"&&(o+=" from "+i),o}function c(n,e){let{hour:r,minute:t,plain:i}=n,o=typeof n.second=="number"&&n.second>0?n.second:0;return e.ampm?nr({hour:r,minute:t,second:o,plain:i},e):Y({hour:r,minute:t,second:o},{pad:!0,sep:e.style.sep})}function nr(n,e){let{hour:r,minute:t,second:i,plain:o}=n,u=e.style;if(!o&&+t==0&&!i){if(+r==0)return u.midnight;if(+r==12)return u.midday}return Y({hour:r%12||12,minute:t,second:i},{lean:!0,sep:u.sep})+(u.closeUp?"":" ")+(r<12?u.am:u.pm)}function m(n,e){return kn(n,pe,e)}function P(n,e){return+n==1?e:e+"s"}function O(n){return n.short?"-":n.style.through}function M(n){let e=Math.abs(n),r=G[e];return r||(e=(e%100-20)%10,r=G[e]||G[0]),n+r}function er(n,e){let r=g[n]||ye[n];return r&&r[e.short?1:0]}function en(n,e){let r=n===7||n==="7"?0:n,t=y[r]||be[r];return t&&t[e.short?1:0]}var rr={describe:ke,fallback:"an unrecognizable cron pattern",options:Oe,reboot:"at system startup",sentence:n=>"Runs "+n+"."},Wn=rr;/**
1
+ "use strict";(()=>{var qn={SUN:0,MON:1,TUE:2,WED:3,THU:4,FRI:5,SAT:6},Vn={JAN:1,FEB:2,MAR:3,APR:4,MAY:5,JUN:6,JUL:7,AUG:8,SEP:9,OCT:10,NOV:11,DEC:12},h={second:{cyclic:!0,max:59,min:0,top:59},minute:{cyclic:!0,max:59,min:0,top:59},hour:{cyclic:!0,max:23,min:0,top:23},date:{aliases:{"?":"*"},cyclic:!0,max:31,min:1,top:31},month:{cyclic:!0,max:12,min:1,numbers:Vn,top:12},weekday:{aliases:{"?":"*",L:"6"},cyclic:!0,max:7,min:0,numbers:qn,top:6},year:{max:9999,min:1970}},k=["second","minute","hour","date","month","weekday","year"],U={"@annually":"0 0 1 1 *","@yearly":"0 0 1 1 *","@monthly":"0 0 1 * *","@weekly":"0 0 * * 0","@daily":"0 0 * * *","@midnight":"0 0 * * *","@hourly":"0 * * * *"},Q=6;function s(n,e){return(""+n).indexOf(e)!==-1}function F(n){return Array.from(new Set(n))}function I(n){return/^\d+$/.test(n)}function d(n,e){return I(n)?+n:e[n.toUpperCase()]}function an(n){return k.forEach(function(r){Jn(n[r],h[r],r)}),n}function Jn(n,e,r){typeof n!="string"&&typeof n!="number"&&sn(n,r);let t=""+n;t!=="*"&&(r==="date"&&C(t)||r==="weekday"&&w(t,e)||t.split(",").forEach(function(o){$n(o,e)||sn(o,r)}))}function C(n){if(n==="L"||n==="LW"||n==="WL")return!0;let e=/^L-(\d{1,2})$/.exec(n);if(e)return+e[1]>=1&&+e[1]<=30;let r=/^(\d{1,2})W$|^W(\d{1,2})$/.exec(n);if(r){let t=+(r[1]||r[2]);return t>=1&&t<=31}return!1}function w(n,e){if(/L$/.test(n))return v(n.slice(0,-1),e);let r=n.split("#");return r.length===2?v(r[0],e)&&/^[1-5]$/.test(r[1]):!1}function $n(n,e){return s(n,"/")?Yn(n,e):s(n,"-")?ln(n,e):v(n,e)}function Yn(n,e){let r=n.split("/");return r.length!==2||!I(r[1])||+r[1]<1?!1:r[0]==="*"||v(r[0],e)||ln(r[0],e,!0)}function ln(n,e,r){let t=n.split("-");return t.length!==2||!v(t[0],e)||!v(t[1],e)?!1:e.cyclic&&!r?!0:d(t[0],e.numbers)<=d(t[1],e.numbers)}function v(n,e){return n==="*"?!1:I(n)?+n>=e.min&&+n<=e.max:e.numbers?n.toUpperCase()in e.numbers:!1}function sn(n,e){throw new Error('`cronli5` was passed an invalid field value "'+n+'" for the '+e+" field.")}function mn(n){k.forEach(function(r){let t=h[r].aliases,i=t&&t[""+n[r]];i&&(n[r]=i)})}function fn(n){return k.forEach(function(r){let t=""+n[r];if(r==="date"&&C(t)||r==="weekday"&&w(t,h[r])){n[r]=t;return}n[r]=Bn(t,h[r])}),n}function Bn(n,e){let r=""+n;if(r==="*")return r;let t=r.split(",").map(function(o){return Zn(Kn(Gn(o,e),e),e)});return t.indexOf("*")!==-1?"*":F(t).sort(function(o,u){return cn(o,e)-cn(u,e)}).join(",")}function Gn(n,e){let r=n.split("/");if(!e.cyclic||r.length!==2||+r[1]!=1)return n;let t=r[0];return s(t,"-")?t:t==="*"||d(t,e.numbers)===e.min?"*":t+"-"+e.top}function Kn(n,e){let r=n.split("/");if(!e.cyclic||typeof e.top!="number"||r.length!==2||s(r[0],"-"))return n;let t=r[0];return(t==="*"?e.min:d(t,e.numbers))+ +r[1]<=e.top?n:t==="*"?""+e.min:t}function Zn(n,e){let r=n.split("/")[0];if(!s(r,"-"))return n;let t=r.split("-");return d(t[0],e.numbers)!==d(t[1],e.numbers)?n:t[0]}function cn(n,e){let r=n.split("/")[0].split("-")[0];return r==="*"?e.min:d(r,e.numbers)}function dn(n,e){let r=n instanceof Array;if(n===null||typeof n>"u"||n===""||r&&n.length===0)throw new Error("`cronli5` expects a non-empty cron pattern as the first argument.");if(r)return gn(n,e);if(typeof n=="object")return Xn(n);if(typeof n=="string")return _n(n,e);throw new Error("`cronli5` was passed an unexpected type.")}function gn(n,e){if(n.length>7)throw new Error("`cronli5` was passed a cron pattern with more than seven fields.");return!e.seconds&&n.length<(e.years?7:6)&&n.unshift("0"),{second:n[0]||"0",minute:n[1]||"*",hour:n[2]||"*",date:n[3]||"*",month:n[4]||"*",weekday:n[5]||"*",year:n[6]||"*"}}function Xn(n){if(!n.second&&!n.minute&&!n.hour)throw new Error("`cronli5` expects that any object being interpreted as a cron pattern have at least one of the following properties: `second`, `minute`, or `hour`");let e=typeof n.second<"u",r=typeof n.minute<"u",t=e?"*":"0",i=e||r?"*":"0";return{second:N(n.second,"0"),minute:N(n.minute,t),hour:N(n.hour,i),date:N(n.date,"*"),month:N(n.month,"*"),weekday:N(n.weekday,"*"),year:N(n.year,"*")}}function N(n,e){return typeof n>"u"?e:n}function _n(n,e){let r=ne(n).split(/\s+/);return gn(r,e)}function ne(n){let e=n.trim();if(e.charAt(0)!=="@")return n;let r=e.toLowerCase();if(Object.hasOwn(U,r))return U[r];throw new Error("`cronli5` does not recognize the macro `"+e+"`.")}function hn(n){return n!=="*"&&!s(n,",")&&!s(n,"-")&&!s(n,"/")}function q(n){return s(n,"-")&&!s(n,",")&&!s(n,"/")}function ee(n){return s(n,"/")&&!s(n,",")}function V(n){return n!=="*"&&!s(n,"-")&&!s(n,"/")}function J(n){return n!=="*"&&!q(n)&&!ee(n)}function R(n,e,r){let t=[],i=n;for(;i<=r;)t.push(i),i+=e;return t}function pn(n,e,r,t){let i=n.split("/"),o=+i[1];if(s(i[0],"-")){let m=i[0].split("-");return R(d(m[0],t),o,d(m[1],t))}let u=i[0]==="*"?e:d(i[0],t);return R(u,o,r)}function $(n,e,r){let t=[];return n.split(",").forEach(function(o){if(s(o,"/"))t.push(...pn(o,e,r));else if(s(o,"-")){let u=o.split("-");+u[0]<=+u[1]?t.push(...R(+u[0],1,+u[1])):(t.push(...R(+u[0],1,r)),t.push(...R(e,1,+u[1])))}else t.push(+o)}),F(t)}function re(n){return V(n)?n.split(",").map(Number):[0]}function te(n){if(n==="*")return[0,59];if(q(n)){let e=n.split("-");if(+e[0]<=+e[1])return[+e[0],+e[1]]}return null}function ie(n){return n==="*"?59:Math.max(...$(n,0,59))}function oe(n){if(hn(n)&&n!=="0")return+n}function ue(n,e){return n==="*"?"wildcard":e==="date"&&C(n)||e==="weekday"&&w(n,h.weekday)?"quartz":s(n,",")?"list":s(n,"/")?"step":s(n,"-")?"range":"single"}function se(n,e,r){return e==="wildcard"||e==="quartz"?null:n.split(",").map(function(i){if(s(i,"/")){let o=i.split("/");return{fires:pn(i,r.min,r.top,r.numbers),interval:+o[1],kind:"step",startToken:o[0]}}return s(i,"-")?{bounds:i.split("-"),kind:"range"}:{kind:"single",value:i}})}function yn(n){let e={},r={};k.forEach(function(u){e[u]=ue(n[u],u),r[u]=se(n[u],e[u],h[u])});let i={analyses:{clockSecond:oe(n.second),lastMinuteFire:ie(n.minute),minuteSpan:te(n.minute),segments:r},pattern:n,shapes:e};return{...i,plan:ae(i)}}function ae(n){let{analyses:e,pattern:r,shapes:t}=n;if(r.second!=="0"){let i=le(r,t,e);if(i)return i}return bn(r,t,e)||On(r,t,e)}function le(n,e,r){let t=ce(n,e);return t||(n.hour==="*"&&e.minute==="single"&&n.second!=="*"?{kind:"secondsWithinMinute",singleSecond:e.second==="single"}:e.second==="single"&&V(n.minute)&&J(n.hour)?null:{kind:"composeSeconds",rest:bn(n,e,r,!0)||On(n,e,r,!0)})}function ce(n,e){return n.minute!=="*"||n.hour!=="*"?null:n.second==="*"?{kind:"everySecond"}:e.second==="single"?{kind:"secondPastMinute"}:{kind:"standaloneSeconds"}}function bn(n,e,r,t=!1){if(e.minute==="step")return{hours:fe(n,e,r),kind:"minuteFrequency"};if(e.hour==="single"&&r.minuteSpan)return{hour:+n.hour,kind:"minuteSpanInHour",span:r.minuteSpan};let i=de(n,e);if(i)return i;let o=me(n,e);if(o)return o;if(n.hour==="*")return ge(n,e,t)}function Sn(n){let[e,r]=n.split("/"),t=e==="*"?0:+e;return e.indexOf("-")===-1&&24%+r===0&&t<+r}function me(n,e){return e.hour!=="step"?null:n.minute==="*"?Sn(n.hour)?{form:"wildcard",kind:"minuteSpanAcrossHourStep"}:{form:"wildcard",kind:"minutesAcrossHours",times:M(n.hour)}:e.minute==="range"?{form:"range",kind:"minuteSpanAcrossHourStep"}:null}function fe(n,e,r){if(e.hour==="list")return{kind:"during",times:M(n.hour)};if(e.hour==="range"){let t=n.hour.split("-");return{from:+t[0],kind:"window",last:r.lastMinuteFire,to:+t[1]}}return e.hour==="single"?{from:+n.hour,kind:"window",last:r.lastMinuteFire,to:+n.hour}:e.hour==="step"?Sn(n.hour)?{kind:"step"}:{kind:"during",times:M(n.hour)}:{kind:"none"}}function de(n,e){return J(n.hour)?n.minute==="*"?{form:"wildcard",kind:"minutesAcrossHours",times:M(n.hour)}:e.minute==="range"||e.minute==="list"&&s(n.minute,"-")&&!s(n.minute,"/")?{form:e.minute==="range"?"range":"list",kind:"minutesAcrossHours",times:M(n.hour)}:null:null}function ge(n,e,r){if(e.minute==="range")return{kind:"rangeOfMinutes"};if(e.minute==="list")return{kind:"multipleMinutes"};if(n.minute==="*")return{kind:"everyMinute"};if(n.minute!=="0"||r)return{kind:"singleMinute"}}function On(n,e,r,t=!1){let i=t&&n.minute==="0";return e.hour==="range"&&!i?he(n,e,r):e.hour==="step"&&n.minute==="0"&&!t?{kind:"hourStep"}:n.hour==="*"&&!i?{kind:"everyHour"}:pe(n,r,i)}function he(n,e,r){let t=n.hour.split("-"),i="lead";return n.minute==="*"?i="wildcard":e.minute==="range"&&(i="range"),{boundMinute:e.minute==="range"||e.minute==="list"?null:r.lastMinuteFire,from:+t[0],kind:"hourRange",last:r.lastMinuteFire,minuteForm:i,to:+t[1]}}function pe(n,e,r=!1){let t=$(n.hour,0,23),i=re(n.minute);if(!r&&t.length*i.length>Q)return{fold:i.length===1,kind:"compactClockTimes",minute:i[0]};let o=[];return t.forEach(function(m){i.forEach(function(D){o.push({hour:m,minute:D,second:e.clockSecond})})}),{kind:"clockTimes",times:o}}function M(n){let e=$(n,0,23);return e.length<=Q?{fires:e,kind:"fires"}:{kind:"segments"}}function kn(n,e){let r=dn(n,e);return mn(r),an(r),fn(r)}function Y(n){return n=""+n,n.length<2?"0"+n:n}function Nn(n,e,r){return r.short?n:e[n]||n}function B(n,{sep:e,pad:r,lean:t}){let i=r?Y(n.hour):""+n.hour;return t&&!n.minute&&!n.second?i:i+e+Y(n.minute)+(n.second?e+Y(n.second):"")}var G={gb:{am:"am",closeUp:!0,dayFirst:!0,midday:"midday",midnight:"midnight",ordinals:!1,pm:"pm",sep:".",serialComma:!1,through:" to "},us:{am:"a.m.",closeUp:!1,dayFirst:!1,midday:"noon",midnight:"midnight",ordinals:!1,pm:"p.m.",sep:":",serialComma:!0,through:" through "},house:{am:"AM",closeUp:!1,dayFirst:!1,midday:"noon",midnight:"midnight",ordinals:!0,pm:"PM",sep:":",serialComma:!0,through:" - "}};function zn(n){return typeof n=="object"&&n!==null?{...G.us,...n}:G[n==="uk"?"gb":n]||G.us}var ye=["zero","one","two","three","four","five","six","seven","eight","nine","ten"],K=["th","st","nd","rd"],g=[null,["January","Jan"],["February","Feb"],["March","Mar"],["April","Apr"],["May","May"],["June","Jun"],["July","Jul"],["August","Aug"],["September","Sep"],["October","Oct"],["November","Nov"],["December","Dec"]],y=[["Sunday","Sun"],["Monday","Mon"],["Tuesday","Tue"],["Wednesday","Wed"],["Thursday","Thu"],["Friday","Fri"],["Saturday","Sat"]],be={JAN:g[1],FEB:g[2],MAR:g[3],APR:g[4],MAY:g[5],JUN:g[6],JUL:g[7],AUG:g[8],SEP:g[9],OCT:g[10],NOV:g[11],DEC:g[12]},Se={SUN:y[0],MON:y[1],TUE:y[2],WED:y[3],THU:y[4],FRI:y[5],SAT:y[6]},Oe=[null,"first","second","third","fourth","fifth"];function ke(n){return n=n||{},{ampm:typeof n.ampm=="boolean"?n.ampm:!0,lenient:!!n.lenient,seconds:!!n.seconds,short:!!n.short,style:zn(n.dialect),years:!!n.years}}function Ne(n,e){return _e(Pn(n,n.plan,e),n,e)}function Pn(n,e,r){let t=Ve[e.kind];return t(n,e,r)}function ze(n,e,r){return"every second"+a(n,r)}function Pe(n,e,r){return H(n,r)+a(n,r)}function ve(n,e,r){let t=n.pattern.second;return c(t,r)+" "+P(t,"second")+" past the minute, every minute"+a(n,r)}function Ce(n,e,r){let t=n.pattern.minute,i=c(t,r),o=P(t,"minute");if(e.singleSecond){let u=n.pattern.second;return i+" "+o+" and "+c(u,r)+" "+P(u,"second")+" past the hour, every hour"+a(n,r)}return H(n,r)+", "+i+" "+o+" past the hour, every hour"+a(n,r)}function we(n,e,r){return H(n,r)+", "+Pn(n,e.rest,r)}function H(n,e){let r=n.pattern.second,t=n.shapes.second;if(r==="*")return"every second";if(t==="step")return Rn(n.analyses.segments.second[0],"second","minute",e);if(t==="range"){let i=r.split("-"),o=A(i,e);return"every second from "+o(i[0])+O(e)+o(i[1])+" past the minute"}return t==="single"?"at "+c(r,e)+" "+P(r,"second")+" past the minute":b(T(n.analyses.segments.second,e),"second","minute",e)}function Re(n,e,r){return"every minute"+a(n,r)}function Me(n,e,r){let t=n.pattern.minute;return c(t,r)+" "+P(t,"minute")+" past the hour, every hour"+a(n,r)}function xe(n,e,r){return L(n.pattern.minute,r)+a(n,r)}function Te(n,e,r){return b(T(n.analyses.segments.minute,r),"minute","hour",r)+a(n,r)}function Fe(n,e,r){let t=Rn(n.analyses.segments.minute[0],"minute","hour",r);return e.hours.kind==="during"?t+=" during the "+_(n,e.hours.times,!1,r)+" hours":e.hours.kind==="window"?t+=" "+wn(e.hours,r):e.hours.kind==="step"&&(t+=" "+vn(n.analyses.segments.hour[0],r)),t+a(n,r)}function Ie(n,e,r){return"every minute from "+l({hour:e.hour,minute:e.span[0]},r)+O(r)+l({hour:e.hour,minute:e.span[1]},r)+a(n,r)}function He(n,e,r){if(e.form==="wildcard")return"every minute during the "+_(n,e.times,!1,r)+" hours"+a(n,r);let t=_(n,e.times,!0,r);return(e.form==="range"?L(n.pattern.minute,r):b(T(n.analyses.segments.minute,r),"minute","hour",r))+", at "+t+a(n,r)}var Le={2:"other",3:"third",4:"fourth",6:"sixth",8:"eighth",12:"twelfth"};function vn(n,e){let r="during every "+Le[n.interval]+" hour",t=n.startToken==="*"?0:+n.startToken;return t===0?r:r+" starting at "+l({hour:t,minute:0},e)}function Ae(n,e,r){let t=n.analyses.segments.hour[0];return e.form==="wildcard"?"every minute "+vn(t,r)+a(n,r):L(n.pattern.minute,r)+", "+Mn(t,r)+a(n,r)}function L(n,e){let r=n.split("-"),t=A(r,e);return"every minute from "+t(r[0])+O(e)+t(r[1])+" past the hour"}function Ee(n,e,r){return"every hour"+a(n,r)}function We(n,e,r){let t=wn(je(e),r);return e.minuteForm==="wildcard"?"every minute "+t+a(n,r):e.minuteForm==="range"?L(n.pattern.minute,r)+", "+t+a(n,r):Cn(n,r)+" "+t+a(n,r)}function Cn(n,e){return n.pattern.minute==="0"?"every hour":b(T(n.analyses.segments.minute,e),"minute","hour",e)}function De(n,e,r){return Mn(n.analyses.segments.hour[0],r)+a(n,r)}function je(n){return{from:n.from,last:n.boundMinute??0,to:n.to}}function wn(n,e){return"from "+l({hour:n.from,minute:0},e)+O(e)+l({hour:n.to,minute:n.last},e)}function Ue(n,e,r){let t=tn(e.times),i=e.times.map(function(u){return l({hour:u.hour,minute:u.minute,second:u.second,plain:t},r)});return xn(n,r)+"at "+S(i,r)}function Qe(n,e,r){if(e.fold){if(n.analyses.segments.hour.some(function(m){return m.kind==="range"})&&!n.analyses.clockSecond)return qe(n,e,r)+a(n,r);let o={minute:e.minute,second:n.analyses.clockSecond};return xn(n,r)+"at "+nn(n,o,!0,r)}let t=b(T(n.analyses.segments.minute,r),"minute","hour",r)+", at "+nn(n,{minute:0,second:null},!0,r)+a(n,r);return n.analyses.clockSecond?H(n,r)+", "+t:t}function qe(n,e,r){let t=e.minute,i=[],o=[];n.analyses.segments.hour.forEach(function(f){f.kind==="range"?i.push("from "+l({hour:f.bounds[0],minute:0},r)+O(r)+l({hour:f.bounds[1],minute:t},r)):f.kind==="step"?o.push(...f.fires):o.push(+f.value)});let u=Cn(n,r)+" "+S(i,r);return o.length&&(u+=" and at "+S(o.map(function(f){return l({hour:f,minute:t},r)}),r)),u}var Ve={clockTimes:Ue,compactClockTimes:Qe,composeSeconds:we,everyHour:Ee,everyMinute:Re,everySecond:ze,hourRange:We,hourStep:De,minuteFrequency:Fe,minuteSpanAcrossHourStep:Ae,minuteSpanInHour:Ie,minutesAcrossHours:He,multipleMinutes:Te,rangeOfMinutes:xe,secondPastMinute:ve,secondsWithinMinute:Ce,singleMinute:Me,standaloneSeconds:Pe};function Rn(n,e,r,t){if(n.startToken.indexOf("-")!==-1)return b(Z(n.fires,t),e,r,t);let i=n.startToken==="*"?0:+n.startToken,o=n.interval;return i!==0?n.fires.length<=3?b(Z(n.fires,t),e,r,t):"every "+c(o,t)+" "+e+"s from "+c(i,t)+" "+P(i,e)+" past the "+r:60%o===0?"every "+c(o,t)+" "+e+"s":n.fires.length<=2?b(Z(n.fires,t),e,r,t):"every "+c(o,t)+" "+e+"s past the "+r}function Mn(n,e){if(n.startToken.indexOf("-")!==-1)return"at "+X(n.fires,e);let r=n.startToken==="*"?0:+n.startToken,t=n.interval;return r===0&&24%t===0?"every "+c(t,e)+" hours":n.fires.length<=3?"at "+X(n.fires,e):r===0?"every "+c(t,e)+" hours from midnight":"every "+c(t,e)+" hours from "+l({hour:r,minute:0},e)}function A(n,e){let r=n.some(function(i){return+i>10});return function(i){return r?""+i:c(i,e)}}function Z(n,e){return n.map(A(n,e))}function T(n,e){let r=n.flatMap(function(o){return o.kind==="range"?o.bounds:o.kind==="step"?o.fires:[o.value]}),t=A(r,e);return n.flatMap(function(o){return o.kind==="range"?[t(o.bounds[0])+O(e)+t(o.bounds[1])]:o.kind==="step"?o.fires.map(t):[t(o.value)]})}function b(n,e,r,t){return"at "+S(n,t)+" "+e+"s past the "+r}function Je(n,e,r){return(+n==0||+n==12)&&+e==0&&!(typeof r=="number"&&r>0)}function tn(n){let e=n.filter(function(t){return Je(t.hour,t.minute,t.second)});return e.length>0&&e.length<n.length}function X(n,e){let r=tn(n.map(function(o){return{hour:o,minute:0}})),t=n.map(function(o){return l({hour:o,minute:0,plain:r},e)});return S(t,e)}function _(n,e,r,t){return e.kind==="fires"?X(e.fires,t):nn(n,{minute:0,second:null},r,t)}function $e(n){return n.kind==="range"?n.bounds:n.kind==="step"?n.fires:[n.value]}function nn(n,e,r,t){let{minute:i,second:o}=e,u=n.analyses.segments.hour,m=tn(u.flatMap(function(p){return $e(p).map(function(j){return{hour:+j,minute:i,second:o}})})),f=[];return u.forEach(function(p){p.kind==="step"?f.push(...p.fires.map(function(j){return l({hour:j,minute:i,second:o,plain:m},t)})):p.kind==="range"?f.push(l({hour:p.bounds[0],minute:i,second:o,plain:m},t)+O(t)+l({hour:p.bounds[1],minute:i,second:o,plain:m},t)):f.push(l({hour:p.value,minute:i,second:o,plain:m},t))}),S(Ye(f,u,r),t)}function Ye(n,e,r){let t=e.some(function(o){return o.kind==="range"});return!r||!t?n:n.map(function(o,u){return u===0?o:"at "+o})}function S(n,e){if(n.length<=1)return n.join("");if(n.length===2)return n[0]+" and "+n[1];let r=e.style.serialComma?", and ":" and ";return n.slice(0,-1).join(", ")+r+n[n.length-1]}var Be={all:"",month:"in ",stepDate:"on ",weekday:"on "},Ge={all:"every day",month:"every day in ",stepDate:"",weekday:"every "};function a(n,e){let r=Tn(n,Be,e);return r&&" "+r}function xn(n,e){return Tn(n,Ge,e)+" "}function Tn(n,e,r){let t=n.pattern;return t.date!=="*"&&t.weekday!=="*"?Ze(n,r):t.date!=="*"?Ke(n,e,r):t.weekday!=="*"?(Hn(t.weekday,r)||e.weekday+Wn(n,r))+z(n,r):t.month!=="*"?e.month+E(n,r):e.all}function Ke(n,e,r){let t=n.pattern,i=In(t.date,r);return i?i+z(n,r):on(t.date)?e.stepDate+An(t.date)+z(n,r):t.month!=="*"&&!Fn(n)?"on the "+en(n,r)+z(n,r):t.month!=="*"?"on "+Ln(n,r):"on the "+en(n,r)}function Fn(n){return!En(n.pattern.month)&&n.analyses.segments.month.every(function(r){return r.kind!=="range"})}function Ze(n,e){let r=n.pattern,t=Hn(r.weekday,e)||"on "+Wn(n,e),i=In(r.date,e);return i?i+z(n,e)+" or "+t:on(r.date)?An(r.date)+z(n,e)+" or "+t:r.month!=="*"&&Fn(n)?"on "+Ln(n,e)+" or "+t+" in "+E(n,e):"on the "+en(n,e)+" or "+t+z(n,e)}function In(n,e){if(n==="L")return"on the last day of the month";if(n==="LW"||n==="WL")return"on the last weekday of the month";let r=/^L-(\d{1,2})$/.exec(n);if(r)return c(+r[1],e)+" "+P(r[1],"day")+" before the last day of the month";let t=/^(\d{1,2})W$|^W(\d{1,2})$/.exec(n);if(t)return"on the weekday nearest the "+x(t[1]||t[2])}function Hn(n,e){let r=n.split("#");if(r.length===2)return"on the "+Oe[+r[1]]+" "+rn(r[0],e)+" of the month";if(/L$/.test(n))return"on the last "+rn(n.slice(0,-1),e)+" of the month"}function Ln(n,e){let r=E(n,e),t=W(n.analyses.segments.date,e.style.ordinals?x:Xe,e);return e.style.dayFirst?t+" "+r:r+" "+t}function Xe(n){return""+n}function z(n,e){return n.pattern.month==="*"?"":" in "+E(n,e)}function An(n){let e=n.split("/"),r=+e[1],t=e[0],o=(r===2?"every other":"every "+x(r))+" day of the month";return t!=="*"&&t!=="1"&&(o+=" from the "+x(t)),o}function en(n,e){return W(n.analyses.segments.date,x,e)}function E(n,e){let r=En(n.pattern.month);return r||W(n.analyses.segments.month,function(i){return tr(i,e)},e)}function En(n){if(!on(n))return null;let[e,r]=n.split("/");return+r!=2?null:e==="*"||e==="1"?"every odd-numbered month":e==="2"?"every even-numbered month":null}function Wn(n,e){return W(n.analyses.segments.weekday,function(t){return rn(t,e)},e)}function W(n,e,r){let t=[];return n.forEach(function(o){o.kind==="step"?t.push(...o.fires.map(e)):o.kind==="range"?t.push(o.bounds.map(e).join(O(r))):t.push(e(o.value))}),S(t,r)}function on(n){return n.indexOf("/")!==-1&&n.indexOf("-")===-1&&n.indexOf(",")===-1}function _e(n,e,r){let t=e.pattern.year;if(t==="*")return n;if(t.indexOf("/")!==-1)return n+" "+er(t,r);let i=nr(t,r);if(t.indexOf("-")===-1&&t.indexOf(",")===-1&&e.pattern.date!=="*"&&n.indexOf(" at ")!==-1){let o=r.style.dayFirst?" ":", ";return n.replace(" at ",o+i+" at ")}return n+" in "+i}function nr(n,e){return n.indexOf(",")!==-1?S(n.split(","),e):n}function er(n,e){let r=n.split("/"),t=+r[1],i=r[0];if(t<=1)return"every year";let o="every "+c(t,e)+" years";return i!=="*"&&i!=="0"&&(o+=" from "+i),o}function l(n,e){let{hour:r,minute:t,plain:i}=n,o=typeof n.second=="number"&&n.second>0?n.second:0;return e.ampm?rr({hour:r,minute:t,second:o,plain:i},e):B({hour:r,minute:t,second:o},{pad:!0,sep:e.style.sep})}function rr(n,e){let{hour:r,minute:t,second:i,plain:o}=n,u=e.style;if(!o&&+t==0&&!i){if(+r==0)return u.midnight;if(+r==12)return u.midday}return B({hour:r%12||12,minute:t,second:i},{lean:!0,sep:u.sep})+(u.closeUp?"":" ")+(r<12?u.am:u.pm)}function c(n,e){return Nn(n,ye,e)}function P(n,e){return+n==1?e:e+"s"}function O(n){return n.short?"-":n.style.through}function x(n){let e=Math.abs(n),r=K[e];return r||(e=(e%100-20)%10,r=K[e]||K[0]),n+r}function tr(n,e){let r=g[n]||be[n];return r&&r[e.short?1:0]}function rn(n,e){let r=n===7||n==="7"?0:n,t=y[r]||Se[r];return t&&t[e.short?1:0]}var ir={describe:Ne,fallback:"an unrecognizable cron pattern",options:ke,reboot:"at system startup",sentence:n=>"Runs "+n+"."},Dn=ir;/**
2
2
  * @license MIT, Copyright (c) 2026 Andrew Brož
3
- */function tr(n,e){let r=e&&e.lang||Wn,t=r.options(e);if(!t.lenient)return Dn(jn(n,r,t),r,e);try{return Dn(jn(n,r,t),r,e)}catch{return r.fallback}}function Dn(n,e,r){return r&&r.sentence?e.sentence(n):n}function jn(n,e,r){if(typeof n=="string"&&n.trim().toLowerCase()==="@reboot")return e.reboot;let t=pn(On(n,r)),i=e.strategy?e.strategy(t,t.plan):t.plan;return e.describe({...t,plan:i},r)}var on=tr;typeof globalThis<"u"&&Object.assign(globalThis,{cronli5:on});var Dr=on;})();
3
+ */function or(n,e){let r=e&&e.lang||Dn,t=r.options(e);if(!t.lenient)return jn(Un(n,r,t),r,e);try{return jn(Un(n,r,t),r,e)}catch{return r.fallback}}function jn(n,e,r){return r&&r.sentence?e.sentence(n):n}function Un(n,e,r){if(typeof n=="string"&&n.trim().toLowerCase()==="@reboot")return e.reboot;let t=yn(kn(n,r)),i=e.strategy?e.strategy(t,t.plan):t.plan;return e.describe({...t,plan:i},r)}var un=or;typeof globalThis<"u"&&Object.assign(globalThis,{cronli5:un});var Ur=un;})();
package/dist/cronli5.cjs CHANGED
@@ -532,7 +532,7 @@ function planSeconds(pattern, shapes, analyses) {
532
532
  }
533
533
  return {
534
534
  kind: "composeSeconds",
535
- rest: planMinutes(pattern, shapes, analyses) || planHours(pattern, shapes, analyses)
535
+ rest: planMinutes(pattern, shapes, analyses, true) || planHours(pattern, shapes, analyses, true)
536
536
  };
537
537
  }
538
538
  function planStandaloneSeconds(pattern, shapes) {
@@ -547,7 +547,7 @@ function planStandaloneSeconds(pattern, shapes) {
547
547
  }
548
548
  return { kind: "standaloneSeconds" };
549
549
  }
550
- function planMinutes(pattern, shapes, analyses) {
550
+ function planMinutes(pattern, shapes, analyses, subMinuteSecond = false) {
551
551
  if (shapes.minute === "step") {
552
552
  return {
553
553
  hours: planFrequencyHours(pattern, shapes, analyses),
@@ -570,7 +570,7 @@ function planMinutes(pattern, shapes, analyses) {
570
570
  return underStep;
571
571
  }
572
572
  if (pattern.hour === "*") {
573
- return planMinutesUnderOpenHour(pattern, shapes);
573
+ return planMinutesUnderOpenHour(pattern, shapes, subMinuteSecond);
574
574
  }
575
575
  }
576
576
  function cleanHourStride(hourField) {
@@ -640,7 +640,7 @@ function planMinutesAcrossHours(pattern, shapes) {
640
640
  }
641
641
  return null;
642
642
  }
643
- function planMinutesUnderOpenHour(pattern, shapes) {
643
+ function planMinutesUnderOpenHour(pattern, shapes, subMinuteSecond) {
644
644
  if (shapes.minute === "range") {
645
645
  return { kind: "rangeOfMinutes" };
646
646
  }
@@ -650,39 +650,45 @@ function planMinutesUnderOpenHour(pattern, shapes) {
650
650
  if (pattern.minute === "*") {
651
651
  return { kind: "everyMinute" };
652
652
  }
653
- if (pattern.minute !== "0") {
653
+ if (pattern.minute !== "0" || subMinuteSecond) {
654
654
  return { kind: "singleMinute" };
655
655
  }
656
656
  }
657
- function planHours(pattern, shapes, analyses) {
658
- if (shapes.hour === "range") {
659
- const bounds = pattern.hour.split("-");
660
- let minuteForm = "lead";
661
- if (pattern.minute === "*") {
662
- minuteForm = "wildcard";
663
- } else if (shapes.minute === "range") {
664
- minuteForm = "range";
665
- }
666
- return {
667
- from: +bounds[0],
668
- kind: "hourRange",
669
- last: analyses.lastMinuteFire,
670
- minuteForm,
671
- to: +bounds[1]
672
- };
657
+ function planHours(pattern, shapes, analyses, subMinuteSecond = false) {
658
+ const absorbsMinuteZero = subMinuteSecond && pattern.minute === "0";
659
+ if (shapes.hour === "range" && !absorbsMinuteZero) {
660
+ return planHourRange(pattern, shapes, analyses);
673
661
  }
674
- if (shapes.hour === "step" && pattern.minute === "0") {
662
+ if (shapes.hour === "step" && pattern.minute === "0" && !subMinuteSecond) {
675
663
  return { kind: "hourStep" };
676
664
  }
677
- if (pattern.hour === "*") {
665
+ if (pattern.hour === "*" && !absorbsMinuteZero) {
678
666
  return { kind: "everyHour" };
679
667
  }
680
- return planClockTimes(pattern, analyses);
668
+ return planClockTimes(pattern, analyses, absorbsMinuteZero);
681
669
  }
682
- function planClockTimes(pattern, analyses) {
670
+ function planHourRange(pattern, shapes, analyses) {
671
+ const bounds = pattern.hour.split("-");
672
+ let minuteForm = "lead";
673
+ if (pattern.minute === "*") {
674
+ minuteForm = "wildcard";
675
+ } else if (shapes.minute === "range") {
676
+ minuteForm = "range";
677
+ }
678
+ const multiValued = shapes.minute === "range" || shapes.minute === "list";
679
+ return {
680
+ boundMinute: multiValued ? null : analyses.lastMinuteFire,
681
+ from: +bounds[0],
682
+ kind: "hourRange",
683
+ last: analyses.lastMinuteFire,
684
+ minuteForm,
685
+ to: +bounds[1]
686
+ };
687
+ }
688
+ function planClockTimes(pattern, analyses, enumerate = false) {
683
689
  const hours = enumerateFires(pattern.hour, 0, 23);
684
690
  const minutes = enumerateValues(pattern.minute);
685
- if (hours.length * minutes.length > maxClockTimes) {
691
+ if (!enumerate && hours.length * minutes.length > maxClockTimes) {
686
692
  return {
687
693
  fold: minutes.length === 1,
688
694
  kind: "compactClockTimes",
@@ -996,7 +1002,7 @@ function renderEveryHour(ir, plan, opts) {
996
1002
  return "every hour" + trailingQualifier(ir, opts);
997
1003
  }
998
1004
  function renderHourRange(ir, plan, opts) {
999
- const window = hourWindow(plan, opts);
1005
+ const window = hourWindow(boundedWindow(plan), opts);
1000
1006
  if (plan.minuteForm === "wildcard") {
1001
1007
  return "every minute " + window + trailingQualifier(ir, opts);
1002
1008
  }
@@ -1019,6 +1025,9 @@ function rangeMinuteLead(ir, opts) {
1019
1025
  function renderHourStep(ir, plan, opts) {
1020
1026
  return stepHours(ir.analyses.segments.hour[0], opts) + trailingQualifier(ir, opts);
1021
1027
  }
1028
+ function boundedWindow(plan) {
1029
+ return { from: plan.from, last: plan.boundMinute ?? 0, to: plan.to };
1030
+ }
1022
1031
  function hourWindow(window, opts) {
1023
1032
  return "from " + getTime({ hour: window.from, minute: 0 }, opts) + through(opts) + getTime({ hour: window.to, minute: window.last }, opts);
1024
1033
  }
package/dist/cronli5.js CHANGED
@@ -506,7 +506,7 @@ function planSeconds(pattern, shapes, analyses) {
506
506
  }
507
507
  return {
508
508
  kind: "composeSeconds",
509
- rest: planMinutes(pattern, shapes, analyses) || planHours(pattern, shapes, analyses)
509
+ rest: planMinutes(pattern, shapes, analyses, true) || planHours(pattern, shapes, analyses, true)
510
510
  };
511
511
  }
512
512
  function planStandaloneSeconds(pattern, shapes) {
@@ -521,7 +521,7 @@ function planStandaloneSeconds(pattern, shapes) {
521
521
  }
522
522
  return { kind: "standaloneSeconds" };
523
523
  }
524
- function planMinutes(pattern, shapes, analyses) {
524
+ function planMinutes(pattern, shapes, analyses, subMinuteSecond = false) {
525
525
  if (shapes.minute === "step") {
526
526
  return {
527
527
  hours: planFrequencyHours(pattern, shapes, analyses),
@@ -544,7 +544,7 @@ function planMinutes(pattern, shapes, analyses) {
544
544
  return underStep;
545
545
  }
546
546
  if (pattern.hour === "*") {
547
- return planMinutesUnderOpenHour(pattern, shapes);
547
+ return planMinutesUnderOpenHour(pattern, shapes, subMinuteSecond);
548
548
  }
549
549
  }
550
550
  function cleanHourStride(hourField) {
@@ -614,7 +614,7 @@ function planMinutesAcrossHours(pattern, shapes) {
614
614
  }
615
615
  return null;
616
616
  }
617
- function planMinutesUnderOpenHour(pattern, shapes) {
617
+ function planMinutesUnderOpenHour(pattern, shapes, subMinuteSecond) {
618
618
  if (shapes.minute === "range") {
619
619
  return { kind: "rangeOfMinutes" };
620
620
  }
@@ -624,39 +624,45 @@ function planMinutesUnderOpenHour(pattern, shapes) {
624
624
  if (pattern.minute === "*") {
625
625
  return { kind: "everyMinute" };
626
626
  }
627
- if (pattern.minute !== "0") {
627
+ if (pattern.minute !== "0" || subMinuteSecond) {
628
628
  return { kind: "singleMinute" };
629
629
  }
630
630
  }
631
- function planHours(pattern, shapes, analyses) {
632
- if (shapes.hour === "range") {
633
- const bounds = pattern.hour.split("-");
634
- let minuteForm = "lead";
635
- if (pattern.minute === "*") {
636
- minuteForm = "wildcard";
637
- } else if (shapes.minute === "range") {
638
- minuteForm = "range";
639
- }
640
- return {
641
- from: +bounds[0],
642
- kind: "hourRange",
643
- last: analyses.lastMinuteFire,
644
- minuteForm,
645
- to: +bounds[1]
646
- };
631
+ function planHours(pattern, shapes, analyses, subMinuteSecond = false) {
632
+ const absorbsMinuteZero = subMinuteSecond && pattern.minute === "0";
633
+ if (shapes.hour === "range" && !absorbsMinuteZero) {
634
+ return planHourRange(pattern, shapes, analyses);
647
635
  }
648
- if (shapes.hour === "step" && pattern.minute === "0") {
636
+ if (shapes.hour === "step" && pattern.minute === "0" && !subMinuteSecond) {
649
637
  return { kind: "hourStep" };
650
638
  }
651
- if (pattern.hour === "*") {
639
+ if (pattern.hour === "*" && !absorbsMinuteZero) {
652
640
  return { kind: "everyHour" };
653
641
  }
654
- return planClockTimes(pattern, analyses);
642
+ return planClockTimes(pattern, analyses, absorbsMinuteZero);
655
643
  }
656
- function planClockTimes(pattern, analyses) {
644
+ function planHourRange(pattern, shapes, analyses) {
645
+ const bounds = pattern.hour.split("-");
646
+ let minuteForm = "lead";
647
+ if (pattern.minute === "*") {
648
+ minuteForm = "wildcard";
649
+ } else if (shapes.minute === "range") {
650
+ minuteForm = "range";
651
+ }
652
+ const multiValued = shapes.minute === "range" || shapes.minute === "list";
653
+ return {
654
+ boundMinute: multiValued ? null : analyses.lastMinuteFire,
655
+ from: +bounds[0],
656
+ kind: "hourRange",
657
+ last: analyses.lastMinuteFire,
658
+ minuteForm,
659
+ to: +bounds[1]
660
+ };
661
+ }
662
+ function planClockTimes(pattern, analyses, enumerate = false) {
657
663
  const hours = enumerateFires(pattern.hour, 0, 23);
658
664
  const minutes = enumerateValues(pattern.minute);
659
- if (hours.length * minutes.length > maxClockTimes) {
665
+ if (!enumerate && hours.length * minutes.length > maxClockTimes) {
660
666
  return {
661
667
  fold: minutes.length === 1,
662
668
  kind: "compactClockTimes",
@@ -970,7 +976,7 @@ function renderEveryHour(ir, plan, opts) {
970
976
  return "every hour" + trailingQualifier(ir, opts);
971
977
  }
972
978
  function renderHourRange(ir, plan, opts) {
973
- const window = hourWindow(plan, opts);
979
+ const window = hourWindow(boundedWindow(plan), opts);
974
980
  if (plan.minuteForm === "wildcard") {
975
981
  return "every minute " + window + trailingQualifier(ir, opts);
976
982
  }
@@ -993,6 +999,9 @@ function rangeMinuteLead(ir, opts) {
993
999
  function renderHourStep(ir, plan, opts) {
994
1000
  return stepHours(ir.analyses.segments.hour[0], opts) + trailingQualifier(ir, opts);
995
1001
  }
1002
+ function boundedWindow(plan) {
1003
+ return { from: plan.from, last: plan.boundMinute ?? 0, to: plan.to };
1004
+ }
996
1005
  function hourWindow(window, opts) {
997
1006
  return "from " + getTime({ hour: window.from, minute: 0 }, opts) + through(opts) + getTime({ hour: window.to, minute: window.last }, opts);
998
1007
  }
package/dist/lang/de.cjs CHANGED
@@ -433,7 +433,12 @@ function hourStepPhrase(ir) {
433
433
  return cleanStep(segment, 24) ? everyN(segment.interval, UNITS.hour) : atHours(segment.fires);
434
434
  }
435
435
  function renderHourRange(ir, plan, opts) {
436
- const window = hourWindow(plan.from, plan.to, plan.last, opts.style.sep);
436
+ const window = hourWindow(
437
+ plan.from,
438
+ plan.to,
439
+ plan.boundMinute ?? 0,
440
+ opts.style.sep
441
+ );
437
442
  if (plan.minuteForm === "wildcard") {
438
443
  return "jede Minute " + window;
439
444
  }
package/dist/lang/de.js CHANGED
@@ -407,7 +407,12 @@ function hourStepPhrase(ir) {
407
407
  return cleanStep(segment, 24) ? everyN(segment.interval, UNITS.hour) : atHours(segment.fires);
408
408
  }
409
409
  function renderHourRange(ir, plan, opts) {
410
- const window = hourWindow(plan.from, plan.to, plan.last, opts.style.sep);
410
+ const window = hourWindow(
411
+ plan.from,
412
+ plan.to,
413
+ plan.boundMinute ?? 0,
414
+ opts.style.sep
415
+ );
411
416
  if (plan.minuteForm === "wildcard") {
412
417
  return "jede Minute " + window;
413
418
  }
package/dist/lang/en.cjs CHANGED
@@ -307,7 +307,7 @@ function renderEveryHour(ir, plan, opts) {
307
307
  return "every hour" + trailingQualifier(ir, opts);
308
308
  }
309
309
  function renderHourRange(ir, plan, opts) {
310
- const window = hourWindow(plan, opts);
310
+ const window = hourWindow(boundedWindow(plan), opts);
311
311
  if (plan.minuteForm === "wildcard") {
312
312
  return "every minute " + window + trailingQualifier(ir, opts);
313
313
  }
@@ -330,6 +330,9 @@ function rangeMinuteLead(ir, opts) {
330
330
  function renderHourStep(ir, plan, opts) {
331
331
  return stepHours(ir.analyses.segments.hour[0], opts) + trailingQualifier(ir, opts);
332
332
  }
333
+ function boundedWindow(plan) {
334
+ return { from: plan.from, last: plan.boundMinute ?? 0, to: plan.to };
335
+ }
333
336
  function hourWindow(window, opts) {
334
337
  return "from " + getTime({ hour: window.from, minute: 0 }, opts) + through(opts) + getTime({ hour: window.to, minute: window.last }, opts);
335
338
  }
package/dist/lang/en.js CHANGED
@@ -281,7 +281,7 @@ function renderEveryHour(ir, plan, opts) {
281
281
  return "every hour" + trailingQualifier(ir, opts);
282
282
  }
283
283
  function renderHourRange(ir, plan, opts) {
284
- const window = hourWindow(plan, opts);
284
+ const window = hourWindow(boundedWindow(plan), opts);
285
285
  if (plan.minuteForm === "wildcard") {
286
286
  return "every minute " + window + trailingQualifier(ir, opts);
287
287
  }
@@ -304,6 +304,9 @@ function rangeMinuteLead(ir, opts) {
304
304
  function renderHourStep(ir, plan, opts) {
305
305
  return stepHours(ir.analyses.segments.hour[0], opts) + trailingQualifier(ir, opts);
306
306
  }
307
+ function boundedWindow(plan) {
308
+ return { from: plan.from, last: plan.boundMinute ?? 0, to: plan.to };
309
+ }
307
310
  function hourWindow(window, opts) {
308
311
  return "from " + getTime({ hour: window.from, minute: 0 }, opts) + through(opts) + getTime({ hour: window.to, minute: window.last }, opts);
309
312
  }
package/dist/lang/es.cjs CHANGED
@@ -331,7 +331,7 @@ function renderEveryHour(ir, plan, opts) {
331
331
  return "cada hora" + trailingQualifier(ir, opts);
332
332
  }
333
333
  function renderHourRange(ir, plan, opts) {
334
- const window = hourWindow(plan, opts);
334
+ const window = hourWindow(boundedWindow(plan), opts);
335
335
  if (plan.minuteForm === "wildcard") {
336
336
  return "cada minuto " + window + trailingQualifier(ir, opts);
337
337
  }
@@ -347,6 +347,9 @@ function renderHourRange(ir, plan, opts) {
347
347
  function renderHourStep(ir, plan, opts) {
348
348
  return stepHours(stepSegment(ir.analyses.segments.hour), opts) + trailingQualifier(ir, opts);
349
349
  }
350
+ function boundedWindow(plan) {
351
+ return { from: plan.from, last: plan.boundMinute ?? 0, to: plan.to };
352
+ }
350
353
  function hourWindow(window, opts) {
351
354
  return timeRange(
352
355
  { hour: window.from, minute: 0 },
package/dist/lang/es.js CHANGED
@@ -305,7 +305,7 @@ function renderEveryHour(ir, plan, opts) {
305
305
  return "cada hora" + trailingQualifier(ir, opts);
306
306
  }
307
307
  function renderHourRange(ir, plan, opts) {
308
- const window = hourWindow(plan, opts);
308
+ const window = hourWindow(boundedWindow(plan), opts);
309
309
  if (plan.minuteForm === "wildcard") {
310
310
  return "cada minuto " + window + trailingQualifier(ir, opts);
311
311
  }
@@ -321,6 +321,9 @@ function renderHourRange(ir, plan, opts) {
321
321
  function renderHourStep(ir, plan, opts) {
322
322
  return stepHours(stepSegment(ir.analyses.segments.hour), opts) + trailingQualifier(ir, opts);
323
323
  }
324
+ function boundedWindow(plan) {
325
+ return { from: plan.from, last: plan.boundMinute ?? 0, to: plan.to };
326
+ }
324
327
  function hourWindow(window, opts) {
325
328
  return timeRange(
326
329
  { hour: window.from, minute: 0 },
package/dist/lang/fi.cjs CHANGED
@@ -465,7 +465,7 @@ function renderEveryHour(ir, plan, opts) {
465
465
  return "joka tunti" + trailingQualifier(ir, opts);
466
466
  }
467
467
  function renderHourRange(ir, plan, opts) {
468
- const window = hourWindow(plan, opts);
468
+ const window = hourWindow(boundedWindow(plan), opts);
469
469
  if (plan.minuteForm === "wildcard") {
470
470
  return "joka minuutti " + window + trailingQualifier(ir, opts);
471
471
  }
@@ -487,6 +487,9 @@ function renderHourRange(ir, plan, opts) {
487
487
  function renderHourStep(ir, plan, opts) {
488
488
  return stepHours(stepSegment(ir.analyses.segments.hour), opts) + trailingQualifier(ir, opts);
489
489
  }
490
+ function boundedWindow(plan) {
491
+ return { from: plan.from, last: plan.boundMinute ?? 0, to: plan.to };
492
+ }
490
493
  function hourWindow(window, opts) {
491
494
  return kloRange(
492
495
  { hour: window.from, minute: 0 },
package/dist/lang/fi.js CHANGED
@@ -439,7 +439,7 @@ function renderEveryHour(ir, plan, opts) {
439
439
  return "joka tunti" + trailingQualifier(ir, opts);
440
440
  }
441
441
  function renderHourRange(ir, plan, opts) {
442
- const window = hourWindow(plan, opts);
442
+ const window = hourWindow(boundedWindow(plan), opts);
443
443
  if (plan.minuteForm === "wildcard") {
444
444
  return "joka minuutti " + window + trailingQualifier(ir, opts);
445
445
  }
@@ -461,6 +461,9 @@ function renderHourRange(ir, plan, opts) {
461
461
  function renderHourStep(ir, plan, opts) {
462
462
  return stepHours(stepSegment(ir.analyses.segments.hour), opts) + trailingQualifier(ir, opts);
463
463
  }
464
+ function boundedWindow(plan) {
465
+ return { from: plan.from, last: plan.boundMinute ?? 0, to: plan.to };
466
+ }
464
467
  function hourWindow(window, opts) {
465
468
  return kloRange(
466
469
  { hour: window.from, minute: 0 },
package/dist/lang/zh.cjs CHANGED
@@ -320,15 +320,12 @@ function composeSecondsOnHour(ir, plan, opts) {
320
320
  const sec = secondClause(ir);
321
321
  const { rest } = plan;
322
322
  const restText = render(ir, rest, opts);
323
- if (rest.kind === "everyHour") {
324
- return sec + "\uFF0C\u6BCF\u5C0F\u65F6";
325
- }
326
- if (rest.kind === "hourStep") {
327
- return sec + "\uFF0C" + restText;
328
- }
329
323
  if ((rest.kind === "clockTimes" || rest.kind === "compactClockTimes") && isDaily(ir)) {
330
324
  return "\u6BCF\u5929" + restText + sec;
331
325
  }
326
+ if (rest.kind === "singleMinute") {
327
+ return restText + "\uFF0C" + sec;
328
+ }
332
329
  return restText + sec;
333
330
  }
334
331
  function composeSecondsCadence(ir) {
@@ -348,17 +345,12 @@ function composeSecondsCadence(ir) {
348
345
  function composeSecondsListed(ir) {
349
346
  const sec = secondClause(ir);
350
347
  const minutes = "\u6BCF\u5C0F\u65F6" + valueList(fieldSegments(ir, "minute"), "\u5206");
351
- const minuteSegs = fieldSegments(ir, "minute");
352
348
  if (ir.shapes.hour === "wildcard") {
353
349
  return minutes + "\uFF0C" + sec;
354
350
  }
355
351
  if (isHourCadence(ir)) {
356
352
  return cadence(stepSegment(ir, "hour").interval, UNITS.hour) + "\uFF0C" + minutes + "\uFF0C" + sec;
357
353
  }
358
- if (ir.shapes.hour === "range" && minuteSegs.length === 1 && minuteSegs[0].kind === "range") {
359
- const [from, to] = fieldSegments(ir, "hour")[0].bounds;
360
- return "\u5728" + hourWord(+from) + "\u81F3" + to + "\u70B9" + minuteSegs[0].bounds[1] + "\u5206\u4E4B\u95F4\uFF0C" + sec;
361
- }
362
354
  return hourFrame(ir) + minutes + "\uFF0C" + sec;
363
355
  }
364
356
  function renderComposeSeconds(ir, plan, opts) {
package/dist/lang/zh.js CHANGED
@@ -294,15 +294,12 @@ function composeSecondsOnHour(ir, plan, opts) {
294
294
  const sec = secondClause(ir);
295
295
  const { rest } = plan;
296
296
  const restText = render(ir, rest, opts);
297
- if (rest.kind === "everyHour") {
298
- return sec + "\uFF0C\u6BCF\u5C0F\u65F6";
299
- }
300
- if (rest.kind === "hourStep") {
301
- return sec + "\uFF0C" + restText;
302
- }
303
297
  if ((rest.kind === "clockTimes" || rest.kind === "compactClockTimes") && isDaily(ir)) {
304
298
  return "\u6BCF\u5929" + restText + sec;
305
299
  }
300
+ if (rest.kind === "singleMinute") {
301
+ return restText + "\uFF0C" + sec;
302
+ }
306
303
  return restText + sec;
307
304
  }
308
305
  function composeSecondsCadence(ir) {
@@ -322,17 +319,12 @@ function composeSecondsCadence(ir) {
322
319
  function composeSecondsListed(ir) {
323
320
  const sec = secondClause(ir);
324
321
  const minutes = "\u6BCF\u5C0F\u65F6" + valueList(fieldSegments(ir, "minute"), "\u5206");
325
- const minuteSegs = fieldSegments(ir, "minute");
326
322
  if (ir.shapes.hour === "wildcard") {
327
323
  return minutes + "\uFF0C" + sec;
328
324
  }
329
325
  if (isHourCadence(ir)) {
330
326
  return cadence(stepSegment(ir, "hour").interval, UNITS.hour) + "\uFF0C" + minutes + "\uFF0C" + sec;
331
327
  }
332
- if (ir.shapes.hour === "range" && minuteSegs.length === 1 && minuteSegs[0].kind === "range") {
333
- const [from, to] = fieldSegments(ir, "hour")[0].bounds;
334
- return "\u5728" + hourWord(+from) + "\u81F3" + to + "\u70B9" + minuteSegs[0].bounds[1] + "\u5206\u4E4B\u95F4\uFF0C" + sec;
335
- }
336
328
  return hourFrame(ir) + minutes + "\uFF0C" + sec;
337
329
  }
338
330
  function renderComposeSeconds(ir, plan, opts) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cronli5",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Cron Like I'm Five: A Cron to English Utility",
5
5
  "repository": {
6
6
  "type": "git",
@@ -269,10 +269,13 @@ function planSeconds(
269
269
  return null;
270
270
  }
271
271
 
272
+ // The second makes the cadence sub-minute, so a minute of 0 is a real
273
+ // restriction that must be stated, not absorbed into an hourly idiom (which
274
+ // would silently drop it). Route minute 0 to the minute-explicit forms.
272
275
  return {
273
276
  kind: 'composeSeconds',
274
- rest: planMinutes(pattern, shapes, analyses) ||
275
- planHours(pattern, shapes, analyses)
277
+ rest: planMinutes(pattern, shapes, analyses, true) ||
278
+ planHours(pattern, shapes, analyses, true)
276
279
  };
277
280
  }
278
281
 
@@ -303,7 +306,8 @@ function planStandaloneSeconds(
303
306
  function planMinutes(
304
307
  pattern: Pattern,
305
308
  shapes: Shapes,
306
- analyses: Analyses
309
+ analyses: Analyses,
310
+ subMinuteSecond = false
307
311
  ): PlanNode | undefined {
308
312
  if (shapes.minute === 'step') {
309
313
  return {
@@ -333,7 +337,7 @@ function planMinutes(
333
337
  }
334
338
 
335
339
  if (pattern.hour === '*') {
336
- return planMinutesUnderOpenHour(pattern, shapes);
340
+ return planMinutesUnderOpenHour(pattern, shapes, subMinuteSecond);
337
341
  }
338
342
  }
339
343
 
@@ -451,7 +455,8 @@ function planMinutesAcrossHours(
451
455
  // Minute strategies that only stand on their own under a wildcard hour.
452
456
  function planMinutesUnderOpenHour(
453
457
  pattern: Pattern,
454
- shapes: Shapes
458
+ shapes: Shapes,
459
+ subMinuteSecond: boolean
455
460
  ): PlanNode | undefined {
456
461
  if (shapes.minute === 'range') {
457
462
  return {kind: 'rangeOfMinutes'};
@@ -465,56 +470,89 @@ function planMinutesUnderOpenHour(
465
470
  return {kind: 'everyMinute'};
466
471
  }
467
472
 
468
- if (pattern.minute !== '0') {
473
+ // Minute 0 normally defers to "every hour" so a standalone `0 * * * *`
474
+ // stays terse; under a sub-minute second it must be stated, so name it.
475
+ if (pattern.minute !== '0' || subMinuteSecond) {
469
476
  return {kind: 'singleMinute'};
470
477
  }
471
478
  }
472
479
 
473
- // Hour strategies: the chain's last resort always produces a plan.
480
+ // Hour strategies: the chain's last resort always produces a plan. Under a
481
+ // sub-minute second a minute of 0 is a real restriction, so the absorbing
482
+ // idioms (hour range, hour step, every hour) are skipped for it and the hour
483
+ // is enumerated as clock times instead, stating the :00.
474
484
  function planHours(
475
485
  pattern: Pattern,
476
486
  shapes: Shapes,
477
- analyses: Analyses
487
+ analyses: Analyses,
488
+ subMinuteSecond = false
478
489
  ): PlanNode {
479
- if (shapes.hour === 'range') {
480
- const bounds = pattern.hour.split('-');
481
- let minuteForm: 'lead' | 'wildcard' | 'range' = 'lead';
482
-
483
- if (pattern.minute === '*') {
484
- minuteForm = 'wildcard';
485
- }
486
- else if (shapes.minute === 'range') {
487
- minuteForm = 'range';
488
- }
490
+ const absorbsMinuteZero = subMinuteSecond && pattern.minute === '0';
489
491
 
490
- return {
491
- from: +bounds[0],
492
- kind: 'hourRange',
493
- last: analyses.lastMinuteFire,
494
- minuteForm,
495
- to: +bounds[1]
496
- };
492
+ if (shapes.hour === 'range' && !absorbsMinuteZero) {
493
+ return planHourRange(pattern, shapes, analyses);
497
494
  }
498
495
 
499
- if (shapes.hour === 'step' && pattern.minute === '0') {
496
+ if (shapes.hour === 'step' && pattern.minute === '0' && !subMinuteSecond) {
500
497
  return {kind: 'hourStep'};
501
498
  }
502
499
 
503
- if (pattern.hour === '*') {
500
+ if (pattern.hour === '*' && !absorbsMinuteZero) {
504
501
  return {kind: 'everyHour'};
505
502
  }
506
503
 
507
- return planClockTimes(pattern, analyses);
504
+ // When minute 0 must be stated, enumerate the on-the-hour times explicitly:
505
+ // the compact fold of a contiguous hour range would otherwise restate the
506
+ // hour-range idiom ("every hour from X through Y") and re-drop the :00.
507
+ return planClockTimes(pattern, analyses, absorbsMinuteZero);
508
+ }
509
+
510
+ // The hour-range plan: a window from the first hour through the last. The
511
+ // minute clause leads (a single fire or a list), fires every minute (a range),
512
+ // or fills the window (a wildcard). A multi-valued minute (list or range)
513
+ // closes the window on the bare hour, stating its minutes separately; a single
514
+ // fire or a wildcard names an exact closing minute (its fire, or the wildcard's
515
+ // last :59) — otherwise the glued last fire reads as a continuous span.
516
+ function planHourRange(
517
+ pattern: Pattern,
518
+ shapes: Shapes,
519
+ analyses: Analyses
520
+ ): PlanNode {
521
+ const bounds = pattern.hour.split('-');
522
+ let minuteForm: 'lead' | 'wildcard' | 'range' = 'lead';
523
+
524
+ if (pattern.minute === '*') {
525
+ minuteForm = 'wildcard';
526
+ }
527
+ else if (shapes.minute === 'range') {
528
+ minuteForm = 'range';
529
+ }
530
+
531
+ const multiValued = shapes.minute === 'range' || shapes.minute === 'list';
532
+
533
+ return {
534
+ boundMinute: multiValued ? null : analyses.lastMinuteFire,
535
+ from: +bounds[0],
536
+ kind: 'hourRange',
537
+ last: analyses.lastMinuteFire,
538
+ minuteForm,
539
+ to: +bounds[1]
540
+ };
508
541
  }
509
542
 
510
543
  // Enumerated clock times up to the cap; past it, a compact form (a single
511
544
  // minute folds into hour-segment windows; a minute list leads with its own
512
- // clause).
513
- function planClockTimes(pattern: Pattern, analyses: Analyses): PlanNode {
545
+ // clause). `enumerate` forces the explicit list past the cap, used when a
546
+ // minute restriction must be named rather than folded into an hour idiom.
547
+ function planClockTimes(
548
+ pattern: Pattern,
549
+ analyses: Analyses,
550
+ enumerate = false
551
+ ): PlanNode {
514
552
  const hours = enumerateFires(pattern.hour, 0, 23);
515
553
  const minutes = enumerateValues(pattern.minute);
516
554
 
517
- if (hours.length * minutes.length > maxClockTimes) {
555
+ if (!enumerate && hours.length * minutes.length > maxClockTimes) {
518
556
  return {
519
557
  fold: minutes.length === 1,
520
558
  kind: 'compactClockTimes',
package/src/core/ir.ts CHANGED
@@ -78,6 +78,12 @@ export type PlanNode =
78
78
  from: number;
79
79
  to: number;
80
80
  last: number;
81
+ // The minute to show on the closing bound, or `null` to close on the
82
+ // bare hour with the minutes stated separately. A single fire or a
83
+ // wildcard names an exact closing minute (the fire, or `:59`); a minute
84
+ // list or range would otherwise glue its last fire onto the bound and
85
+ // read as a continuous span, so it closes bare instead.
86
+ boundMinute: number | null;
81
87
  minuteForm: 'lead' | 'wildcard' | 'range';
82
88
  }
83
89
  | {kind: 'hourStep'}
@@ -657,7 +657,11 @@ function renderHourRange(
657
657
  plan: Extract<PlanNode, {kind: 'hourRange'}>,
658
658
  opts: Opts
659
659
  ): string {
660
- const window = hourWindow(plan.from, plan.to, plan.last, opts.style.sep);
660
+ // A bare close (`boundMinute` null) lands on the top of the final hour
661
+ // (minute 0), matching the minute-0 baseline, with the minutes stated
662
+ // separately; a single fire or wildcard names an exact closing minute.
663
+ const window = hourWindow(plan.from, plan.to, plan.boundMinute ?? 0,
664
+ opts.style.sep);
661
665
 
662
666
  if (plan.minuteForm === 'wildcard') {
663
667
  return 'jede Minute ' + window;
@@ -376,7 +376,7 @@ function renderEveryHour(ir: IR, plan: PlanOf<'everyHour'>,
376
376
  // minute; a discrete minute anchors as a lead clause.
377
377
  function renderHourRange(ir: IR, plan: PlanOf<'hourRange'>,
378
378
  opts: NormalizedOptions): string {
379
- const window = hourWindow(plan, opts);
379
+ const window = hourWindow(boundedWindow(plan), opts);
380
380
 
381
381
  if (plan.minuteForm === 'wildcard') {
382
382
  return 'every minute ' + window + trailingQualifier(ir, opts);
@@ -411,6 +411,14 @@ function renderHourStep(ir: IR, plan: PlanOf<'hourStep'>,
411
411
  trailingQualifier(ir, opts);
412
412
  }
413
413
 
414
+ // The hour-range plan as a window whose closing minute honors `boundMinute`:
415
+ // a bare close (`null`) lands on the top of the final hour (`:00`), matching
416
+ // the minute-0 baseline, with the minutes stated separately elsewhere.
417
+ function boundedWindow(plan: PlanOf<'hourRange'>):
418
+ {from: number; to: number; last: number} {
419
+ return {from: plan.from, last: plan.boundMinute ?? 0, to: plan.to};
420
+ }
421
+
414
422
  // An hour window phrase, e.g. "from 9 a.m. through 5:45 p.m.". Windows
415
423
  // open at the top of the first hour and close at the minute field's last
416
424
  // fire within the final hour.
@@ -525,7 +525,7 @@ function renderHourRange(
525
525
  plan: Extract<PlanNode, {kind: 'hourRange'}>,
526
526
  opts: Opts
527
527
  ): string {
528
- const window = hourWindow(plan, opts);
528
+ const window = hourWindow(boundedWindow(plan), opts);
529
529
 
530
530
  if (plan.minuteForm === 'wildcard') {
531
531
  return 'cada minuto ' + window + trailingQualifier(ir, opts);
@@ -558,6 +558,15 @@ function renderHourStep(
558
558
  trailingQualifier(ir, opts);
559
559
  }
560
560
 
561
+ // The hour-range plan as a window whose closing minute honors `boundMinute`:
562
+ // a bare close (`null`) lands on the top of the final hour (minute 0),
563
+ // matching the minute-0 baseline, with the minutes stated separately.
564
+ function boundedWindow(
565
+ plan: Extract<PlanNode, {kind: 'hourRange'}>
566
+ ): {from: number; to: number; last: number} {
567
+ return {from: plan.from, last: plan.boundMinute ?? 0, to: plan.to};
568
+ }
569
+
561
570
  // "de las 9:00 a las 17:45": a window from the top of the first hour to
562
571
  // the minute field's last fire within the final hour.
563
572
  function hourWindow(
@@ -686,7 +686,7 @@ function renderHourRange(
686
686
  plan: Extract<PlanNode, {kind: 'hourRange'}>,
687
687
  opts: NormalizedOptions
688
688
  ): string {
689
- const window = hourWindow(plan, opts);
689
+ const window = hourWindow(boundedWindow(plan), opts);
690
690
 
691
691
  if (plan.minuteForm === 'wildcard') {
692
692
  return 'joka minuutti ' + window + trailingQualifier(ir, opts);
@@ -726,6 +726,16 @@ function renderHourStep(
726
726
  trailingQualifier(ir, opts);
727
727
  }
728
728
 
729
+ // The hour-range plan as a window whose closing minute honors `boundMinute`:
730
+ // a bare close (`null`) lands on the top of the final hour (minute 0), so
731
+ // `kloRange` renders the bare "klo 9–17" form, with the minutes stated
732
+ // separately; a single fire or wildcard names an exact closing minute.
733
+ function boundedWindow(
734
+ plan: Extract<PlanNode, {kind: 'hourRange'}>
735
+ ): HourWindow {
736
+ return {from: plan.from, last: plan.boundMinute ?? 0, to: plan.to};
737
+ }
738
+
729
739
  // "klo 9.00–17.45": a window from the top of the first hour to the
730
740
  // minute field's last fire within the final hour.
731
741
  function hourWindow(window: HourWindow, opts: NormalizedOptions): string {
@@ -423,19 +423,17 @@ function composeSecondsOnHour(ir: IR, plan: PlanNode, opts: Opts): string {
423
423
  const {rest} = plan as Extract<PlanNode, {kind: 'composeSeconds'}>;
424
424
  const restText = render(ir, rest, opts);
425
425
 
426
- if (rest.kind === 'everyHour') {
427
- return sec + ',每小时';
428
- }
429
-
430
- if (rest.kind === 'hourStep') {
431
- return sec + ',' + restText;
432
- }
433
-
434
426
  if ((rest.kind === 'clockTimes' || rest.kind === 'compactClockTimes') &&
435
427
  isDaily(ir)) {
436
428
  return '每天' + restText + sec;
437
429
  }
438
430
 
431
+ // A stated minute (e.g. minute 0 under a sub-minute second) takes the same
432
+ // "," connector the listed-minute path uses.
433
+ if (rest.kind === 'singleMinute') {
434
+ return restText + ',' + sec;
435
+ }
436
+
439
437
  return restText + sec;
440
438
  }
441
439
 
@@ -460,10 +458,12 @@ function composeSecondsCadence(ir: IR): string {
460
458
  }
461
459
 
462
460
  // Listed/ranged minute: "每小时<minutes>,每秒", confined by any hour frame.
461
+ // A minute list or range under an hour range closes on the bare hour frame
462
+ // ("在9点至17点之间"), stating its minutes separately, rather than gluing its
463
+ // last fire onto the window end ("…17点30分") and reading as a continuous span.
463
464
  function composeSecondsListed(ir: IR): string {
464
465
  const sec = secondClause(ir);
465
466
  const minutes = '每小时' + valueList(fieldSegments(ir, 'minute'), '分');
466
- const minuteSegs = fieldSegments(ir, 'minute');
467
467
 
468
468
  if (ir.shapes.hour === 'wildcard') {
469
469
  return minutes + ',' + sec;
@@ -474,16 +474,6 @@ function composeSecondsListed(ir: IR): string {
474
474
  minutes + ',' + sec;
475
475
  }
476
476
 
477
- // A minute range under an hour range folds into the window end ("…17点30分").
478
- if (ir.shapes.hour === 'range' && minuteSegs.length === 1 &&
479
- minuteSegs[0].kind === 'range') {
480
- const [from, to] = (fieldSegments(ir, 'hour')[0] as
481
- Extract<Segment, {kind: 'range'}>).bounds;
482
-
483
- return '在' + hourWord(+from) + '至' + to + '点' +
484
- minuteSegs[0].bounds[1] + '分之间,' + sec;
485
- }
486
-
487
477
  return hourFrame(ir) + minutes + ',' + sec;
488
478
  }
489
479
 
@@ -101,6 +101,7 @@ export type PlanNode = {
101
101
  from: number;
102
102
  to: number;
103
103
  last: number;
104
+ boundMinute: number | null;
104
105
  minuteForm: 'lead' | 'wildcard' | 'range';
105
106
  } | {
106
107
  kind: 'hourStep';