Skip to content

Instantly share code, notes, and snippets.

@lukeed
Last active May 7, 2023 18:53
Show Gist options
  • Star 30 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save lukeed/1bbdde31b5861c95f44c3a49a88f5213 to your computer and use it in GitHub Desktop.
Save lukeed/1bbdde31b5861c95f44c3a49a88f5213 to your computer and use it in GitHub Desktop.
cron syntax & human readable output — https://t.co/CBThaezwzC
// https://crontab.guru/
// https://www.typescriptlang.org/play?ts=4.9.5#code/C4TwDgpgBAwgSgeQHJQLxQAYBIDe9kB0AogB5gBOEAzlQJYD2AdgL5S75LFmU0Mtt5EnUhWp0mrdkK6jeEgRxk9xLDAG4AUAHotUPVAB6Afg0bQkKABVqwAIxpY0gBIBXALYBDRgB4ARAGYABigATigAKgiI3wA+TR19QxMzcGhrKmAAJgdFV08fX0iwyJLY+N19Y1NzNJt-HOd3Lz9wrXriqPCy7Qq9KpSLdOAAFgbCPObCtoitUZLouJ7E-pqrGwBWMc4Jgtb6+dLFhMrk1aGANi2CHZaZ-c7u477T1LWMgHYrm99bTdbsg6PXpJaqvIYADi+TV2UXuXSOwJWYJsYXQuWhtxK00O5WWL0GNlswTRjXymKg+y0AKBeNBBIytnsJPGGMKsJm2WGNJOdNqDOyzO2rOCmUpAOxs25z15bzs9UF11ZYVFHLuUC5CNpAz5dlGCu+rV+M0ukX2UpB2tlRv1rIOFIWuJ5SwAtK6AMYuYCu52mAAmEDdABsPJQoIwPG5qGAPG7oBwoDgNHoIGR6ORgFBViJlHwHABycJ5qAAHzD7gARhByCXMK1cIwK1XmOpTMnU+nM68bpYoCngBBGL6qI5kDEHEnElZeyR+4Ph9gcLRGAAzKtQACykkXK7XTi3S9X1YAIpuBAe1+vT7hz8eAOrNieTqBGKy0SPeJwAGnXY77A6HmDXju1Y9n+c5QBk5BLgA5g+T6JC+R4eCA3iXp+UAnuhR63r+M7-vOQGHhh06zgBkEwXB8EIcRYEAb4vhQI+VH6C+lhMcxUAAFyATglisEwAhHpRHHcYwEAAG5Vuxk6iRJUlUbJknkJoE4JN6jETmcb4QB+9AuOQ6HrkunoQGO6BMUZDb9iR+FQAWRbSS+Th6dWtHDvZegvnmcnkCAUBuMZ-ZFgk8wAPpQOFoXSdxzn6TZ4ELjeUBOPFAENm4lbkM2z6YD5fkBVZ0D0MuUAABYuQIe4YFAIXJRF9VRU+MUVW5tZaIRu6pcO6WZdlL4YHl-mBUVJWDbgRBKShX52eV+l5jE2W1a0KWRRF0Vhj5THcZZJldTxSXrntPVNtVjnJS1eHgR5-UeBmBUmc6uCbtVCSHZEq2NTJ51xa1iXAXVrXHVl1U3RmuB7pxuAAAoeL6qELS9uiHStDXrbFrmXQBGB1tuREABpHY2wM5Rgt1DYVj04Ke0YZL2k0CBNVYoXj6F5rN5DzYtSMzCjkXrWJSlbRuw17dj7W4xehMZSdjFPk5F2ke5hYk2NOCM75qGs-dQUIzVuitG9DVrU130Y4r+3-SlgNE31uX0+Nk2a3Z2sQJzUDFWVLmU1VeszMjRufYkzU-Zj8440lBPW9LxP9ar6soeuWvDW7Htx47LMzS5nOI37PMB-zm1BxtSkqYkqww3DKBR5lZmy5OVeh1AwReYEgR5utDfm-YLe2O3Jud7Z2Qt5kfdfQP4H1C3-ij0X48AaMLfDDP+jcXPw6bC36zL3oq97ZcLfnNvXFQGvUCfC37xH7vrWQi34JXyfe1hC3IQPwuSDNqX+irEhIBIBGOlT5AzMtJU+zc7IAGUXCDmQg-U+3c7LriYL6WBHc9pDzspYFw1AUEgDgXtSedlbwQF9GJKguD8GtQXpg0q+lyGoP7ntDedkABiUEKFoNavvSBt19IcJNgLKsmhEhMVWEgxgwBSr-3fEAomICnzwJynmAAUl4FwIY8GcMbhgvMLCIDlnIOo3ylDG6ELzOuEMbpSomPNtQvMABBCgtBAw2Nssw8xDCx57yUco6BrstHm3PnZXxgZNGMJvko+xLhoIuAyK48Cz9IEQDAP2aO8SAJEiUQgN0wB6CZXScORkSikD0EkmkgJtlbA6KPAGCA5SBE+S-noH+yFUJMEkVhW6EB0LEIgAAa1wbXaSSFrKtXstJTyG52mlT2uMjiL5ekDOQrM5WXlgq6FWgcCZK8oCLNwaLDq1ZbxS16iDDCyFpE6Rwr7TZURbzbJ3rsiA-T9m-XDv9Y51cZax3tmrR2t5Wb7I9pI6AAB3Z5fTs43PqgcLQ9yRLF3kvBba0yDkS2rIdL5wMHk5T2cssZqy7ZMygPspcAhxGSMufDLmRtIhvRxdxPFflfqHN2Sc75RLfKCQuQA7wOFWBkqetMqlP4aW3MOvC5ijKIWvMbmLVlnzG5A1tgNX58c+WAuWcC0qYKIVu0FVTYVvLRU53FTMSVClEXkG2SiiRMy3niwOuy7F8ynkvPxY3a6nK-KksYHTYlDsmZOzzG4aZUKEi3INhEBlbqlnMrlQq51KrVa-ypfyqAZK05BsTs7MNusI0wtzha5FsbZXm3leitlWLk1qv+Zqvy2rdX9P1X6rNGsc0hrzWKwtUbWjFoaYLS1gjrVPnWiM6ALLK1HiTRgbZL4KX2s9YWHFCyZUevNl6jAIKBAIHIL6JcHhAzeCPAtd2o16ahrtaa+q06SgxqZWipKirzbKrOaqgNOBU28vTV4X0mYdU7r3Qeo9J7+LnuJZeyR17wq3t2fetd8by3vKIs+2yr6Va-N3fu8MIHT2Nv8qi2gJVaAZloMOASbaUIArskCkq27wXNvzRsm9nQ4UxuHTaqZdrH3-UxUqm2s7XUPoJUWH5H6F0itPbgLDwHj1MaNtO+lCLhMJsrah8C6H+rbuk0BnDcnWC-qgIewM3K-7foWsOA1EnjXydWopuDymEOi2Q2udTaUBMk20zgGTenQNGcHP6rlgaNbUbzLR-9TbIWnqs0a98JroUwY3Oa9jhcS0Luc463jM6V2lvXbZTdXmfOHv02ewLflgsJy1l26DxFe3wfdYh2yf0UMzs8wBnT2Hit+cM8Z0zaaLMZtbbW7NVW7XhuY4lqN-avoqaQ5llr1a32Fd011vD4Ggt-JG7msb0XiOkfI0Nj96rQvhfo3q2zLHe1sYRRxodm0x1dIy6y6di251cckSso+1FZv5cJe+jb6qjz1tK9uyD1iLvhVaLB8I9W408YW-x6ONajuOyBzRrVdGANg-1XtjNw4PB9fM92yHWhYPTaLj9hKLmjmtbEwD1HrN8PY92xm-b7tDv06DSdjHEWoAMai8TmY0PrtSqtZx9Lk6nWvaE05kTGGUdBrR2F2B0W-VCrtZJwXUOksw8cw1+HrnafeoZgz9HeDVfktizpUVGbcdkaM4T98-Kava4lbDstTXqdVsR6c+XnONZK4oRb9XlKbMCrtwdsrJuufA-w-z8btLSc65F3dwdaXUUOtZXxl9AmcuU4Apuyjx7Y+Y+gMzgVHPyubfbaNyRCfNlJ7q3ruHkuPlG-+1XwHJfefl9t6zvHDvcBfqdxbovHby8u8b1oN3zePdU-m4bxbfvO+m+Vw20vBGdvh-7-bijw2Qvd7O4xivUeKvBonwloXuc+0pbTwOpFjzh2aGYKYBI0DYkkPQuWFwzjsPQSgOWIGPQG6H0jVJmDYFJKsKFEeLQNBCRt4KBD7muKWAuOhigTjK+vIlOJnpWpHEgTHDxHjJRNxIgeWqyngTnkjm+rgEQYJsfKQWhkTCTLgHxHQYpEIjKEMDAXARmOgNAbAfAVMIEAxKWFMLYCIVAFMJkBIVMP4DIa0FyDWL4OsBIecDEEsE6KsEVkerItHEMnoPAggnmIyHXhONfI3FUkolUqYY8vAmYrYP4DYcfO-NlJOuRIwLBLYNQTgB-BkGwTxB-NVG4cAFBB4cwJkN4R-IOP4S4UEapu4bBP4JEcwHujEbgB-FBipFmI7LoZlOhBAntAkbXIoi+AUdxAuNod4EgFJjgBAp-JwTYKFPHKFEyFAOqrYKzOzPNI6NKJaEME0ZNKFAKG0Y7JkJ0VnJqJociBkAMUzKFPKCMUGv4OMXNJMb0WcI0c0XqIsRrMMCsRzGsRaBsTMc0ZYegO0WMZnKsT0UcdMcALMb5EMcMeqpkJcWzBMTcUiPSPcaca0e0R0VcQcZ8fiDqA8SAEMX8aMQCe8dcRoesXcWCS0Qse0csYCd0XCbcd8YiSqOcaMaiTCUCRiVUEAA
type CRON = `${CRON.Expression} ${CRON.Expression} ${CRON.Expression} ${CRON.Expression} ${CRON.Expression}`;
// ^?
type Test1 = CRON.Human<"30 9 * * *">;
// ^?
type Test2 = CRON.Human<"* 9 * * *">;
// ^?
type Test3 = CRON.Human<"*/3 9 * * *">;
// ^?
type Test4 = CRON.Human<"*/3 */4 * * *">;
// ^?
type Test5 = CRON.Human<"*/3 * * * *">;
// ^?
type Test6 = CRON.Human<"* */3 * * *">;
// ^?
type Test7 = CRON.Human<"15 */2 * * *">;
// ^?
type Test8 = CRON.Human<"* * 3 * *">;
// ^?
type Test9 = CRON.Human<"* * */3 * *">;
// ^?
type Test10 = CRON.Human<"* * 3 */2 *">;
// ^?
type Test11 = CRON.Human<"* * 3 */2 4">;
// ^?
type Test12 = CRON.Human<"0 23 */2 */3 */4">;
// ^?
type Test13 = CRON.Human<"9 23 */2 */3 4">;
// ^?
type Test14 = CRON.Human<"*/15 */6 * 3 *">;
// ^?
type Test15 = CRON.Human<"* * * 3 *">;
// ^?
// ---cut---
declare namespace CRON {
export type Expression = '*' | number | `*/${number}`;
export type Human<T extends CRON> =
T extends `${infer M} ${infer H} ${infer DM} ${infer MM} ${infer DW}`
? Time<H,M> extends `${infer T extends string}`
? Day<MM, DM, DW> extends `${infer D extends string}`
? D extends ""
? T
: `${T} on ${D}`
: never
: never
: never;
// ---
type Time<Hour, Minute> =
Minute extends '*'
? Hour extends '*' ? 'every minute' // * * _ _ _
: Hour extends `${infer H extends number}` ? `every minute of hour ${H}` // * H _ _ _
: Hour extends `*/${infer H extends number}` ? `every minute of every ${Every<H, 'hour'>}` // * */H _ _ _
: never
: Minute extends `${infer M extends number}`
? Hour extends '*' ? `at minute-${M}` // M * _ _ _
: Hour extends `${infer H extends number}` ? `at ${H}:${Pad<M>}` // M H _ _ _
: Hour extends `*/${infer X extends number}` ? `at minute-${M} past every ${Every<X, 'hour'>}` // M */H _ _ _
: never
: Minute extends `*/${infer M extends number}`
? Hour extends '*' ? `every ${Every<M, 'minute'>}` // */M * _ _ _
: Hour extends `${infer H extends number}` ? `every ${Every<M, 'minute'>} of hour-${H}` // */M H _ _ _
: Hour extends `*/${infer X extends number}` ? `every ${Every<M, 'minute'>} of every ${Every<X, 'hour'>}` // */M */H _ _ _
: never
: never;
type Pad<N extends number> =
N extends 0 ? '00'
: N extends 1 ? '01'
: N extends 2 ? '02'
: N extends 3 ? '03'
: N extends 4 ? '04'
: N extends 5 ? '05'
: N extends 6 ? '06'
: N extends 7 ? '07'
: N extends 8 ? '08'
: N extends 9 ? '09'
: `${N}`;
type DayName<N extends number> =
N extends 0 ? 'Sunday'
: N extends 1 ? 'Monday'
: N extends 2 ? 'Tuesday'
: N extends 3 ? 'Wednesday'
: N extends 4 ? 'Thursday'
: N extends 5 ? 'Friday'
: N extends 6 ? 'Saturday'
: never;
type MonthName<N extends number> =
N extends 1 ? 'January'
: N extends 2 ? 'February'
: N extends 3 ? 'March'
: N extends 4 ? 'April'
: N extends 5 ? 'May'
: N extends 6 ? 'June'
: N extends 7 ? 'July'
: N extends 8 ? 'August'
: N extends 9 ? 'September'
: N extends 10 ? 'October'
: N extends 11 ? 'November'
: N extends 12 ? 'December'
: never;
type Day<Month, Date, Weekday> =
Date extends '*'
? Month extends '*'
? Weekday extends '*' ? '' // _ _ * * *
: Weekday extends `${infer W extends number}` ? DayName<W> // _ _ * * W
: Weekday extends `*/${infer W extends number}` ? `every ${Every<W, 'day of the week'>}` // _ _ * * */W
: never
: Month extends `${infer M extends number}`
? Weekday extends '*' ? `every day in ${MonthName<M>}` // _ _ * M *
: Weekday extends `${infer W extends number}` ? `every ${DayName<W>} in ${MonthName<M>}` // _ _ * M W
: Weekday extends `*/${infer W extends number}` ? `every ${Every<W, 'day of the week'>} in ${MonthName<M>}` // _ _ * M */W
: never
: Month extends `*/${infer M extends number}`
? Weekday extends '*' ? `every day in every ${Every<M, 'month'>}` // _ _ * */M *
: Weekday extends `${infer W extends number}` ? `every ${DayName<W>} in every ${Every<M, 'month'>}` // _ _ * */M W
: Weekday extends `*/${infer W extends number}` ? `every ${Every<W, 'day of the week'>} in every ${Every<M, 'month'>}` // _ _ * */M */W
: never
: never
: Date extends `${infer D extends number}`
? Month extends '*'
? Weekday extends '*' ? `the ${Ordinal<D>} of every month` // _ _ D * *
: Weekday extends `${infer W extends number}` ? `every ${DayName<W>} and the ${Ordinal<D>} of every month` // _ _ D * W
: Weekday extends `*/${infer W extends number}` ? `every ${Ordinal<D>} of the month if it is on every ${Every<W, 'day of the week'>}` // _ _ D * */W
: never
: Month extends `${infer M extends number}`
? Weekday extends '*' ? `every ${MonthName<M>} ${Ordinal<D>}` // _ _ D M *
: Weekday extends `${infer W extends number}` ? `the ${Ordinal<D>} and all ${DayName<W>}s in ${MonthName<M>}` // _ _ D M W
: Weekday extends `*/${infer W extends number}` ? `the ${Ordinal<D>} and every ${Every<W, 'day of the week'>} in ${MonthName<M>}` // _ _ D M */W
: never
: Month extends `*/${infer M extends number}`
? Weekday extends '*' ? `the ${Ordinal<D>} of every ${Every<M, 'month'>}` // _ _ D */M *
: Weekday extends `${infer W extends number}` ? `the ${Ordinal<D>} and all ${DayName<W>}s in every ${Every<M, 'month'>}` // _ _ D */M W
: Weekday extends `*/${infer W extends number}` ? `the ${Ordinal<D>} of every ${Every<M, 'month'>} if it is on every ${Every<W, 'day of the week'>}` // _ _ D */M */W
: never
: never
: Date extends `*/${infer D extends number}`
? Month extends '*'
? Weekday extends '*' ? `every ${Every<D, 'day of the month'>}` // _ _ */D * *
: Weekday extends `${infer W extends number}` ? `every ${Every<D, 'day of the month'>} if it is a ${DayName<W>}` // _ _ */D * W
: Weekday extends `*/${infer W extends number}` ? `every ${Every<D, 'of the month'>} if it is on every ${Every<W, 'day of the week'>}` // _ _ */D * */W
: never
: Month extends `${infer M extends number}`
? Weekday extends '*' ? `every ${Every<D, 'day'>} in ${MonthName<M>}` // _ _ */D M *
: Weekday extends `${infer W extends number}` ? `every ${Every<D, 'day'>} in ${MonthName<M>} if it is a ${DayName<W>}` // _ _ */D M W
: Weekday extends `*/${infer W extends number}` ? `every ${Every<D, 'day'>} in ${MonthName<M>} if it is on every ${Every<W, 'day of the week'>}` // _ _ */D M */W
: never
: Month extends `*/${infer M extends number}`
? Weekday extends '*' ? `every ${Every<D, 'day of the month'>} in every ${Every<M, 'month'>}` // _ _ */D */M *
: Weekday extends `${infer W extends number}` ? `every ${Every<D, 'day of the month'>} if it is a ${DayName<W>} in every ${Every<M, 'month'>}` // _ _ */D */M W
: Weekday extends `*/${infer W extends number}` ? `every ${Every<D, 'day of the month'>} if it is on every ${Every<W, 'day of the week'>} in every ${Every<M, 'month'>}` // _ _ */D */M */W
: never
: never
: never;
}
// unused, building block / tester
type _Digit<T extends number | `${number}` | `*/${number}`> =
T extends `*/${infer X extends number}` ? `${X}`
: T extends `${infer X extends number}` ? `${X}`
: T extends number ? `${T}`
: never;
type TestDigit = _Digit<"*/0" | "*/1" | "*/2" | "*/3" | "*/4" | "5" | 6>
// ^?
type Ordinal<N extends number> =
N extends 11 ? '11th'
: N extends 12 ? '12th'
: N extends 13 ? '13th'
: `${N}` extends `${string}1` ? `${N}st`
: `${N}` extends `${string}2` ? `${N}nd`
: `${N}` extends `${string}3` ? `${N}rd`
: `${N}th`;
type Every<N extends number, S extends string> = N extends 1 ? S : `${Ordinal<N>} ${S}`;
type Test_Every_1 = Every<1, 'hour'>;
// ^?
type Test_Every_2 = Every<2, 'hour'>;
// ^?
type Test_Every_3 = Every<3, 'hour'>;
// ^?
type Test_Every_4 = Every<4, 'hour'>;
// ^?
type Test_Every_12 = Every<12, 'hour'>;
// ^?
type Test_Every_22 = Every<22, 'hour'>;
// ^?
type Test_Every_11 = Every<11, 'hour'>;
// ^?
type Test_Every_21 = Every<21, 'hour'>;
// ^?
type Test_Every_13 = Every<13, 'hour'>;
// ^?
type Test_Every_23 = Every<23, 'hour'>;
// ^?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment