djalaali 0.1.0__tar.gz
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.
- djalaali-0.1.0/PKG-INFO +65 -0
- djalaali-0.1.0/README.md +31 -0
- djalaali-0.1.0/djalaali/__init__.py +1 -0
- djalaali-0.1.0/djalaali/_calgo.c +336 -0
- djalaali-0.1.0/djalaali/algo.py +235 -0
- djalaali-0.1.0/djalaali/apps.py +132 -0
- djalaali-0.1.0/djalaali/forms.py +172 -0
- djalaali-0.1.0/djalaali/jdate.py +341 -0
- djalaali-0.1.0/djalaali/jdatetime.py +222 -0
- djalaali-0.1.0/djalaali/models.py +10 -0
- djalaali-0.1.0/djalaali/static/djalaali/css/djalaali.css +199 -0
- djalaali-0.1.0/djalaali/static/djalaali/js/djalaali-core.js +123 -0
- djalaali-0.1.0/djalaali/static/djalaali/js/djalaali.js +378 -0
- djalaali-0.1.0/djalaali/templatetags/__init__.py +0 -0
- djalaali-0.1.0/djalaali/templatetags/djalaali_tags.py +24 -0
- djalaali-0.1.0/djalaali.egg-info/PKG-INFO +65 -0
- djalaali-0.1.0/djalaali.egg-info/SOURCES.txt +27 -0
- djalaali-0.1.0/djalaali.egg-info/dependency_links.txt +1 -0
- djalaali-0.1.0/djalaali.egg-info/requires.txt +6 -0
- djalaali-0.1.0/djalaali.egg-info/top_level.txt +1 -0
- djalaali-0.1.0/pyproject.toml +65 -0
- djalaali-0.1.0/setup.cfg +4 -0
- djalaali-0.1.0/setup.py +18 -0
- djalaali-0.1.0/tests/test_algo.py +176 -0
- djalaali-0.1.0/tests/test_apps_forms.py +218 -0
- djalaali-0.1.0/tests/test_calgo.py +71 -0
- djalaali-0.1.0/tests/test_integration.py +99 -0
- djalaali-0.1.0/tests/test_jdate.py +207 -0
- djalaali-0.1.0/tests/test_jdatetime.py +143 -0
djalaali-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: djalaali
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Jalali (Persian) calendar support for Django
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: django,jalali,persian,calendar,jdate
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Environment :: Web Environment
|
|
9
|
+
Classifier: Framework :: Django
|
|
10
|
+
Classifier: Framework :: Django :: 4.2
|
|
11
|
+
Classifier: Framework :: Django :: 5.0
|
|
12
|
+
Classifier: Framework :: Django :: 5.1
|
|
13
|
+
Classifier: Framework :: Django :: 5.2
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: C
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
26
|
+
Classifier: Topic :: Software Development :: Internationalization
|
|
27
|
+
Requires-Python: >=3.9
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
Requires-Dist: Django>=4.2
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-xdist; extra == "dev"
|
|
34
|
+
|
|
35
|
+
# djalaali
|
|
36
|
+
|
|
37
|
+
Jalali (Persian) calendar support for Django: `jdate` and `jdatetime` types,
|
|
38
|
+
model and form fields, and template tags.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
pip install djalaali
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Add `"djalaali"` to `INSTALLED_APPS`.
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from djalaali.jdate import jdate
|
|
52
|
+
|
|
53
|
+
d = jdate(1403, 1, 1)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Development
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
make install
|
|
60
|
+
make test
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
MIT
|
djalaali-0.1.0/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# djalaali
|
|
2
|
+
|
|
3
|
+
Jalali (Persian) calendar support for Django: `jdate` and `jdatetime` types,
|
|
4
|
+
model and form fields, and template tags.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pip install djalaali
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Add `"djalaali"` to `INSTALLED_APPS`.
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from djalaali.jdate import jdate
|
|
18
|
+
|
|
19
|
+
d = jdate(1403, 1, 1)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Development
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
make install
|
|
26
|
+
make test
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## License
|
|
30
|
+
|
|
31
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* C implementation of the Jalaali conversion algorithm in djalaali/algo.py.
|
|
3
|
+
*
|
|
4
|
+
* Ported from https://github.com/jalaali/go-jalaali
|
|
5
|
+
*
|
|
6
|
+
* Built against the stable ABI (abi3), so a single wheel per platform serves
|
|
7
|
+
* every supported Python version. C integer division and remainder truncate
|
|
8
|
+
* toward zero, exactly like Go's, so the formulas are copied verbatim.
|
|
9
|
+
*
|
|
10
|
+
* Arguments that do not fit comfortably in 64-bit arithmetic are handed to
|
|
11
|
+
* the pure-Python implementation registered through setup(), which keeps
|
|
12
|
+
* the behaviour (including exceptions) identical for every input.
|
|
13
|
+
*/
|
|
14
|
+
#define PY_SSIZE_T_CLEAN
|
|
15
|
+
#include <Python.h>
|
|
16
|
+
#include <stdint.h>
|
|
17
|
+
|
|
18
|
+
#define N_BREAKS 20
|
|
19
|
+
#define MAX_ARG 1000000000000LL /* keeps every intermediate within int64 */
|
|
20
|
+
|
|
21
|
+
static const int64_t BREAKS[N_BREAKS] = {
|
|
22
|
+
-61, 9, 38, 199, 426, 686, 756, 818, 1111, 1181,
|
|
23
|
+
1210, 1635, 2060, 2097, 2192, 2262, 2324, 2394, 2456, 3178,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
static PyObject *error_type = NULL;
|
|
27
|
+
static PyObject *py_to_jalaali = NULL;
|
|
28
|
+
static PyObject *py_to_gregorian = NULL;
|
|
29
|
+
static PyObject *py_is_leap_year = NULL;
|
|
30
|
+
static PyObject *py_month_length = NULL;
|
|
31
|
+
|
|
32
|
+
/* Returns 0 on success, -1 with an exception set, 1 if too large for C. */
|
|
33
|
+
static int
|
|
34
|
+
get_arg(PyObject *obj, int64_t *out)
|
|
35
|
+
{
|
|
36
|
+
int overflow = 0;
|
|
37
|
+
long long v = PyLong_AsLongLongAndOverflow(obj, &overflow);
|
|
38
|
+
if (v == -1 && PyErr_Occurred()) {
|
|
39
|
+
return -1;
|
|
40
|
+
}
|
|
41
|
+
if (overflow || v > MAX_ARG || v < -MAX_ARG) {
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
*out = v;
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static int
|
|
49
|
+
get_args(PyObject *args, int n, int64_t *out)
|
|
50
|
+
{
|
|
51
|
+
if (PyTuple_Size(args) != n) {
|
|
52
|
+
PyErr_Format(PyExc_TypeError, "expected %d arguments, got %zd", n,
|
|
53
|
+
PyTuple_Size(args));
|
|
54
|
+
return -1;
|
|
55
|
+
}
|
|
56
|
+
int too_big = 0;
|
|
57
|
+
for (int i = 0; i < n; i++) {
|
|
58
|
+
int r = get_arg(PyTuple_GetItem(args, i), &out[i]);
|
|
59
|
+
if (r < 0) {
|
|
60
|
+
return -1;
|
|
61
|
+
}
|
|
62
|
+
too_big |= r;
|
|
63
|
+
}
|
|
64
|
+
return too_big;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
static void
|
|
68
|
+
raise_invalid_year(int64_t year)
|
|
69
|
+
{
|
|
70
|
+
PyObject *exc = PyObject_CallFunction(error_type, "L", (long long)year);
|
|
71
|
+
if (exc != NULL) {
|
|
72
|
+
PyErr_SetObject(error_type, exc);
|
|
73
|
+
Py_DECREF(exc);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Returns 0 on success, -1 (without an exception set) for an invalid year. */
|
|
78
|
+
static int
|
|
79
|
+
jal_cal(int64_t jy, int64_t *leap, int64_t *gy, int64_t *march)
|
|
80
|
+
{
|
|
81
|
+
int64_t g = jy + 621;
|
|
82
|
+
int64_t leap_j = -14;
|
|
83
|
+
int64_t jp = BREAKS[0];
|
|
84
|
+
int64_t jump = 0;
|
|
85
|
+
|
|
86
|
+
if (jy < jp || jy >= BREAKS[N_BREAKS - 1]) {
|
|
87
|
+
return -1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (int i = 1; i < N_BREAKS; i++) {
|
|
91
|
+
int64_t jm = BREAKS[i];
|
|
92
|
+
jump = jm - jp;
|
|
93
|
+
if (jy < jm) {
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
leap_j += (jump / 33) * 8 + (jump % 33) / 4;
|
|
97
|
+
jp = jm;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
int64_t n = jy - jp;
|
|
101
|
+
leap_j += (n / 33) * 8 + (n % 33 + 3) / 4;
|
|
102
|
+
if (jump % 33 == 4 && jump - n == 4) {
|
|
103
|
+
leap_j += 1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
int64_t leap_g = g / 4 - ((g / 100 + 1) * 3) / 4 - 150;
|
|
107
|
+
|
|
108
|
+
if (jump - n < 6) {
|
|
109
|
+
n = n - jump + ((jump + 4) / 33) * 33;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
int64_t l = ((n + 1) % 33 - 1) % 4;
|
|
113
|
+
if (l == -1) {
|
|
114
|
+
l = 4;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
*leap = l;
|
|
118
|
+
*gy = g;
|
|
119
|
+
*march = 20 + leap_j - leap_g;
|
|
120
|
+
return 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
static int64_t
|
|
124
|
+
g2d(int64_t gy, int64_t gm, int64_t gd)
|
|
125
|
+
{
|
|
126
|
+
int64_t d = ((gy + (gm - 8) / 6 + 100100) * 1461) / 4 +
|
|
127
|
+
(153 * ((gm + 9) % 12) + 2) / 5 + gd - 34840408;
|
|
128
|
+
d = d - (((gy + 100100 + (gm - 8) / 6) / 100) * 3) / 4 + 752;
|
|
129
|
+
return d;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
static void
|
|
133
|
+
d2g(int64_t jdn, int64_t *gy, int64_t *gm, int64_t *gd)
|
|
134
|
+
{
|
|
135
|
+
int64_t j = 4 * jdn + 139361631;
|
|
136
|
+
j = j + (((4 * jdn + 183187720) / 146097) * 3) / 4 * 4 - 3908;
|
|
137
|
+
int64_t i = ((j % 1461) / 4) * 5 + 308;
|
|
138
|
+
*gd = (i % 153) / 5 + 1;
|
|
139
|
+
*gm = (i / 153) % 12 + 1;
|
|
140
|
+
*gy = j / 1461 - 100100 + (8 - *gm) / 6;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
static int64_t
|
|
144
|
+
month_len(int64_t jy, int64_t jm, int *err)
|
|
145
|
+
{
|
|
146
|
+
*err = 0;
|
|
147
|
+
if (jm <= 6) {
|
|
148
|
+
return 31;
|
|
149
|
+
}
|
|
150
|
+
if (jm <= 11) {
|
|
151
|
+
return 30;
|
|
152
|
+
}
|
|
153
|
+
int64_t leap, gy, march;
|
|
154
|
+
if (jal_cal(jy, &leap, &gy, &march) < 0) {
|
|
155
|
+
*err = 1;
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
|
158
|
+
return leap == 0 ? 30 : 29;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
static PyObject *
|
|
162
|
+
fallback(PyObject *func, PyObject *args)
|
|
163
|
+
{
|
|
164
|
+
return PyObject_CallObject(func, args);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
static PyObject *
|
|
168
|
+
calgo_to_jalaali(PyObject *self, PyObject *args)
|
|
169
|
+
{
|
|
170
|
+
int64_t a[3];
|
|
171
|
+
int r = get_args(args, 3, a);
|
|
172
|
+
if (r < 0) {
|
|
173
|
+
return NULL;
|
|
174
|
+
}
|
|
175
|
+
if (r > 0) {
|
|
176
|
+
return fallback(py_to_jalaali, args);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
int64_t gy, gm, gd;
|
|
180
|
+
int64_t jdn = g2d(a[0], a[1], a[2]);
|
|
181
|
+
d2g(jdn, &gy, &gm, &gd);
|
|
182
|
+
|
|
183
|
+
int64_t jy = gy - 621;
|
|
184
|
+
int64_t leap, g, march;
|
|
185
|
+
if (jal_cal(jy, &leap, &g, &march) < 0) {
|
|
186
|
+
raise_invalid_year(jy);
|
|
187
|
+
return NULL;
|
|
188
|
+
}
|
|
189
|
+
int64_t k = jdn - g2d(gy, 3, march);
|
|
190
|
+
int64_t jm, jd;
|
|
191
|
+
if (k >= 0) {
|
|
192
|
+
if (k <= 185) {
|
|
193
|
+
return Py_BuildValue("(LLL)", (long long)jy,
|
|
194
|
+
(long long)(1 + k / 31),
|
|
195
|
+
(long long)(k % 31 + 1));
|
|
196
|
+
}
|
|
197
|
+
k -= 186;
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
jy -= 1;
|
|
201
|
+
k += 179;
|
|
202
|
+
if (leap == 1) {
|
|
203
|
+
k += 1;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
jm = 7 + k / 30;
|
|
207
|
+
jd = k % 30 + 1;
|
|
208
|
+
return Py_BuildValue("(LLL)", (long long)jy, (long long)jm,
|
|
209
|
+
(long long)jd);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
static PyObject *
|
|
213
|
+
calgo_to_gregorian(PyObject *self, PyObject *args)
|
|
214
|
+
{
|
|
215
|
+
int64_t a[3];
|
|
216
|
+
int r = get_args(args, 3, a);
|
|
217
|
+
if (r < 0) {
|
|
218
|
+
return NULL;
|
|
219
|
+
}
|
|
220
|
+
if (r > 0) {
|
|
221
|
+
return fallback(py_to_gregorian, args);
|
|
222
|
+
}
|
|
223
|
+
int64_t jy = a[0], jm = a[1], jd = a[2];
|
|
224
|
+
|
|
225
|
+
int valid = !(jy < -61 || jy > 3177 || jm < 1 || jm > 12 || jd < 1);
|
|
226
|
+
if (valid) {
|
|
227
|
+
int err;
|
|
228
|
+
valid = jd <= month_len(jy, jm, &err);
|
|
229
|
+
}
|
|
230
|
+
if (!valid) {
|
|
231
|
+
return PyErr_Format(
|
|
232
|
+
PyExc_ValueError,
|
|
233
|
+
"invalid jalaali date: year=%lld, month=%lld, day=%lld",
|
|
234
|
+
(long long)jy, (long long)jm, (long long)jd);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
int64_t leap, gy, march;
|
|
238
|
+
if (jal_cal(jy, &leap, &gy, &march) < 0) {
|
|
239
|
+
raise_invalid_year(jy);
|
|
240
|
+
return NULL;
|
|
241
|
+
}
|
|
242
|
+
int64_t jm7 = jm / 7;
|
|
243
|
+
int64_t jdn = g2d(gy, 3, march) + (jm - 1) * 31 - jm7 * (jm - 7) + jd - 1;
|
|
244
|
+
int64_t y, m, d;
|
|
245
|
+
d2g(jdn, &y, &m, &d);
|
|
246
|
+
return Py_BuildValue("(LLL)", (long long)y, (long long)m, (long long)d);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
static PyObject *
|
|
250
|
+
calgo_is_leap_year(PyObject *self, PyObject *args)
|
|
251
|
+
{
|
|
252
|
+
int64_t a[1];
|
|
253
|
+
int r = get_args(args, 1, a);
|
|
254
|
+
if (r < 0) {
|
|
255
|
+
return NULL;
|
|
256
|
+
}
|
|
257
|
+
if (r > 0) {
|
|
258
|
+
return fallback(py_is_leap_year, args);
|
|
259
|
+
}
|
|
260
|
+
int64_t leap, gy, march;
|
|
261
|
+
if (jal_cal(a[0], &leap, &gy, &march) < 0) {
|
|
262
|
+
raise_invalid_year(a[0]);
|
|
263
|
+
return NULL;
|
|
264
|
+
}
|
|
265
|
+
return PyBool_FromLong(leap == 0);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
static PyObject *
|
|
269
|
+
calgo_month_length(PyObject *self, PyObject *args)
|
|
270
|
+
{
|
|
271
|
+
int64_t a[2];
|
|
272
|
+
int r = get_args(args, 2, a);
|
|
273
|
+
if (r < 0) {
|
|
274
|
+
return NULL;
|
|
275
|
+
}
|
|
276
|
+
if (r > 0) {
|
|
277
|
+
return fallback(py_month_length, args);
|
|
278
|
+
}
|
|
279
|
+
int err;
|
|
280
|
+
int64_t n = month_len(a[0], a[1], &err);
|
|
281
|
+
if (err) {
|
|
282
|
+
raise_invalid_year(a[0]);
|
|
283
|
+
return NULL;
|
|
284
|
+
}
|
|
285
|
+
return PyLong_FromLongLong(n);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
static PyObject *
|
|
289
|
+
calgo_setup(PyObject *self, PyObject *args)
|
|
290
|
+
{
|
|
291
|
+
PyObject *e, *a, *b, *c, *d;
|
|
292
|
+
if (!PyArg_ParseTuple(args, "OOOOO", &e, &a, &b, &c, &d)) {
|
|
293
|
+
return NULL;
|
|
294
|
+
}
|
|
295
|
+
Py_INCREF(e);
|
|
296
|
+
Py_XDECREF(error_type);
|
|
297
|
+
error_type = e;
|
|
298
|
+
Py_INCREF(a);
|
|
299
|
+
Py_XDECREF(py_to_jalaali);
|
|
300
|
+
py_to_jalaali = a;
|
|
301
|
+
Py_INCREF(b);
|
|
302
|
+
Py_XDECREF(py_to_gregorian);
|
|
303
|
+
py_to_gregorian = b;
|
|
304
|
+
Py_INCREF(c);
|
|
305
|
+
Py_XDECREF(py_is_leap_year);
|
|
306
|
+
py_is_leap_year = c;
|
|
307
|
+
Py_INCREF(d);
|
|
308
|
+
Py_XDECREF(py_month_length);
|
|
309
|
+
py_month_length = d;
|
|
310
|
+
Py_RETURN_NONE;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
static PyMethodDef methods[] = {
|
|
314
|
+
{"to_jalaali", calgo_to_jalaali, METH_VARARGS,
|
|
315
|
+
"to_jalaali(gy, gm, gd) -> (jy, jm, jd)"},
|
|
316
|
+
{"to_gregorian", calgo_to_gregorian, METH_VARARGS,
|
|
317
|
+
"to_gregorian(jy, jm, jd) -> (gy, gm, gd)"},
|
|
318
|
+
{"is_leap_year", calgo_is_leap_year, METH_VARARGS,
|
|
319
|
+
"is_leap_year(jy) -> bool"},
|
|
320
|
+
{"month_length", calgo_month_length, METH_VARARGS,
|
|
321
|
+
"month_length(jy, jm) -> int"},
|
|
322
|
+
{"setup", calgo_setup, METH_VARARGS,
|
|
323
|
+
"setup(error_type, to_jalaali, to_gregorian, is_leap_year, "
|
|
324
|
+
"month_length): register the exception class and Python fallbacks"},
|
|
325
|
+
{NULL, NULL, 0, NULL},
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
static struct PyModuleDef module = {
|
|
329
|
+
PyModuleDef_HEAD_INIT, "djalaali._calgo", NULL, -1, methods,
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
PyMODINIT_FUNC
|
|
333
|
+
PyInit__calgo(void)
|
|
334
|
+
{
|
|
335
|
+
return PyModule_Create(&module);
|
|
336
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""
|
|
2
|
+
jalaali - A Python implementation of the Jalaali calendar conversion.
|
|
3
|
+
|
|
4
|
+
Ported from https://github.com/jalaali/go-jalaali
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from enum import IntEnum
|
|
9
|
+
|
|
10
|
+
# Jalaali leap year break points
|
|
11
|
+
BREAKS = [
|
|
12
|
+
-61,
|
|
13
|
+
9,
|
|
14
|
+
38,
|
|
15
|
+
199,
|
|
16
|
+
426,
|
|
17
|
+
686,
|
|
18
|
+
756,
|
|
19
|
+
818,
|
|
20
|
+
1111,
|
|
21
|
+
1181,
|
|
22
|
+
1210,
|
|
23
|
+
1635,
|
|
24
|
+
2060,
|
|
25
|
+
2097,
|
|
26
|
+
2192,
|
|
27
|
+
2262,
|
|
28
|
+
2324,
|
|
29
|
+
2394,
|
|
30
|
+
2456,
|
|
31
|
+
3178,
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Month(IntEnum):
|
|
36
|
+
"""Represents a Jalaali month (1-12)."""
|
|
37
|
+
|
|
38
|
+
Farvardin = 1
|
|
39
|
+
Ordibehesht = 2
|
|
40
|
+
Khordad = 3
|
|
41
|
+
Tir = 4
|
|
42
|
+
Mordad = 5
|
|
43
|
+
Shahrivar = 6
|
|
44
|
+
Mehr = 7
|
|
45
|
+
Aban = 8
|
|
46
|
+
Azar = 9
|
|
47
|
+
Dey = 10
|
|
48
|
+
Bahman = 11
|
|
49
|
+
Esfand = 12
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ErrorInvalidYear(Exception):
|
|
53
|
+
"""Raised when year is out of valid range."""
|
|
54
|
+
|
|
55
|
+
def __init__(self, year):
|
|
56
|
+
self.year = year
|
|
57
|
+
super().__init__(f"jalaali: {year} is invalid year")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _div(a, b):
|
|
61
|
+
"""Integer division truncated toward zero (matches Go's `/` for ints)."""
|
|
62
|
+
q = abs(a) // abs(b)
|
|
63
|
+
if (a < 0) != (b < 0):
|
|
64
|
+
q = -q
|
|
65
|
+
return q
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _mod(a, b):
|
|
69
|
+
"""Remainder with sign of dividend (matches Go's `%` for ints)."""
|
|
70
|
+
r = abs(a) % abs(b)
|
|
71
|
+
if a < 0:
|
|
72
|
+
r = -r
|
|
73
|
+
return r
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def to_jalaali(gy, gm, gd):
|
|
77
|
+
"""
|
|
78
|
+
Convert a Gregorian date to a Jalaali date.
|
|
79
|
+
|
|
80
|
+
Returns a tuple (jy, jm, jd).
|
|
81
|
+
Raises ErrorInvalidYear if the resulting Jalaali year is out of range.
|
|
82
|
+
"""
|
|
83
|
+
return _d2j(_g2d(gy, gm, gd))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def to_gregorian(jy, jm, jd):
|
|
87
|
+
"""
|
|
88
|
+
Convert a Jalaali date to a Gregorian date.
|
|
89
|
+
|
|
90
|
+
Returns a tuple (gy, gm, gd).
|
|
91
|
+
Raises ValueError for an invalid Jalaali date.
|
|
92
|
+
Raises ErrorInvalidYear if the year is out of range.
|
|
93
|
+
"""
|
|
94
|
+
if not _is_valid_date(jy, jm, jd):
|
|
95
|
+
raise ValueError(
|
|
96
|
+
f"invalid jalaali date: year={jy}, month={jm}, day={jd}"
|
|
97
|
+
)
|
|
98
|
+
jdn = _j2d(jy, jm, jd)
|
|
99
|
+
return _d2g(jdn)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def is_leap_year(jy):
|
|
103
|
+
"""Return True if the given Jalaali year is a leap year."""
|
|
104
|
+
leap, _, _, _ = _jal_cal(jy)
|
|
105
|
+
return leap == 0
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def month_length(jy, jm):
|
|
109
|
+
"""Return the number of days in the given Jalaali month."""
|
|
110
|
+
if jm <= 6:
|
|
111
|
+
return 31
|
|
112
|
+
elif jm <= 11:
|
|
113
|
+
return 30
|
|
114
|
+
if is_leap_year(jy):
|
|
115
|
+
return 30
|
|
116
|
+
return 29
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _is_valid_date(jy, jm, jd):
|
|
120
|
+
if jy < -61 or jy > 3177 or jm < 1 or jm > 12 or jd < 1:
|
|
121
|
+
return False
|
|
122
|
+
return jd <= month_length(jy, jm)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _j2d(jy, jm, jd):
|
|
126
|
+
_, gy, march, _ = _jal_cal(jy)
|
|
127
|
+
return _g2d(gy, 3, march) + (jm - 1) * 31 - _div(jm, 7) * (jm - 7) + jd - 1
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _d2j(jdn):
|
|
131
|
+
gy, _, _ = _d2g(jdn)
|
|
132
|
+
jy = gy - 621
|
|
133
|
+
leap, _, march, _ = _jal_cal(jy)
|
|
134
|
+
jdn1f = _g2d(gy, 3, march)
|
|
135
|
+
k = jdn - jdn1f
|
|
136
|
+
if k >= 0:
|
|
137
|
+
if k <= 185:
|
|
138
|
+
jm = 1 + _div(k, 31)
|
|
139
|
+
jd = _mod(k, 31) + 1
|
|
140
|
+
return jy, jm, jd
|
|
141
|
+
k -= 186
|
|
142
|
+
else:
|
|
143
|
+
jy -= 1
|
|
144
|
+
k += 179
|
|
145
|
+
if leap == 1:
|
|
146
|
+
k += 1
|
|
147
|
+
jm = 7 + _div(k, 30)
|
|
148
|
+
jd = _mod(k, 30) + 1
|
|
149
|
+
return jy, jm, jd
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _jal_cal(jy):
|
|
153
|
+
bl = len(BREAKS)
|
|
154
|
+
gy = jy + 621
|
|
155
|
+
leap_j = -14
|
|
156
|
+
jp = BREAKS[0]
|
|
157
|
+
jump = 0
|
|
158
|
+
|
|
159
|
+
if jy < jp or jy >= BREAKS[bl - 1]:
|
|
160
|
+
raise ErrorInvalidYear(jy)
|
|
161
|
+
|
|
162
|
+
for i in range(1, bl): # pragma: no branch
|
|
163
|
+
jm = BREAKS[i]
|
|
164
|
+
jump = jm - jp
|
|
165
|
+
if jy < jm:
|
|
166
|
+
break
|
|
167
|
+
leap_j += _div(jump, 33) * 8 + _div(_mod(jump, 33), 4)
|
|
168
|
+
jp = jm
|
|
169
|
+
|
|
170
|
+
n = jy - jp
|
|
171
|
+
leap_j += _div(n, 33) * 8 + _div(_mod(n, 33) + 3, 4)
|
|
172
|
+
if _mod(jump, 33) == 4 and jump - n == 4:
|
|
173
|
+
leap_j += 1
|
|
174
|
+
|
|
175
|
+
leap_g = _div(gy, 4) - _div((_div(gy, 100) + 1) * 3, 4) - 150
|
|
176
|
+
march = 20 + leap_j - leap_g
|
|
177
|
+
|
|
178
|
+
if jump - n < 6:
|
|
179
|
+
n = n - jump + _div(jump + 4, 33) * 33
|
|
180
|
+
|
|
181
|
+
leap = _mod(_mod(n + 1, 33) - 1, 4)
|
|
182
|
+
if leap == -1:
|
|
183
|
+
leap = 4
|
|
184
|
+
|
|
185
|
+
return leap, gy, march, None
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _g2d(gy, gm, gd):
|
|
189
|
+
d = (
|
|
190
|
+
_div((gy + _div(gm - 8, 6) + 100100) * 1461, 4)
|
|
191
|
+
+ _div(153 * _mod(gm + 9, 12) + 2, 5)
|
|
192
|
+
+ gd
|
|
193
|
+
- 34840408
|
|
194
|
+
)
|
|
195
|
+
d = d - _div(_div(gy + 100100 + _div(gm - 8, 6), 100) * 3, 4) + 752
|
|
196
|
+
return d
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _d2g(jdn):
|
|
200
|
+
j = 4 * jdn + 139361631
|
|
201
|
+
j = j + _div(_div(4 * jdn + 183187720, 146097) * 3, 4) * 4 - 3908
|
|
202
|
+
i = _div(_mod(j, 1461), 4) * 5 + 308
|
|
203
|
+
gd = _div(_mod(i, 153), 5) + 1
|
|
204
|
+
gm = _mod(_div(i, 153), 12) + 1
|
|
205
|
+
gy = _div(j, 1461) - 100100 + _div(8 - gm, 6)
|
|
206
|
+
return gy, gm, gd
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# Keep the pure-Python implementations reachable (tests compare them against
|
|
210
|
+
# the C accelerator), then swap in the C versions when they are available.
|
|
211
|
+
_py_to_jalaali = to_jalaali
|
|
212
|
+
_py_to_gregorian = to_gregorian
|
|
213
|
+
_py_is_leap_year = is_leap_year
|
|
214
|
+
_py_month_length = month_length
|
|
215
|
+
|
|
216
|
+
HAS_C_EXTENSION = False
|
|
217
|
+
|
|
218
|
+
if not os.environ.get("DJALAALI_PURE_PYTHON"): # pragma: no cover
|
|
219
|
+
try:
|
|
220
|
+
from . import _calgo
|
|
221
|
+
except ImportError:
|
|
222
|
+
pass
|
|
223
|
+
else:
|
|
224
|
+
_calgo.setup(
|
|
225
|
+
ErrorInvalidYear,
|
|
226
|
+
_py_to_jalaali,
|
|
227
|
+
_py_to_gregorian,
|
|
228
|
+
_py_is_leap_year,
|
|
229
|
+
_py_month_length,
|
|
230
|
+
)
|
|
231
|
+
to_jalaali = _calgo.to_jalaali
|
|
232
|
+
to_gregorian = _calgo.to_gregorian
|
|
233
|
+
is_leap_year = _calgo.is_leap_year
|
|
234
|
+
month_length = _calgo.month_length
|
|
235
|
+
HAS_C_EXTENSION = True
|