Skip to content

Commit dcfd55a

Browse files
committed
Add Kurdish calendar conversion module
Initial implementation of the kurdical Go module for Kurdish calendar utilities. Includes conversion functions between Gregorian and Kurdish calendars, support for two historical epochs, month names in five Kurdish dialects, and comprehensive tests. Updated README with usage instructions and API documentation.
1 parent dfbceff commit dcfd55a

6 files changed

Lines changed: 450 additions & 1 deletion

File tree

README.md

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,75 @@
1-
# kurdical
1+
# kurdical
2+
3+
A Go module for Kurdish calendar utilities.
4+
5+
This module provides conversion between Gregorian and Kurdish calendars, supporting two historical epochs and month names in five Kurdish dialects.
6+
7+
## Features
8+
9+
- Convert Gregorian dates to Kurdish calendar
10+
- Convert Kurdish calendar dates back to Gregorian
11+
- Support for two Kurdish historical epochs:
12+
- Median Kingdom (Diako)
13+
- Fall of Nineveh (Cyaxares)
14+
- Month names in 5 Kurdish dialects: Laki, Hawrami, Sorani, Kalhuri, Kurmanji
15+
16+
## Installation
17+
18+
```bash
19+
go get github.com/rojcode/kurdical
20+
```
21+
22+
## Usage
23+
24+
```go
25+
package main
26+
27+
import (
28+
"fmt"
29+
"time"
30+
"github.com/rojcode/kurdical"
31+
)
32+
33+
func main() {
34+
// Convert Gregorian to Kurdish
35+
t := time.Date(2023, 3, 21, 0, 0, 0, 0, time.UTC)
36+
k := kurdical.GregorianToKurdish(t, kurdical.Sorani, kurdical.MedianKingdom)
37+
fmt.Printf("Kurdish date: %d-%d-%d %s\n", k.Year, k.Month, k.Day, k.MonthName)
38+
39+
// Convert Kurdish to Gregorian
40+
g, err := kurdical.KurdishToGregorian(k)
41+
if err != nil {
42+
fmt.Println(err)
43+
} else {
44+
fmt.Printf("Gregorian date: %s\n", g.Format("2006-01-02"))
45+
}
46+
}
47+
```
48+
49+
## API
50+
51+
### Types
52+
53+
- `Dialect`: Enum for Kurdish dialects (Laki, Hawrami, Sorani, Kalhuri, Kurmanji)
54+
- `Epoch`: Enum for historical epochs (MedianKingdom, FallOfNineveh)
55+
- `KurdishDate`: Struct representing a date in the Kurdish calendar
56+
57+
### Functions
58+
59+
- `GregorianToKurdish(t time.Time, dialect Dialect, epoch Epoch) KurdishDate`
60+
- `KurdishToGregorian(k KurdishDate) (time.Time, error)`
61+
62+
## Kurdish Calendar Details
63+
64+
The Kurdish calendar is based on the Solar Hijri calendar with adjusted epochs.
65+
66+
- Median Kingdom epoch: Kurdish year = Solar Hijri year + 1321
67+
- Fall of Nineveh epoch: Kurdish year = Solar Hijri year + 1233
68+
69+
## Cultural Notes
70+
71+
This module respects Kurdish cultural heritage by providing accurate month names in authentic dialects. The UTF-8 encoding ensures proper display of Kurdish characters.
72+
73+
## License
74+
75+
See LICENSE file.

conversions.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package kurdical
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
9+
}
10+
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
24+
}
25+
26+
// isSolarHijriLeap determines if a Solar Hijri year is leap.
27+
func isSolarHijriLeap(year int) bool {
28+
return (year+1)%4 == 0
29+
}
30+
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
40+
}
41+
if sJDN <= daysInYear {
42+
break
43+
}
44+
sJDN -= daysInYear
45+
sYear++
46+
}
47+
monthDays := []int{31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29}
48+
if isSolarHijriLeap(sYear) {
49+
monthDays[11] = 30
50+
}
51+
sMonth = 1
52+
for sMonth <= 12 {
53+
if sJDN <= monthDays[sMonth-1] {
54+
sDay = sJDN
55+
break
56+
}
57+
sJDN -= monthDays[sMonth-1]
58+
sMonth++
59+
}
60+
return
61+
}
62+
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+
}
71+
}
72+
monthDays := []int{31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29}
73+
if isSolarHijriLeap(sYear) {
74+
monthDays[11] = 30
75+
}
76+
for m := 1; m < sMonth; m++ {
77+
days += monthDays[m-1]
78+
}
79+
days += sDay - 1
80+
jdn := 1948087 + days
81+
gYear, gMonth, gDay = jdnToGregorian(jdn)
82+
return
83+
}

dialects.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package kurdical
2+
3+
// monthNames holds the month names for each Kurdish dialect.
4+
var monthNames = map[Dialect][]string{
5+
Laki: {
6+
"په‌نجه",
7+
"میریان",
8+
"گاکور",
9+
"ئاگرانی",
10+
"مردار",
11+
"ماله‌ژیر",
12+
"ماله‌ژیر دوماینه",
13+
"تۊلته‌کن",
14+
"مانگ سیه",
15+
"نورووژ",
16+
"خاکه لیه",
17+
"مانگ لیه",
18+
},
19+
Hawrami: {
20+
"نه‌ورۆز",
21+
"پاژه‌ره‌ژ",
22+
"چێڵکڕ",
23+
"کۆپڕ",
24+
"گه‌لاوێژ",
25+
"ئاوه‌وه‌ره",
26+
"ترازیێ",
27+
"گه‌ڵاخه‌زان",
28+
"که‌ڵه‌هه‌رز",
29+
"ئارگا",
30+
"رابڕان",
31+
"سیاوکام",
32+
},
33+
Sorani: {
34+
"خاکه‌لێوه",
35+
"گوڵان",
36+
"جۆزه‌ردان",
37+
"پووشپه‌ڕ",
38+
"گه‌لاوێژ",
39+
"خه‌رمانان",
40+
"ره‌زبه‌ر",
41+
"خه‌زه‌ڵوه‌ر",
42+
"سه‌رماوه‌ز",
43+
"به‌فرانبار",
44+
"رێبه‌ندان",
45+
"ره‌شه‌مێ",
46+
},
47+
Kalhuri: {
48+
"جه‌ژنان (جه‌شنان)",
49+
"گولان",
50+
"زه‌ردان",
51+
"په‌رپه‌ر",
52+
"گ‌لاویژ",
53+
"نوخشان",
54+
"به‌ران",
55+
"خه‌زان",
56+
"ساران",
57+
"به‌فران",
58+
"به‌ندان",
59+
"ره‌مشان",
60+
},
61+
Kurmanji: {
62+
"نیسان",
63+
"گوڵان",
64+
"حه‌زیران",
65+
"تیرمه",
66+
"ته‌باخ",
67+
"ئیلون",
68+
"جوتمه",
69+
"مژدار",
70+
"کانوون",
71+
"چله",
72+
"سبات",
73+
"ئادار",
74+
},
75+
}

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/rojcode/kurdical
2+
3+
go 1.25.4

kurdical.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package kurdical
2+
3+
import (
4+
"fmt"
5+
"time"
6+
)
7+
8+
// Dialect represents the Kurdish dialect for month names.
9+
type Dialect int
10+
11+
// Epoch represents the historical origin for Kurdish year calculation.
12+
type Epoch int
13+
14+
const (
15+
// Laki dialect
16+
Laki Dialect = iota
17+
// Hawrami dialect
18+
Hawrami
19+
// Sorani dialect
20+
Sorani
21+
// Kalhuri dialect
22+
Kalhuri
23+
// Kurmanji dialect
24+
Kurmanji
25+
)
26+
27+
const (
28+
// MedianKingdom epoch (Diako)
29+
MedianKingdom Epoch = iota
30+
// FallOfNineveh epoch (Cyaxares)
31+
FallOfNineveh
32+
)
33+
34+
// KurdishDate represents a date in the Kurdish calendar.
35+
type KurdishDate struct {
36+
Year int
37+
Month int
38+
Day int
39+
MonthName string
40+
Dialect Dialect
41+
Epoch Epoch
42+
}
43+
44+
// epochOffsets maps epochs to their year offsets from Solar Hijri.
45+
var epochOffsets = map[Epoch]int{
46+
MedianKingdom: 1321,
47+
FallOfNineveh: 1233,
48+
}
49+
50+
// GregorianToKurdish converts a Gregorian time.Time to a KurdishDate.
51+
func GregorianToKurdish(t time.Time, dialect Dialect, epoch Epoch) KurdishDate {
52+
year, month, day := t.Date()
53+
sYear, sMonth, sDay := gregorianToSolarHijri(year, int(month), day)
54+
kYear := sYear + epochOffsets[epoch]
55+
monthName := monthNames[dialect][sMonth-1]
56+
return KurdishDate{
57+
Year: kYear,
58+
Month: sMonth,
59+
Day: sDay,
60+
MonthName: monthName,
61+
Dialect: dialect,
62+
Epoch: epoch,
63+
}
64+
}
65+
66+
// KurdishToGregorian converts a KurdishDate to a Gregorian time.Time.
67+
func KurdishToGregorian(k KurdishDate) (time.Time, error) {
68+
if k.Month < 1 || k.Month > 12 {
69+
return time.Time{}, fmt.Errorf("invalid month: %d", k.Month)
70+
}
71+
monthDays := []int{31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29}
72+
sYear := k.Year - epochOffsets[k.Epoch]
73+
if isSolarHijriLeap(sYear) {
74+
monthDays[11] = 30
75+
}
76+
if k.Day < 1 || k.Day > monthDays[k.Month-1] {
77+
return time.Time{}, fmt.Errorf("invalid day: %d for month %d in year %d", k.Day, k.Month, k.Year)
78+
}
79+
gYear, gMonth, gDay := solarHijriToGregorian(sYear, k.Month, k.Day)
80+
return time.Date(gYear, time.Month(gMonth), gDay, 0, 0, 0, 0, time.UTC), nil
81+
}

0 commit comments

Comments
 (0)