[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"sanity-OizTwDXR6a5r-iLUpC01jkK8NK-n7jldiUSA4CW2dlk":3,"sanity-FzJJs2TwPLb4bBvvA6tV8geoHwZiqkNBPapewydSNHc":835},{"data":4,"sourceMap":-1},{"latestPodcast":5,"latestReleases":14,"post":39,"recent":810},[6],{"_id":7,"publishedAt":8,"slug":9,"sponsored":12,"title":13},"4d0175f4-40a8-47eb-9bb3-a453b326aa7d","2026-07-03T07:40:00.000Z",{"_type":10,"current":11},"slug","the-good-the-bad-and-the-ai-apps",null,"The good, the bad, and the AI apps",[15,21,27,33],{"_id":16,"publishedAt":17,"slug":18,"title":20},"eb5b66eb-9410-4329-83bb-22bbff39402a","2026-04-28T13:00:00.000Z",{"_type":10,"current":19},"turn-scattered-knowledge-into-trusted-intelligence","Turning scattered knowledge into trusted intelligence: Stack Internal 2026.3",{"_id":22,"publishedAt":23,"slug":24,"title":26},"369c2401-b62e-4a37-8ff8-bf603023ecad","2026-03-02T15:03:00.988Z",{"_type":10,"current":25},"what-s-new-at-stack-overflow-march-2026","What’s new at Stack Overflow: March 2026",{"_id":28,"publishedAt":29,"slug":30,"title":32},"5e9053a4-07ea-447c-91ea-29e0b6228537","2026-02-02T15:00:00.000Z",{"_type":10,"current":31},"what-s-new-at-stack-overflow-february-2026","What’s new at Stack Overflow: February 2026",{"_id":34,"publishedAt":35,"slug":36,"title":38},"a1b538eb-a8a6-46d0-80a1-ac70ec9bb935","2026-01-05T10:00:00.000-05:00",{"_type":10,"current":37},"what-s-new-at-stack-overflow-january-2026","What’s new at Stack Overflow: January 2026",{"_createdAt":40,"_id":41,"_rev":42,"_type":43,"_updatedAt":44,"author":45,"body":61,"comments":788,"dateUrl":789,"excerpt":790,"image":791,"legacyBody":794,"product":12,"publishedAt":797,"slug":798,"sponsored":12,"tags":800,"title":809,"visible":788},"2023-05-25T09:37:04Z","wp-post-5147","dgl3SCUzppW3U2LvCoSK4S","blogPost","2023-07-13T14:55:01Z",[46],{"_createdAt":47,"_id":48,"_rev":49,"_type":50,"_updatedAt":51,"avatar":52,"employee":57,"name":58,"slug":59},"2023-05-23T16:27:18Z","wp-author-98","V4QUz6N4dC6YqomOXmf51f","blogAuthor","2023-12-14T17:26:19Z",{"_type":53,"asset":54},"image",{"_ref":55,"_type":56},"image-2aa54cc8630265ed245a9d72af4238af19e553f0-1024x1024-jpg","reference","former","Bret Copeland",{"current":60},"bret",[62,138,147,179,188,212,220,260,302,310,342,350,497,505,523,526,549,552,560,563,571,574,582,585,593,596,604,607,622,625,633,641,644,652,666,674,691,699,707,710,734,742,750,755],{"_key":63,"_type":64,"children":65,"markDefs":125,"style":137},"33b72002c44f","block",[66,71,76,80,85,89,94,98,103,107,112,116,121],{"_key":67,"_type":68,"marks":69,"text":70},"33b72002c44f0","span",[],"There are lots of domain-specific languages for schedules. The most prominent might be ",{"_key":72,"_type":68,"marks":73,"text":75},"33b72002c44f1",[74],"f7293c268abf","Cron",{"_key":77,"_type":68,"marks":78,"text":79},"33b72002c44f2",[]," (for *nix scheduled tasks), but there's also ",{"_key":81,"_type":68,"marks":82,"text":84},"33b72002c44f3",[83],"980f978a64ca","RRULE",{"_key":86,"_type":68,"marks":87,"text":88},"33b72002c44f4",[]," (for iCalendar events) and many others. Cron isn't exactly what I'd call human-friendly. Can you tell me what ",{"_key":90,"_type":68,"marks":91,"text":93},"33b72002c44f5",[92],"code","10 8,20 * 8 1-5",{"_key":95,"_type":68,"marks":96,"text":97},"33b72002c44f6",[]," means if you don't use Cron often? Some DSLs go in the opposite direction and are wildly verbose, or will sacrifice expressiveness for simplicity. So... I wrote my own DSL to ",{"_key":99,"_type":68,"marks":100,"text":102},"33b72002c44f7",[101],"509157f7f9b1","solve those problems",{"_key":104,"_type":68,"marks":105,"text":106},"33b72002c44f8",[],". It's called Schyntax, and we're already using it in production at Stack Overflow. Part 1 of this post is about the language itself. In ",{"_key":108,"_type":68,"marks":109,"text":111},"33b72002c44f9",[110],"e37ea6e642c9","Part 2",{"_key":113,"_type":68,"marks":114,"text":115},"33b72002c44f10",[],", we'll look at how to setup Schyntax-based scheduled task runners in both JavaScript and C# (there is also a Go implementation in progress, if you'd like to know when it's ready, follow me ",{"_key":117,"_type":68,"marks":118,"text":120},"33b72002c44f11",[119],"5714674cd080","on Twitter",{"_key":122,"_type":68,"marks":123,"text":124},"33b72002c44f12",[],"... where I almost never tweet anything).",[126,129,131,133,135],{"_key":74,"_type":127,"href":128,"reference":12},"link","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCron",{"_key":83,"_type":127,"href":130,"reference":12},"http:\u002F\u002Fwww.kanzaki.com\u002Fdocs\u002Fical\u002Frrule.html",{"_key":101,"_type":127,"href":132,"reference":12},"https:\u002F\u002Fxkcd.com\u002F927\u002F",{"_key":110,"_type":127,"href":134,"reference":12},"http:\u002F\u002Fbret.codes\u002Fschyntax-part-2",{"_key":119,"_type":127,"href":136,"reference":12},"https:\u002F\u002Ftwitter.com\u002Fbretcope","normal",{"_key":139,"_type":64,"children":140,"markDefs":145,"style":146},"e71368bb91fe",[141],{"_key":142,"_type":68,"marks":143,"text":144},"e71368bb91fe0",[],"Schyntax Syntax",[],"h3",{"_key":148,"_type":64,"children":149,"markDefs":178,"style":137},"beeb52cc155a",[150,154,158,162,166,170,174],{"_key":151,"_type":68,"marks":152,"text":153},"beeb52cc155a0",[],"In contrast with other scheduling DSLs, Schyntax explicitly tries to be powerful, easy to remember, and terse, but human readable. To a programmer, the syntax should feel immediately familiar and intuitive. For example, ",{"_key":155,"_type":68,"marks":156,"text":157},"beeb52cc155a1",[92],"hour(5)",{"_key":159,"_type":68,"marks":160,"text":161},"beeb52cc155a2",[]," means to run on the fifth hour of every day. ",{"_key":163,"_type":68,"marks":164,"text":165},"beeb52cc155a3",[92],"minute(*)",{"_key":167,"_type":68,"marks":168,"text":169},"beeb52cc155a4",[]," means to run every minute. ",{"_key":171,"_type":68,"marks":172,"text":173},"beeb52cc155a5",[92],"seconds(3,8,52)",{"_key":175,"_type":68,"marks":176,"text":177},"beeb52cc155a6",[]," would run on the third, eighth, and fifty-second seconds of every minute.",[],{"_key":180,"_type":64,"children":181,"markDefs":186,"style":187},"091df0c1f087",[182],{"_key":183,"_type":68,"marks":184,"text":185},"091df0c1f0870",[],"Ranges",[],"h4",{"_key":189,"_type":64,"children":190,"markDefs":211,"style":137},"8606ec8ac5b8",[191,195,199,203,207],{"_key":192,"_type":68,"marks":193,"text":194},"8606ec8ac5b80",[],"Want to define a range? Borrowing range operators from modern programming languages, ",{"_key":196,"_type":68,"marks":197,"text":198},"8606ec8ac5b81",[92],"minute(5..7)",{"_key":200,"_type":68,"marks":201,"text":202},"8606ec8ac5b82",[]," would run every minute between the fifth and seventh minutes, inclusive (5,6,7). ",{"_key":204,"_type":68,"marks":205,"text":206},"8606ec8ac5b83",[92],"minute(5..\u003C7)",{"_key":208,"_type":68,"marks":209,"text":210},"8606ec8ac5b84",[]," is the same range, except it excludes the end value (5,6).",[],{"_key":213,"_type":64,"children":214,"markDefs":219,"style":187},"ecaae9979276",[215],{"_key":216,"_type":68,"marks":217,"text":218},"ecaae99792760",[],"Intervals",[],{"_key":221,"_type":64,"children":222,"markDefs":259,"style":137},"7ccb46c6df13",[223,227,231,235,239,243,247,251,255],{"_key":224,"_type":68,"marks":225,"text":226},"7ccb46c6df130",[],"What if you want to run every fifth minute? As a programmer, if you wanted to run a command on every fifth iteration of a loop, you might use the modulus operator (typically ",{"_key":228,"_type":68,"marks":229,"text":230},"7ccb46c6df131",[92],"%",{"_key":232,"_type":68,"marks":233,"text":234},"7ccb46c6df132",[],"). {% highlight javascript %} for (var i = 0; i \u003C 50000; i++) { if (i % 5 == 0) { \u002F\u002F code to run on every fifth iteration } } {% endhighlight %} Schyntax captures the spirit of that by using ",{"_key":236,"_type":68,"marks":237,"text":238},"7ccb46c6df133",[92],"minute(* % 5)",{"_key":240,"_type":68,"marks":241,"text":242},"7ccb46c6df134",[]," to indicate \"run every five minutes.\" Or, you could use ",{"_key":244,"_type":68,"marks":245,"text":246},"7ccb46c6df135",[92],"minute(10..22 % 3)",{"_key":248,"_type":68,"marks":249,"text":250},"7ccb46c6df136",[]," to run every third minute starting at the tenth, and ending at the twenty-second (effectively the same as ",{"_key":252,"_type":68,"marks":253,"text":254},"7ccb46c6df137",[92],"minute(10,13,16,19,22)",{"_key":256,"_type":68,"marks":257,"text":258},"7ccb46c6df138",[],").",[],{"_key":261,"_type":64,"children":262,"markDefs":300,"style":301},"fd98ea46f76c",[263,267,271,275,279,283,287,291,296],{"_key":264,"_type":68,"marks":265,"text":266},"fd98ea46f76c0",[],"Intervals always reset at the beginning of a range. For example, ",{"_key":268,"_type":68,"marks":269,"text":270},"fd98ea46f76c1",[92],"minutes(*)",{"_key":272,"_type":68,"marks":273,"text":274},"fd98ea46f76c2",[]," is implicitly the range ",{"_key":276,"_type":68,"marks":277,"text":278},"fd98ea46f76c3",[92],"minutes(0..59)",{"_key":280,"_type":68,"marks":281,"text":282},"fd98ea46f76c4",[],", so the interval is always relative to the 0 minute of each hour. ",{"_key":284,"_type":68,"marks":285,"text":286},"fd98ea46f76c5",[92],"minutes(*%17)",{"_key":288,"_type":68,"marks":289,"text":290},"fd98ea46f76c6",[]," does ",{"_key":292,"_type":68,"marks":293,"text":295},"fd98ea46f76c7",[294],"strong","NOT",{"_key":297,"_type":68,"marks":298,"text":299},"fd98ea46f76c8",[]," mean \"run every 17th minute of the day.\" It means, \"run every 17th minute of every hour starting at the first (0) minute of the hour.\"",[],"blockquote",{"_key":303,"_type":64,"children":304,"markDefs":309,"style":187},"7c566ce745d6",[305],{"_key":306,"_type":68,"marks":307,"text":308},"7c566ce745d60",[],"Exclusions",[],{"_key":311,"_type":64,"children":312,"markDefs":341,"style":137},"07d616c354cb",[313,317,321,325,329,333,337],{"_key":314,"_type":68,"marks":315,"text":316},"07d616c354cb0",[],"The logical negation operator is ",{"_key":318,"_type":68,"marks":319,"text":320},"07d616c354cb1",[92],"!",{"_key":322,"_type":68,"marks":323,"text":324},"07d616c354cb2",[]," in many programming languages. So if you want to say, \"run every minute except the third,\" you could write ",{"_key":326,"_type":68,"marks":327,"text":328},"07d616c354cb3",[92],"minute(!3)",{"_key":330,"_type":68,"marks":331,"text":332},"07d616c354cb4",[],". To run every fourth hour except the fifteenth, use ",{"_key":334,"_type":68,"marks":335,"text":336},"07d616c354cb5",[92],"hours(*%4, !15)",{"_key":338,"_type":68,"marks":339,"text":340},"07d616c354cb6",[],". You can exclude any value, range, or interval. Exclusions always take precedence, so be careful not to exclude too much.",[],{"_key":343,"_type":64,"children":344,"markDefs":349,"style":187},"079596b0f162",[345],{"_key":346,"_type":68,"marks":347,"text":348},"079596b0f1620",[],"Expression Names",[],{"_key":351,"_type":64,"children":352,"markDefs":494,"style":137},"2dc648525fb6",[353,357,361,365,369,373,377,381,385,388,392,395,399,402,406,410,414,418,422,426,430,433,437,441,445,448,451,455,459,463,467,470,474,477,481,485,490],{"_key":354,"_type":68,"marks":355,"text":356},"2dc648525fb60",[],"You might have noticed that both plural and singular expression names work (i.e. ",{"_key":358,"_type":68,"marks":359,"text":360},"2dc648525fb61",[92],"hour()",{"_key":362,"_type":68,"marks":363,"text":364},"2dc648525fb62",[]," vs ",{"_key":366,"_type":68,"marks":367,"text":368},"2dc648525fb63",[92],"hours()",{"_key":370,"_type":68,"marks":371,"text":372},"2dc648525fb64",[],"). In fact, most expressions have several aliases to make it easier to remember without having to go look at the documentation. For example, for minutes, you could use ",{"_key":374,"_type":68,"marks":375,"text":376},"2dc648525fb65",[92],"m",{"_key":378,"_type":68,"marks":379,"text":380},"2dc648525fb66",[],", ",{"_key":382,"_type":68,"marks":383,"text":384},"2dc648525fb67",[92],"min",{"_key":386,"_type":68,"marks":387,"text":380},"2dc648525fb68",[],{"_key":389,"_type":68,"marks":390,"text":391},"2dc648525fb69",[92],"minute",{"_key":393,"_type":68,"marks":394,"text":380},"2dc648525fb610",[],{"_key":396,"_type":68,"marks":397,"text":398},"2dc648525fb611",[92],"minutes",{"_key":400,"_type":68,"marks":401,"text":380},"2dc648525fb612",[],{"_key":403,"_type":68,"marks":404,"text":405},"2dc648525fb613",[92],"minuteOfHour",{"_key":407,"_type":68,"marks":408,"text":409},"2dc648525fb614",[]," or ",{"_key":411,"_type":68,"marks":412,"text":413},"2dc648525fb615",[92],"minutesOfHour",{"_key":415,"_type":68,"marks":416,"text":417},"2dc648525fb616",[],". Schyntax is whitespace-insensitive and case-insensitive, so ",{"_key":419,"_type":68,"marks":420,"text":421},"2dc648525fb617",[92],"hours(*)",{"_key":423,"_type":68,"marks":424,"text":425},"2dc648525fb618",[]," is the same as ",{"_key":427,"_type":68,"marks":428,"text":429},"2dc648525fb619",[92],"HOURS ( * )",{"_key":431,"_type":68,"marks":432,"text":425},"2dc648525fb620",[],{"_key":434,"_type":68,"marks":435,"text":436},"2dc648525fb621",[92],"hOuRs( *)",{"_key":438,"_type":68,"marks":439,"text":440},"2dc648525fb622",[],". In addition to the ",{"_key":442,"_type":68,"marks":443,"text":444},"2dc648525fb623",[92],"hours",{"_key":446,"_type":68,"marks":447,"text":380},"2dc648525fb624",[],{"_key":449,"_type":68,"marks":450,"text":398},"2dc648525fb625",[92],{"_key":452,"_type":68,"marks":453,"text":454},"2dc648525fb626",[],", and ",{"_key":456,"_type":68,"marks":457,"text":458},"2dc648525fb627",[92],"seconds",{"_key":460,"_type":68,"marks":461,"text":462},"2dc648525fb628",[]," expressions we've seen so far, Schyntax also supports ",{"_key":464,"_type":68,"marks":465,"text":466},"2dc648525fb629",[92],"daysOfWeek",{"_key":468,"_type":68,"marks":469,"text":380},"2dc648525fb630",[],{"_key":471,"_type":68,"marks":472,"text":473},"2dc648525fb631",[92],"daysOfMonth",{"_key":475,"_type":68,"marks":476,"text":454},"2dc648525fb632",[],{"_key":478,"_type":68,"marks":479,"text":480},"2dc648525fb633",[92],"dates",{"_key":482,"_type":68,"marks":483,"text":484},"2dc648525fb634",[]," expressions. Complete documentation of the supported expressions can be found on the ",{"_key":486,"_type":68,"marks":487,"text":489},"2dc648525fb635",[488],"d0481406ff59","Schyntax GitHub page",{"_key":491,"_type":68,"marks":492,"text":493},"2dc648525fb636",[],".",[495],{"_key":488,"_type":127,"href":496,"reference":12},"https:\u002F\u002Fgithub.com\u002Fschyntax\u002Fschyntax#expressions",{"_key":498,"_type":64,"children":499,"markDefs":504,"style":146},"ac5dfd55499f",[500],{"_key":501,"_type":68,"marks":502,"text":503},"ac5dfd55499f0",[],"Examples",[],{"_key":506,"_type":64,"children":507,"level":520,"listItem":521,"markDefs":522,"style":137},"e826a1f11945",[508,512,516],{"_key":509,"_type":68,"marks":510,"text":511},"e826a1f119450",[],"Every hour from 0900 UTC until, and including, 1700 UTC (",{"_key":513,"_type":68,"marks":514,"text":515},"e826a1f119451",[294],"all dates and times are UTC in Schyntax",{"_key":517,"_type":68,"marks":518,"text":519},"e826a1f119452",[],"):",1,"bullet",[],{"_key":524,"_type":92,"code":525,"markDefs":12},"eaedf0cd5c0c","    hours(9..17)",{"_key":527,"_type":64,"children":528,"level":520,"listItem":521,"markDefs":548,"style":137},"d6a048fbf065",[529,533,537,541,545],{"_key":530,"_type":68,"marks":531,"text":532},"d6a048fbf0650",[],"Every five minutes from 0900 until, but not including, 1700 (notice the half-open range operator ",{"_key":534,"_type":68,"marks":535,"text":536},"d6a048fbf0651",[92],"..\u003C",{"_key":538,"_type":68,"marks":539,"text":540},"d6a048fbf0652",[]," instead of ",{"_key":542,"_type":68,"marks":543,"text":544},"d6a048fbf0653",[92],"..",{"_key":546,"_type":68,"marks":547,"text":519},"d6a048fbf0654",[],[],{"_key":550,"_type":92,"code":551,"markDefs":12},"8465ea040b23","    hours(9 ..",{"_key":553,"_type":64,"children":554,"level":520,"listItem":521,"markDefs":559,"style":137},"f63be807e133",[555],{"_key":556,"_type":68,"marks":557,"text":558},"f63be807e1330",[],"Same as the previous schedule, except only on Monday through Friday (inclusive):",[],{"_key":561,"_type":92,"code":562,"markDefs":12},"fc4698502cab","    days(mon..fri) hours(9 ..",{"_key":564,"_type":64,"children":565,"level":520,"listItem":521,"markDefs":570,"style":137},"15a49175cb9c",[566],{"_key":567,"_type":68,"marks":568,"text":569},"15a49175cb9c0",[],"Same as previous, except it won't run any time during the noon hour (UTC):",[],{"_key":572,"_type":92,"code":573,"markDefs":12},"83f7eba1b576","    days(mon..fri) hours(9..",{"_key":575,"_type":64,"children":576,"level":520,"listItem":521,"markDefs":581,"style":137},"d621ae01b97f",[577],{"_key":578,"_type":68,"marks":579,"text":580},"d621ae01b97f0",[],"Noon UTC every Monday through Friday, except on Christmas:",[],{"_key":583,"_type":92,"code":584,"markDefs":12},"3fd1f5c3d9cd","    days(mon..fri) hour(12) date(!12\u002F25)",{"_key":586,"_type":64,"children":587,"level":520,"listItem":521,"markDefs":592,"style":137},"9d40ae522544",[588],{"_key":589,"_type":68,"marks":590,"text":591},"9d40ae5225440",[],"Noon UTC on the first and last days of the month:",[],{"_key":594,"_type":92,"code":595,"markDefs":12},"f2aea26a3e59","    daysofmonth(1, -1) hour(12)",{"_key":597,"_type":64,"children":598,"level":520,"listItem":521,"markDefs":603,"style":137},"d95acd76a453",[599],{"_key":600,"_type":68,"marks":601,"text":602},"d95acd76a4530",[],"Every minute between 2300 and 0100 UTC (ranges which wrap around are okay):",[],{"_key":605,"_type":92,"code":606,"markDefs":12},"bf306ad88084","    hours(23..",{"_key":608,"_type":64,"children":609,"level":520,"listItem":521,"markDefs":621,"style":137},"e0d87ee11ef4",[610,614,617],{"_key":611,"_type":68,"marks":612,"text":613},"e0d87ee11ef40",[],"Here's the Schyntax version of the Cron schedule example I gave earlier (",{"_key":615,"_type":68,"marks":616,"text":93},"e0d87ee11ef41",[92],{"_key":618,"_type":68,"marks":619,"text":620},"e0d87ee11ef42",[],"). See if this feels more readable to you:",[],{"_key":623,"_type":92,"code":624,"markDefs":12},"db7e543bdf12","    minute(10), hours(8,20) days(mon..fri) dates(8\u002F1..8\u002F31)",{"_key":626,"_type":64,"children":627,"markDefs":632,"style":187},"d0ed2d8832b6",[628],{"_key":629,"_type":68,"marks":630,"text":631},"d0ed2d8832b60",[],"Groups",[],{"_key":634,"_type":64,"children":635,"markDefs":640,"style":137},"970c6b28d668",[636],{"_key":637,"_type":68,"marks":638,"text":639},"970c6b28d6680",[],"What if you want to run every five minutes during the week, and every half hour on weekends? You can do that by grouping expressions inside curly braces:",[],{"_key":642,"_type":92,"code":643,"markDefs":12},"5448a80a6235","    { days(mon..fri) min(*%5) } { days(sat..sun) min(*%30) }",{"_key":645,"_type":64,"children":646,"markDefs":651,"style":137},"735392ee5b5c",[647],{"_key":648,"_type":68,"marks":649,"text":650},"735392ee5b5c0",[],"You could break the groups up into multiple lines if you wanted. Remember, Schyntax is whitespace-insensitive.",[],{"_key":653,"_type":64,"children":654,"markDefs":663,"style":301},"5730bb845a9a",[655,660],{"_key":656,"_type":68,"marks":657,"text":659},"5730bb845a9a0",[658],"2d81a0f2ed2b","Full syntax documentation is available on GitHub",{"_key":661,"_type":68,"marks":662,"text":493},"5730bb845a9a1",[],[664],{"_key":658,"_type":127,"href":665,"reference":12},"https:\u002F\u002Fgithub.com\u002Fschyntax\u002Fschyntax",{"_key":667,"_type":64,"children":668,"markDefs":673,"style":146},"f2d2d7d66995",[669],{"_key":670,"_type":68,"marks":671,"text":672},"f2d2d7d669950",[],"Implementations",[],{"_key":675,"_type":64,"children":676,"markDefs":689,"style":137},"dfc0e0fd47bd",[677,681,685],{"_key":678,"_type":68,"marks":679,"text":680},"dfc0e0fd47bd0",[],"Although you'll generally want to use the \"Schtick\" task runner (described in ",{"_key":682,"_type":68,"marks":683,"text":111},"dfc0e0fd47bd1",[684],"048ee3f69922",{"_key":686,"_type":68,"marks":687,"text":688},"dfc0e0fd47bd2",[],"), you can actually use the DSL directly to generate schedules. Let's show how to extract the next five event times for a schedule in both JavaScript and C#.",[690],{"_key":684,"_type":127,"href":134,"reference":12},{"_key":692,"_type":64,"children":693,"markDefs":698,"style":187},"bbfe149e7dde",[694],{"_key":695,"_type":68,"marks":696,"text":697},"bbfe149e7dde0",[],"JavaScript",[],{"_key":700,"_type":64,"children":701,"markDefs":706,"style":137},"93137c53ce69",[702],{"_key":703,"_type":68,"marks":704,"text":705},"93137c53ce690",[],"Schyntax is available on npm",[],{"_key":708,"_type":53,"alt":709,"markDefs":12},"a83df682d14e","npm version",{"_key":711,"_type":64,"children":712,"markDefs":733,"style":137},"ff33797d21bf",[713,717,721,725,729],{"_key":714,"_type":68,"marks":715,"text":716},"ff33797d21bf0",[],"via ",{"_key":718,"_type":68,"marks":719,"text":720},"ff33797d21bf1",[92],"npm install schyntax",{"_key":722,"_type":68,"marks":723,"text":724},"ff33797d21bf2",[],". {% highlight javascript %} var schyntax = require('schyntax'); var sch = schyntax('min(*%2)'); \u002F\u002F create Schedule object var events = []; var d = new Date(); \u002F\u002F start from right now for (var i = 0; i \u003C 5; i++) { d = sch.next(d); \u002F\u002F get next event time after the ",{"_key":726,"_type":68,"marks":727,"text":728},"ff33797d21bf3",[92],"d",{"_key":730,"_type":68,"marks":731,"text":732},"ff33797d21bf4",[]," Date argument events.push(d); } {% endhighlight %}",[],{"_key":735,"_type":64,"children":736,"markDefs":741,"style":187},"61db2ebd15ec",[737],{"_key":738,"_type":68,"marks":739,"text":740},"61db2ebd15ec0",[],"C# .NET",[],{"_key":743,"_type":64,"children":744,"markDefs":749,"style":137},"2d000db4c0fa",[745],{"_key":746,"_type":68,"marks":747,"text":748},"2d000db4c0fa0",[],"Schyntax for .NET is available on nuget.org.",[],{"_key":751,"_type":53,"alt":752,"asset":753,"markDefs":12},"cd4f13a0ecca","NuGet version",{"_ref":754,"_type":56},"image-ffc2787fad53cb2e6baef1318987303c843e5252-134x20-svg",{"_key":756,"_type":64,"children":757,"markDefs":786,"style":137},"39d40f40214b",[758,762,765,769,773,777,782],{"_key":759,"_type":68,"marks":760,"text":761},"39d40f40214b0",[],"{% highlight csharp %} using Schyntax; var sch = new Schedule(\"min(*%2)\"); \u002F\u002F create Schedule object var events = new List(); var d = DateTimeOffset.UtcNow; \u002F\u002F start from right now for (var i = 0; i \u003C 5; i++) { d = sch.Next(d); \u002F\u002F get next event time after the ",{"_key":763,"_type":68,"marks":764,"text":728},"39d40f40214b1",[92],{"_key":766,"_type":68,"marks":767,"text":768},"39d40f40214b2",[]," Date argument events.Add(d); } {% endhighlight %} → ",{"_key":770,"_type":68,"marks":771,"text":772},"39d40f40214b3",[294],"goto",{"_key":774,"_type":68,"marks":775,"text":776},"39d40f40214b4",[],": ",{"_key":778,"_type":68,"marks":779,"text":781},"39d40f40214b5",[780],"8d075fb458d3","In Part 2, we'll use a scheduled task runner to put schyntax to work.",{"_key":783,"_type":68,"marks":784,"text":785},"39d40f40214b6",[],". We'll also look at how my team at Stack Overflow is using it to improve consistency in our scheduled tasks.",[787],{"_key":780,"_type":127,"href":134,"reference":12},true,"2015\u002F08\u002F21","",{"_type":53,"asset":792},{"_ref":793,"_type":56},"image-125d372ec95098313698bae776e804940a5f464b-6803x4804-jpg",{"code":795,"language":796},"There are lots of domain-specific languages for schedules. The most prominent might be \u003Ca href=\"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCron\">Cron\u003C\u002Fa> (for *nix scheduled tasks), but there's also \u003Ca href=\"http:\u002F\u002Fwww.kanzaki.com\u002Fdocs\u002Fical\u002Frrule.html\">RRULE\u003C\u002Fa> (for iCalendar events) and many others. Cron isn't exactly what I'd call human-friendly. Can you tell me what \u003Ccode>10 8,20 * 8 1-5\u003C\u002Fcode> means if you don't use Cron often? Some DSLs go in the opposite direction and are wildly verbose, or will sacrifice expressiveness for simplicity.\n\nSo... I wrote my own DSL to \u003Ca href=\"https:\u002F\u002Fxkcd.com\u002F927\u002F\">solve those problems\u003C\u002Fa>. It's called Schyntax, and we're already using it in production at Stack Overflow. Part 1 of this post is about the language itself. In \u003Ca href=\"http:\u002F\u002Fbret.codes\u002Fschyntax-part-2\">Part 2\u003C\u002Fa>, we'll look at how to setup Schyntax-based scheduled task runners in both JavaScript and C# (there is also a Go implementation in progress, if you'd like to know when it's ready, follow me \u003Ca href=\"https:\u002F\u002Ftwitter.com\u002Fbretcope\">on Twitter\u003C\u002Fa>... where I almost never tweet anything).\n\n\u003Ch3>Schyntax Syntax\u003C\u002Fh3>\n\nIn contrast with other scheduling DSLs, Schyntax explicitly tries to be powerful, easy to remember, and terse, but human readable. To a programmer, the syntax should feel immediately familiar and intuitive. For example, \u003Ccode>hour(5)\u003C\u002Fcode> means to run on the fifth hour of every day. \u003Ccode>minute(*)\u003C\u002Fcode> means to run every minute. \u003Ccode>seconds(3,8,52)\u003C\u002Fcode> would run on the third, eighth, and fifty-second seconds of every minute.\n\n\u003Ch4>Ranges\u003C\u002Fh4>\n\nWant to define a range? Borrowing range operators from modern programming languages, \u003Ccode>minute(5..7)\u003C\u002Fcode> would run every minute between the fifth and seventh minutes, inclusive (5,6,7). \u003Ccode>minute(5..&lt;7)\u003C\u002Fcode> is the same range, except it excludes the end value (5,6).\n\n\u003Ch4>Intervals\u003C\u002Fh4>\n\nWhat if you want to run every fifth minute? As a programmer, if you wanted to run a command on every fifth iteration of a loop, you might use the modulus operator (typically \u003Ccode>%\u003C\u002Fcode>).\n\n{% highlight javascript %}\nfor (var i = 0; i &lt; 50000; i++) {\nif (i % 5 == 0) {\n\u002F\u002F code to run on every fifth iteration\n}\n}\n{% endhighlight %}\n\nSchyntax captures the spirit of that by using \u003Ccode>minute(* % 5)\u003C\u002Fcode> to indicate \"run every five minutes.\" Or, you could use \u003Ccode>minute(10..22 % 3)\u003C\u002Fcode> to run every third minute starting at the tenth, and ending at the twenty-second (effectively the same as \u003Ccode>minute(10,13,16,19,22)\u003C\u002Fcode>).\n\n\u003Cblockquote>Intervals always reset at the beginning of a range. For example, \u003Ccode>minutes(*)\u003C\u002Fcode> is implicitly the range \u003Ccode>minutes(0..59)\u003C\u002Fcode>, so the interval is always relative to the 0 minute of each hour. \u003Ccode>minutes(*%17)\u003C\u002Fcode> does \u003Cstrong>NOT\u003C\u002Fstrong> mean \"run every 17th minute of the day.\" It means, \"run every 17th minute of every hour starting at the first (0) minute of the hour.\"\u003C\u002Fblockquote>\n\n\u003Ch4>Exclusions\u003C\u002Fh4>\n\nThe logical negation operator is \u003Ccode>!\u003C\u002Fcode> in many programming languages. So if you want to say, \"run every minute except the third,\" you could write \u003Ccode>minute(!3)\u003C\u002Fcode>. To run every fourth hour except the fifteenth, use \u003Ccode>hours(*%4, !15)\u003C\u002Fcode>. You can exclude any value, range, or interval. Exclusions always take precedence, so be careful not to exclude too much.\n\n\u003Ch4>Expression Names\u003C\u002Fh4>\n\nYou might have noticed that both plural and singular expression names work (i.e. \u003Ccode>hour()\u003C\u002Fcode> vs \u003Ccode>hours()\u003C\u002Fcode>). In fact, most expressions have several aliases to make it easier to remember without having to go look at the documentation. For example, for minutes, you could use \u003Ccode>m\u003C\u002Fcode>, \u003Ccode>min\u003C\u002Fcode>, \u003Ccode>minute\u003C\u002Fcode>, \u003Ccode>minutes\u003C\u002Fcode>, \u003Ccode>minuteOfHour\u003C\u002Fcode> or \u003Ccode>minutesOfHour\u003C\u002Fcode>.\n\nSchyntax is whitespace-insensitive and case-insensitive, so \u003Ccode>hours(*)\u003C\u002Fcode> is the same as \u003Ccode>HOURS ( * )\u003C\u002Fcode> is the same as \u003Ccode>hOuRs(    *)\u003C\u002Fcode>.\n\nIn addition to the \u003Ccode>hours\u003C\u002Fcode>, \u003Ccode>minutes\u003C\u002Fcode>, and \u003Ccode>seconds\u003C\u002Fcode> expressions we've seen so far, Schyntax also supports \u003Ccode>daysOfWeek\u003C\u002Fcode>, \u003Ccode>daysOfMonth\u003C\u002Fcode>, and \u003Ccode>dates\u003C\u002Fcode> expressions. Complete documentation of the supported expressions can be found on the \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fschyntax\u002Fschyntax#expressions\">Schyntax GitHub page\u003C\u002Fa>.\n\n\u003Ch3>Examples\u003C\u002Fh3>\n\n\u003Cul>\n    \u003Cli>Every hour from 0900 UTC until, and including, 1700 UTC (\u003Cstrong>all dates and times are UTC in Schyntax\u003C\u002Fstrong>):\n\u003Cpre>\u003Ccode>    hours(9..17)\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C\u002Fli>\n    \u003Cli>Every five minutes from 0900 until, but not including, 1700 (notice the half-open range operator \u003Ccode>..&lt;\u003C\u002Fcode> instead of \u003Ccode>..\u003C\u002Fcode>):\n\u003Cpre>\u003Ccode>    hours(9 ..\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C\u002Fli>\n    \u003Cli>Same as the previous schedule, except only on Monday through Friday (inclusive):\n\u003Cpre>\u003Ccode>    days(mon..fri) hours(9 ..\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C\u002Fli>\n    \u003Cli>Same as previous, except it won't run any time during the noon hour (UTC):\n\u003Cpre>\u003Ccode>    days(mon..fri) hours(9..\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C\u002Fli>\n    \u003Cli>Noon UTC every Monday through Friday, except on Christmas:\n\u003Cpre>\u003Ccode>    days(mon..fri) hour(12) date(!12\u002F25)\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C\u002Fli>\n    \u003Cli>Noon UTC on the first and last days of the month:\n\u003Cpre>\u003Ccode>    daysofmonth(1, -1) hour(12)\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C\u002Fli>\n    \u003Cli>Every minute between 2300 and 0100 UTC (ranges which wrap around are okay):\n\u003Cpre>\u003Ccode>    hours(23..\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C\u002Fli>\n    \u003Cli>Here's the Schyntax version of the Cron schedule example I gave earlier (\u003Ccode>10 8,20 * 8 1-5\u003C\u002Fcode>). See if this feels more readable to you:\n\u003Cpre>\u003Ccode>    minute(10), hours(8,20) days(mon..fri) dates(8\u002F1..8\u002F31)\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Ch4>Groups\u003C\u002Fh4>\n\nWhat if you want to run every five minutes during the week, and every half hour on weekends? You can do that by grouping expressions inside curly braces:\n\n\u003Cpre>\u003Ccode>    { days(mon..fri) min(*%5) } { days(sat..sun) min(*%30) }\u003C\u002Fcode>\u003C\u002Fpre>\n\nYou could break the groups up into multiple lines if you wanted. Remember, Schyntax is whitespace-insensitive.\n\n\u003Cblockquote>\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fschyntax\u002Fschyntax\">Full syntax documentation is available on GitHub\u003C\u002Fa>.\u003C\u002Fblockquote>\n\n\u003Ch3>Implementations\u003C\u002Fh3>\n\nAlthough you'll generally want to use the \"Schtick\" task runner (described in \u003Ca href=\"http:\u002F\u002Fbret.codes\u002Fschyntax-part-2\">Part 2\u003C\u002Fa>), you can actually use the DSL directly to generate schedules. Let's show how to extract the next five event times for a schedule in both JavaScript and C#.\n\n\u003Ch4>JavaScript\u003C\u002Fh4>\n\nSchyntax is available on npm \u003Ca href=\"http:\u002F\u002Fbadge.fury.io\u002Fjs\u002Fschyntax\">\u003Cimg src=\"http:\u002F\u002Fstackoverflow.blog\u002Fwp-content\u002Fuploads\u002F2017\u002F02\u002Fschyntax.svg\" alt=\"npm version\" \u002F>\u003C\u002Fa> via \u003Ccode>npm install schyntax\u003C\u002Fcode>.\n\n{% highlight javascript %}\nvar schyntax = require('schyntax');\n\nvar sch = schyntax('min(*%2)'); \u002F\u002F create Schedule object\nvar events = [];\nvar d = new Date(); \u002F\u002F start from right now\nfor (var i = 0; i &lt; 5; i++) {\nd = sch.next(d); \u002F\u002F get next event time after the \u003Ccode>d\u003C\u002Fcode> Date argument\nevents.push(d);\n}\n{% endhighlight %}\n\n\u003Ch4>C# .NET\u003C\u002Fh4>\n\nSchyntax for .NET is available on nuget.org. \u003Ca href=\"http:\u002F\u002Fbadge.fury.io\u002Fnu\u002FSchyntax\">\u003Cimg src=\"http:\u002F\u002Fstackoverflow.blog\u002Fwp-content\u002Fuploads\u002F2017\u002F02\u002FSchyntax.svg\" alt=\"NuGet version\" \u002F>\u003C\u002Fa>\n\n{% highlight csharp %}\nusing Schyntax;\n\nvar sch = new Schedule(\"min(*%2)\"); \u002F\u002F create Schedule object\nvar events = new List();\nvar d = DateTimeOffset.UtcNow; \u002F\u002F start from right now\nfor (var i = 0; i &lt; 5; i++)\n{\nd = sch.Next(d); \u002F\u002F get next event time after the \u003Ccode>d\u003C\u002Fcode> Date argument\nevents.Add(d);\n}\n{% endhighlight %}\n\n→ \u003Cstrong>goto\u003C\u002Fstrong>: \u003Ca href=\"http:\u002F\u002Fbret.codes\u002Fschyntax-part-2\">In Part 2, we'll use a scheduled task runner to put schyntax to work.\u003C\u002Fa>. We'll also look at how my team at Stack Overflow is using it to improve consistency in our scheduled tasks.","html","2015-08-21T12:00:00.000Z",{"current":799},"a-better-syntax-for-scheduled-tasks",[801],{"_createdAt":802,"_id":803,"_rev":804,"_type":805,"_updatedAt":802,"slug":806,"title":808},"2023-05-23T16:43:21Z","wp-tagcat-engineering","9HpbCsT2tq0xwozQfkc4ih","blogTag",{"current":807},"engineering","Engineering","A Better Syntax for Scheduled Tasks",[811,817,823,829],{"_id":812,"publishedAt":813,"slug":814,"sponsored":12,"title":816},"28e560af-f0aa-4d46-bd90-f435ad604aa7","2026-06-26T14:00:27.102Z",{"_type":10,"current":815},"paging-charity-how-can-engineering-leaders-avoid-becoming-bond-villains","Paging Charity! How can engineering leaders avoid becoming Bond villains?",{"_id":818,"publishedAt":819,"slug":820,"sponsored":12,"title":822},"4b22c2a3-3779-4966-93eb-5230391dbdce","2026-06-23T14:08:58.595Z",{"_type":10,"current":821},"your-ai-shipped-a-backend-that-boots-that-is-the-whole-problem","Your AI shipped a backend that boots. That is the whole problem.",{"_id":824,"publishedAt":825,"slug":826,"sponsored":12,"title":828},"5cf362e1-fe7b-45af-b69c-914731c6a052","2026-06-23T14:00:00.000Z",{"_type":10,"current":827},"the-2026-developer-survey-is-now-open-for-human-developers-only","The 2026 Developer Survey is now open (for human developers only)!",{"_id":830,"publishedAt":831,"slug":832,"sponsored":12,"title":834},"30b995f7-7cb9-4dd8-bf71-d0685940a32b","2026-06-19T14:00:00.000Z",{"_type":10,"current":833},"dispatches-from-o-reilly-from-capabilities-to-responsibilities","Dispatches from O'Reilly: From capabilities to responsibilities",{"data":836,"sourceMap":-1},{"count":520,"lastTimestamp":837},"2023-05-25T09:45:54Z"]