Repository: bradymholt/cRonstrue (ts)

Issue:
multiple Days of Week with hashtags is outputting null
**Cron string:**
`0 0 * * 0#1,0#2`

**Expected Output:**
```
At 12:00 AM, on the 1st Sunday and 2nd Sunday of the month
```
**Actual Output:**
```
At 12:00 AM, on the null Sunday and Sunday of the month
```


Pull request description:
Fix multiple days of week outputs null
fix #199

I have moved the code that adds numeric prefixes (first, second, etc) to the `getSingleItemDescription`. It was previously in the `getDescriptionFormat` and tried to find correct prefix for e.g. `#3,1#2`, because it took only the first weekday into consideration.

The code diff looks crazy, but if you take a closer look then you will se that I only moved the `switch` statement to another method and concatenated properly.

It seems to fix the problem and does not break any tests :-)

Patch:
diff --git a/src/expressionDescriptor.ts b/src/expressionDescriptor.ts
index 0eec3ae..ee4ea50 100644
--- a/src/expressionDescriptor.ts
+++ b/src/expressionDescriptor.ts
@@ -307,36 +307,19 @@ export class ExpressionDescriptor {
         (s, form) => {
           let exp: string = s;
           if (s.indexOf("#") > -1) {
-            exp = s.substr(0, s.indexOf("#"));
+            exp = s.substring(0, s.indexOf("#"));
           } else if (s.indexOf("L") > -1) {
             exp = exp.replace("L", "");
           }
 
-          return this.i18n.daysOfTheWeekInCase
+          let description = this.i18n.daysOfTheWeekInCase
             ? this.i18n.daysOfTheWeekInCase(form)[parseInt(exp)]
             : daysOfWeekNames[parseInt(exp)];
-        },
-        (s) => {
-          if (parseInt(s) == 1) {
-            // rather than "every 1 days" just return empty string
-            return "";
-          } else {
-            return StringUtilities.format(this.i18n.commaEveryX0DaysOfTheWeek(s), s);
-          }
-        },
-        (s) => {
-          // If both DOM and DOW are specified, the cron will execute at either time.
-          const beginFrom = s.substring(0, s.indexOf("-"));
 
-          const domSpecified = this.expressionParts[3] != "*";
-          return domSpecified ? this.i18n.commaAndX0ThroughX1(beginFrom) : this.i18n.commaX0ThroughX1(beginFrom);
-        },
-        (s) => {
-          let format: string | null = null;
           if (s.indexOf("#") > -1) {
+            let dayOfWeekOfMonthDescription: string | null = null;
             let dayOfWeekOfMonthNumber: string = s.substring(s.indexOf("#") + 1);
             let dayOfWeekNumber = s.substring(0, s.indexOf("#"));
-            let dayOfWeekOfMonthDescription: string | null = null;
             switch (dayOfWeekOfMonthNumber) {
               case "1":
                 dayOfWeekOfMonthDescription = this.i18n.first(dayOfWeekNumber);
@@ -354,11 +337,31 @@ export class ExpressionDescriptor {
                 dayOfWeekOfMonthDescription = this.i18n.fifth(dayOfWeekNumber);
                 break;
             }
+            description = dayOfWeekOfMonthDescription + " " + description;
+          }
 
-            format =
-              this.i18n.commaOnThe(dayOfWeekOfMonthNumber) +
-              dayOfWeekOfMonthDescription +
-              this.i18n.spaceX0OfTheMonth();
+          return description;
+        },
+        (s) => {
+          if (parseInt(s) == 1) {
+            // rather than "every 1 days" just return empty string
+            return "";
+          } else {
+            return StringUtilities.format(this.i18n.commaEveryX0DaysOfTheWeek(s), s);
+          }
+        },
+        (s) => {
+          // If both DOM and DOW are specified, the cron will execute at either time.
+          const beginFrom = s.substring(0, s.indexOf("-"));
+
+          const domSpecified = this.expressionParts[3] != "*";
+          return domSpecified ? this.i18n.commaAndX0ThroughX1(beginFrom) : this.i18n.commaX0ThroughX1(beginFrom);
+        },
+        (s) => {
+          let format: string | null = null;
+          if (s.indexOf("#") > -1) {
+            let dayOfWeekOfMonthNumber: string = s.substring(s.indexOf("#") + 1);
+            format = this.i18n.commaOnThe(dayOfWeekOfMonthNumber).trim() + this.i18n.spaceX0OfTheMonth();
           } else if (s.indexOf("L") > -1) {
             format = this.i18n.commaOnTheLastX0OfTheMonth(s.replace("L", ""));
           } else {


Test patch:
diff --git a/test/cronstrue.ts b/test/cronstrue.ts
index 27c1d36..da194f8 100644
--- a/test/cronstrue.ts
+++ b/test/cronstrue.ts
@@ -369,6 +369,10 @@ describe("Cronstrue", function () {
       assert.equal(cronstrue.toString(this.test?.title as string), "Every minute, on the third Monday of the month");
     });
 
+    it("* * * * MON#3,THU#1", function () {
+      assert.equal(cronstrue.toString(this.test?.title as string), "Every minute, on the third Monday and first Thursday of the month");
+    });
+
     it("5-10 * * * * *", function () {
       assert.equal(cronstrue.toString(this.test?.title as string), "Seconds 5 through 10 past the minute");
     });