cronli5 0.1.1 → 0.1.2

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,33 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.1.2]
10
+
11
+ ### Changed
12
+
13
+ - **Non-uniform steps in the time fields now list their fires** instead of
14
+ reading "every N". A step is a true "every N" cadence only when it tiles the
15
+ field's cycle evenly — `step` divides the cycle (60 for minutes/seconds, 24 for
16
+ hours) **and** `start < step`; otherwise the gap at the field boundary differs.
17
+ `*/7 * * * *` fires at :00, :07, …, :56 — the :56→:00 gap is 4 minutes, not 7 —
18
+ so it now reads "at 0, 7, 14, 21, 28, 35, 42, 49, and 56 minutes past the hour"
19
+ rather than "every seven minutes". `7/6` and `11/6` (the step divides 60 but
20
+ starts past it) enumerate too. Genuine "every N" steps (`*/6`, `*/15`, `5/6`,
21
+ `11/12`, `*/2` hours) are unchanged. German already rendered these; English,
22
+ Spanish, Finnish, and Chinese now match. Date/month/weekday steps are unchanged
23
+ (their cycles vary — a separate follow-up).
24
+ - **Chinese no longer drops the start of an offset step.** `5/6` had rendered as
25
+ "每6分钟" — identical to `*/6`, the wrong schedule — and now enumerates its
26
+ fires ("每小时5、11、…、59分"), matching how Chinese already rendered offset
27
+ second and hour steps.
28
+
29
+ ### Fixed
30
+
31
+ - **German** no longer doubles the period when a sentence ends in an ordinal
32
+ ("…am 3., 5. und 8." instead of "…8.."), and an hour list no longer takes
33
+ "von" ("in den Stunden 9, 11 und 13 Uhr", not "von 9, 11 und 13 Uhr"; genuine
34
+ "von … bis" ranges are unchanged).
35
+
9
36
  ## [0.1.1]
10
37
 
11
38
  ### Fixed
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},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;/**
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},p={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}},O=["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 T(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 O.forEach(function(r){Jn(n[r],p[r],r)}),n}function Jn(n,e,r){typeof n!="string"&&typeof n!="number"&&un(n,r);let t=""+n;t!=="*"&&(r==="date"&&v(t)||r==="weekday"&&w(t,e)||t.split(",").forEach(function(o){$n(o,e)||un(o,r)}))}function v(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 C(n.slice(0,-1),e);let r=n.split("#");return r.length===2?C(r[0],e)&&/^[1-5]$/.test(r[1]):!1}function $n(n,e){return s(n,"/")?Yn(n,e):s(n,"-")?an(n,e):C(n,e)}function Yn(n,e){let r=n.split("/");return r.length!==2||!I(r[1])||+r[1]<1?!1:r[0]==="*"||C(r[0],e)||an(r[0],e,!0)}function an(n,e,r){let t=n.split("-");return t.length!==2||!C(t[0],e)||!C(t[1],e)?!1:e.cyclic&&!r?!0:d(t[0],e.numbers)<=d(t[1],e.numbers)}function C(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.")}var Bn={hour:24,minute:60,second:60};function cn(n){O.forEach(function(r){let t=p[r].aliases,i=t&&t[""+n[r]];i&&(n[r]=i)})}function mn(n){return O.forEach(function(r){let t=""+n[r];if(r==="date"&&v(t)||r==="weekday"&&w(t,p[r])){n[r]=t;return}n[r]=Gn(t,r,p[r])}),n}function Gn(n,e,r){let t=""+n;if(t==="*")return t;let i=Bn[e],o=t.split(",").map(function(a){return Xn(_n(Zn(Kn(a,r),r),r),r,i)}).join(",").split(",");return o.indexOf("*")!==-1?"*":T(o).sort(function(a,c){return ln(a,r)-ln(c,r)}).join(",")}function Kn(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 Zn(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,r){let t=n.split("/");if(typeof r!="number"||t.length!==2||s(t[0],"-"))return n;let i=+t[1],o=t[0]==="*"?e.min:d(t[0]);if(r%i===0&&o<i)return n;let u=[];for(let a=o;a<=e.top;a+=i)u.push(a);return u.join(",")}function _n(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 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 fn(n,e);if(typeof n=="object")return ne(n);if(typeof n=="string")return ee(n,e);throw new Error("`cronli5` was passed an unexpected type.")}function fn(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 ne(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:k(n.second,"0"),minute:k(n.minute,t),hour:k(n.hour,i),date:k(n.date,"*"),month:k(n.month,"*"),weekday:k(n.weekday,"*"),year:k(n.year,"*")}}function k(n,e){return typeof n>"u"?e:n}function ee(n,e){let r=re(n).split(/\s+/);return fn(r,e)}function re(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 gn(n){return n!=="*"&&!s(n,",")&&!s(n,"-")&&!s(n,"/")}function q(n){return s(n,"-")&&!s(n,",")&&!s(n,"/")}function te(n){return s(n,"/")&&!s(n,",")}function V(n){return n!=="*"&&!s(n,"-")&&!s(n,"/")}function J(n){return n!=="*"&&!q(n)&&!te(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 a=i[0].split("-");return R(d(a[0],t),o,d(a[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)}),T(t)}function ie(n){return V(n)?n.split(",").map(Number):[0]}function oe(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 ue(n){return n==="*"?59:Math.max(...$(n,0,59))}function se(n){if(gn(n)&&n!=="0")return+n}function ae(n,e){return n==="*"?"wildcard":e==="date"&&v(n)||e==="weekday"&&w(n,p.weekday)?"quartz":s(n,",")?"list":s(n,"/")?"step":s(n,"-")?"range":"single"}function le(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 hn(n){let e={},r={};O.forEach(function(u){e[u]=ae(n[u],u),r[u]=le(n[u],e[u],p[u])});let i={analyses:{clockSecond:se(n.second),lastMinuteFire:ue(n.minute),minuteSpan:oe(n.minute),segments:r},pattern:n,shapes:e};return{...i,plan:ce(i)}}function ce(n){let{analyses:e,pattern:r,shapes:t}=n;if(r.second!=="0"){let i=me(r,t,e);if(i)return i}return yn(r,t,e)||Sn(r,t,e)}function me(n,e,r){let t=de(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:yn(n,e,r,!0)||Sn(n,e,r,!0)})}function de(n,e){return n.minute!=="*"||n.hour!=="*"?null:n.second==="*"?{kind:"everySecond"}:e.second==="single"?{kind:"secondPastMinute"}:{kind:"standaloneSeconds"}}function yn(n,e,r,t=!1){if(e.minute==="step")return{hours:ge(n,e,r),kind:"minuteFrequency"};if(e.hour==="single"&&r.minuteSpan)return{hour:+n.hour,kind:"minuteSpanInHour",span:r.minuteSpan};let i=pe(n,e);if(i)return i;let o=fe(n,e);if(o)return o;if(n.hour==="*")return he(n,e,t)}function bn(n){let[e,r]=n.split("/"),t=e==="*"?0:+e;return e.indexOf("-")===-1&&24%+r===0&&t<+r}function fe(n,e){return e.hour!=="step"?null:n.minute==="*"?bn(n.hour)?{form:"wildcard",kind:"minuteSpanAcrossHourStep"}:{form:"wildcard",kind:"minutesAcrossHours",times:M(n.hour)}:e.minute==="range"?{form:"range",kind:"minuteSpanAcrossHourStep"}:null}function ge(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"?bn(n.hour)?{kind:"step"}:{kind:"during",times:M(n.hour)}:{kind:"none"}}function pe(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 he(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 Sn(n,e,r,t=!1){let i=t&&n.minute==="0";return e.hour==="range"&&!i?ye(n,e,r):e.hour==="step"&&n.minute==="0"&&!t?{kind:"hourStep"}:n.hour==="*"&&!i?{kind:"everyHour"}:be(n,r,i)}function ye(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 be(n,e,r=!1){let t=$(n.hour,0,23),i=ie(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(a){i.forEach(function(j){o.push({hour:a,minute:j,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 On(n,e){let r=dn(n,e);return cn(r),sn(r),mn(r)}function Y(n){return n=""+n,n.length<2?"0"+n:n}function kn(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 Nn(n){return typeof n=="object"&&n!==null?{...G.us,...n}:G[n==="uk"?"gb":n]||G.us}var Se=["zero","one","two","three","four","five","six","seven","eight","nine","ten"],K=["th","st","nd","rd"],f=[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"]],Oe={JAN:f[1],FEB:f[2],MAR:f[3],APR:f[4],MAY:f[5],JUN:f[6],JUL:f[7],AUG:f[8],SEP:f[9],OCT:f[10],NOV:f[11],DEC:f[12]},ke={SUN:y[0],MON:y[1],TUE:y[2],WED:y[3],THU:y[4],FRI:y[5],SAT:y[6]},Ne=[null,"first","second","third","fourth","fifth"];function ze(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 Pe(n,e){return er(Pn(n,n.plan,e),n,e)}function Pn(n,e,r){let t=$e[e.kind];return t(n,e,r)}function Ce(n,e,r){return"every second"+l(n,r)}function ve(n,e,r){return H(n,r)+l(n,r)}function we(n,e,r){let t=n.pattern.second;return g(t,r)+" "+P(t,"second")+" past the minute, every minute"+l(n,r)}function Re(n,e,r){let t=n.pattern.minute,i=g(t,r),o=P(t,"minute");if(e.singleSecond){let u=n.pattern.second;return i+" "+o+" and "+g(u,r)+" "+P(u,"second")+" past the hour, every hour"+l(n,r)}return H(n,r)+", "+i+" "+o+" past the hour, every hour"+l(n,r)}function Me(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])+S(e)+o(i[1])+" past the minute"}return t==="single"?"at "+g(r,e)+" "+P(r,"second")+" past the minute":z(F(n.analyses.segments.second,e),"second","minute",e)}function xe(n,e,r){return"every minute"+l(n,r)}function Fe(n,e,r){let t=n.pattern.minute;return g(t,r)+" "+P(t,"minute")+" past the hour, every hour"+l(n,r)}function Te(n,e,r){return L(n.pattern.minute,r)+l(n,r)}function Ie(n,e,r){return z(F(n.analyses.segments.minute,r),"minute","hour",r)+l(n,r)}function He(n,e,r){let t=Rn(n.analyses.segments.minute[0],"minute","hour",r);return e.hours.kind==="during"?t+=" during the "+X(n,e.hours.times,!1,r)+" hours":e.hours.kind==="window"?t+=" "+wn(e.hours,r):e.hours.kind==="step"&&(t+=" "+Cn(n.analyses.segments.hour[0],r)),t+l(n,r)}function Le(n,e,r){return"every minute from "+m({hour:e.hour,minute:e.span[0]},r)+S(r)+m({hour:e.hour,minute:e.span[1]},r)+l(n,r)}function Ae(n,e,r){if(e.form==="wildcard")return"every minute during the "+X(n,e.times,!1,r)+" hours"+l(n,r);let t=X(n,e.times,!0,r);return(e.form==="range"?L(n.pattern.minute,r):z(F(n.analyses.segments.minute,r),"minute","hour",r))+", at "+t+l(n,r)}var Ee={2:"other",3:"third",4:"fourth",6:"sixth",8:"eighth",12:"twelfth"};function Cn(n,e){let r="during every "+Ee[n.interval]+" hour",t=n.startToken==="*"?0:+n.startToken;return t===0?r:r+" starting at "+m({hour:t,minute:0},e)}function We(n,e,r){let t=n.analyses.segments.hour[0];return e.form==="wildcard"?"every minute "+Cn(t,r)+l(n,r):L(n.pattern.minute,r)+", "+Mn(t,r)+l(n,r)}function L(n,e){let r=n.split("-"),t=A(r,e);return"every minute from "+t(r[0])+S(e)+t(r[1])+" past the hour"}function je(n,e,r){return"every hour"+l(n,r)}function De(n,e,r){let t=wn(Qe(e),r);return e.minuteForm==="wildcard"?"every minute "+t+l(n,r):e.minuteForm==="range"?L(n.pattern.minute,r)+", "+t+l(n,r):vn(n,r)+" "+t+l(n,r)}function vn(n,e){return n.pattern.minute==="0"?"every hour":z(F(n.analyses.segments.minute,e),"minute","hour",e)}function Ue(n,e,r){return Mn(n.analyses.segments.hour[0],r)+l(n,r)}function Qe(n){return{from:n.from,last:n.boundMinute??0,to:n.to}}function wn(n,e){return"from "+m({hour:n.from,minute:0},e)+S(e)+m({hour:n.to,minute:n.last},e)}function qe(n,e,r){let t=rn(e.times),i=e.times.map(function(u){return m({hour:u.hour,minute:u.minute,second:u.second,plain:t},r)});return xn(n,r)+"at "+b(i,r)}function Ve(n,e,r){if(e.fold){if(n.analyses.segments.hour.some(function(a){return a.kind==="range"})&&!n.analyses.clockSecond)return Je(n,e,r)+l(n,r);let o={minute:e.minute,second:n.analyses.clockSecond};return xn(n,r)+"at "+_(n,o,!0,r)}let t=z(F(n.analyses.segments.minute,r),"minute","hour",r)+", at "+_(n,{minute:0,second:null},!0,r)+l(n,r);return n.analyses.clockSecond?H(n,r)+", "+t:t}function Je(n,e,r){let t=e.minute,i=[],o=[];n.analyses.segments.hour.forEach(function(c){c.kind==="range"?i.push("from "+m({hour:c.bounds[0],minute:0},r)+S(r)+m({hour:c.bounds[1],minute:t},r)):c.kind==="step"?o.push(...c.fires):o.push(+c.value)});let u=vn(n,r)+" "+b(i,r);return o.length&&(u+=" and at "+b(o.map(function(c){return m({hour:c,minute:t},r)}),r)),u}var $e={clockTimes:qe,compactClockTimes:Ve,composeSeconds:Me,everyHour:je,everyMinute:xe,everySecond:Ce,hourRange:De,hourStep:Ue,minuteFrequency:He,minuteSpanAcrossHourStep:We,minuteSpanInHour:Le,minutesAcrossHours:Ae,multipleMinutes:Ie,rangeOfMinutes:Te,secondPastMinute:we,secondsWithinMinute:Re,singleMinute:Fe,standaloneSeconds:ve};function Rn(n,e,r,t){if(n.startToken.indexOf("-")!==-1)return z(zn(n.fires,t),e,r,t);let i=n.startToken==="*"?0:+n.startToken,o=n.interval;return i!==0?n.fires.length<=3?z(zn(n.fires,t),e,r,t):"every "+g(o,t)+" "+e+"s from "+g(i,t)+" "+P(i,e)+" past the "+r:"every "+g(o,t)+" "+e+"s"}function Mn(n,e){if(n.startToken.indexOf("-")!==-1)return"at "+Z(n.fires,e);let r=n.startToken==="*"?0:+n.startToken,t=n.interval;return r===0?"every "+g(t,e)+" hours":n.fires.length<=3?"at "+Z(n.fires,e):"every "+g(t,e)+" hours from "+m({hour:r,minute:0},e)}function A(n,e){let r=n.some(function(i){return+i>10});return function(i){return r?""+i:g(i,e)}}function zn(n,e){return n.map(A(n,e))}function F(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])+S(e)+t(o.bounds[1])]:o.kind==="step"?o.fires.map(t):[t(o.value)]})}function z(n,e,r,t){return"at "+b(n,t)+" "+e+"s past the "+r}function Ye(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 Ye(t.hour,t.minute,t.second)});return e.length>0&&e.length<n.length}function Z(n,e){let r=rn(n.map(function(o){return{hour:o,minute:0}})),t=n.map(function(o){return m({hour:o,minute:0,plain:r},e)});return b(t,e)}function X(n,e,r,t){return e.kind==="fires"?Z(e.fires,t):_(n,{minute:0,second:null},r,t)}function Be(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,a=rn(u.flatMap(function(h){return Be(h).map(function(D){return{hour:+D,minute:i,second:o}})})),c=[];return u.forEach(function(h){h.kind==="step"?c.push(...h.fires.map(function(D){return m({hour:D,minute:i,second:o,plain:a},t)})):h.kind==="range"?c.push(m({hour:h.bounds[0],minute:i,second:o,plain:a},t)+S(t)+m({hour:h.bounds[1],minute:i,second:o,plain:a},t)):c.push(m({hour:h.value,minute:i,second:o,plain:a},t))}),b(Ge(c,u,r),t)}function Ge(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 b(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 Ke={all:"",month:"in ",stepDate:"on ",weekday:"on "},Ze={all:"every day",month:"every day in ",stepDate:"",weekday:"every "};function l(n,e){let r=Fn(n,Ke,e);return r&&" "+r}function xn(n,e){return Fn(n,Ze,e)+" "}function Fn(n,e,r){let t=n.pattern;return t.date!=="*"&&t.weekday!=="*"?_e(n,r):t.date!=="*"?Xe(n,e,r):t.weekday!=="*"?(Hn(t.weekday,r)||e.weekday+Wn(n,r))+N(n,r):t.month!=="*"?e.month+E(n,r):e.all}function Xe(n,e,r){let t=n.pattern,i=In(t.date,r);return i?i+N(n,r):tn(t.date)?e.stepDate+An(t.date)+N(n,r):t.month!=="*"&&!Tn(n)?"on the "+nn(n,r)+N(n,r):t.month!=="*"?"on "+Ln(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 _e(n,e){let r=n.pattern,t=Hn(r.weekday,e)||"on "+Wn(n,e),i=In(r.date,e);return i?i+N(n,e)+" or "+t:tn(r.date)?An(r.date)+N(n,e)+" or "+t:r.month!=="*"&&Tn(n)?"on "+Ln(n,e)+" or "+t+" in "+E(n,e):"on the "+nn(n,e)+" or "+t+N(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 g(+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 "+Ne[+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 Ln(n,e){let r=E(n,e),t=W(n.analyses.segments.date,e.style.ordinals?x:nr,e);return e.style.dayFirst?t+" "+r:r+" "+t}function nr(n){return""+n}function N(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 nn(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 or(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 Wn(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(S(r))):t.push(e(o.value))}),b(t,r)}function tn(n){return n.indexOf("/")!==-1&&n.indexOf("-")===-1&&n.indexOf(",")===-1}function er(n,e,r){let t=e.pattern.year;if(t==="*")return n;if(t.indexOf("/")!==-1)return n+" "+tr(t,r);let i=rr(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 rr(n,e){return n.indexOf(",")!==-1?b(n.split(","),e):n}function tr(n,e){let r=n.split("/"),t=+r[1],i=r[0];if(t<=1)return"every year";let o="every "+g(t,e)+" years";return i!=="*"&&i!=="0"&&(o+=" from "+i),o}function m(n,e){let{hour:r,minute:t,plain:i}=n,o=typeof n.second=="number"&&n.second>0?n.second:0;return e.ampm?ir({hour:r,minute:t,second:o,plain:i},e):B({hour:r,minute:t,second:o},{pad:!0,sep:e.style.sep})}function ir(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 g(n,e){return kn(n,Se,e)}function P(n,e){return+n==1?e:e+"s"}function S(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 or(n,e){let r=f[n]||Oe[n];return r&&r[e.short?1:0]}function en(n,e){let r=n===7||n==="7"?0:n,t=y[r]||ke[r];return t&&t[e.short?1:0]}var ur={describe:Pe,fallback:"an unrecognizable cron pattern",options:ze,reboot:"at system startup",sentence:n=>"Runs "+n+"."},jn=ur;/**
2
2
  * @license MIT, Copyright (c) 2026 Andrew Brož
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;})();
3
+ */function sr(n,e){let r=e&&e.lang||jn,t=r.options(e);if(!t.lenient)return Dn(Un(n,r,t),r,e);try{return Dn(Un(n,r,t),r,e)}catch{return r.fallback}}function Dn(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=hn(On(n,r)),i=e.strategy?e.strategy(t,t.plan):t.plan;return e.describe({...t,plan:i},r)}var on=sr;typeof globalThis<"u"&&Object.assign(globalThis,{cronli5:on});var qr=on;})();
package/dist/cronli5.cjs CHANGED
@@ -194,6 +194,11 @@ function throwInvalidField(value, field) {
194
194
  }
195
195
 
196
196
  // src/core/normalize.ts
197
+ var timeFieldCycle = {
198
+ hour: 24,
199
+ minute: 60,
200
+ second: 60
201
+ };
197
202
  function applyQuartzAliases(cronPattern) {
198
203
  fieldOrder.forEach(function apply(field) {
199
204
  const aliases = fieldSpecs[field].aliases;
@@ -210,21 +215,26 @@ function normalizeCronPattern(cronPattern) {
210
215
  cronPattern[field] = value;
211
216
  return;
212
217
  }
213
- cronPattern[field] = normalizeField(value, fieldSpecs[field]);
218
+ cronPattern[field] = normalizeField(value, field, fieldSpecs[field]);
214
219
  });
215
220
  return cronPattern;
216
221
  }
217
- function normalizeField(value, spec) {
222
+ function normalizeField(value, field, spec) {
218
223
  const stringValue = "" + value;
219
224
  if (stringValue === "*") {
220
225
  return stringValue;
221
226
  }
227
+ const cycle = timeFieldCycle[field];
222
228
  const segments = stringValue.split(",").map(function canonical(segment) {
223
- return collapseDegenerateRange(
224
- collapseOnceStep(collapseUnitStep(segment, spec), spec),
225
- spec
229
+ return enumerateNonUniformStep(
230
+ collapseDegenerateRange(
231
+ collapseOnceStep(collapseUnitStep(segment, spec), spec),
232
+ spec
233
+ ),
234
+ spec,
235
+ cycle
226
236
  );
227
- });
237
+ }).join(",").split(",");
228
238
  if (segments.indexOf("*") !== -1) {
229
239
  return "*";
230
240
  }
@@ -258,6 +268,22 @@ function collapseOnceStep(segment, spec) {
258
268
  }
259
269
  return start === "*" ? "" + spec.min : start;
260
270
  }
271
+ function enumerateNonUniformStep(segment, spec, cycle) {
272
+ const parts = segment.split("/");
273
+ if (typeof cycle !== "number" || parts.length !== 2 || includes(parts[0], "-")) {
274
+ return segment;
275
+ }
276
+ const interval = +parts[1];
277
+ const start = parts[0] === "*" ? spec.min : toFieldNumber(parts[0]);
278
+ if (cycle % interval === 0 && start < interval) {
279
+ return segment;
280
+ }
281
+ const fires = [];
282
+ for (let value = start; value <= spec.top; value += interval) {
283
+ fires.push(value);
284
+ }
285
+ return fires.join(",");
286
+ }
261
287
  function collapseDegenerateRange(segment, spec) {
262
288
  const start = segment.split("/")[0];
263
289
  if (!includes(start, "-")) {
@@ -1126,13 +1152,7 @@ function stepCycle60(segment, unit, anchor, opts) {
1126
1152
  }
1127
1153
  return "every " + getNumber(interval, opts) + " " + unit + "s from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
1128
1154
  }
1129
- if (60 % interval === 0) {
1130
- return "every " + getNumber(interval, opts) + " " + unit + "s";
1131
- }
1132
- if (segment.fires.length <= 2) {
1133
- return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
1134
- }
1135
- return "every " + getNumber(interval, opts) + " " + unit + "s past the " + anchor;
1155
+ return "every " + getNumber(interval, opts) + " " + unit + "s";
1136
1156
  }
1137
1157
  function stepHours(segment, opts) {
1138
1158
  if (segment.startToken.indexOf("-") !== -1) {
@@ -1140,15 +1160,12 @@ function stepHours(segment, opts) {
1140
1160
  }
1141
1161
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
1142
1162
  const interval = segment.interval;
1143
- if (start === 0 && 24 % interval === 0) {
1163
+ if (start === 0) {
1144
1164
  return "every " + getNumber(interval, opts) + " hours";
1145
1165
  }
1146
1166
  if (segment.fires.length <= 3) {
1147
1167
  return "at " + hourTimes(segment.fires, opts);
1148
1168
  }
1149
- if (start === 0) {
1150
- return "every " + getNumber(interval, opts) + " hours from midnight";
1151
- }
1152
1169
  return "every " + getNumber(interval, opts) + " hours from " + getTime({ hour: start, minute: 0 }, opts);
1153
1170
  }
1154
1171
  function seriesNumber(values, opts) {
package/dist/cronli5.js CHANGED
@@ -168,6 +168,11 @@ function throwInvalidField(value, field) {
168
168
  }
169
169
 
170
170
  // src/core/normalize.ts
171
+ var timeFieldCycle = {
172
+ hour: 24,
173
+ minute: 60,
174
+ second: 60
175
+ };
171
176
  function applyQuartzAliases(cronPattern) {
172
177
  fieldOrder.forEach(function apply(field) {
173
178
  const aliases = fieldSpecs[field].aliases;
@@ -184,21 +189,26 @@ function normalizeCronPattern(cronPattern) {
184
189
  cronPattern[field] = value;
185
190
  return;
186
191
  }
187
- cronPattern[field] = normalizeField(value, fieldSpecs[field]);
192
+ cronPattern[field] = normalizeField(value, field, fieldSpecs[field]);
188
193
  });
189
194
  return cronPattern;
190
195
  }
191
- function normalizeField(value, spec) {
196
+ function normalizeField(value, field, spec) {
192
197
  const stringValue = "" + value;
193
198
  if (stringValue === "*") {
194
199
  return stringValue;
195
200
  }
201
+ const cycle = timeFieldCycle[field];
196
202
  const segments = stringValue.split(",").map(function canonical(segment) {
197
- return collapseDegenerateRange(
198
- collapseOnceStep(collapseUnitStep(segment, spec), spec),
199
- spec
203
+ return enumerateNonUniformStep(
204
+ collapseDegenerateRange(
205
+ collapseOnceStep(collapseUnitStep(segment, spec), spec),
206
+ spec
207
+ ),
208
+ spec,
209
+ cycle
200
210
  );
201
- });
211
+ }).join(",").split(",");
202
212
  if (segments.indexOf("*") !== -1) {
203
213
  return "*";
204
214
  }
@@ -232,6 +242,22 @@ function collapseOnceStep(segment, spec) {
232
242
  }
233
243
  return start === "*" ? "" + spec.min : start;
234
244
  }
245
+ function enumerateNonUniformStep(segment, spec, cycle) {
246
+ const parts = segment.split("/");
247
+ if (typeof cycle !== "number" || parts.length !== 2 || includes(parts[0], "-")) {
248
+ return segment;
249
+ }
250
+ const interval = +parts[1];
251
+ const start = parts[0] === "*" ? spec.min : toFieldNumber(parts[0]);
252
+ if (cycle % interval === 0 && start < interval) {
253
+ return segment;
254
+ }
255
+ const fires = [];
256
+ for (let value = start; value <= spec.top; value += interval) {
257
+ fires.push(value);
258
+ }
259
+ return fires.join(",");
260
+ }
235
261
  function collapseDegenerateRange(segment, spec) {
236
262
  const start = segment.split("/")[0];
237
263
  if (!includes(start, "-")) {
@@ -1100,13 +1126,7 @@ function stepCycle60(segment, unit, anchor, opts) {
1100
1126
  }
1101
1127
  return "every " + getNumber(interval, opts) + " " + unit + "s from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
1102
1128
  }
1103
- if (60 % interval === 0) {
1104
- return "every " + getNumber(interval, opts) + " " + unit + "s";
1105
- }
1106
- if (segment.fires.length <= 2) {
1107
- return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
1108
- }
1109
- return "every " + getNumber(interval, opts) + " " + unit + "s past the " + anchor;
1129
+ return "every " + getNumber(interval, opts) + " " + unit + "s";
1110
1130
  }
1111
1131
  function stepHours(segment, opts) {
1112
1132
  if (segment.startToken.indexOf("-") !== -1) {
@@ -1114,15 +1134,12 @@ function stepHours(segment, opts) {
1114
1134
  }
1115
1135
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
1116
1136
  const interval = segment.interval;
1117
- if (start === 0 && 24 % interval === 0) {
1137
+ if (start === 0) {
1118
1138
  return "every " + getNumber(interval, opts) + " hours";
1119
1139
  }
1120
1140
  if (segment.fires.length <= 3) {
1121
1141
  return "at " + hourTimes(segment.fires, opts);
1122
1142
  }
1123
- if (start === 0) {
1124
- return "every " + getNumber(interval, opts) + " hours from midnight";
1125
- }
1126
1143
  return "every " + getNumber(interval, opts) + " hours from " + getTime({ hour: start, minute: 0 }, opts);
1127
1144
  }
1128
1145
  function seriesNumber(values, opts) {
package/dist/lang/de.cjs CHANGED
@@ -352,7 +352,7 @@ function duringHours(ir, times, sep) {
352
352
  if (windows.length <= 3 || times.kind !== "fires") {
353
353
  return joinList(windows);
354
354
  }
355
- return "in den Stunden von " + joinList(times.fires.map(String)) + " Uhr";
355
+ return "in den Stunden " + joinList(times.fires.map(String)) + " Uhr";
356
356
  }
357
357
  function renderEverySecond() {
358
358
  return everyUnit(UNITS.second);
@@ -545,7 +545,9 @@ var de2 = {
545
545
  fallback: "ein unlesbares Cron-Muster",
546
546
  options: normalizeOptions,
547
547
  reboot: "beim Systemstart",
548
- sentence: (description) => "L\xE4uft " + description + "."
548
+ // A description ending in a German ordinal already carries its period
549
+ // ("…am 8."), so closing the sentence must not double it.
550
+ sentence: (description) => "L\xE4uft " + description + (description.endsWith(".") ? "" : ".")
549
551
  };
550
552
  var index_default = de2;
551
553
  module.exports = module.exports.default;
package/dist/lang/de.js CHANGED
@@ -326,7 +326,7 @@ function duringHours(ir, times, sep) {
326
326
  if (windows.length <= 3 || times.kind !== "fires") {
327
327
  return joinList(windows);
328
328
  }
329
- return "in den Stunden von " + joinList(times.fires.map(String)) + " Uhr";
329
+ return "in den Stunden " + joinList(times.fires.map(String)) + " Uhr";
330
330
  }
331
331
  function renderEverySecond() {
332
332
  return everyUnit(UNITS.second);
@@ -519,7 +519,9 @@ var de2 = {
519
519
  fallback: "ein unlesbares Cron-Muster",
520
520
  options: normalizeOptions,
521
521
  reboot: "beim Systemstart",
522
- sentence: (description) => "L\xE4uft " + description + "."
522
+ // A description ending in a German ordinal already carries its period
523
+ // ("…am 8."), so closing the sentence must not double it.
524
+ sentence: (description) => "L\xE4uft " + description + (description.endsWith(".") ? "" : ".")
523
525
  };
524
526
  var index_default = de2;
525
527
  export {
package/dist/lang/en.cjs CHANGED
@@ -431,13 +431,7 @@ function stepCycle60(segment, unit, anchor, opts) {
431
431
  }
432
432
  return "every " + getNumber(interval, opts) + " " + unit + "s from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
433
433
  }
434
- if (60 % interval === 0) {
435
- return "every " + getNumber(interval, opts) + " " + unit + "s";
436
- }
437
- if (segment.fires.length <= 2) {
438
- return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
439
- }
440
- return "every " + getNumber(interval, opts) + " " + unit + "s past the " + anchor;
434
+ return "every " + getNumber(interval, opts) + " " + unit + "s";
441
435
  }
442
436
  function stepHours(segment, opts) {
443
437
  if (segment.startToken.indexOf("-") !== -1) {
@@ -445,15 +439,12 @@ function stepHours(segment, opts) {
445
439
  }
446
440
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
447
441
  const interval = segment.interval;
448
- if (start === 0 && 24 % interval === 0) {
442
+ if (start === 0) {
449
443
  return "every " + getNumber(interval, opts) + " hours";
450
444
  }
451
445
  if (segment.fires.length <= 3) {
452
446
  return "at " + hourTimes(segment.fires, opts);
453
447
  }
454
- if (start === 0) {
455
- return "every " + getNumber(interval, opts) + " hours from midnight";
456
- }
457
448
  return "every " + getNumber(interval, opts) + " hours from " + getTime({ hour: start, minute: 0 }, opts);
458
449
  }
459
450
  function seriesNumber(values, opts) {
package/dist/lang/en.js CHANGED
@@ -405,13 +405,7 @@ function stepCycle60(segment, unit, anchor, opts) {
405
405
  }
406
406
  return "every " + getNumber(interval, opts) + " " + unit + "s from " + getNumber(start, opts) + " " + pluralize(start, unit) + " past the " + anchor;
407
407
  }
408
- if (60 % interval === 0) {
409
- return "every " + getNumber(interval, opts) + " " + unit + "s";
410
- }
411
- if (segment.fires.length <= 2) {
412
- return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
413
- }
414
- return "every " + getNumber(interval, opts) + " " + unit + "s past the " + anchor;
408
+ return "every " + getNumber(interval, opts) + " " + unit + "s";
415
409
  }
416
410
  function stepHours(segment, opts) {
417
411
  if (segment.startToken.indexOf("-") !== -1) {
@@ -419,15 +413,12 @@ function stepHours(segment, opts) {
419
413
  }
420
414
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
421
415
  const interval = segment.interval;
422
- if (start === 0 && 24 % interval === 0) {
416
+ if (start === 0) {
423
417
  return "every " + getNumber(interval, opts) + " hours";
424
418
  }
425
419
  if (segment.fires.length <= 3) {
426
420
  return "at " + hourTimes(segment.fires, opts);
427
421
  }
428
- if (start === 0) {
429
- return "every " + getNumber(interval, opts) + " hours from midnight";
430
- }
431
422
  return "every " + getNumber(interval, opts) + " hours from " + getTime({ hour: start, minute: 0 }, opts);
432
423
  }
433
424
  function seriesNumber(values, opts) {
package/dist/lang/es.cjs CHANGED
@@ -600,13 +600,7 @@ function stepCycle60(segment, unit, anchor, opts) {
600
600
  }
601
601
  return "cada " + numero(interval, opts) + " " + unit + "s a partir del " + unit + " " + start + " de cada " + anchor;
602
602
  }
603
- if (60 % interval === 0) {
604
- return "cada " + numero(interval, opts) + " " + unit + "s";
605
- }
606
- if (segment.fires.length <= 2) {
607
- return "en los " + unit + "s " + joinList(wordList(segment.fires)) + " de cada " + anchor;
608
- }
609
- return "cada " + numero(interval, opts) + " " + unit + "s de cada " + anchor;
603
+ return "cada " + numero(interval, opts) + " " + unit + "s";
610
604
  }
611
605
  function stepHours(segment, opts) {
612
606
  if (segment.startToken.indexOf("-") !== -1) {
@@ -614,15 +608,12 @@ function stepHours(segment, opts) {
614
608
  }
615
609
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
616
610
  const interval = segment.interval;
617
- if (start === 0 && 24 % interval === 0) {
611
+ if (start === 0) {
618
612
  return "cada " + numero(interval, opts) + " horas";
619
613
  }
620
614
  if (segment.fires.length <= 3) {
621
615
  return groupClockTimesByArticle(atTimes(segment.fires, opts));
622
616
  }
623
- if (start === 0) {
624
- return "cada " + numero(interval, opts) + " horas desde medianoche";
625
- }
626
617
  return "cada " + numero(interval, opts) + " horas a partir de " + timePhrase(start, 0, null, opts);
627
618
  }
628
619
  function atTimes(hours, opts) {
package/dist/lang/es.js CHANGED
@@ -574,13 +574,7 @@ function stepCycle60(segment, unit, anchor, opts) {
574
574
  }
575
575
  return "cada " + numero(interval, opts) + " " + unit + "s a partir del " + unit + " " + start + " de cada " + anchor;
576
576
  }
577
- if (60 % interval === 0) {
578
- return "cada " + numero(interval, opts) + " " + unit + "s";
579
- }
580
- if (segment.fires.length <= 2) {
581
- return "en los " + unit + "s " + joinList(wordList(segment.fires)) + " de cada " + anchor;
582
- }
583
- return "cada " + numero(interval, opts) + " " + unit + "s de cada " + anchor;
577
+ return "cada " + numero(interval, opts) + " " + unit + "s";
584
578
  }
585
579
  function stepHours(segment, opts) {
586
580
  if (segment.startToken.indexOf("-") !== -1) {
@@ -588,15 +582,12 @@ function stepHours(segment, opts) {
588
582
  }
589
583
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
590
584
  const interval = segment.interval;
591
- if (start === 0 && 24 % interval === 0) {
585
+ if (start === 0) {
592
586
  return "cada " + numero(interval, opts) + " horas";
593
587
  }
594
588
  if (segment.fires.length <= 3) {
595
589
  return groupClockTimesByArticle(atTimes(segment.fires, opts));
596
590
  }
597
- if (start === 0) {
598
- return "cada " + numero(interval, opts) + " horas desde medianoche";
599
- }
600
591
  return "cada " + numero(interval, opts) + " horas a partir de " + timePhrase(start, 0, null, opts);
601
592
  }
602
593
  function atTimes(hours, opts) {
package/dist/lang/fi.cjs CHANGED
@@ -194,15 +194,13 @@ var units = {
194
194
  mark: "joka tunti",
195
195
  anchor: "jokaisen tunnin",
196
196
  ela: "minuutista",
197
- gen: "minuutin",
198
- restart: "tasatunnista alkaen"
197
+ gen: "minuutin"
199
198
  },
200
199
  second: {
201
200
  mark: "joka minuutti",
202
201
  anchor: "jokaisen minuutin",
203
202
  ela: "sekunnista",
204
- gen: "sekunnin",
205
- restart: "joka minuutti"
203
+ gen: "sekunnin"
206
204
  }
207
205
  };
208
206
  function atMarks(values, unit, withMark) {
@@ -561,13 +559,7 @@ function stepCycle60(segment, unit, opts) {
561
559
  }
562
560
  return cadence + " " + unit.anchor + " " + unit.ela + " " + start + " alkaen";
563
561
  }
564
- if (60 % interval === 0) {
565
- return cadence;
566
- }
567
- if (segment.fires.length <= 2) {
568
- return atMarks(joinList(wordList(segment.fires)), unit, true);
569
- }
570
- return cadence + " " + unit.restart;
562
+ return cadence;
571
563
  }
572
564
  function stepHours(segment, opts) {
573
565
  if (segment.startToken.indexOf("-") !== -1) {
@@ -576,15 +568,12 @@ function stepHours(segment, opts) {
576
568
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
577
569
  const interval = segment.interval;
578
570
  const cadence = genitive(interval, opts) + " tunnin v\xE4lein";
579
- if (start === 0 && 24 % interval === 0) {
571
+ if (start === 0) {
580
572
  return cadence;
581
573
  }
582
574
  if (segment.fires.length <= 3) {
583
575
  return kloList(segment.fires, opts);
584
576
  }
585
- if (start === 0) {
586
- return cadence + " keskiy\xF6st\xE4 alkaen";
587
- }
588
577
  return cadence + " klo " + hourElatives[start] + " alkaen";
589
578
  }
590
579
  function kloList(hours, opts) {
package/dist/lang/fi.js CHANGED
@@ -168,15 +168,13 @@ var units = {
168
168
  mark: "joka tunti",
169
169
  anchor: "jokaisen tunnin",
170
170
  ela: "minuutista",
171
- gen: "minuutin",
172
- restart: "tasatunnista alkaen"
171
+ gen: "minuutin"
173
172
  },
174
173
  second: {
175
174
  mark: "joka minuutti",
176
175
  anchor: "jokaisen minuutin",
177
176
  ela: "sekunnista",
178
- gen: "sekunnin",
179
- restart: "joka minuutti"
177
+ gen: "sekunnin"
180
178
  }
181
179
  };
182
180
  function atMarks(values, unit, withMark) {
@@ -535,13 +533,7 @@ function stepCycle60(segment, unit, opts) {
535
533
  }
536
534
  return cadence + " " + unit.anchor + " " + unit.ela + " " + start + " alkaen";
537
535
  }
538
- if (60 % interval === 0) {
539
- return cadence;
540
- }
541
- if (segment.fires.length <= 2) {
542
- return atMarks(joinList(wordList(segment.fires)), unit, true);
543
- }
544
- return cadence + " " + unit.restart;
536
+ return cadence;
545
537
  }
546
538
  function stepHours(segment, opts) {
547
539
  if (segment.startToken.indexOf("-") !== -1) {
@@ -550,15 +542,12 @@ function stepHours(segment, opts) {
550
542
  const start = segment.startToken === "*" ? 0 : +segment.startToken;
551
543
  const interval = segment.interval;
552
544
  const cadence = genitive(interval, opts) + " tunnin v\xE4lein";
553
- if (start === 0 && 24 % interval === 0) {
545
+ if (start === 0) {
554
546
  return cadence;
555
547
  }
556
548
  if (segment.fires.length <= 3) {
557
549
  return kloList(segment.fires, opts);
558
550
  }
559
- if (start === 0) {
560
- return cadence + " keskiy\xF6st\xE4 alkaen";
561
- }
562
551
  return cadence + " klo " + hourElatives[start] + " alkaen";
563
552
  }
564
553
  function kloList(hours, opts) {
package/dist/lang/zh.cjs CHANGED
@@ -197,7 +197,8 @@ function hourFrame(ir) {
197
197
  return "\u5728" + hourList(ir) + "\uFF0C";
198
198
  }
199
199
  function renderMinuteFrequency(ir, plan) {
200
- const base = cadence(stepSegment(ir, "minute").interval, UNITS.minute);
200
+ const minuteStep = stepSegment(ir, "minute");
201
+ const base = minuteStep.startToken === "*" ? cadence(minuteStep.interval, UNITS.minute) : renderMinutePast(ir);
201
202
  const { hours } = plan;
202
203
  if (hours.kind === "step") {
203
204
  return cadence(stepSegment(ir, "hour").interval, UNITS.hour) + base;
@@ -265,9 +266,6 @@ function renderHourStep(ir) {
265
266
  if (segment.fires.length <= 2) {
266
267
  return joinAnd(segment.fires.map(hourWord));
267
268
  }
268
- if (24 % segment.interval !== 0) {
269
- return "\u4ECE" + hourWord(segment.fires[0]) + "\u8D77\uFF0C" + cadence(segment.interval, UNITS.hour);
270
- }
271
269
  return cadence(segment.interval, UNITS.hour);
272
270
  }
273
271
  function renderRangeOfMinutes(ir) {
package/dist/lang/zh.js CHANGED
@@ -171,7 +171,8 @@ function hourFrame(ir) {
171
171
  return "\u5728" + hourList(ir) + "\uFF0C";
172
172
  }
173
173
  function renderMinuteFrequency(ir, plan) {
174
- const base = cadence(stepSegment(ir, "minute").interval, UNITS.minute);
174
+ const minuteStep = stepSegment(ir, "minute");
175
+ const base = minuteStep.startToken === "*" ? cadence(minuteStep.interval, UNITS.minute) : renderMinutePast(ir);
175
176
  const { hours } = plan;
176
177
  if (hours.kind === "step") {
177
178
  return cadence(stepSegment(ir, "hour").interval, UNITS.hour) + base;
@@ -239,9 +240,6 @@ function renderHourStep(ir) {
239
240
  if (segment.fires.length <= 2) {
240
241
  return joinAnd(segment.fires.map(hourWord));
241
242
  }
242
- if (24 % segment.interval !== 0) {
243
- return "\u4ECE" + hourWord(segment.fires[0]) + "\u8D77\uFF0C" + cadence(segment.interval, UNITS.hour);
244
- }
245
243
  return cadence(segment.interval, UNITS.hour);
246
244
  }
247
245
  function renderRangeOfMinutes(ir) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cronli5",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Cron Like I'm Five: A Cron to English Utility",
5
5
  "repository": {
6
6
  "type": "git",
@@ -4,10 +4,20 @@
4
4
 
5
5
  import {fieldOrder, fieldSpecs} from './specs.js';
6
6
  import type {CronLike, FieldSpec} from './specs.js';
7
- import type {Pattern} from './ir.js';
7
+ import type {Field, Pattern} from './ir.js';
8
8
  import {includes, toFieldNumber, unique} from './util.js';
9
9
  import {isQuartzDate, isQuartzWeekday} from './validate.js';
10
10
 
11
+ // The fixed-cycle time fields: their step intervals are measured against a
12
+ // closed cycle (60 seconds, 60 minutes, 24 hours), so a step is a true
13
+ // "every N" cadence only when it tiles that cycle. The calendar fields
14
+ // (date/month/weekday) have variable cycles and keep their step form.
15
+ const timeFieldCycle: Partial<Record<Field, number>> = {
16
+ hour: 24,
17
+ minute: 60,
18
+ second: 60
19
+ };
20
+
11
21
  // Quartz aliases: `?` reads "no specific value" (equivalent to `*`) in the
12
22
  // date and weekday fields, and a bare `L` weekday means Saturday.
13
23
  function applyQuartzAliases(cronPattern: CronLike): void {
@@ -40,7 +50,7 @@ function normalizeCronPattern(cronPattern: CronLike): Pattern {
40
50
  return;
41
51
  }
42
52
 
43
- cronPattern[field] = normalizeField(value, fieldSpecs[field]);
53
+ cronPattern[field] = normalizeField(value, field, fieldSpecs[field]);
44
54
  });
45
55
 
46
56
  // Every field is now a canonical string.
@@ -48,17 +58,20 @@ function normalizeCronPattern(cronPattern: CronLike): Pattern {
48
58
  }
49
59
 
50
60
  // Canonicalize a single validated field value to a string.
51
- function normalizeField(value: string, spec: FieldSpec): string {
61
+ function normalizeField(value: string, field: Field, spec: FieldSpec): string {
52
62
  const stringValue = '' + value;
53
63
 
54
64
  if (stringValue === '*') {
55
65
  return stringValue;
56
66
  }
57
67
 
68
+ const cycle = timeFieldCycle[field];
58
69
  const segments = stringValue.split(',').map(function canonical(segment) {
59
- return collapseDegenerateRange(
60
- collapseOnceStep(collapseUnitStep(segment, spec), spec), spec);
61
- });
70
+ return enumerateNonUniformStep(
71
+ collapseDegenerateRange(
72
+ collapseOnceStep(collapseUnitStep(segment, spec), spec), spec),
73
+ spec, cycle);
74
+ }).join(',').split(',');
62
75
 
63
76
  // A full-cycle segment covers the whole field.
64
77
  if (segments.indexOf('*') !== -1) {
@@ -115,6 +128,41 @@ function collapseOnceStep(segment: string, spec: FieldSpec): string {
115
128
  return start === '*' ? '' + spec.min : start;
116
129
  }
117
130
 
131
+ // An unbounded step in a fixed-cycle time field is a true "every N" cadence
132
+ // only when it tiles the cycle: the interval divides it evenly and the start
133
+ // falls within the first interval (`*/15`, `5/6`). A step that fails either
134
+ // test fires at irregular points within the cycle, so it reads as the literal
135
+ // list of those fires (`*/7` is `0,7,14,…`), the same as if it were written
136
+ // out. Calendar fields (no `cycle`), bounded steps (`9-17/2`, a per-window
137
+ // stride), and non-step segments are left untouched.
138
+ function enumerateNonUniformStep(
139
+ segment: string,
140
+ spec: FieldSpec,
141
+ cycle: number | undefined
142
+ ): string {
143
+ const parts = segment.split('/');
144
+
145
+ if (typeof cycle !== 'number' || parts.length !== 2 ||
146
+ includes(parts[0], '-')) {
147
+ return segment;
148
+ }
149
+
150
+ const interval = +parts[1];
151
+ const start = parts[0] === '*' ? spec.min : toFieldNumber(parts[0]);
152
+
153
+ if (cycle % interval === 0 && start < interval) {
154
+ return segment;
155
+ }
156
+
157
+ const fires = [];
158
+
159
+ for (let value = start; value <= (spec.top as number); value += interval) {
160
+ fires.push(value);
161
+ }
162
+
163
+ return fires.join(',');
164
+ }
165
+
118
166
  // A degenerate range (`9-9`) fires once, so it reads as its single value.
119
167
  // A stepped degenerate range (`9-9/5`) likewise fires only at its start.
120
168
  function collapseDegenerateRange(segment: string, spec: FieldSpec): string {
@@ -462,7 +462,9 @@ function duringHours(ir: IR, times: HourTimesPlan, sep: string): string {
462
462
  return joinList(windows);
463
463
  }
464
464
 
465
- return 'in den Stunden von ' + joinList(times.fires.map(String)) + ' Uhr';
465
+ // A discrete set of hours is a list, not a range, so it takes no "von"
466
+ // (which would read as "von X bis Y"); it mirrors the minute list form.
467
+ return 'in den Stunden ' + joinList(times.fires.map(String)) + ' Uhr';
466
468
  }
467
469
 
468
470
  // --- Renderers. ---
@@ -818,7 +820,10 @@ const de: Language<GermanStyle> = {
818
820
  fallback: 'ein unlesbares Cron-Muster',
819
821
  options: normalizeOptions,
820
822
  reboot: 'beim Systemstart',
821
- sentence: (description) => 'Läuft ' + description + '.'
823
+ // A description ending in a German ordinal already carries its period
824
+ // ("…am 8."), so closing the sentence must not double it.
825
+ sentence: (description) =>
826
+ 'Läuft ' + description + (description.endsWith('.') ? '' : '.')
822
827
  };
823
828
 
824
829
  export default de;
@@ -547,7 +547,10 @@ const renderers = {
547
547
  // Phrase a `start/interval` step segment for a field that cycles every 60
548
548
  // units (seconds and minutes). `unit` is the singular noun and `anchor` is
549
549
  // the larger unit the values are counted against. Interval-one steps never
550
- // arrive here: normalization collapses them to ranges or `*`.
550
+ // arrive here: normalization collapses them to ranges or `*`. Nor do uneven
551
+ // steps that fail to tile the cycle: normalization rewrites those to the
552
+ // literal list of their fires, so only a clean cadence (interval dividing
553
+ // 60, start within the first interval) reaches a step renderer.
551
554
  function stepCycle60(segment: StepSegment, unit: string,
552
555
  anchor: string, opts: NormalizedOptions): string {
553
556
  // A bounded start (`a-b/n`) applies the interval within the range.
@@ -559,6 +562,8 @@ function stepCycle60(segment: StepSegment, unit: string,
559
562
  const interval = segment.interval;
560
563
 
561
564
  if (start !== 0) {
565
+ // A short offset cadence lists its fires; a longer one names the
566
+ // interval and its starting offset ("every six minutes from five …").
562
567
  if (segment.fires.length <= 3) {
563
568
  return listPastThe(numberWords(segment.fires, opts), unit, anchor,
564
569
  opts);
@@ -569,18 +574,8 @@ function stepCycle60(segment: StepSegment, unit: string,
569
574
  ' past the ' + anchor;
570
575
  }
571
576
 
572
- // A step reads as a natural cadence ("every N minutes") only when it
573
- // divides the cycle evenly, mirroring the hour field's `24 % n` rule.
574
- if (60 % interval === 0) {
575
- return 'every ' + getNumber(interval, opts) + ' ' + unit + 's';
576
- }
577
-
578
- if (segment.fires.length <= 2) {
579
- return listPastThe(numberWords(segment.fires, opts), unit, anchor, opts);
580
- }
581
-
582
- return 'every ' + getNumber(interval, opts) + ' ' + unit +
583
- 's past the ' + anchor;
577
+ // A clean stride from the top of the cycle is the bare cadence.
578
+ return 'every ' + getNumber(interval, opts) + ' ' + unit + 's';
584
579
  }
585
580
 
586
581
  // Phrase a `start/interval` step segment for the hour field (cycles every
@@ -594,18 +589,18 @@ function stepHours(segment: StepSegment, opts: NormalizedOptions): string {
594
589
  const start = segment.startToken === '*' ? 0 : +segment.startToken;
595
590
  const interval = segment.interval;
596
591
 
597
- if (start === 0 && 24 % interval === 0) {
592
+ // A clean stride from midnight is the bare cadence. (An uneven stride is
593
+ // rewritten to its fires upstream and never reaches here.)
594
+ if (start === 0) {
598
595
  return 'every ' + getNumber(interval, opts) + ' hours';
599
596
  }
600
597
 
598
+ // A short offset cadence lists its fires; a longer one names the interval
599
+ // and its start ("every three hours from 2 a.m.").
601
600
  if (segment.fires.length <= 3) {
602
601
  return 'at ' + hourTimes(segment.fires, opts);
603
602
  }
604
603
 
605
- if (start === 0) {
606
- return 'every ' + getNumber(interval, opts) + ' hours from midnight';
607
- }
608
-
609
604
  return 'every ' + getNumber(interval, opts) + ' hours from ' +
610
605
  getTime({hour: start, minute: 0}, opts);
611
606
  }
@@ -1015,17 +1015,9 @@ function stepCycle60(
1015
1015
  unit + ' ' + start + ' de cada ' + anchor;
1016
1016
  }
1017
1017
 
1018
- if (60 % interval === 0) {
1019
- return 'cada ' + numero(interval, opts) + ' ' + unit + 's';
1020
- }
1021
-
1022
- if (segment.fires.length <= 2) {
1023
- return 'en los ' + unit + 's ' + joinList(wordList(segment.fires)) +
1024
- ' de cada ' + anchor;
1025
- }
1026
-
1027
- return 'cada ' + numero(interval, opts) + ' ' + unit + 's de cada ' +
1028
- anchor;
1018
+ // A clean stride from the top of the cycle is the bare cadence. (An uneven
1019
+ // stride is rewritten to its fires upstream and never reaches here.)
1020
+ return 'cada ' + numero(interval, opts) + ' ' + unit + 's';
1029
1021
  }
1030
1022
 
1031
1023
  // "cada seis horas", "a las 9:00, a las 11:00 y a la 1:00", or "cada
@@ -1038,7 +1030,9 @@ function stepHours(segment: StepSegment, opts: Opts): string {
1038
1030
  const start = segment.startToken === '*' ? 0 : +segment.startToken;
1039
1031
  const interval = segment.interval;
1040
1032
 
1041
- if (start === 0 && 24 % interval === 0) {
1033
+ // A clean stride from midnight is the bare cadence. (An uneven stride is
1034
+ // rewritten to its fires upstream and never reaches here.)
1035
+ if (start === 0) {
1042
1036
  return 'cada ' + numero(interval, opts) + ' horas';
1043
1037
  }
1044
1038
 
@@ -1046,10 +1040,6 @@ function stepHours(segment: StepSegment, opts: Opts): string {
1046
1040
  return groupClockTimesByArticle(atTimes(segment.fires, opts));
1047
1041
  }
1048
1042
 
1049
- if (start === 0) {
1050
- return 'cada ' + numero(interval, opts) + ' horas desde medianoche';
1051
- }
1052
-
1053
1043
  return 'cada ' + numero(interval, opts) + ' horas a partir de ' +
1054
1044
  timePhrase(start, 0, null, opts);
1055
1045
  }
@@ -62,7 +62,6 @@ interface UnitForms {
62
62
  anchor: string;
63
63
  ela: string;
64
64
  gen: string;
65
- restart: string;
66
65
  }
67
66
 
68
67
  // Genitive numerals for the "N <unit>in välein" construction, spelled
@@ -179,15 +178,13 @@ const units: {minute: UnitForms; second: UnitForms} = {
179
178
  mark: 'joka tunti',
180
179
  anchor: 'jokaisen tunnin',
181
180
  ela: 'minuutista',
182
- gen: 'minuutin',
183
- restart: 'tasatunnista alkaen'
181
+ gen: 'minuutin'
184
182
  },
185
183
  second: {
186
184
  mark: 'joka minuutti',
187
185
  anchor: 'jokaisen minuutin',
188
186
  ela: 'sekunnista',
189
- gen: 'sekunnin',
190
- restart: 'joka minuutti'
187
+ gen: 'sekunnin'
191
188
  }
192
189
  };
193
190
 
@@ -858,15 +855,9 @@ function stepCycle60(
858
855
  ' alkaen';
859
856
  }
860
857
 
861
- if (60 % interval === 0) {
862
- return cadence;
863
- }
864
-
865
- if (segment.fires.length <= 2) {
866
- return atMarks(joinList(wordList(segment.fires)), unit, true);
867
- }
868
-
869
- return cadence + ' ' + unit.restart;
858
+ // A clean stride from the top of the cycle is the bare cadence. (An uneven
859
+ // stride is rewritten to its fires upstream and never reaches here.)
860
+ return cadence;
870
861
  }
871
862
 
872
863
  // "kahden tunnin välein", "klo 0, 10 ja 20", or "viiden tunnin välein
@@ -880,7 +871,9 @@ function stepHours(segment: StepSegment, opts: NormalizedOptions): string {
880
871
  const interval = segment.interval;
881
872
  const cadence = genitive(interval, opts) + ' tunnin välein';
882
873
 
883
- if (start === 0 && 24 % interval === 0) {
874
+ // A clean stride from midnight is the bare cadence. (An uneven stride is
875
+ // rewritten to its fires upstream and never reaches here.)
876
+ if (start === 0) {
884
877
  return cadence;
885
878
  }
886
879
 
@@ -888,10 +881,6 @@ function stepHours(segment: StepSegment, opts: NormalizedOptions): string {
888
881
  return kloList(segment.fires, opts);
889
882
  }
890
883
 
891
- if (start === 0) {
892
- return cadence + ' keskiyöstä alkaen';
893
- }
894
-
895
884
  return cadence + ' klo ' + hourElatives[start] + ' alkaen';
896
885
  }
897
886
 
@@ -217,7 +217,12 @@ function hourFrame(ir: IR): string {
217
217
 
218
218
  // A repeating minute step, optionally confined to active hours.
219
219
  function renderMinuteFrequency(ir: IR, plan: PlanNode): string {
220
- const base = cadence(stepSegment(ir, 'minute').interval, UNITS.minute);
220
+ const minuteStep = stepSegment(ir, 'minute');
221
+ // A "每N分钟" cadence is only faithful from the top of the hour; an offset
222
+ // step (5/6 fires at :05,:11,…) enumerates its fires instead.
223
+ const base = minuteStep.startToken === '*' ?
224
+ cadence(minuteStep.interval, UNITS.minute) :
225
+ renderMinutePast(ir);
221
226
  const {hours} = plan as Extract<PlanNode, {kind: 'minuteFrequency'}>;
222
227
 
223
228
  if (hours.kind === 'step') {
@@ -323,8 +328,9 @@ function renderHourRange(ir: IR, plan: PlanNode): string {
323
328
  range.last + '分之间,每分钟';
324
329
  }
325
330
 
326
- // A stepped hour field: "每2小时", or "从凌晨0点起,每5小时" when the step does not
327
- // divide 24 (the cadence wraps), or its discrete fires as clock words.
331
+ // A stepped hour field: "每2小时", or its two fires as clock words when the
332
+ // stride fires only twice. An uneven stride (one that does not divide 24) is
333
+ // rewritten to its fire list upstream and never reaches here.
328
334
  function renderHourStep(ir: IR): string {
329
335
  const segment = stepSegment(ir, 'hour');
330
336
 
@@ -332,16 +338,11 @@ function renderHourStep(ir: IR): string {
332
338
  return hourList(ir);
333
339
  }
334
340
 
335
- // A step that fires only twice reads as two clock times ("凌晨0点和13点").
341
+ // A step that fires only twice reads as two clock times ("凌晨0点和正午").
336
342
  if (segment.fires.length <= 2) {
337
343
  return joinAnd(segment.fires.map(hourWord));
338
344
  }
339
345
 
340
- if (24 % segment.interval !== 0) {
341
- return '从' + hourWord(segment.fires[0]) + '起,' +
342
- cadence(segment.interval, UNITS.hour);
343
- }
344
-
345
346
  return cadence(segment.interval, UNITS.hour);
346
347
  }
347
348