kurdish_date 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +50 -0
- data/LICENSE +21 -0
- data/README.md +183 -0
- data/lib/kurdish_date/locale.rb +86 -0
- data/lib/kurdish_date/version.rb +3 -0
- data/lib/kurdish_date.rb +272 -0
- metadata +98 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e3abb81f51a490c36e4031b79fe07736ccdaecbd511720aeed2d5ba2aeb6219f
|
|
4
|
+
data.tar.gz: 706e4ace385092932d785148801aa33cef16ccae41b9027f86898582476f3e01
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0e151ba85cc7911ead063325d308b8e583000ddfeedbaa9b5f5f48f3ca6f083b2bbaeb8f6ea02de76de449a6c1cb1f3e2eb1c217d3ed2092cf19ddf0c3fc75a1
|
|
7
|
+
data.tar.gz: c3467a4f83a69a9437bd0caf522100dea22754d2be70526ad757a0e11228d3660f9d9196c0efb93a020732c808ec1a32749b77615504d363df387d799f9cb07d
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `kurdish_date` will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.2.0] - 2026-08-25
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Two scripts**: `KurdishDate.script` / `KurdishDate.script=` to pick
|
|
14
|
+
the default output script (`:latin` or `:sorani`).
|
|
15
|
+
- All month and weekday names available in both Latin (Hawar-style
|
|
16
|
+
transliteration) and Sorani (native Arabic-based) scripts.
|
|
17
|
+
- `Locale.month_name(month, script)` and
|
|
18
|
+
`Locale.weekday_name(weekday_index, script)` helpers.
|
|
19
|
+
- New `strftime` tokens: `%BB`, `%AA`, `%aa` — always render in
|
|
20
|
+
Sorani, regardless of the default script, so a single format string
|
|
21
|
+
can mix scripts (e.g. `"%A، %-d %BB %Y"`).
|
|
22
|
+
- `#weekday_name(script = nil)` and `#month_name(script = nil)` accept
|
|
23
|
+
an explicit script per call.
|
|
24
|
+
- `Locale` module reorganised: `MONTHS` and `WEEKDAYS` are now
|
|
25
|
+
hashes keyed by `:latin` / `:sorani`.
|
|
26
|
+
- Corrected spellings: `Şemme`, `Yekşemme`, `Duşemme`, `Seyşemme`,
|
|
27
|
+
`Çwarşemme`, `Pêncşemme`, `Heynî`; `Xezelwer`, `Pûşper`.
|
|
28
|
+
|
|
29
|
+
## [0.1.0] - 2026-08-25
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
- Initial release.
|
|
33
|
+
- `KurdishDate::KurdishDate.from_gregorian(date)` — convert a Gregorian
|
|
34
|
+
`Date` / `DateTime` / `Time` to a Kurdish Madhi date.
|
|
35
|
+
- `KurdishDate::KurdishDate.from_kurdish(year, month, day)` — build a
|
|
36
|
+
Kurdish date directly.
|
|
37
|
+
- `KurdishDate::KurdishDate.today` / `.now` — current date in the Kurdish
|
|
38
|
+
calendar.
|
|
39
|
+
- `#to_gregorian` / `#to_date` / `#to_time` / `#to_datetime` — convert
|
|
40
|
+
back to a Ruby date / time.
|
|
41
|
+
- `#strftime` with Kurdish month / weekday names, supporting
|
|
42
|
+
`%Y`, `%y`, `%m`, `%-m`, `%d`, `%-d`, `%B`, `%A`, `%a`.
|
|
43
|
+
- `KurdishDate::Locale::MONTHS` and `KurdishDate::Locale::WEEKDAYS` —
|
|
44
|
+
Central Kurdish (Sorani) month and weekday names.
|
|
45
|
+
- Borkowski-based astronomical conversion, accurate to within one day
|
|
46
|
+
over a window of roughly 5,000 years.
|
|
47
|
+
- Minitest test suite (39 tests, 61 assertions).
|
|
48
|
+
|
|
49
|
+
[Unreleased]: https://github.com/rojcode/kurdish_date/compare/v0.1.0...HEAD
|
|
50
|
+
[0.1.0]: https://github.com/rojcode/kurdish_date/releases/tag/v0.1.0
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kurdish_date contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# kurdish_date
|
|
2
|
+
|
|
3
|
+
A Ruby library for working with the **Kurdish (Sorani) calendar**, also
|
|
4
|
+
known as the Kurdish Madhi (Median) calendar.
|
|
5
|
+
|
|
6
|
+
The day-by-day structure is the same as the Solar Hijri (Jalali) calendar
|
|
7
|
+
used in Iran — six 31-day months, five 30-day months, and a final 29-day
|
|
8
|
+
month (30 in a leap year). The year numbering is the **Madhi era**, which
|
|
9
|
+
counts from the traditional founding of the Median kingdom by Diako
|
|
10
|
+
around 700 BCE. The offset from the Solar Hijri year is fixed at **+1321**,
|
|
11
|
+
so the Hijri-Shamsi year 1405 corresponds to the Kurdish Madhi year 2726.
|
|
12
|
+
|
|
13
|
+
The conversion algorithm is the [Borkowski approximation][borkowski] —
|
|
14
|
+
the same approach used by .NET `PersianCalendar`, the Java
|
|
15
|
+
`PersianDate` library, and `jalaali-js`. It is astronomically accurate to
|
|
16
|
+
within one day over a window of roughly 5,000 years.
|
|
17
|
+
|
|
18
|
+
[borkowski]: http://www.astro.uni.torun.pl/~kb/Papers/EMP/PersianC-EMP.htm
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
Add to your `Gemfile`:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
gem "kurdish_date"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or install directly:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
gem install kurdish_date
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
require "kurdish_date"
|
|
38
|
+
require "date"
|
|
39
|
+
|
|
40
|
+
# Convert a Gregorian date into the Kurdish (Madhi) calendar.
|
|
41
|
+
KurdishDate::KurdishDate.from_gregorian(Date.new(2024, 3, 20))
|
|
42
|
+
# => #<KurdishDate 2724-01-01 (Çwarşemme)>
|
|
43
|
+
|
|
44
|
+
# Convert a Kurdish date back to a Gregorian Date.
|
|
45
|
+
KurdishDate::KurdishDate.from_kurdish(2724, 1, 1).to_gregorian
|
|
46
|
+
# => #<Date: 2024-03-20 ...>
|
|
47
|
+
|
|
48
|
+
# Today, in the Kurdish calendar.
|
|
49
|
+
KurdishDate::KurdishDate.today
|
|
50
|
+
# => #<KurdishDate ...>
|
|
51
|
+
|
|
52
|
+
# Format with strftime (subset of strftime tokens).
|
|
53
|
+
d = KurdishDate::KurdishDate.from_kurdish(2724, 3, 9)
|
|
54
|
+
d.strftime("%Y-%m-%d") # => "2724-03-09"
|
|
55
|
+
d.strftime("%-d %B %Y") # => "9 Cozerdan 2724" (Latin, the default)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Two scripts: Latin and Sorani
|
|
59
|
+
|
|
60
|
+
Month and weekday names are available in two scripts:
|
|
61
|
+
|
|
62
|
+
* **Latin** — Hawar-style transliteration (`Xakelêwe`, `Çwarşemme`).
|
|
63
|
+
* **Sorani** — the native Arabic-based Sorani script
|
|
64
|
+
(`خاکەلێوە`, `چوارشەممە`).
|
|
65
|
+
|
|
66
|
+
Set the default script globally:
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
KurdishDate::KurdishDate.script = :sorani
|
|
70
|
+
|
|
71
|
+
d = KurdishDate::KurdishDate.from_gregorian(Date.new(2024, 3, 20))
|
|
72
|
+
d.month_name # => "خاکەلێوە"
|
|
73
|
+
d.weekday_name # => "چوارشەممە"
|
|
74
|
+
d.strftime("%-d %B %Y") # => "1 خاکەلێوە 2724"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Or pass the script per call:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
d.month_name(:latin) # => "Xakelêwe"
|
|
81
|
+
d.month_name(:sorani) # => "خاکەلێوە"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Or mix scripts in a single format string using the `BB` / `AA` / `aa`
|
|
85
|
+
tokens (always Sorani, regardless of the default):
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
d.strftime("%A، %-d %BB %Y") # => "چوارشەممە، 1 خاکەلێوە 2724"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## API
|
|
92
|
+
|
|
93
|
+
### Factory methods
|
|
94
|
+
|
|
95
|
+
| Method | Description |
|
|
96
|
+
| --- | --- |
|
|
97
|
+
| `KurdishDate.from_gregorian(date)` | Build from a `Date` / `DateTime` / `Time`. |
|
|
98
|
+
| `KurdishDate.from_kurdish(year, month, day)` | Build from a Kurdish date. |
|
|
99
|
+
| `KurdishDate.today` | Today, in the Kurdish calendar. |
|
|
100
|
+
| `KurdishDate.now` | Now, in the Kurdish calendar. |
|
|
101
|
+
|
|
102
|
+
### Instance methods
|
|
103
|
+
|
|
104
|
+
| Method | Description |
|
|
105
|
+
| --- | --- |
|
|
106
|
+
| `#year`, `#month`, `#day` | Kurdish Madhi year / month / day. |
|
|
107
|
+
| `#to_gregorian` | Convert back to a `Date`. |
|
|
108
|
+
| `#to_date`, `#to_time`, `#to_datetime` | Convenience conversions. |
|
|
109
|
+
| `#weekday_name(script = nil)` | Kurdish weekday, e.g. `"Çwarşemme"` or `"چوارشەممە"`. |
|
|
110
|
+
| `#month_name(script = nil)` | Kurdish month, e.g. `"Gulan"` or `"گوڵان"`. |
|
|
111
|
+
| `#leap?` | True if the underlying Solar Hijri year is a leap year. |
|
|
112
|
+
| `#month_days` | Number of days in this month. |
|
|
113
|
+
| `#strftime(format, script: nil)` | Format with the tokens listed below. |
|
|
114
|
+
|
|
115
|
+
### `strftime` tokens
|
|
116
|
+
|
|
117
|
+
| Token | Meaning | Script |
|
|
118
|
+
| --- | --- | --- |
|
|
119
|
+
| `%Y` | 4-digit year | — |
|
|
120
|
+
| `%y` | 2-digit year | — |
|
|
121
|
+
| `%m` | Zero-padded month | — |
|
|
122
|
+
| `%-m` | Unpadded month | — |
|
|
123
|
+
| `%d` | Zero-padded day | — |
|
|
124
|
+
| `%-d` | Unpadded day | — |
|
|
125
|
+
| `%B` | Full month name | default script |
|
|
126
|
+
| `%BB` | Full month name | **Sorani** (always) |
|
|
127
|
+
| `%b` | Abbreviated month name | default script |
|
|
128
|
+
| `%A` | Full weekday name | default script |
|
|
129
|
+
| `%AA` | Full weekday name | **Sorani** (always) |
|
|
130
|
+
| `%a` | Abbreviated weekday name | default script |
|
|
131
|
+
| `%aa` | Abbreviated weekday name | **Sorani** (always) |
|
|
132
|
+
|
|
133
|
+
### Calendar predicates
|
|
134
|
+
|
|
135
|
+
| Method | Description |
|
|
136
|
+
| --- | --- |
|
|
137
|
+
| `KurdishDate.leap?(solar_hijri_year)` | Leap-year check (Solar Hijri year, not Madhi). |
|
|
138
|
+
| `KurdishDate.month_days(solar_hijri_year, month)` | Days in the given month. |
|
|
139
|
+
| `KurdishDate.script` / `KurdishDate.script=` | Default script (`:latin` or `:sorani`). |
|
|
140
|
+
|
|
141
|
+
### `Locale` module
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
KurdishDate::Locale::MONTHS[:latin] # 12 Latin month names
|
|
145
|
+
KurdishDate::Locale::MONTHS[:sorani] # 12 Sorani month names
|
|
146
|
+
KurdishDate::Locale::WEEKDAYS[:latin] # 7 Latin weekday names (Sat..Fri)
|
|
147
|
+
KurdishDate::Locale::WEEKDAYS[:sorani] # 7 Sorani weekday names
|
|
148
|
+
KurdishDate::Locale.month_name(1, :sorani) # => "خاکەلێوە"
|
|
149
|
+
KurdishDate::Locale.weekday_name(0, :latin) # => "Şemme"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Kurdish month names
|
|
153
|
+
|
|
154
|
+
| # | Latin | Sorani | Persian equivalent |
|
|
155
|
+
| - | - | - | - |
|
|
156
|
+
| 1 | Xakelêwe | خاکەلێوە | Farvardin |
|
|
157
|
+
| 2 | Gulan | گوڵان | Ordibehesht |
|
|
158
|
+
| 3 | Cozerdan | جۆزەردان | Khordad |
|
|
159
|
+
| 4 | Pûşper | پووشپەڕ | Tir |
|
|
160
|
+
| 5 | Gelawêj | گەلاوێژ | Mordad |
|
|
161
|
+
| 6 | Xermanan | خەرمانان | Shahrivar |
|
|
162
|
+
| 7 | Rezber | ڕەزبەر | Mehr |
|
|
163
|
+
| 8 | Xezelwer | خەزەڵوەر | Aban |
|
|
164
|
+
| 9 | Sermawêz | سەرماوەز | Azar |
|
|
165
|
+
| 10 | Befranbar | بەفرانبار | Dey |
|
|
166
|
+
| 11 | Rêbendan | ڕێبەندان | Bahman |
|
|
167
|
+
| 12 | Reşemê | ڕەشەمێ | Esfand |
|
|
168
|
+
|
|
169
|
+
The Kurdish week starts on **Saturday**.
|
|
170
|
+
|
|
171
|
+
## Development
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
bundle install
|
|
175
|
+
rake test
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The test suite uses Minitest (stdlib). An RSpec suite is also included in
|
|
179
|
+
`spec/` for projects that prefer RSpec; install RSpec separately to use it.
|
|
180
|
+
|
|
181
|
+
## License
|
|
182
|
+
|
|
183
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
module KurdishDate
|
|
2
|
+
# Locale data for the Kurdish (Sorani) calendar.
|
|
3
|
+
#
|
|
4
|
+
# The Kurdish (Sorani) calendar is the same astronomical calendar as
|
|
5
|
+
# the Solar Hijri (Jalali) calendar used in Iran. This module holds
|
|
6
|
+
# the localised month and weekday names in Central Kurdish.
|
|
7
|
+
#
|
|
8
|
+
# Two scripts are provided for each entry:
|
|
9
|
+
#
|
|
10
|
+
# * `:latin` — the standard transliteration (Hawar-style) used in
|
|
11
|
+
# everyday Latin-script Kurdish writing.
|
|
12
|
+
# * `:sorani` — the native Sorani script (Arabic-based), which is the
|
|
13
|
+
# standard orthography in Iraqi Kurdistan and the Iranian Kurdish
|
|
14
|
+
# regions for daily, literary, and official use.
|
|
15
|
+
module Locale
|
|
16
|
+
# Kurdish month names. The first six months have 31 days, the next
|
|
17
|
+
# five have 30 days, and the 12th month has 29 (or 30 in a leap year).
|
|
18
|
+
MONTHS = {
|
|
19
|
+
latin: [
|
|
20
|
+
"Xakelêwe", # 1
|
|
21
|
+
"Gulan", # 2
|
|
22
|
+
"Cozerdan", # 3
|
|
23
|
+
"Pûşper", # 4
|
|
24
|
+
"Gelawêj", # 5
|
|
25
|
+
"Xermanan", # 6
|
|
26
|
+
"Rezber", # 7
|
|
27
|
+
"Xezelwer", # 8
|
|
28
|
+
"Sermawez", # 9
|
|
29
|
+
"Befranbar", # 10
|
|
30
|
+
"Rêbendan", # 11
|
|
31
|
+
"Reşemê" # 12
|
|
32
|
+
].freeze,
|
|
33
|
+
sorani: [
|
|
34
|
+
"خاکەلێوە", # 1
|
|
35
|
+
"گوڵان", # 2
|
|
36
|
+
"جۆزەردان", # 3
|
|
37
|
+
"پووشپەڕ", # 4
|
|
38
|
+
"گەلاوێژ", # 5
|
|
39
|
+
"خەرمانان", # 6
|
|
40
|
+
"ڕەزبەر", # 7
|
|
41
|
+
"خەزەڵوەر", # 8
|
|
42
|
+
"سەرماوەز", # 9
|
|
43
|
+
"بەفرانبار", # 10
|
|
44
|
+
"ڕێبەندان", # 11
|
|
45
|
+
"ڕەشەمێ" # 12
|
|
46
|
+
].freeze
|
|
47
|
+
}.freeze
|
|
48
|
+
|
|
49
|
+
# Weekday names. The Kurdish week starts on Saturday.
|
|
50
|
+
WEEKDAYS = {
|
|
51
|
+
latin: [
|
|
52
|
+
"Şemme", # 0 — Saturday — شەممە
|
|
53
|
+
"Yekşemme", # 1 — Sunday — یەکشەممە
|
|
54
|
+
"Duşemme", # 2 — Monday — دووشەممە
|
|
55
|
+
"Seyşemme", # 3 — Tuesday — سێشەممە
|
|
56
|
+
"Çwarşemme", # 4 — Wednesday — چوارشەممە
|
|
57
|
+
"Pêncşemme", # 5 — Thursday — پێنجشەممە
|
|
58
|
+
"Heynî" # 6 — Friday — هەینی
|
|
59
|
+
].freeze,
|
|
60
|
+
sorani: [
|
|
61
|
+
"شەممە", # 0 — Saturday
|
|
62
|
+
"یەکشەممە", # 1 — Sunday
|
|
63
|
+
"دووشەممە", # 2 — Monday
|
|
64
|
+
"سێشەممە", # 3 — Tuesday
|
|
65
|
+
"چوارشەممە", # 4 — Wednesday
|
|
66
|
+
"پێنجشەممە", # 5 — Thursday
|
|
67
|
+
"هەینی" # 6 — Friday
|
|
68
|
+
].freeze
|
|
69
|
+
}.freeze
|
|
70
|
+
|
|
71
|
+
# Default script when none is specified.
|
|
72
|
+
DEFAULT_SCRIPT = :latin
|
|
73
|
+
|
|
74
|
+
# Returns the month name for the given 1-based month number, in the
|
|
75
|
+
# requested script (:latin or :sorani).
|
|
76
|
+
def self.month_name(month, script = DEFAULT_SCRIPT)
|
|
77
|
+
MONTHS.fetch(script)[month - 1]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Returns the weekday name for the given 0-based weekday index
|
|
81
|
+
# (Saturday = 0), in the requested script (:latin or :sorani).
|
|
82
|
+
def self.weekday_name(weekday_index, script = DEFAULT_SCRIPT)
|
|
83
|
+
WEEKDAYS.fetch(script)[weekday_index]
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
data/lib/kurdish_date.rb
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
require "date"
|
|
2
|
+
require "kurdish_date/version"
|
|
3
|
+
require "kurdish_date/locale"
|
|
4
|
+
|
|
5
|
+
module KurdishDate
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
class InvalidDateError < Error; end
|
|
8
|
+
|
|
9
|
+
# Represents a date in the Kurdish (Sorani) calendar.
|
|
10
|
+
#
|
|
11
|
+
# The Kurdish (Sorani) calendar uses the same astronomical year
|
|
12
|
+
# structure as the Solar Hijri (Jalali) calendar used in Iran — the
|
|
13
|
+
# first six months have 31 days, the next five 30 days, and the
|
|
14
|
+
# final month (Esme Xakêle / Reşemê / Esfand) has 29 days in a
|
|
15
|
+
# common year and 30 in a leap year.
|
|
16
|
+
#
|
|
17
|
+
# The year numbering is the **Madhi (Kurdish Median) era**, counting
|
|
18
|
+
# from the traditional founding of the Median kingdom by Diako
|
|
19
|
+
# around 700 BCE. The offset is fixed at +1321 years from the
|
|
20
|
+
# Solar Hijri (Jalali) year, so a Hijri-Shamsi year of 1405
|
|
21
|
+
# corresponds to the Kurdish Madhi year of 2726.
|
|
22
|
+
#
|
|
23
|
+
# The day-by-day conversion uses Borkowski's analytic approximation
|
|
24
|
+
# (the same algorithm used by .NET PersianCalendar, jalaali-js, and
|
|
25
|
+
# the Java PersianDate library), accurate to within one day over a
|
|
26
|
+
# window of roughly 5,000 years.
|
|
27
|
+
#
|
|
28
|
+
# Month and weekday names are available in two scripts:
|
|
29
|
+
#
|
|
30
|
+
# * `:latin` — Hawar-style Latin transliteration
|
|
31
|
+
# * `:sorani` — the native Arabic-based Sorani script
|
|
32
|
+
#
|
|
33
|
+
# Use {KurdishDate.script=}, {KurdishDate.script}, or the per-call
|
|
34
|
+
# `:script` option on the formatting methods to choose.
|
|
35
|
+
class KurdishDate
|
|
36
|
+
MONTHS = Locale::MONTHS
|
|
37
|
+
WEEKDAYS = Locale::WEEKDAYS
|
|
38
|
+
|
|
39
|
+
# Ruby's Date#wday: 0=Sun, 1=Mon, ..., 5=Fri, 6=Sat.
|
|
40
|
+
KURDISH_WEEK_START = 6 # Saturday
|
|
41
|
+
|
|
42
|
+
# Borkowski's reference constants. The Solar Hijri epoch (1 Farwardin
|
|
43
|
+
# 1 SH) corresponds to JDN 2,121,446.
|
|
44
|
+
CYCLE_DAYS = 1_029_983 # days in a 2820-year super-cycle
|
|
45
|
+
CYCLE_YEARS = 2_820
|
|
46
|
+
PERSIAN_EPOCH = 2_121_446
|
|
47
|
+
YEAR_LENGTH = 365.24219858156028
|
|
48
|
+
LEAP_THRESHOLD = 0.24219858156028
|
|
49
|
+
|
|
50
|
+
# Offset from Solar Hijri year to Kurdish Madhi (Diako) year.
|
|
51
|
+
MADHI_OFFSET = 1321
|
|
52
|
+
|
|
53
|
+
@script = Locale::DEFAULT_SCRIPT
|
|
54
|
+
|
|
55
|
+
class << self
|
|
56
|
+
# Default script (:latin or :sorani) used by formatting helpers
|
|
57
|
+
# when no script is passed explicitly.
|
|
58
|
+
attr_accessor :script
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
attr_reader :year, :month, :day
|
|
62
|
+
|
|
63
|
+
def initialize(year, month, day)
|
|
64
|
+
unless year.is_a?(Integer) && month.is_a?(Integer) && day.is_a?(Integer)
|
|
65
|
+
raise InvalidDateError, "year, month and day must be Integers"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
unless month.between?(1, 12)
|
|
69
|
+
raise InvalidDateError, "month must be between 1 and 12, got #{month}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Calendar rules (leap year, month lengths) are defined on the
|
|
73
|
+
# Solar Hijri year; the Madhi year differs by a fixed offset.
|
|
74
|
+
solar_year = year - MADHI_OFFSET
|
|
75
|
+
max_day = self.class.month_days(solar_year, month)
|
|
76
|
+
unless day.between?(1, max_day)
|
|
77
|
+
raise InvalidDateError,
|
|
78
|
+
"day must be between 1 and #{max_day} for month #{month} of year #{year}, got #{day}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
@year = year
|
|
82
|
+
@month = month
|
|
83
|
+
@day = day
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# ---- factory methods ------------------------------------------------
|
|
87
|
+
|
|
88
|
+
# Build a KurdishDate from a Gregorian date (Date / DateTime / Time).
|
|
89
|
+
def self.from_gregorian(date)
|
|
90
|
+
jd = date.respond_to?(:jd) ? date.jd : ::Date.parse(date.to_s).jd
|
|
91
|
+
jy, jm, jd2 = jalali_from_jd(jd)
|
|
92
|
+
new(jy + MADHI_OFFSET, jm, jd2)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Build a KurdishDate from a Kurdish Madhi (year, month, day).
|
|
96
|
+
def self.from_kurdish(year, month, day)
|
|
97
|
+
new(year, month, day)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Today in the Kurdish calendar.
|
|
101
|
+
def self.today
|
|
102
|
+
from_gregorian(::Date.today)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Now in the Kurdish calendar.
|
|
106
|
+
def self.now
|
|
107
|
+
from_gregorian(::DateTime.now)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# ---- accessors ------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
def to_gregorian
|
|
113
|
+
jd = self.class.jd_from_jalali(@year - MADHI_OFFSET, @month, @day)
|
|
114
|
+
::Date.jd(jd)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def to_date
|
|
118
|
+
to_gregorian
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def to_time
|
|
122
|
+
to_gregorian.to_time
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def to_datetime
|
|
126
|
+
jd = self.class.jd_from_jalali(@year - MADHI_OFFSET, @month, @day)
|
|
127
|
+
::DateTime.jd(jd)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Returns the Kurdish weekday index, 0-based, starting at Saturday.
|
|
131
|
+
def weekday_index
|
|
132
|
+
((to_gregorian.wday - KURDISH_WEEK_START) % 7)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Full Kurdish weekday name, e.g. "Şemme" (Latin) or
|
|
136
|
+
# "شەممە" (Sorani). `script` defaults to the class-level setting.
|
|
137
|
+
def weekday_name(script = nil)
|
|
138
|
+
Locale.weekday_name(weekday_index, script || self.class.script)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Full Kurdish month name, e.g. "Gulan" (Latin) or
|
|
142
|
+
# "گوڵان" (Sorani). `script` defaults to the class-level setting.
|
|
143
|
+
def month_name(script = nil)
|
|
144
|
+
Locale.month_name(@month, script || self.class.script)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def leap?
|
|
148
|
+
self.class.leap?(@year - MADHI_OFFSET)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def month_days
|
|
152
|
+
self.class.month_days(@year - MADHI_OFFSET, @month)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# ---- formatting -----------------------------------------------------
|
|
156
|
+
|
|
157
|
+
# Format the date. Supported tokens:
|
|
158
|
+
# %Y 4-digit year %y 2-digit year
|
|
159
|
+
# %m zero-padded month %-m unpadded month
|
|
160
|
+
# %d zero-padded day %-d unpadded day
|
|
161
|
+
# %B full month name (Latin) %BB full month name (Sorani)
|
|
162
|
+
# %b abbreviated month name (Latin)
|
|
163
|
+
# %A full weekday name (Latin) %AA full weekday name (Sorani)
|
|
164
|
+
# %a abbreviated weekday name (Latin) %aa abbreviated weekday name (Sorani)
|
|
165
|
+
#
|
|
166
|
+
# The single-letter tokens (%B / %A / %a / %b) use the default
|
|
167
|
+
# script set by `KurdishDate.script=`. The double-letter tokens
|
|
168
|
+
# (%BB / %AA / %aa) always use Sorani so a mixed-language format
|
|
169
|
+
# string is possible.
|
|
170
|
+
def strftime(format = "%Y-%m-%d", script: nil)
|
|
171
|
+
script = (script || self.class.script).to_sym
|
|
172
|
+
format.gsub(/%-?Y|%-?y|%-?m|%-?d|%BB|%AA|%aa|%B|%b|%A|%a/) do |token|
|
|
173
|
+
case token
|
|
174
|
+
when "%Y" then @year.to_s.rjust(4, "0")
|
|
175
|
+
when "%y" then (@year % 100).to_s.rjust(2, "0")
|
|
176
|
+
when "%m" then @month.to_s.rjust(2, "0")
|
|
177
|
+
when "%-m" then @month.to_s
|
|
178
|
+
when "%d" then @day.to_s.rjust(2, "0")
|
|
179
|
+
when "%-d" then @day.to_s
|
|
180
|
+
when "%BB" then Locale.month_name(@month, :sorani)
|
|
181
|
+
when "%AA" then Locale.weekday_name(weekday_index, :sorani)
|
|
182
|
+
when "%aa" then Locale.weekday_name(weekday_index, :sorani)[0, 3]
|
|
183
|
+
when "%B" then Locale.month_name(@month, script)
|
|
184
|
+
when "%b" then Locale.month_name(@month, script)[0, 3]
|
|
185
|
+
when "%A" then Locale.weekday_name(weekday_index, script)
|
|
186
|
+
when "%a" then Locale.weekday_name(weekday_index, script)[0, 3]
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def to_s(format = "%Y-%m-%d", **opts)
|
|
192
|
+
strftime(format, **opts)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def inspect
|
|
196
|
+
"#<KurdishDate #{strftime("%Y-%m-%d")} (#{weekday_name})>"
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def ==(other)
|
|
200
|
+
other.is_a?(KurdishDate) &&
|
|
201
|
+
year == other.year && month == other.month && day == other.day
|
|
202
|
+
end
|
|
203
|
+
alias eql? ==
|
|
204
|
+
|
|
205
|
+
def hash
|
|
206
|
+
[@year, @month, @day].hash
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def to_a
|
|
210
|
+
[@year, @month, @day]
|
|
211
|
+
end
|
|
212
|
+
alias deconstruct to_a
|
|
213
|
+
|
|
214
|
+
# ---- calendar predicates -------------------------------------------
|
|
215
|
+
|
|
216
|
+
# Note: leap year is a property of the *solar* year, which is shared
|
|
217
|
+
# between the Solar Hijri and Kurdish Madhi calendars.
|
|
218
|
+
def self.leap?(solar_hijri_year)
|
|
219
|
+
((solar_hijri_year + 2346) * LEAP_THRESHOLD) % 1 < LEAP_THRESHOLD
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def self.month_days(solar_hijri_year, month)
|
|
223
|
+
return 31 if month.between?(1, 6)
|
|
224
|
+
return 30 if month.between?(7, 11)
|
|
225
|
+
leap?(solar_hijri_year) ? 30 : 29
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# ---- internal: JDN <-> Solar-Hijri (Borkowski) ---------------------
|
|
229
|
+
# All internal conversions operate on Solar Hijri year numbers.
|
|
230
|
+
# The Madhi offset is applied at the public API boundary.
|
|
231
|
+
|
|
232
|
+
# JDN -> (Solar Hijri year, month, day). Borkowski's approximation.
|
|
233
|
+
def self.jalali_from_jd(jd)
|
|
234
|
+
jd = jd.to_i
|
|
235
|
+
offset = jd - PERSIAN_EPOCH
|
|
236
|
+
cycle_no = offset / CYCLE_DAYS
|
|
237
|
+
cycle_no -= 1 if offset < 0
|
|
238
|
+
cycle_start = PERSIAN_EPOCH + cycle_no * CYCLE_DAYS
|
|
239
|
+
yc = ((jd - cycle_start) / YEAR_LENGTH).floor
|
|
240
|
+
year = yc + 475 + cycle_no * CYCLE_YEARS
|
|
241
|
+
lll = PERSIAN_EPOCH + cycle_no * CYCLE_DAYS + (yc * YEAR_LENGTH).floor
|
|
242
|
+
day = jd - lll + 1
|
|
243
|
+
if day > (leap?(year) ? 366 : 365)
|
|
244
|
+
year += 1
|
|
245
|
+
day = 1
|
|
246
|
+
end
|
|
247
|
+
month = 1
|
|
248
|
+
d = day
|
|
249
|
+
while month <= 12 && d > month_days(year, month)
|
|
250
|
+
d -= month_days(year, month)
|
|
251
|
+
month += 1
|
|
252
|
+
end
|
|
253
|
+
[year, month, d]
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# (Solar Hijri year, month, day) -> JDN. Borkowski's reverse.
|
|
257
|
+
def self.jd_from_jalali(year, month, day)
|
|
258
|
+
era = (year - 475) / CYCLE_YEARS
|
|
259
|
+
era -= 1 if (year - 475) < 0
|
|
260
|
+
y_c = (year - 475) - era * CYCLE_YEARS
|
|
261
|
+
first_d = PERSIAN_EPOCH + era * CYCLE_DAYS + (y_c * YEAR_LENGTH).floor
|
|
262
|
+
first_d + (day_of_year(year, month, day) - 1)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Day-of-year in the Solar Hijri / Kurdish calendar.
|
|
266
|
+
def self.day_of_year(year, month, day)
|
|
267
|
+
sum = 0
|
|
268
|
+
(1...month).each { |m| sum += month_days(year, m) }
|
|
269
|
+
sum + day
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: kurdish_date
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- RojCode
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-08-25 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '13.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '13.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.12'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.12'
|
|
55
|
+
description: A Ruby library for working with the Kurdish (Sorani) calendar. Provides
|
|
56
|
+
conversion to/from the Gregorian calendar, Kurdish month and weekday names, and
|
|
57
|
+
formatting helpers for Central Kurdish (Sorani).
|
|
58
|
+
email:
|
|
59
|
+
- rojcode@example.com
|
|
60
|
+
executables: []
|
|
61
|
+
extensions: []
|
|
62
|
+
extra_rdoc_files: []
|
|
63
|
+
files:
|
|
64
|
+
- CHANGELOG.md
|
|
65
|
+
- LICENSE
|
|
66
|
+
- README.md
|
|
67
|
+
- lib/kurdish_date.rb
|
|
68
|
+
- lib/kurdish_date/locale.rb
|
|
69
|
+
- lib/kurdish_date/version.rb
|
|
70
|
+
homepage: https://github.com/rojcode/kurdish_date
|
|
71
|
+
licenses:
|
|
72
|
+
- MIT
|
|
73
|
+
metadata:
|
|
74
|
+
homepage_uri: https://github.com/rojcode/kurdish_date
|
|
75
|
+
source_code_uri: https://github.com/rojcode/kurdish_date
|
|
76
|
+
changelog_uri: https://github.com/rojcode/kurdish_date/blob/main/CHANGELOG.md
|
|
77
|
+
bug_tracker_uri: https://github.com/rojcode/kurdish_date/issues
|
|
78
|
+
rubygems_mfa_required: 'true'
|
|
79
|
+
post_install_message:
|
|
80
|
+
rdoc_options: []
|
|
81
|
+
require_paths:
|
|
82
|
+
- lib
|
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: 2.7.0
|
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
|
+
requirements:
|
|
90
|
+
- - ">="
|
|
91
|
+
- !ruby/object:Gem::Version
|
|
92
|
+
version: '0'
|
|
93
|
+
requirements: []
|
|
94
|
+
rubygems_version: 3.0.3.1
|
|
95
|
+
signing_key:
|
|
96
|
+
specification_version: 4
|
|
97
|
+
summary: Kurdish (Sorani) calendar — convert Gregorian dates to/from Kurdish dates.
|
|
98
|
+
test_files: []
|