Repository: adhocore/gronx (go)

Issue:
`NextTickAfter` returning "unreachable year segment" error when `inclRefTime` is `false`
Thank you very much for the previous fix in #51. 
I have encountered another test case on `v1.19.4` that seems problematic.

### Description

When I use `NextTickAfter` with `inclRefTime` set to `false`, I encounter an "unreachable year segment: 2024" error, even though the next valid occurrence within the year 2024 should still be reachable. This happens when checking for the next tick of `"30 15 31 12 * 2024"` as of November 8, 2024, at 22:18:16 UTC. I would expect this call to return December 31, 2024, at 15:30, but instead, it throws an error.

### Test Case to Reproduce

Here is a minimal test case to reproduce the issue:

```go
package main

import (
	"fmt"
	"time"

	"github.com/adhocore/gronx"
)

func TestIsUnreachableYear_CurrentYear() {
	// Set reference time ("now") to November 8, 2024, at 22:18:16 UTC
	now := time.Date(2024, time.November, 8, 22, 18, 16, 0, time.UTC)

	// Cron expression for 15:30 on December 31, 2024
	cronExpr := "30 15 31 12 * 2024"

	// Expected outcome: December 31, 2024, at 15:30
	expectedTime := time.Date(2024, time.December, 31, 15, 30, 0, 0, time.UTC)

	// Call NextTickAfter with inclRefTime set to false
	actualTime, err := gronx.NextTickAfter(cronExpr, now, false)

	// Simplified check for error and time
	if err != nil {
		fmt.Printf("Test FAILED: Did not expect an error, but got: %s\n", err)
	} else if !actualTime.Equal(expectedTime) {
		fmt.Printf("Test FAILED: Expected next tick to be %v, but got %v.\n", expectedTime, actualTime)
	} else {
		fmt.Println("Test PASSED: Year segment 2024 is reachable, and NextTickAfter returned the correct next tick.")
	}
}

func main() {
	TestIsUnreachableYear_CurrentYear()
}
```

### Expected Behavior
With `inclRefTime` set to `false`, the next tick after November 8, 2024, at 22:18:16 UTC for `"30 15 31 12 * 2024"` should still be December 31, 2024, at 15:30. The `inclRefTime` parameter should not affect the year reachability in this case.

### Observed Behavior
The `NextTickAfter` function returns an error: `unreachable year segment: 2024`

This error occurs only when `inclRefTime` is set to `false`, and it seems like a bug because the next valid time within 2024 should still be reachable.

Thank you for looking into this! Please let me know if additional details are needed.


Pull request:
Remove inclRefTime from isUnreachableYear
Fixes #53 

Patch:
diff --git a/next.go b/next.go
index a99ddcc..f52375e 100644
--- a/next.go
+++ b/next.go
@@ -29,7 +29,7 @@ func NextTickAfter(expr string, start time.Time, inclRefTime bool) (time.Time, e
 	}
 
 	segments, _ := Segments(expr)
-	if len(segments) > 6 && isUnreachableYear(segments[6], next, inclRefTime, false) {
+	if len(segments) > 6 && isUnreachableYear(segments[6], next, false) {
 		return next, fmt.Errorf("unreachable year segment: %s", segments[6])
 	}
 
@@ -123,18 +123,12 @@ over:
 
 var dashRe = regexp.MustCompile(`/.*$`)
 
-func isUnreachableYear(year string, ref time.Time, incl bool, reverse bool) bool {
+func isUnreachableYear(year string, ref time.Time, reverse bool) bool {
 	if year == "*" || year == "?" {
 		return false
 	}
 
-	edge, inc := ref.Year(), 1
-	if !incl {
-		if reverse {
-			inc = -1
-		}
-		edge += inc
-	}
+	edge := ref.Year()
 	for _, offset := range strings.Split(year, ",") {
 		if strings.Index(offset, "*/") == 0 || strings.Index(offset, "0/") == 0 {
 			return false
diff --git a/prev.go b/prev.go
index cc2fc94..4f29ef1 100644
--- a/prev.go
+++ b/prev.go
@@ -19,7 +19,7 @@ func PrevTickBefore(expr string, start time.Time, inclRefTime bool) (time.Time,
 	}
 
 	segments, _ := Segments(expr)
-	if len(segments) > 6 && isUnreachableYear(segments[6], prev, inclRefTime, true) {
+	if len(segments) > 6 && isUnreachableYear(segments[6], prev, true) {
 		return prev, fmt.Errorf("unreachable year segment: %s", segments[6])
 	}
 


Test patch:
diff --git a/next_test.go b/next_test.go
index 51a746c..aa56475 100644
--- a/next_test.go
+++ b/next_test.go
@@ -2,6 +2,7 @@ package gronx
 
 import (
 	"fmt"
+	"strings"
 	"testing"
 	"time"
 )
@@ -73,17 +74,96 @@ func TestNextTickAfter(t *testing.T) {
 	})
 }
 
-// https://github.com/adhocore/gronx/issues/51
-func TestIsUnreachableYearCurrentYear(t *testing.T) {
+func TestIsUnreachableYearPrevTickBefore(t *testing.T) {
 	now := time.Date(2024, time.November, 8, 22, 18, 16, 0, time.UTC)
+	tests := []struct {
+		name         string
+		cronExpr     string
+		expectedTime time.Time
+		expectError  bool
+	}{
+		{
+			// https://github.com/adhocore/gronx/issues/51
+			name:         "Current Year - Previous Tick",
+			cronExpr:     "30 15 4 11 * 2024",
+			expectedTime: time.Date(2024, time.November, 4, 15, 30, 0, 0, time.UTC),
+			expectError:  false,
+		},
+		{
+			name:         "Next Year - Previous Tick (Unreachable Year)",
+			cronExpr:     "30 15 4 11 * 2025",
+			expectedTime: time.Time{}, // Error expected
+			expectError:  true,
+		},
+		{
+			name:         "Previous Year - Previous Tick",
+			cronExpr:     "30 15 4 11 * 2023",
+			expectedTime: time.Date(2023, time.November, 4, 15, 30, 0, 0, time.UTC),
+			expectError:  false,
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			actualTime, err := PrevTickBefore(tc.cronExpr, now, true)
+			if tc.expectError {
+				if err == nil || !strings.Contains(err.Error(), "unreachable year segment") {
+					t.Errorf("expected unreachable year error, got: %v", err)
+				}
+			} else {
+				if err != nil {
+					t.Errorf("unexpected error: %v", err)
+				} else if !actualTime.Equal(tc.expectedTime) {
+					t.Errorf("expected previous tick to be %v, got %v", tc.expectedTime, actualTime)
+				}
+			}
+		})
+	}
+}
 
-	cronExpr := "30 15 4 11 * 2024"
-	expectedTime := time.Date(2024, time.November, 4, 15, 30, 0, 0, time.UTC)
-	actualTime, err := PrevTickBefore(cronExpr, now, true)
+func TestIsUnreachableYearNextTickAfter(t *testing.T) {
+	now := time.Date(2024, time.November, 8, 22, 18, 16, 0, time.UTC)
+	tests := []struct {
+		name         string
+		cronExpr     string
+		expectedTime time.Time
+		expectError  bool
+	}{
+		{
+			// https://github.com/adhocore/gronx/issues/53
+			name:         "Current Year - Next Tick",
+			cronExpr:     "30 15 31 12 * 2024",
+			expectedTime: time.Date(2024, time.December, 31, 15, 30, 0, 0, time.UTC),
+			expectError:  false,
+		},
+		{
+			name:         "Next Year - Next Tick",
+			cronExpr:     "30 15 31 12 * 2025",
+			expectedTime: time.Date(2025, time.December, 31, 15, 30, 0, 0, time.UTC),
+			expectError:  false,
+		},
+		{
+			name:         "Previous Year - Next Tick (Unreachable Year)",
+			cronExpr:     "30 15 31 12 * 2023",
+			expectedTime: time.Time{}, // Error expected
+			expectError:  true,
+		},
+	}
 
-	if err != nil {
-		t.Errorf("got unexpected error: %s", err)
-	} else if !actualTime.Equal(expectedTime) {
-		t.Errorf("expected previous tick to be %v, got %v", expectedTime, actualTime)
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			actualTime, err := NextTickAfter(tc.cronExpr, now, false)
+			if tc.expectError {
+				if err == nil || !strings.Contains(err.Error(), "unreachable year segment") {
+					t.Errorf("expected unreachable year error, got: %v", err)
+				}
+			} else {
+				if err != nil {
+					t.Errorf("unexpected error: %v", err)
+				} else if !actualTime.Equal(tc.expectedTime) {
+					t.Errorf("expected next tick to be %v, got %v", tc.expectedTime, actualTime)
+				}
+			}
+		})
 	}
 }