|
1 | 1 | package kurdical |
2 | 2 |
|
3 | | -// julianDayNumber calculates the Julian Day Number for a Gregorian date. |
4 | | -func julianDayNumber(year, month, day int) int { |
5 | | - a := (14 - month) / 12 |
6 | | - y := year + 4800 - a |
7 | | - m := month + 12*a - 3 |
8 | | - return day + (153*m+2)/5 + 365*y + y/4 - y/100 + y/400 - 32045 |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "time" |
| 6 | +) |
| 7 | + |
| 8 | +var ( |
| 9 | + breaks = [...]int{-61, 9, 38, 199, 426, 686, 756, 818, 1111, 1181, 1210, |
| 10 | + 1635, 2060, 2097, 2192, 2262, 2324, 2394, 2456, 3178} |
| 11 | +) |
| 12 | + |
| 13 | +// gregorianToSolarHijri converts Gregorian date to Solar Hijri. |
| 14 | +func gregorianToSolarHijri(gYear, gMonth, gDay int) (sYear, sMonth, sDay int) { |
| 15 | + jy, jm, jd, err := toJalaali(gYear, time.Month(gMonth), gDay) |
| 16 | + if err != nil { |
| 17 | + // For invalid dates, return 0 |
| 18 | + return 0, 0, 0 |
| 19 | + } |
| 20 | + return jy, int(jm), jd |
9 | 21 | } |
10 | 22 |
|
11 | | -// jdnToGregorian converts Julian Day Number to Gregorian date. |
12 | | -func jdnToGregorian(jdn int) (year, month, day int) { |
13 | | - l := jdn + 68569 |
14 | | - n := (4 * l) / 146097 |
15 | | - l = l - (146097*n+3)/4 |
16 | | - i := (4000 * (l + 1)) / 1461001 |
17 | | - l = l - (1461*i)/4 + 31 |
18 | | - j := (80 * l) / 2447 |
19 | | - day = l - (2447*j)/80 |
20 | | - l = j / 11 |
21 | | - month = j + 2 - 12*l |
22 | | - year = 100*(n-49) + i + l |
23 | | - return |
| 23 | +// solarHijriToGregorian converts Solar Hijri date to Gregorian. |
| 24 | +func solarHijriToGregorian(sYear, sMonth, sDay int) (gYear, gMonth, gDay int) { |
| 25 | + gy, gm, gd, err := toGregorian(sYear, Month(sMonth), sDay) |
| 26 | + if err != nil { |
| 27 | + return 0, 0, 0 |
| 28 | + } |
| 29 | + return gy, int(gm), gd |
24 | 30 | } |
25 | 31 |
|
26 | | -// isSolarHijriLeap determines if a Solar Hijri year is leap. |
27 | | -func isSolarHijriLeap(year int) bool { |
28 | | - return (year+1)%4 == 0 |
| 32 | +// Month represents a month of the year. |
| 33 | +type Month int |
| 34 | + |
| 35 | +const ( |
| 36 | + Farvardin Month = 1 + iota |
| 37 | + Ordibehesht |
| 38 | + Khordad |
| 39 | + Tir |
| 40 | + Mordad |
| 41 | + Shahrivar |
| 42 | + Mehr |
| 43 | + Aban |
| 44 | + Azar |
| 45 | + Dey |
| 46 | + Bahman |
| 47 | + Esfand |
| 48 | +) |
| 49 | + |
| 50 | +// toJalaali converts Gregorian to Jalaali date. |
| 51 | +func toJalaali(gregorianYear int, gregorianMonth time.Month, gregorianDay int) (int, Month, int, error) { |
| 52 | + jy, jm, jd, err := d2j(g2d(gregorianYear, int(gregorianMonth), gregorianDay)) |
| 53 | + return jy, Month(jm), jd, err |
29 | 54 | } |
30 | 55 |
|
31 | | -// gregorianToSolarHijri converts Gregorian date to Solar Hijri. |
32 | | -func gregorianToSolarHijri(gYear, gMonth, gDay int) (sYear, sMonth, sDay int) { |
33 | | - gJDN := julianDayNumber(gYear, gMonth, gDay) |
34 | | - sJDN := gJDN - 1948087 |
35 | | - sYear = 1 |
36 | | - for { |
37 | | - daysInYear := 365 |
38 | | - if isSolarHijriLeap(sYear) { |
39 | | - daysInYear = 366 |
| 56 | +// toGregorian converts Jalaali to Gregorian date. |
| 57 | +func toGregorian(jalaaliYear int, jalaaliMonth Month, jalaaliDay int) (int, time.Month, int, error) { |
| 58 | + jdn, err := j2d(jalaaliYear, int(jalaaliMonth), jalaaliDay) |
| 59 | + if err != nil { |
| 60 | + return 0, 0, 0, err |
| 61 | + } |
| 62 | + gy, gm, gd := d2g(jdn) |
| 63 | + return gy, time.Month(gm), gd, nil |
| 64 | +} |
| 65 | + |
| 66 | +func j2d(jy, jm, jd int) (jdn int, err error) { |
| 67 | + _, gy, march, err := jalCal(jy) |
| 68 | + if err != nil { |
| 69 | + return 0, err |
| 70 | + } |
| 71 | + return g2d(gy, 3, march) + (jm-1)*31 - div(jm, 7)*(jm-7) + jd - 1, nil |
| 72 | +} |
| 73 | + |
| 74 | +func d2j(jdn int) (int, int, int, error) { |
| 75 | + gy, _, _ := d2g(jdn) |
| 76 | + jy := gy - 621 |
| 77 | + leap, _, march, err := jalCal(jy) |
| 78 | + jdn1f := g2d(gy, 3, march) |
| 79 | + |
| 80 | + if err != nil { |
| 81 | + return 0, 0, 0, err |
| 82 | + } |
| 83 | + |
| 84 | + k := jdn - jdn1f |
| 85 | + if k >= 0 { |
| 86 | + if k <= 185 { |
| 87 | + jm := 1 + div(k, 31) |
| 88 | + jd := mod(k, 31) + 1 |
| 89 | + return jy, jm, jd, nil |
40 | 90 | } |
41 | | - if sJDN <= daysInYear { |
42 | | - break |
| 91 | + k -= 186 |
| 92 | + } else { |
| 93 | + jy-- |
| 94 | + k += 179 |
| 95 | + if leap == 1 { |
| 96 | + k++ |
43 | 97 | } |
44 | | - sJDN -= daysInYear |
45 | | - sYear++ |
46 | 98 | } |
47 | | - monthDays := []int{31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29} |
48 | | - if isSolarHijriLeap(sYear) { |
49 | | - monthDays[11] = 30 |
| 99 | + jm := 7 + div(k, 30) |
| 100 | + jd := mod(k, 30) + 1 |
| 101 | + return jy, jm, jd, nil |
| 102 | +} |
| 103 | + |
| 104 | +func jalCal(jy int) (int, int, int, error) { |
| 105 | + bl, gy, leapJ, jp := len(breaks), jy+621, -14, breaks[0] |
| 106 | + jump := 0 |
| 107 | + |
| 108 | + if jy < jp || jy >= breaks[bl-1] { |
| 109 | + return 0, 0, 0, &ErrorInvalidYear{jy} |
50 | 110 | } |
51 | | - sMonth = 1 |
52 | | - for sMonth <= 12 { |
53 | | - if sJDN <= monthDays[sMonth-1] { |
54 | | - sDay = sJDN |
| 111 | + |
| 112 | + for i := 1; i < bl; i++ { |
| 113 | + jm := breaks[i] |
| 114 | + jump = jm - jp |
| 115 | + if jy < jm { |
55 | 116 | break |
56 | 117 | } |
57 | | - sJDN -= monthDays[sMonth-1] |
58 | | - sMonth++ |
| 118 | + leapJ += div(jump, 33)*8 + div(mod(jump, 33), 4) |
| 119 | + jp = jm |
59 | 120 | } |
60 | | - return |
61 | | -} |
| 121 | + n := jy - jp |
62 | 122 |
|
63 | | -// solarHijriToGregorian converts Solar Hijri date to Gregorian. |
64 | | -func solarHijriToGregorian(sYear, sMonth, sDay int) (gYear, gMonth, gDay int) { |
65 | | - days := 0 |
66 | | - for y := 1; y < sYear; y++ { |
67 | | - days += 365 |
68 | | - if isSolarHijriLeap(y) { |
69 | | - days++ |
70 | | - } |
| 123 | + leapJ += div(n, 33)*8 + div(mod(n, 33)+3, 4) |
| 124 | + if mod(jump, 33) == 4 && jump-n == 4 { |
| 125 | + leapJ++ |
71 | 126 | } |
72 | | - monthDays := []int{31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29} |
73 | | - if isSolarHijriLeap(sYear) { |
74 | | - monthDays[11] = 30 |
| 127 | + |
| 128 | + leapG := div(gy, 4) - div((div(gy, 100)+1)*3, 4) - 150 |
| 129 | + |
| 130 | + march := 20 + leapJ - leapG |
| 131 | + |
| 132 | + if jump-n < 6 { |
| 133 | + n -= jump + div(jump+4, 33)*33 |
75 | 134 | } |
76 | | - for m := 1; m < sMonth; m++ { |
77 | | - days += monthDays[m-1] |
| 135 | + leap := mod(mod(n+1, 33)-1, 4) |
| 136 | + if leap == -1 { |
| 137 | + leap = 4 |
78 | 138 | } |
79 | | - days += sDay - 1 |
80 | | - jdn := 1948087 + days |
81 | | - gYear, gMonth, gDay = jdnToGregorian(jdn) |
82 | | - return |
| 139 | + |
| 140 | + return leap, gy, march, nil |
| 141 | +} |
| 142 | + |
| 143 | +func g2d(gy, gm, gd int) int { |
| 144 | + d := div((gy+div(gm-8, 6)+100100)*1461, 4) + |
| 145 | + div(153*mod(gm+9, 12)+2, 5) + |
| 146 | + gd - 34840408 |
| 147 | + d = d - div(div(gy+100100+div(gm-8, 6), 100)*3, 4) + 752 |
| 148 | + return d |
| 149 | +} |
| 150 | + |
| 151 | +func d2g(jdn int) (int, int, int) { |
| 152 | + j := 4*jdn + 139361631 |
| 153 | + j = j + div(div(4*jdn+183187720, 146097)*3, 4)*4 - 3908 |
| 154 | + i := div(mod(j, 1461), 4)*5 + 308 |
| 155 | + gd := div(mod(i, 153), 5) + 1 |
| 156 | + gm := mod(div(i, 153), 12) + 1 |
| 157 | + gy := div(j, 1461) - 100100 + div(8-gm, 6) |
| 158 | + return gy, gm, gd |
| 159 | +} |
| 160 | + |
| 161 | +func div(a, b int) int { |
| 162 | + return a / b |
| 163 | +} |
| 164 | + |
| 165 | +func mod(a, b int) int { |
| 166 | + return a % b |
| 167 | +} |
| 168 | + |
| 169 | +// ErrorInvalidYear represents an error for invalid year. |
| 170 | +type ErrorInvalidYear struct { |
| 171 | + Year int |
| 172 | +} |
| 173 | + |
| 174 | +func (e *ErrorInvalidYear) Error() string { |
| 175 | + return fmt.Sprintf("invalid year: %d", e.Year) |
| 176 | +} |
| 177 | + |
| 178 | +// isSolarHijriLeap determines if a Solar Hijri year is leap. |
| 179 | +func isSolarHijriLeap(year int) bool { |
| 180 | + leap, _, _, err := jalCal(year) |
| 181 | + return err == nil && leap == 1 |
83 | 182 | } |
0 commit comments