diff --git a/internal/sub/remark_vars.go b/internal/sub/remark_vars.go index 28dc5b617..07a06ac41 100644 --- a/internal/sub/remark_vars.go +++ b/internal/sub/remark_vars.go @@ -423,6 +423,13 @@ func gregorianToJalali(gy, gm, gd int) (jy, jm, jd int) { refJDN := 2460311 days := int64(jdn - refJDN) jy, jm, jd = 1402, 10, 11 + // Leap years follow y % 33, so whole 33-year blocks are skipped: a node-reported expiry + // can lie hundreds of millions of years away, and the walk below goes month by month. + const jalaliCycleDays = 33*365 + 8 + if cycles := days / jalaliCycleDays; cycles != 0 { + jy += int(cycles) * 33 + days -= cycles * jalaliCycleDays + } // Walk forward for days > 0 { diff --git a/internal/sub/remark_vars_test.go b/internal/sub/remark_vars_test.go index 55bfa7dab..31684ce5f 100644 --- a/internal/sub/remark_vars_test.go +++ b/internal/sub/remark_vars_test.go @@ -4,6 +4,7 @@ import ( "net/url" "strings" "testing" + "time" "github.com/mhsanaei/3x-ui/v3/internal/database/model" "github.com/mhsanaei/3x-ui/v3/internal/xray" @@ -529,6 +530,8 @@ func TestGregorianToJalali(t *testing.T) { {2024, 1, 1, 1402, 10, 11}, {2000, 3, 20, 1379, 1, 1}, {1979, 2, 11, 1357, 11, 22}, + // 2124-01-01 lies three 33-year blocks and a year past the reference. + {2124, 1, 1, 1502, 10, 11}, } for _, c := range cases { jy, jm, jd := gregorianToJalali(c.gy, c.gm, c.gd) @@ -539,6 +542,19 @@ func TestGregorianToJalali(t *testing.T) { } } +// A node reports the expiry the remark renders, so a date hundreds of millions of years +// out must not walk the calendar month by month on every subscription fetch. +func TestGregorianToJalaliFarDatesTakeConstantTime(t *testing.T) { + start := time.Now() + if jy, _, _ := gregorianToJalali(292000000, 1, 1); jy < 290000000 { + t.Fatalf("year 292000000 converted to Jalali year %d", jy) + } + _, _, _ = gregorianToJalali(-292000000, 1, 1) + if took := time.Since(start); took > 100*time.Millisecond { + t.Fatalf("converting far dates took %v", took) + } +} + func TestJalaliExpireDateLabel(t *testing.T) { if got := jalaliExpireDateLabel(0); got != "" { t.Errorf("jalaliExpireDateLabel(0) = %q, want empty", got)