persiantools 3.0.1__tar.gz → 4.0.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.
- {persiantools-3.0.1 → persiantools-4.0.0}/LICENSE +2 -2
- {persiantools-3.0.1/persiantools.egg-info → persiantools-4.0.0}/PKG-INFO +15 -16
- {persiantools-3.0.1 → persiantools-4.0.0}/README.md +10 -12
- {persiantools-3.0.1 → persiantools-4.0.0}/persiantools/__init__.py +2 -2
- {persiantools-3.0.1 → persiantools-4.0.0}/persiantools/digits.py +12 -6
- {persiantools-3.0.1 → persiantools-4.0.0}/persiantools/jdatetime.py +62 -44
- {persiantools-3.0.1 → persiantools-4.0.0/persiantools.egg-info}/PKG-INFO +15 -16
- {persiantools-3.0.1 → persiantools-4.0.0}/persiantools.egg-info/SOURCES.txt +7 -1
- persiantools-4.0.0/persiantools.egg-info/requires.txt +1 -0
- {persiantools-3.0.1 → persiantools-4.0.0}/setup.py +5 -4
- persiantools-4.0.0/tests/test_characters.py +35 -0
- persiantools-4.0.0/tests/test_digits.py +64 -0
- persiantools-4.0.0/tests/test_jalalidate.py +318 -0
- persiantools-4.0.0/tests/test_jalalidatetime.py +326 -0
- persiantools-4.0.0/tests/test_utils.py +22 -0
- {persiantools-3.0.1 → persiantools-4.0.0}/MANIFEST.in +0 -0
- {persiantools-3.0.1 → persiantools-4.0.0}/persiantools/characters.py +0 -0
- {persiantools-3.0.1 → persiantools-4.0.0}/persiantools/utils.py +0 -0
- {persiantools-3.0.1 → persiantools-4.0.0}/persiantools.egg-info/dependency_links.txt +0 -0
- {persiantools-3.0.1 → persiantools-4.0.0}/persiantools.egg-info/not-zip-safe +0 -0
- {persiantools-3.0.1 → persiantools-4.0.0}/persiantools.egg-info/top_level.txt +0 -0
- {persiantools-3.0.1 → persiantools-4.0.0}/pyproject.toml +0 -0
- {persiantools-3.0.1 → persiantools-4.0.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2016-
|
|
3
|
+
Copyright (c) 2016-2024 Majid Hajiloo
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: persiantools
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.0
|
|
4
4
|
Summary: Jalali date and datetime with other tools
|
|
5
5
|
Home-page: https://github.com/majiidd/persiantools
|
|
6
6
|
Author: Majid Hajiloo
|
|
@@ -13,11 +13,11 @@ Classifier: Natural Language :: Persian
|
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
14
|
Classifier: Programming Language :: Python
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.8
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.9
|
|
20
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
21
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
22
22
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
23
23
|
Classifier: Topic :: Software Development
|
|
@@ -25,9 +25,10 @@ Classifier: Topic :: Software Development :: Libraries
|
|
|
25
25
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
26
|
Classifier: Topic :: Software Development :: Localization
|
|
27
27
|
Classifier: Topic :: Utilities
|
|
28
|
-
Requires-Python: >=3.
|
|
28
|
+
Requires-Python: >=3.8
|
|
29
29
|
Description-Content-Type: text/markdown
|
|
30
30
|
License-File: LICENSE
|
|
31
|
+
Requires-Dist: pytz
|
|
31
32
|
|
|
32
33
|
# PersianTools
|
|
33
34
|
|
|
@@ -38,21 +39,19 @@ License-File: LICENSE
|
|
|
38
39
|
[](https://pypi.org/project/persiantools/)
|
|
39
40
|
[](https://pypi.org/project/persiantools/)
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
- Convert Arabic and Persian characters/digits to each other
|
|
48
|
-
- Convert numbers to words
|
|
42
|
+
Provides Jalali (also known as Shamsi or Persian) dates and datetimes functionalities, among other tools.
|
|
43
|
+
- It converts between Jalali and Gregorian dates and datetimes (based on python datetime's module).
|
|
44
|
+
- It supports operators like `+`, `-`, `==`, and `>=`.
|
|
45
|
+
- It includes timezone support.
|
|
46
|
+
- It converts between Arabic and Persian characters/digits.
|
|
47
|
+
- It turns numbers into Persian words.
|
|
49
48
|
|
|
50
49
|
## Install Package
|
|
51
|
-
|
|
50
|
+
You can install the package using pip with the following command:
|
|
52
51
|
```bash
|
|
53
52
|
python -m pip install persiantools
|
|
54
53
|
```
|
|
55
|
-
Persiantools supports Python 3.
|
|
54
|
+
Persiantools supports Python 3.8+. (_for python 2.7 and 3.5 use [1.5.x](https://github.com/majiidd/persiantools/tree/1.5.x) version_)
|
|
56
55
|
|
|
57
56
|
## How to use
|
|
58
57
|
|
|
@@ -149,7 +148,7 @@ Based on python `strftime()` behavior
|
|
|
149
148
|
```
|
|
150
149
|
|
|
151
150
|
### Operators
|
|
152
|
-
|
|
151
|
+
The package supports various operators for date and time manipulations. Here are some examples:
|
|
153
152
|
```python
|
|
154
153
|
>>> from persiantools.jdatetime import JalaliDate, JalaliDateTime
|
|
155
154
|
>>> import datetime
|
|
@@ -157,7 +156,7 @@ Based on python `strftime()` behavior
|
|
|
157
156
|
>>> JalaliDate(1367, 2, 14) == JalaliDate(datetime.date(1988, 5, 4))
|
|
158
157
|
True
|
|
159
158
|
|
|
160
|
-
>>> JalaliDateTime(1367, 2, 14, 4, 30) >= JalaliDateTime(
|
|
159
|
+
>>> JalaliDateTime(1367, 2, 14, 4, 30) >= JalaliDateTime(1368, 2, 14, 1, 0)
|
|
161
160
|
False
|
|
162
161
|
|
|
163
162
|
>>> JalaliDate(1367, 2, 14) == datetime.date(1988, 5, 4)
|
|
@@ -7,21 +7,19 @@
|
|
|
7
7
|
[](https://pypi.org/project/persiantools/)
|
|
8
8
|
[](https://pypi.org/project/persiantools/)
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- Convert Arabic and Persian characters/digits to each other
|
|
17
|
-
- Convert numbers to words
|
|
10
|
+
Provides Jalali (also known as Shamsi or Persian) dates and datetimes functionalities, among other tools.
|
|
11
|
+
- It converts between Jalali and Gregorian dates and datetimes (based on python datetime's module).
|
|
12
|
+
- It supports operators like `+`, `-`, `==`, and `>=`.
|
|
13
|
+
- It includes timezone support.
|
|
14
|
+
- It converts between Arabic and Persian characters/digits.
|
|
15
|
+
- It turns numbers into Persian words.
|
|
18
16
|
|
|
19
17
|
## Install Package
|
|
20
|
-
|
|
18
|
+
You can install the package using pip with the following command:
|
|
21
19
|
```bash
|
|
22
20
|
python -m pip install persiantools
|
|
23
21
|
```
|
|
24
|
-
Persiantools supports Python 3.
|
|
22
|
+
Persiantools supports Python 3.8+. (_for python 2.7 and 3.5 use [1.5.x](https://github.com/majiidd/persiantools/tree/1.5.x) version_)
|
|
25
23
|
|
|
26
24
|
## How to use
|
|
27
25
|
|
|
@@ -118,7 +116,7 @@ Based on python `strftime()` behavior
|
|
|
118
116
|
```
|
|
119
117
|
|
|
120
118
|
### Operators
|
|
121
|
-
|
|
119
|
+
The package supports various operators for date and time manipulations. Here are some examples:
|
|
122
120
|
```python
|
|
123
121
|
>>> from persiantools.jdatetime import JalaliDate, JalaliDateTime
|
|
124
122
|
>>> import datetime
|
|
@@ -126,7 +124,7 @@ Based on python `strftime()` behavior
|
|
|
126
124
|
>>> JalaliDate(1367, 2, 14) == JalaliDate(datetime.date(1988, 5, 4))
|
|
127
125
|
True
|
|
128
126
|
|
|
129
|
-
>>> JalaliDateTime(1367, 2, 14, 4, 30) >= JalaliDateTime(
|
|
127
|
+
>>> JalaliDateTime(1367, 2, 14, 4, 30) >= JalaliDateTime(1368, 2, 14, 1, 0)
|
|
130
128
|
False
|
|
131
129
|
|
|
132
130
|
>>> JalaliDate(1367, 2, 14) == datetime.date(1988, 5, 4)
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
__title__ = "persiantools"
|
|
9
9
|
__url__ = "https://github.com/majiidd/persiantools"
|
|
10
|
-
__version__ = "
|
|
10
|
+
__version__ = "4.0.0"
|
|
11
11
|
__build__ = __version__
|
|
12
12
|
__author__ = "Majid Hajiloo"
|
|
13
13
|
__author_email__ = "majid.hajiloo@gmail.com"
|
|
14
14
|
__license__ = "MIT"
|
|
15
|
-
__copyright__ = "Copyright (c) 2016-
|
|
15
|
+
__copyright__ = "Copyright (c) 2016-2024 Majid Hajiloo"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import re
|
|
2
2
|
|
|
3
3
|
EN_TO_FA_MAP = {
|
|
4
4
|
"0": "۰",
|
|
@@ -48,6 +48,12 @@ FA_TO_AR_MAP = {
|
|
|
48
48
|
"۸": "٨",
|
|
49
49
|
"۹": "٩",
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
EN_TO_FA_REGEX = re.compile("|".join(EN_TO_FA_MAP.keys()))
|
|
53
|
+
AR_TO_FA_REGEX = re.compile("|".join(AR_TO_FA_MAP.keys()))
|
|
54
|
+
FA_TO_EN_REGEX = re.compile("|".join(FA_TO_EN_MAP.keys()))
|
|
55
|
+
FA_TO_AR_REGEX = re.compile("|".join(FA_TO_AR_MAP.keys()))
|
|
56
|
+
|
|
51
57
|
ONES = ("یک", "دو", "سه", "چهار", "پنج", "شش", "هفت", "هشت", "نه")
|
|
52
58
|
TENS = ("بیست", "سی", "چهل", "پنجاه", "شصت", "هفتاد", "هشتاد", "نود")
|
|
53
59
|
HUNDREDS = ("یکصد", "دویست", "سیصد", "چهارصد", "پانصد", "ششصد", "هفتصد", "هشتصد", "نهصد")
|
|
@@ -80,7 +86,7 @@ DECISION = {
|
|
|
80
86
|
1000: lambda n, depth: HUNDREDS[n // 100 - 1] + _to_word(n % 100, True),
|
|
81
87
|
1000000: lambda n, depth: _to_word(n // 1000, depth) + BIG_RANGE[0] + _to_word(n % 1000, True),
|
|
82
88
|
1000000000: lambda n, depth: _to_word(n // 1000000, depth) + BIG_RANGE[1] + _to_word(n % 1000000, True),
|
|
83
|
-
1000000000000: lambda n, depth: _to_word(n //
|
|
89
|
+
1000000000000: lambda n, depth: _to_word(n // 1000000000, depth) + BIG_RANGE[2] + _to_word(n % 1000000000, True),
|
|
84
90
|
1000000000000000: lambda n, depth: _to_word(n // 1000000000000, depth)
|
|
85
91
|
+ BIG_RANGE[3]
|
|
86
92
|
+ _to_word(n % 1000000000000, True),
|
|
@@ -101,7 +107,7 @@ def en_to_fa(string: str) -> str:
|
|
|
101
107
|
:param string: A string, will be converted
|
|
102
108
|
:rtype: str
|
|
103
109
|
"""
|
|
104
|
-
return
|
|
110
|
+
return EN_TO_FA_REGEX.sub(lambda x: EN_TO_FA_MAP[x.group()], string)
|
|
105
111
|
|
|
106
112
|
|
|
107
113
|
def ar_to_fa(string: str) -> str:
|
|
@@ -114,7 +120,7 @@ def ar_to_fa(string: str) -> str:
|
|
|
114
120
|
:param string: A string, will be converted
|
|
115
121
|
:rtype: str
|
|
116
122
|
"""
|
|
117
|
-
return
|
|
123
|
+
return AR_TO_FA_REGEX.sub(lambda x: AR_TO_FA_MAP[x.group()], string)
|
|
118
124
|
|
|
119
125
|
|
|
120
126
|
def fa_to_en(string: str) -> str:
|
|
@@ -127,7 +133,7 @@ def fa_to_en(string: str) -> str:
|
|
|
127
133
|
:param string: A string, will be converted
|
|
128
134
|
:rtype: str
|
|
129
135
|
"""
|
|
130
|
-
return
|
|
136
|
+
return FA_TO_EN_REGEX.sub(lambda x: FA_TO_EN_MAP[x.group()], string)
|
|
131
137
|
|
|
132
138
|
|
|
133
139
|
def fa_to_ar(string: str) -> str:
|
|
@@ -140,7 +146,7 @@ def fa_to_ar(string: str) -> str:
|
|
|
140
146
|
:param string: A string, will be converted
|
|
141
147
|
:rtype: str
|
|
142
148
|
"""
|
|
143
|
-
return
|
|
149
|
+
return FA_TO_AR_REGEX.sub(lambda x: FA_TO_AR_MAP[x.group()], string)
|
|
144
150
|
|
|
145
151
|
|
|
146
152
|
def _to_word(number: int, depth: bool) -> str:
|
|
@@ -181,7 +181,7 @@ class JalaliDate:
|
|
|
181
181
|
return year, month, day, locale
|
|
182
182
|
|
|
183
183
|
@classmethod
|
|
184
|
-
def
|
|
184
|
+
def check_date(cls, year, month, day):
|
|
185
185
|
try:
|
|
186
186
|
cls._check_date_fields(year, month, day, "en")
|
|
187
187
|
except (ValueError, TypeError):
|
|
@@ -191,11 +191,19 @@ class JalaliDate:
|
|
|
191
191
|
|
|
192
192
|
@staticmethod
|
|
193
193
|
def is_leap(year):
|
|
194
|
-
|
|
194
|
+
"""
|
|
195
|
+
This function calculates whether a given year in the Persian calendar is a leap year.
|
|
195
196
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
It calculates `((year + 2346) * 683) % 2820`. This expression does a few things:
|
|
198
|
+
- It offsets the input year by 2346. This is done to align the Persian calendar with the astronomical solar year.
|
|
199
|
+
- It multiplies the result by 683, which is the number of leap years in a 2820-year cycle.
|
|
200
|
+
- It then takes the result modulo 2820 to get the position of the year within the current 2820-year cycle.
|
|
201
|
+
|
|
202
|
+
If the result of this calculation is less than 683, the year is a leap year in the Persian calendar.
|
|
203
|
+
This is because there are 683 leap years in each 2820-year cycle of the Persian calendar.
|
|
204
|
+
"""
|
|
205
|
+
assert MINYEAR <= year <= MAXYEAR
|
|
206
|
+
return ((year + 2346) * 683) % 2820 < 683
|
|
199
207
|
|
|
200
208
|
@classmethod
|
|
201
209
|
def days_in_month(cls, month, year):
|
|
@@ -220,31 +228,40 @@ class JalaliDate:
|
|
|
220
228
|
day = year.day
|
|
221
229
|
year = year.year
|
|
222
230
|
|
|
223
|
-
|
|
231
|
+
# Days in each month of the Gregorian calendar
|
|
232
|
+
gregorian_days_in_month = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
|
|
224
233
|
|
|
225
|
-
|
|
234
|
+
# Determine the Jalali year
|
|
235
|
+
jalali_year = 0 if year <= 1600 else 979
|
|
226
236
|
year -= 621 if year <= 1600 else 1600
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
237
|
+
|
|
238
|
+
# Determine if the year is a leap year
|
|
239
|
+
leap_year = year + 1 if month > 2 else year
|
|
240
|
+
|
|
241
|
+
# Calculate the number of days
|
|
242
|
+
days = (365 * year) + (leap_year + 3) // 4 - (leap_year + 99) // 100
|
|
243
|
+
days += (leap_year + 399) // 400 - 80 + day + gregorian_days_in_month[month - 1]
|
|
244
|
+
|
|
245
|
+
# Update the Jalali year
|
|
246
|
+
jalali_year += 33 * (days // 12053)
|
|
231
247
|
days %= 12053
|
|
232
|
-
|
|
248
|
+
jalali_year += 4 * (days // 1461)
|
|
233
249
|
days %= 1461
|
|
234
|
-
|
|
250
|
+
jalali_year += (days - 1) // 365
|
|
235
251
|
|
|
236
252
|
if days > 365:
|
|
237
253
|
days = (days - 1) % 365
|
|
238
254
|
|
|
255
|
+
# Determine the Jalali month and day
|
|
239
256
|
if days < 186:
|
|
240
|
-
|
|
241
|
-
|
|
257
|
+
jalali_month = 1 + days // 31
|
|
258
|
+
jalali_day = 1 + (days % 31)
|
|
242
259
|
else:
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
260
|
+
days -= 186
|
|
261
|
+
jalali_month = 7 + days // 30
|
|
262
|
+
jalali_day = 1 + (days % 30)
|
|
246
263
|
|
|
247
|
-
return cls(
|
|
264
|
+
return cls(jalali_year, jalali_month, jalali_day)
|
|
248
265
|
|
|
249
266
|
def to_gregorian(self):
|
|
250
267
|
"""based on jdf.scr.ir"""
|
|
@@ -252,37 +269,40 @@ class JalaliDate:
|
|
|
252
269
|
day = self.day
|
|
253
270
|
year = self.year
|
|
254
271
|
|
|
255
|
-
|
|
272
|
+
# Determine the Gregorian year
|
|
273
|
+
gregorian_year = 621 if year <= 979 else 1600
|
|
256
274
|
year -= 0 if year <= 979 else 979
|
|
257
275
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
days
|
|
276
|
+
# Calculate the number of days
|
|
277
|
+
days_in_month = (month - 1) * 31 if month < 7 else ((month - 7) * 30) + 186
|
|
278
|
+
days = (365 * year) + (year // 33) * 8 + ((year % 33) + 3) // 4
|
|
279
|
+
days += 78 + day + days_in_month
|
|
261
280
|
|
|
262
|
-
|
|
281
|
+
gregorian_year += 400 * (days // 146097)
|
|
263
282
|
days %= 146097
|
|
264
283
|
|
|
265
284
|
if days > 36524:
|
|
266
285
|
days -= 1
|
|
267
|
-
|
|
286
|
+
gregorian_year += 100 * (days // 36524)
|
|
268
287
|
days %= 36524
|
|
269
288
|
|
|
270
289
|
if days >= 365:
|
|
271
290
|
days += 1
|
|
272
291
|
|
|
273
|
-
|
|
292
|
+
gregorian_year += 4 * (days // 1461)
|
|
274
293
|
days %= 1461
|
|
275
|
-
|
|
294
|
+
gregorian_year += (days - 1) // 365
|
|
276
295
|
|
|
277
296
|
if days > 365:
|
|
278
297
|
days = (days - 1) % 365
|
|
279
298
|
|
|
280
|
-
|
|
299
|
+
gregorian_day = days + 1
|
|
281
300
|
|
|
282
|
-
|
|
301
|
+
# Days in each month of the Gregorian calendar
|
|
302
|
+
gregorian_days_in_month = [
|
|
283
303
|
0,
|
|
284
304
|
31,
|
|
285
|
-
29 if (
|
|
305
|
+
29 if (gregorian_year % 4 == 0 and gregorian_year % 100 != 0) or gregorian_year % 400 == 0 else 28,
|
|
286
306
|
31,
|
|
287
307
|
30,
|
|
288
308
|
31,
|
|
@@ -295,13 +315,13 @@ class JalaliDate:
|
|
|
295
315
|
31,
|
|
296
316
|
]
|
|
297
317
|
|
|
298
|
-
|
|
299
|
-
for
|
|
300
|
-
if
|
|
318
|
+
# Determine the Gregorian month
|
|
319
|
+
for _gregorian_month, g in enumerate(gregorian_days_in_month):
|
|
320
|
+
if gregorian_day <= g:
|
|
301
321
|
break
|
|
302
|
-
|
|
322
|
+
gregorian_day -= g
|
|
303
323
|
|
|
304
|
-
return date(
|
|
324
|
+
return date(gregorian_year, _gregorian_month, gregorian_day)
|
|
305
325
|
|
|
306
326
|
@classmethod
|
|
307
327
|
def today(cls):
|
|
@@ -958,7 +978,7 @@ class JalaliDateTime(JalaliDate):
|
|
|
958
978
|
"%B": cls.__seqToRE(cls, month_names, "B"),
|
|
959
979
|
"%H": r"(?P<H>2[0-3]|[0-1]\d|\d)",
|
|
960
980
|
"%I": r"(?P<I>1[0-2]|0[1-9]|[1-9])",
|
|
961
|
-
"%p":
|
|
981
|
+
"%p": cls.__seqToRE(cls, periods, "p"),
|
|
962
982
|
"%M": r"(?P<M>[0-5]\d|\d)",
|
|
963
983
|
"%S": r"(?P<S>6[0-1]|[0-5]\d|\d)",
|
|
964
984
|
"%f": r"(?P<f>\d{1,6})",
|
|
@@ -977,8 +997,8 @@ class JalaliDateTime(JalaliDate):
|
|
|
977
997
|
|
|
978
998
|
data_string_regex = utils.replace(fmt, directives_regex_pattern)
|
|
979
999
|
|
|
980
|
-
if re.match(data_string_regex, data_string):
|
|
981
|
-
directives = re.search(data_string_regex, data_string).groupdict()
|
|
1000
|
+
if re.match(data_string_regex, data_string, re.IGNORECASE):
|
|
1001
|
+
directives = re.search(data_string_regex, data_string, re.IGNORECASE).groupdict()
|
|
982
1002
|
|
|
983
1003
|
if "Y" in directives.keys() and len(directives.get("Y")) < 4:
|
|
984
1004
|
raise ValueError("Year element must contain exactly 4 digits")
|
|
@@ -1100,7 +1120,7 @@ class JalaliDateTime(JalaliDate):
|
|
|
1100
1120
|
def __base_compare(self, other):
|
|
1101
1121
|
assert isinstance(other, JalaliDateTime)
|
|
1102
1122
|
|
|
1103
|
-
y,
|
|
1123
|
+
y, mo, d, h, m, s, ms = [
|
|
1104
1124
|
self._year,
|
|
1105
1125
|
self._month,
|
|
1106
1126
|
self._day,
|
|
@@ -1109,7 +1129,7 @@ class JalaliDateTime(JalaliDate):
|
|
|
1109
1129
|
self._second,
|
|
1110
1130
|
self._microsecond,
|
|
1111
1131
|
]
|
|
1112
|
-
y2,
|
|
1132
|
+
y2, mo2, d2, h2, m2, s2, ms2 = [
|
|
1113
1133
|
other.year,
|
|
1114
1134
|
other.month,
|
|
1115
1135
|
other.day,
|
|
@@ -1121,10 +1141,8 @@ class JalaliDateTime(JalaliDate):
|
|
|
1121
1141
|
|
|
1122
1142
|
return (
|
|
1123
1143
|
0
|
|
1124
|
-
if (y,
|
|
1125
|
-
else 1
|
|
1126
|
-
if (y, m, d, h, m, s, ms) > (y2, m2, d2, h2, m2, s2, ms2)
|
|
1127
|
-
else -1
|
|
1144
|
+
if (y, mo, d, h, m, s, ms) == (y2, mo2, d2, h2, m2, s2, ms2)
|
|
1145
|
+
else 1 if (y, mo, d, h, m, s, ms) > (y2, mo2, d2, h2, m2, s2, ms2) else -1
|
|
1128
1146
|
)
|
|
1129
1147
|
|
|
1130
1148
|
def _cmp(self, other, allow_mixed=False):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: persiantools
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.0
|
|
4
4
|
Summary: Jalali date and datetime with other tools
|
|
5
5
|
Home-page: https://github.com/majiidd/persiantools
|
|
6
6
|
Author: Majid Hajiloo
|
|
@@ -13,11 +13,11 @@ Classifier: Natural Language :: Persian
|
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
14
|
Classifier: Programming Language :: Python
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.8
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.9
|
|
20
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
21
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
22
22
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
23
23
|
Classifier: Topic :: Software Development
|
|
@@ -25,9 +25,10 @@ Classifier: Topic :: Software Development :: Libraries
|
|
|
25
25
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
26
|
Classifier: Topic :: Software Development :: Localization
|
|
27
27
|
Classifier: Topic :: Utilities
|
|
28
|
-
Requires-Python: >=3.
|
|
28
|
+
Requires-Python: >=3.8
|
|
29
29
|
Description-Content-Type: text/markdown
|
|
30
30
|
License-File: LICENSE
|
|
31
|
+
Requires-Dist: pytz
|
|
31
32
|
|
|
32
33
|
# PersianTools
|
|
33
34
|
|
|
@@ -38,21 +39,19 @@ License-File: LICENSE
|
|
|
38
39
|
[](https://pypi.org/project/persiantools/)
|
|
39
40
|
[](https://pypi.org/project/persiantools/)
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
- Convert Arabic and Persian characters/digits to each other
|
|
48
|
-
- Convert numbers to words
|
|
42
|
+
Provides Jalali (also known as Shamsi or Persian) dates and datetimes functionalities, among other tools.
|
|
43
|
+
- It converts between Jalali and Gregorian dates and datetimes (based on python datetime's module).
|
|
44
|
+
- It supports operators like `+`, `-`, `==`, and `>=`.
|
|
45
|
+
- It includes timezone support.
|
|
46
|
+
- It converts between Arabic and Persian characters/digits.
|
|
47
|
+
- It turns numbers into Persian words.
|
|
49
48
|
|
|
50
49
|
## Install Package
|
|
51
|
-
|
|
50
|
+
You can install the package using pip with the following command:
|
|
52
51
|
```bash
|
|
53
52
|
python -m pip install persiantools
|
|
54
53
|
```
|
|
55
|
-
Persiantools supports Python 3.
|
|
54
|
+
Persiantools supports Python 3.8+. (_for python 2.7 and 3.5 use [1.5.x](https://github.com/majiidd/persiantools/tree/1.5.x) version_)
|
|
56
55
|
|
|
57
56
|
## How to use
|
|
58
57
|
|
|
@@ -149,7 +148,7 @@ Based on python `strftime()` behavior
|
|
|
149
148
|
```
|
|
150
149
|
|
|
151
150
|
### Operators
|
|
152
|
-
|
|
151
|
+
The package supports various operators for date and time manipulations. Here are some examples:
|
|
153
152
|
```python
|
|
154
153
|
>>> from persiantools.jdatetime import JalaliDate, JalaliDateTime
|
|
155
154
|
>>> import datetime
|
|
@@ -157,7 +156,7 @@ Based on python `strftime()` behavior
|
|
|
157
156
|
>>> JalaliDate(1367, 2, 14) == JalaliDate(datetime.date(1988, 5, 4))
|
|
158
157
|
True
|
|
159
158
|
|
|
160
|
-
>>> JalaliDateTime(1367, 2, 14, 4, 30) >= JalaliDateTime(
|
|
159
|
+
>>> JalaliDateTime(1367, 2, 14, 4, 30) >= JalaliDateTime(1368, 2, 14, 1, 0)
|
|
161
160
|
False
|
|
162
161
|
|
|
163
162
|
>>> JalaliDate(1367, 2, 14) == datetime.date(1988, 5, 4)
|
|
@@ -12,4 +12,10 @@ persiantools.egg-info/PKG-INFO
|
|
|
12
12
|
persiantools.egg-info/SOURCES.txt
|
|
13
13
|
persiantools.egg-info/dependency_links.txt
|
|
14
14
|
persiantools.egg-info/not-zip-safe
|
|
15
|
-
persiantools.egg-info/
|
|
15
|
+
persiantools.egg-info/requires.txt
|
|
16
|
+
persiantools.egg-info/top_level.txt
|
|
17
|
+
tests/test_characters.py
|
|
18
|
+
tests/test_digits.py
|
|
19
|
+
tests/test_jalalidate.py
|
|
20
|
+
tests/test_jalalidatetime.py
|
|
21
|
+
tests/test_utils.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pytz
|
|
@@ -23,11 +23,11 @@ setup(
|
|
|
23
23
|
"Operating System :: OS Independent",
|
|
24
24
|
"Programming Language :: Python",
|
|
25
25
|
"Programming Language :: Python :: 3",
|
|
26
|
-
"Programming Language :: Python :: 3.6",
|
|
27
|
-
"Programming Language :: Python :: 3.7",
|
|
28
26
|
"Programming Language :: Python :: 3.8",
|
|
29
27
|
"Programming Language :: Python :: 3.9",
|
|
30
28
|
"Programming Language :: Python :: 3.10",
|
|
29
|
+
"Programming Language :: Python :: 3.11",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
31
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
32
32
|
"Programming Language :: Python :: Implementation :: PyPy",
|
|
33
33
|
"Topic :: Software Development",
|
|
@@ -42,10 +42,11 @@ setup(
|
|
|
42
42
|
author="Majid Hajiloo",
|
|
43
43
|
author_email="majid.hajiloo@gmail.com",
|
|
44
44
|
license="MIT",
|
|
45
|
+
license_files=("LICENSE",),
|
|
45
46
|
packages=["persiantools"],
|
|
46
|
-
python_requires=">=3.
|
|
47
|
+
python_requires=">=3.8",
|
|
47
48
|
tests_require=["pytest", "pytest-cov"],
|
|
48
|
-
install_requires=[],
|
|
49
|
+
install_requires=["pytz"],
|
|
49
50
|
include_package_data=True,
|
|
50
51
|
zip_safe=False,
|
|
51
52
|
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from unittest import TestCase
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from persiantools import characters, digits
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestDigits(TestCase):
|
|
9
|
+
def test_ar_to_fa(self):
|
|
10
|
+
self.assertEqual(characters.ar_to_fa("السلام عليكم"), "السلام علیکم")
|
|
11
|
+
self.assertEqual(characters.ar_to_fa("HI ي"), "HI ی")
|
|
12
|
+
self.assertEqual(characters.ar_to_fa("دِ بِ زِ ذِ شِ سِ ى ي ك"), "د ب ز ذ ش س ی ی ک")
|
|
13
|
+
self.assertEqual(characters.ar_to_fa("دِ بِ زِ ذِ شِ سِ ى ي ك"), "د ب ز ذ ش س ی ی ک")
|
|
14
|
+
self.assertEqual(
|
|
15
|
+
characters.ar_to_fa("ظ ط ذ د ز ر و ، . ش س ي ب ل ا ت ن م ك ض ص ث ق ف غ ع ه خ ح ؟"),
|
|
16
|
+
"ظ ط ذ د ز ر و ، . ش س ی ب ل ا ت ن م ک ض ص ث ق ف غ ع ه خ ح ؟",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
with pytest.raises(TypeError):
|
|
20
|
+
characters.ar_to_fa(12345)
|
|
21
|
+
|
|
22
|
+
orig = "السلام عليكم ٠١٢٣٤٥٦٧٨٩"
|
|
23
|
+
converted = characters.ar_to_fa(orig)
|
|
24
|
+
converted = digits.ar_to_fa(converted)
|
|
25
|
+
self.assertEqual(converted, "السلام علیکم ۰۱۲۳۴۵۶۷۸۹")
|
|
26
|
+
|
|
27
|
+
def test_fa_to_fa(self):
|
|
28
|
+
self.assertEqual(characters.ar_to_fa("السلام علیکم"), "السلام علیکم")
|
|
29
|
+
self.assertEqual(characters.ar_to_fa("السلام علیکم"), "السلام علیکم")
|
|
30
|
+
|
|
31
|
+
def test_fa_to_ar(self):
|
|
32
|
+
self.assertEqual(characters.fa_to_ar("کیک"), "كيك")
|
|
33
|
+
|
|
34
|
+
with pytest.raises(TypeError):
|
|
35
|
+
characters.ar_to_fa(12345)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from unittest import TestCase
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from persiantools import digits
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestDigits(TestCase):
|
|
9
|
+
def test_en_to_fa(self):
|
|
10
|
+
self.assertEqual(digits.en_to_fa("0987654321"), "۰۹۸۷۶۵۴۳۲۱")
|
|
11
|
+
self.assertEqual(digits.en_to_fa("0987654321"), "۰۹۸۷۶۵۴۳۲۱")
|
|
12
|
+
self.assertEqual(digits.en_to_fa("۰۹۸۷۶۵۴۳۲۱"), "۰۹۸۷۶۵۴۳۲۱")
|
|
13
|
+
self.assertEqual(digits.en_to_fa("+0987654321 abcd"), "+۰۹۸۷۶۵۴۳۲۱ abcd")
|
|
14
|
+
|
|
15
|
+
with pytest.raises(TypeError):
|
|
16
|
+
digits.en_to_fa(12345)
|
|
17
|
+
|
|
18
|
+
def test_ar_to_fa(self):
|
|
19
|
+
self.assertEqual(digits.ar_to_fa("٠٩٨٧٦٥٤٣٢١"), "۰۹۸۷۶۵۴۳۲۱")
|
|
20
|
+
self.assertEqual(digits.ar_to_fa("٠٩٨٧٦٥٤٣٢١"), "۰۹۸۷۶۵۴۳۲۱")
|
|
21
|
+
|
|
22
|
+
orig = "0987٦٥٤٣۲۱"
|
|
23
|
+
converted = digits.en_to_fa(orig)
|
|
24
|
+
converted = digits.ar_to_fa(converted)
|
|
25
|
+
|
|
26
|
+
self.assertEqual(converted, "۰۹۸۷۶۵۴۳۲۱")
|
|
27
|
+
|
|
28
|
+
def test_fa_to_en(self):
|
|
29
|
+
self.assertEqual(digits.fa_to_en("۰۹۸۷۶۵۴۳۲۱"), "0987654321")
|
|
30
|
+
|
|
31
|
+
def test_fa_to_ar(self):
|
|
32
|
+
self.assertEqual(digits.fa_to_ar("۰۹۸۷۶۵۴۳۲۱"), "٠٩٨٧٦٥٤٣٢١")
|
|
33
|
+
self.assertEqual(digits.fa_to_ar(" ۰۹۸۷۶۵۴۳۲۱"), " ٠٩٨٧٦٥٤٣٢١")
|
|
34
|
+
|
|
35
|
+
def test_to_letter(self):
|
|
36
|
+
self.assertEqual(digits.to_word(1), "یک")
|
|
37
|
+
self.assertEqual(digits.to_word(12), "دوازده")
|
|
38
|
+
self.assertEqual(digits.to_word(49), "چهل و نه")
|
|
39
|
+
self.assertEqual(digits.to_word(77), "هفتاد و هفت")
|
|
40
|
+
self.assertEqual(digits.to_word(250), "دویست و پنجاه")
|
|
41
|
+
self.assertEqual(digits.to_word(809), "هشتصد و نه")
|
|
42
|
+
self.assertEqual(digits.to_word(1001), "یک هزار و یک")
|
|
43
|
+
self.assertEqual(digits.to_word(3512), "سه هزار و پانصد و دوازده")
|
|
44
|
+
self.assertEqual(digits.to_word(10000), "ده هزار")
|
|
45
|
+
self.assertEqual(digits.to_word(20010), "بیست هزار و ده")
|
|
46
|
+
self.assertEqual(digits.to_word(10001), "ده هزار و یک")
|
|
47
|
+
self.assertEqual(digits.to_word(500253), "پانصد هزار و دویست و پنجاه و سه")
|
|
48
|
+
self.assertEqual(digits.to_word(6000123), "شش میلیون و یکصد و بیست و سه")
|
|
49
|
+
self.assertEqual(digits.to_word(1000000985), "یک میلیارد و نهصد و هشتاد و پنج")
|
|
50
|
+
self.assertEqual(digits.to_word(100_000_000_000_004), "یکصد تریلیون و چهار")
|
|
51
|
+
self.assertEqual(digits.to_word(9_512_026_000_000), "نه تریلیون و پانصد و دوازده میلیارد و بیست و شش میلیون")
|
|
52
|
+
|
|
53
|
+
self.assertEqual(digits.to_word(-305), "منفی سیصد و پنج")
|
|
54
|
+
self.assertEqual(digits.to_word(10.02), "ده و دو صدم")
|
|
55
|
+
self.assertEqual(digits.to_word(15.007), "پانزده و هفت هزارم")
|
|
56
|
+
self.assertEqual(digits.to_word(12519.85), "دوازده هزار و پانصد و نوزده و هشتاد و پنج صدم")
|
|
57
|
+
self.assertEqual(digits.to_word(123.50), "یکصد و بیست و سه و پنج دهم")
|
|
58
|
+
|
|
59
|
+
self.assertEqual(
|
|
60
|
+
digits.to_word(-0.1554845), "منفی یک میلیون و پانصد و پنجاه و چهار هزار و هشتصد و چهل و پنج ده میلیونیم"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
with pytest.raises(digits.OutOfRangeException):
|
|
64
|
+
digits.to_word(1000000000000001)
|