Repository: bradymholt/cRonstrue
Pull request #318 (2024-05-05 12:09:11)

Support for '@' expressions
Added support for '@' expressions. 

@yearly       = `0 0 1 1 *` 
@annually   = `0 0 1 1 *` 
@monthly    = `0 0 1 * *` 
@weekly      = `0 0 * * 0` 
@daily          = `0 0 * * *` 
@midnight   = `0 0 * * *` 
@hourly        = `0 * * * *` 

The @ expressions gots translated in a basic cron and given to the origin parse function. Let me know what you think.

bradymholt: Nice!  

Would you be up for adding something to README mentioning this?  Maybe an item on the [Features](https://github.com/bradymholt/cRonstrue?tab=readme-ov-file#features) list and a [usage example](https://github.com/bradymholt/cRonstrue?tab=readme-ov-file#usage)? 
bradymholt: @HB9HIL Looks like there are some test failures.
HB9HIL: > @HB9HIL Looks like there are some test failures.

upps. I forgot to catch the empty case. 

I also added the necessary tests. Sorry :)

Patch:
diff --git a/README.md b/README.md
index ec25ffb..2a3d56a 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,8 @@ This library was ported from the original C# implementation called [cron-express
 - Zero dependencies
 - Supports all cron expression special characters including * / , - ? L W, #
 - Supports 5, 6 (w/ seconds or year), or 7 (w/ seconds and year) part cron expressions
-- Supports [Quartz Job Scheduler](http://www.quartz-scheduler.org/) cron expressions
+- [Quartz Job Scheduler](http://www.quartz-scheduler.org/) cron expressions are supported
+- Supports time specification _nicknames_ (@yearly, @annually, @monthly, @weekly, @daily)
 - i18n support with 30+ languages
 
 ## Demo
@@ -90,6 +91,10 @@ cronstrue.toString("* * * ? * 2-6/2", { dayOfWeekStartIndexZero: false });
 
 cronstrue.toString("* * * 6-8 *", { monthStartIndexZero: true });
 > "Every minute, July through September"
+
+cronstrue.toString("@monthly");
+> "At 12:00 AM, on day 1 of the month"
+
 ```
 
 For more usage examples, including a demonstration of how cRonstrue can handle some very complex cron expressions, you can [reference the unit tests](https://github.com/bradymholt/cRonstrue/blob/main/test/cronstrue.ts).
diff --git a/src/cronParser.ts b/src/cronParser.ts
index d7cb589..c974698 100644
--- a/src/cronParser.ts
+++ b/src/cronParser.ts
@@ -25,13 +25,43 @@ export class CronParser {
    * @returns {string[]}
    */
   parse(): string[] {
-    let parsed = this.extractParts(this.expression);
+    let parsed: string[];
+    
+    var expression = this.expression ?? '';
+
+    if (expression.startsWith('@')) {
+      var special = this.parseSpecial(this.expression);
+      parsed = this.extractParts(special);
+
+    } else {
+      parsed = this.extractParts(this.expression);
+    }
+
     this.normalize(parsed);
     this.validate(parsed);
 
     return parsed;
   }
 
+  parseSpecial(expression: string): string {
+    const specialExpressions: { [key: string]: string } = {
+        '@yearly': '0 0 1 1 *',
+        '@annually': '0 0 1 1 *',
+        '@monthly': '0 0 1 * *',
+        '@weekly': '0 0 * * 0',
+        '@daily': '0 0 * * *',
+        '@midnight': '0 0 * * *',
+        '@hourly': '0 * * * *'
+    };
+
+    const special = specialExpressions[expression];
+    if (!special) {
+        throw new Error('Unknown special expression.');
+    }
+
+    return special;
+  }
+
   protected extractParts(expression: string) {
     if (!this.expression) {
       throw new Error("cron expression is empty");


Test patch:
diff --git a/test/cronParser.ts b/test/cronParser.ts
index 9122ab3..a2273e2 100644
--- a/test/cronParser.ts
+++ b/test/cronParser.ts
@@ -44,5 +44,9 @@ describe("CronParser", function () {
     it("dayOfWeek dangling comma", function () {
       assert.equal(new CronParser("*/5 * * * * ,2").parse()[5], "2");
     });
+
+    it("should parse cron @ expression", function () {
+      assert.equal(new CronParser("@weekly").parse().length, 7);
+    });
   });
 });
diff --git a/test/cronstrue.ts b/test/cronstrue.ts
index b44670d..4415364 100644
--- a/test/cronstrue.ts
+++ b/test/cronstrue.ts
@@ -638,6 +638,36 @@ describe("Cronstrue", function () {
     });
   });
 
+  describe("@ expressions", function () {
+    it("@yearly", function () {
+      assert.equal(cronstrue.toString(this.test?.title as string), "At 12:00 AM, on day 1 of the month, only in January");
+    });
+
+    it("@annually", function () {
+      assert.equal(cronstrue.toString(this.test?.title as string), "At 12:00 AM, on day 1 of the month, only in January");
+    });
+
+    it("@monthly", function () {
+      assert.equal(cronstrue.toString(this.test?.title as string), "At 12:00 AM, on day 1 of the month");
+    });
+
+    it("@weekly", function () {
+      assert.equal(cronstrue.toString(this.test?.title as string), "At 12:00 AM, only on Sunday");
+    });
+
+    it("@daily", function () {
+      assert.equal(cronstrue.toString(this.test?.title as string), "At 12:00 AM");
+    });
+
+    it("@midnight", function () {
+      assert.equal(cronstrue.toString(this.test?.title as string), "At 12:00 AM");
+    });
+
+    it("@hourly", function () {
+      assert.equal(cronstrue.toString(this.test?.title as string), "Every hour");
+    });
+  });
+
   describe("verbose", function () {
     it("30 4 1 * *", function () {
       assert.equal(cronstrue.toString(this.test?.title as string, { verbose: true }), "At 04:30 AM, on day 1 of the month");