nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: adhocore/gronx
Pull request #31 (2023-07-27 00:36:17)

Bugfix for weekday validator
Output from go test:
```
--- FAIL: TestNextTickAfter (0.01s)
    --- FAIL: TestNextTickAfter/next_run_after (0.01s)
        --- FAIL: TestNextTickAfter/next_run_after/next_run_after_excl_#63:_0_0_*_*_1#1 (0.00s)
            next_test.go:66: [0 0 * * 1#1][2009-11-23 00:00:00] expected 2009-12-07 00:00:00, got 2010-01-04 00:00:00
```


I haven't had time to dig into why this case is not working as expected, but I noticed something really weird here. If I say the first Monday of the month it doesn't always give the right answer:

Go Playground example: https://go.dev/play/p/0GJnCNPWCWC

philodavies: Actually got a better repro.. Anytime the first x-day of the month is the 7th, it gets skipped
https://go.dev/play/p/yoI2lcTtyRY

Patch:
diff --git a/validator.go b/validator.go
index d772ad7..e5b1eb2 100644
--- a/validator.go
+++ b/validator.go
@@ -142,5 +142,5 @@ func isValidWeekDay(val string, last int, ref time.Time) (bool, error) {
 		return false, nil
 	}
 
-	return ref.Day()/7 == nth-1, nil
+	return (ref.Day()-1)/7 == nth-1, nil
 }


Test patch:
diff --git a/gronx_test.go b/gronx_test.go
index 580d3c4..a9da1ee 100644
--- a/gronx_test.go
+++ b/gronx_test.go
@@ -207,6 +207,8 @@ func testcases() []Case {
 		{"* * * * 5#2", "2011-07-01 00:00:00", false, "2011-07-08 00:00:00"},
 		{"* * * * 5#1", "2011-07-01 00:00:00", true, "2011-07-01 00:01:00"},
 		{"* * * * 3#4", "2011-07-01 00:00:00", false, "2011-07-27 00:00:00"},
+		{"0 0 * * 1#1", "2009-10-23 00:00:00", false, "2009-11-02 00:00:00"},
+		{"0 0 * * 1#1", "2009-11-23 00:00:00", false, "2009-12-07 00:00:00"},
 		{"5/0 * * * *", "2021-04-19 12:54:00", false, "2018-08-13 00:25:00"},
 		{"5/20 * * * *", "2018-08-13 00:24:00", false, "2018-08-13 00:25:00"},
 		{"5/20 * * * *", "2018-08-13 00:45:00", true, "2018-08-13 01:05:00"},
Repository: adhocore/gronx
Pull request #32 (2023-09-19 13:28:21)

fix: 0 is forbidden for day
According to the cron [specification](https://man.openbsd.org/crontab.5), the allowed values for the `day` segment must be between 1-31, but currently the cron `* * 0 * *` is allowed.

This PR aims at making `IsValid("* * 0 * *")` return false. 


Patch:
diff --git a/checker.go b/checker.go
index 789570d..9381bc4 100644
--- a/checker.go
+++ b/checker.go
@@ -81,14 +81,14 @@ func (c *SegmentChecker) isOffsetDue(offset string, val, pos int) (bool, error)
 		return false, err
 	}
 
-	if !isWeekDay && (val == 0 || nval == 0) {
-		return nval == 0 && val == 0, nil
-	}
-
 	if nval < bounds[0] || nval > bounds[1] {
 		return false, fmt.Errorf("segment#%d: '%s' out of bounds(%d, %d)", pos, offset, bounds[0], bounds[1])
 	}
 
+	if !isWeekDay && (val == 0 || nval == 0) {
+		return nval == 0 && val == 0, nil
+	}
+
 	return nval == val || (isWeekDay && nval == 7 && val == 0), nil
 }
 


Test patch:
diff --git a/gronx_test.go b/gronx_test.go
index a9da1ee..2d00059 100644
--- a/gronx_test.go
+++ b/gronx_test.go
@@ -243,6 +243,7 @@ func errcases() []Case {
 		{"1-Z * * * *", "2011-07-01 00:01:00", false, ""},
 		{"1-Z/2 * * * *", "2011-07-01 00:01:00", false, ""},
 		{"Z-Z/2 * * * *", "2011-07-01 00:01:00", false, ""},
+		{"* * 0 * *", "2011-07-01 00:01:00", false, ""},
 		{"* * * W * *", "", false, ""},
 		{"* * * ZW * *", "", false, ""},
 		{"* * * * 4W", "2011-07-01 00:00:00", false, ""},
Repository: adhocore/gronx
Pull request #37 (2024-03-10 01:09:21)

feat: add custom tag/expr
### summary

add custom tag/expr.  the current tag collection does not meet the requirements, and adding an add method would be more flexible ?


Patch:
diff --git a/gronx.go b/gronx.go
index 9fb5388..0c6fdcb 100644
--- a/gronx.go
+++ b/gronx.go
@@ -29,6 +29,22 @@ var expressions = map[string]string{
 	"@everysecond": "* * * * * *",
 }
 
+func AddTag(tag, expr string) error {
+	_, ok := expressions[tag]
+	if ok {
+		return errors.New("conflict tag")
+	}
+
+	segs, err := Segments(expr)
+	if err != nil {
+		return err
+	}
+	expr = strings.Join(segs, " ")
+
+	expressions[tag] = expr
+	return nil
+}
+
 // SpaceRe is regex for whitespace.
 var SpaceRe = regexp.MustCompile(`\s+`)
 var yearRe = regexp.MustCompile(`\d{4}`)
diff --git a/pkg/tasker/tasker.go b/pkg/tasker/tasker.go
index afdb79e..0f2dc22 100644
--- a/pkg/tasker/tasker.go
+++ b/pkg/tasker/tasker.go
@@ -256,6 +256,11 @@ func (t *Tasker) Run() {
 	t.wait()
 }
 
+// Stop the task manager.
+func (t *Tasker) Stop() {
+	t.abort = true
+}
+
 var dateFormat = "2006/01/02 15:04:05"
 
 func (t *Tasker) doSetup() {


Test patch:
diff --git a/gronx_test.go b/gronx_test.go
index 2d00059..843c432 100644
--- a/gronx_test.go
+++ b/gronx_test.go
@@ -93,6 +93,38 @@ func TestIsValid(t *testing.T) {
 
 }
 
+func TestAddTag(t *testing.T) {
+	t.Run("add good tag", func(t *testing.T) {
+		err := AddTag("@2s", "*/2 * * * * *")
+		if err != nil {
+			t.Error("expected nil, got err")
+		}
+
+		expr, ok := expressions["@2s"]
+		if !ok {
+			t.Error("expected true, got false")
+		}
+
+		if expr != "*/2 * * * * *" {
+			t.Error("expected */2 * * * * *")
+		}
+	})
+
+	t.Run("add conflict tag", func(t *testing.T) {
+		err := AddTag("@2s", "*/2 * * * * *")
+		if err == nil {
+			t.Error("expected err, got nil")
+		}
+	})
+
+	t.Run("add wrong tag", func(t *testing.T) {
+		err := AddTag("@3s", "* * * *")
+		if err == nil {
+			t.Error("expected err, got nil")
+		}
+	})
+}
+
 func TestIsDue(t *testing.T) {
 	gron := New()
 
diff --git a/pkg/tasker/tasker_test.go b/pkg/tasker/tasker_test.go
index 09cdd3a..06bc753 100644
--- a/pkg/tasker/tasker_test.go
+++ b/pkg/tasker/tasker_test.go
@@ -200,3 +200,26 @@ func TestConcurrency(t *testing.T) {
 		}
 	})
 }
+
+func TestStopTasker(t *testing.T) {
+	t.Run("Run", func(t *testing.T) {
+		taskr := New(Option{Verbose: true, Out: "../../test/tasker.out"})
+
+		var incr int
+		taskr.Task("* * * * * *", func(ctx context.Context) (int, error) {
+			incr++
+			return 0, nil
+		}, false)
+
+		go func() {
+			time.Sleep(2 * time.Second)
+			taskr.Stop()
+		}()
+		taskr.Run()
+		fmt.Println(incr)
+
+		if incr != 1 {
+			t.Errorf("the task should run 1x, not %dx", incr)
+		}
+	})
+}
Repository: adhocore/gronx
Pull request #60 (2025-04-29 19:04:28)

IsValid check syntax before evaluating expression
This is one way to address #59 which shows IsValid is sensitive to the
current time referenced by Checker.



Patch:
diff --git a/gronx.go b/gronx.go
index 82c0baa..63b4137 100644
--- a/gronx.go
+++ b/gronx.go
@@ -174,6 +174,12 @@ func IsValid(expr string) bool {
 		return false
 	}
 
+	// First check syntax without time dependency
+	if !isSyntaxValid(segs) {
+		return false
+	}
+
+	// Then check with time dependency
 	for pos, seg := range segs {
 		if _, err := checker.CheckDue(seg, pos); err != nil {
 			return false
@@ -182,3 +188,35 @@ func IsValid(expr string) bool {
 
 	return true
 }
+
+// isSyntaxValid checks if the cron segments are syntactically valid without time dependency.
+// It returns bool.
+func isSyntaxValid(segs []string) bool {
+	for _, seg := range segs {
+		// Check for empty segments
+		if seg == "" {
+			return false
+		}
+
+		// Split by comma to check each part
+		parts := strings.Split(seg, ",")
+		for _, part := range parts {
+			// Check for empty parts
+			if part == "" {
+				return false
+			}
+
+			// Check for invalid characters
+			if strings.ContainsAny(part, "*/") {
+				// If contains /, must have a number after it
+				if strings.Contains(part, "/") {
+					parts := strings.Split(part, "/")
+					if len(parts) != 2 || parts[1] == "" {
+						return false
+					}
+				}
+			}
+		}
+	}
+	return true
+}


Test patch:
diff --git a/gronx_test.go b/gronx_test.go
index 109ba3d..ed65d26 100644
--- a/gronx_test.go
+++ b/gronx_test.go
@@ -99,6 +99,28 @@ func TestIsValid(t *testing.T) {
 		}
 	})
 
+	t.Run("sensitivity to reference time", func(t *testing.T) {
+		originalRef := checker.ref
+		defer func() { checker.ref = originalRef }()
+
+		expr := "*/15, * * * *"
+		moments := []time.Time{
+			time.Date(2025, 4, 29, 12, 13, 0, 0, time.UTC),
+			time.Date(2025, 4, 29, 12, 14, 0, 0, time.UTC),
+			time.Date(2025, 4, 29, 12, 15, 0, 0, time.UTC),
+			time.Date(2025, 4, 29, 12, 16, 0, 0, time.UTC),
+			time.Date(2025, 4, 29, 12, 17, 0, 0, time.UTC),
+		}
+
+		for _, moment := range moments {
+			checker.ref = moment
+
+			if gron.IsValid(expr) {
+				t.Errorf("expected false, got true at %v", moment)
+			}
+		}
+
+	})
 }
 
 func TestAddTag(t *testing.T) {