persiantools 5.2.1__tar.gz → 5.4.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-5.2.1 → persiantools-5.4.0}/LICENSE +1 -1
- {persiantools-5.2.1/persiantools.egg-info → persiantools-5.4.0}/PKG-INFO +19 -15
- {persiantools-5.2.1 → persiantools-5.4.0}/README.md +14 -12
- {persiantools-5.2.1 → persiantools-5.4.0}/persiantools/__init__.py +1 -1
- {persiantools-5.2.1 → persiantools-5.4.0}/persiantools/jdatetime.py +173 -31
- {persiantools-5.2.1 → persiantools-5.4.0/persiantools.egg-info}/PKG-INFO +19 -15
- persiantools-5.4.0/persiantools.egg-info/requires.txt +3 -0
- persiantools-5.4.0/pyproject.toml +39 -0
- {persiantools-5.2.1 → persiantools-5.4.0}/setup.py +15 -7
- {persiantools-5.2.1 → persiantools-5.4.0}/tests/test_digits.py +11 -0
- {persiantools-5.2.1 → persiantools-5.4.0}/tests/test_jalalidate.py +182 -2
- {persiantools-5.2.1 → persiantools-5.4.0}/tests/test_jalalidatetime.py +234 -26
- persiantools-5.2.1/persiantools.egg-info/requires.txt +0 -1
- persiantools-5.2.1/pyproject.toml +0 -27
- {persiantools-5.2.1 → persiantools-5.4.0}/MANIFEST.in +0 -0
- {persiantools-5.2.1 → persiantools-5.4.0}/persiantools/characters.py +0 -0
- {persiantools-5.2.1 → persiantools-5.4.0}/persiantools/digits.py +0 -0
- {persiantools-5.2.1 → persiantools-5.4.0}/persiantools/utils.py +0 -0
- {persiantools-5.2.1 → persiantools-5.4.0}/persiantools.egg-info/SOURCES.txt +0 -0
- {persiantools-5.2.1 → persiantools-5.4.0}/persiantools.egg-info/dependency_links.txt +0 -0
- {persiantools-5.2.1 → persiantools-5.4.0}/persiantools.egg-info/not-zip-safe +0 -0
- {persiantools-5.2.1 → persiantools-5.4.0}/persiantools.egg-info/top_level.txt +0 -0
- {persiantools-5.2.1 → persiantools-5.4.0}/setup.cfg +0 -0
- {persiantools-5.2.1 → persiantools-5.4.0}/tests/test_characters.py +0 -0
- {persiantools-5.2.1 → persiantools-5.4.0}/tests/test_utils.py +0 -0
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: persiantools
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.4.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
|
|
7
7
|
Author-email: majid.hajiloo@gmail.com
|
|
8
8
|
License: MIT
|
|
9
|
+
Project-URL: Source, https://github.com/majiidd/persiantools
|
|
10
|
+
Project-URL: Issues, https://github.com/majiidd/persiantools/issues
|
|
9
11
|
Keywords: jalali shamsi persian digits characters converter jalalidate jalalidatetime date datetime jdate jdatetime farsi
|
|
10
12
|
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
13
|
Classifier: Natural Language :: Persian
|
|
13
14
|
Classifier: Operating System :: OS Independent
|
|
14
15
|
Classifier: Programming Language :: Python
|
|
@@ -28,7 +29,7 @@ Classifier: Topic :: Utilities
|
|
|
28
29
|
Requires-Python: >=3.9
|
|
29
30
|
Description-Content-Type: text/markdown
|
|
30
31
|
License-File: LICENSE
|
|
31
|
-
Requires-Dist:
|
|
32
|
+
Requires-Dist: tzdata; platform_system == "Windows"
|
|
32
33
|
Dynamic: author
|
|
33
34
|
Dynamic: author-email
|
|
34
35
|
Dynamic: classifier
|
|
@@ -38,6 +39,7 @@ Dynamic: home-page
|
|
|
38
39
|
Dynamic: keywords
|
|
39
40
|
Dynamic: license
|
|
40
41
|
Dynamic: license-file
|
|
42
|
+
Dynamic: project-url
|
|
41
43
|
Dynamic: requires-dist
|
|
42
44
|
Dynamic: requires-python
|
|
43
45
|
Dynamic: summary
|
|
@@ -148,15 +150,15 @@ The `JalaliDateTime` object represents a date and time in the Jalali calendar.
|
|
|
148
150
|
|
|
149
151
|
```python
|
|
150
152
|
>>> from persiantools.jdatetime import JalaliDateTime
|
|
151
|
-
>>> import datetime
|
|
153
|
+
>>> import datetime
|
|
152
154
|
|
|
153
155
|
# Current Jalali datetime
|
|
154
156
|
>>> JalaliDateTime.now()
|
|
155
157
|
JalaliDateTime(1404, 3, 16, 2, 17, 14, 907909)
|
|
156
158
|
|
|
157
|
-
|
|
158
|
-
>>> JalaliDateTime.now(
|
|
159
|
-
JalaliDateTime(1404, 3, 16, 2, 17, 14, 907909, tzinfo
|
|
159
|
+
>>> from zoneinfo import ZoneInfo
|
|
160
|
+
>>> JalaliDateTime.now(ZoneInfo("Asia/Tehran"))
|
|
161
|
+
JalaliDateTime(1404, 3, 16, 2, 17, 14, 907909, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tehran'))
|
|
160
162
|
|
|
161
163
|
# Current UTC Jalali datetime
|
|
162
164
|
>>> JalaliDateTime.utcnow()
|
|
@@ -176,20 +178,21 @@ JalaliDateTime(1367, 2, 14, 14, 30, 15)
|
|
|
176
178
|
JalaliDateTime(1400, 1, 1, 15, 30, 0, 10)
|
|
177
179
|
|
|
178
180
|
# Timezone conversion
|
|
179
|
-
>>>
|
|
180
|
-
>>>
|
|
181
|
+
>>> from zoneinfo import ZoneInfo
|
|
182
|
+
>>> tehran_tz = ZoneInfo("Asia/Tehran")
|
|
183
|
+
>>> utc_tz = datetime.timezone.utc
|
|
181
184
|
>>> dt_utc = JalaliDateTime.now(utc_tz)
|
|
182
185
|
>>> dt_tehran = dt_utc.astimezone(tehran_tz)
|
|
183
186
|
>>> dt_utc
|
|
184
|
-
JalaliDateTime(1404, 3, 15, 22, 54, 8, 835877, tzinfo
|
|
187
|
+
JalaliDateTime(1404, 3, 15, 22, 54, 8, 835877, tzinfo=datetime.timezone.utc)
|
|
185
188
|
>>> dt_tehran
|
|
186
|
-
JalaliDateTime(1404, 3, 16, 2, 24, 8, 835877, tzinfo
|
|
189
|
+
JalaliDateTime(1404, 3, 16, 2, 24, 8, 835877, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tehran'))
|
|
187
190
|
```
|
|
188
191
|
|
|
189
192
|
#### Attributes and Methods
|
|
190
193
|
|
|
191
194
|
```python
|
|
192
|
-
>>> dt_obj = JalaliDateTime(1367, 2, 14, 14, 30, 15, 123, tzinfo=
|
|
195
|
+
>>> dt_obj = JalaliDateTime(1367, 2, 14, 14, 30, 15, 123, tzinfo=datetime.timezone.utc)
|
|
193
196
|
|
|
194
197
|
>>> dt_obj.year
|
|
195
198
|
1367
|
|
@@ -206,7 +209,7 @@ JalaliDateTime(1404, 3, 16, 2, 24, 8, 835877, tzinfo=<DstTzInfo 'Asia/Tehran' +0
|
|
|
206
209
|
>>> dt_obj.microsecond
|
|
207
210
|
123
|
|
208
211
|
>>> dt_obj.tzinfo
|
|
209
|
-
|
|
212
|
+
datetime.timezone.utc
|
|
210
213
|
|
|
211
214
|
# Date part as datetime.date (Gregorian)
|
|
212
215
|
>>> dt_obj.date()
|
|
@@ -227,9 +230,9 @@ Based on python `strftime()` behavior
|
|
|
227
230
|
|
|
228
231
|
```python
|
|
229
232
|
>>> from persiantools.jdatetime import JalaliDateTime
|
|
230
|
-
>>> import
|
|
233
|
+
>>> from zoneinfo import ZoneInfo
|
|
231
234
|
|
|
232
|
-
>>> dt = JalaliDateTime(1367, 2, 14, 14, 30, 0, tzinfo=
|
|
235
|
+
>>> dt = JalaliDateTime(1367, 2, 14, 14, 30, 0, tzinfo=ZoneInfo("Asia/Tehran"))
|
|
233
236
|
|
|
234
237
|
>>> dt.strftime("%Y/%m/%d %H:%M:%S")
|
|
235
238
|
'1367/02/14 14:30:00'
|
|
@@ -346,6 +349,7 @@ JalaliDate(1367, 2, 14, Chaharshanbeh)
|
|
|
346
349
|
If you find this project helpful and would like to support its continued development, please consider donating.
|
|
347
350
|
|
|
348
351
|
* **Bitcoin (BTC):** `bc1qg5rp7ymznc98wmhltzvpwl2dvfuvjr33m4hy77`
|
|
352
|
+
* **Ethereum (ETH):** `0xC7D6bf306E456632764D0aD111C8dBBb43a3B9ad`
|
|
349
353
|
* **Tron (TRX):** `TDd63bVWZDBHmwVNFgJ6T2WdWmk9z7PBLg`
|
|
350
354
|
* **Stellar (XLM):** `GDSFPPLY34QSAOTOP4DQDXAI2YDRNRIADZHTN3HCGMQXRLIGPYOEH7L5`
|
|
351
355
|
* **Solana (SOL):** `CXHKgCBqBYy1hbZKGqaSmMzQoTC4Wx2v8QfL9Z7JBo3A`
|
|
@@ -104,15 +104,15 @@ The `JalaliDateTime` object represents a date and time in the Jalali calendar.
|
|
|
104
104
|
|
|
105
105
|
```python
|
|
106
106
|
>>> from persiantools.jdatetime import JalaliDateTime
|
|
107
|
-
>>> import datetime
|
|
107
|
+
>>> import datetime
|
|
108
108
|
|
|
109
109
|
# Current Jalali datetime
|
|
110
110
|
>>> JalaliDateTime.now()
|
|
111
111
|
JalaliDateTime(1404, 3, 16, 2, 17, 14, 907909)
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
>>> JalaliDateTime.now(
|
|
115
|
-
JalaliDateTime(1404, 3, 16, 2, 17, 14, 907909, tzinfo
|
|
113
|
+
>>> from zoneinfo import ZoneInfo
|
|
114
|
+
>>> JalaliDateTime.now(ZoneInfo("Asia/Tehran"))
|
|
115
|
+
JalaliDateTime(1404, 3, 16, 2, 17, 14, 907909, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tehran'))
|
|
116
116
|
|
|
117
117
|
# Current UTC Jalali datetime
|
|
118
118
|
>>> JalaliDateTime.utcnow()
|
|
@@ -132,20 +132,21 @@ JalaliDateTime(1367, 2, 14, 14, 30, 15)
|
|
|
132
132
|
JalaliDateTime(1400, 1, 1, 15, 30, 0, 10)
|
|
133
133
|
|
|
134
134
|
# Timezone conversion
|
|
135
|
-
>>>
|
|
136
|
-
>>>
|
|
135
|
+
>>> from zoneinfo import ZoneInfo
|
|
136
|
+
>>> tehran_tz = ZoneInfo("Asia/Tehran")
|
|
137
|
+
>>> utc_tz = datetime.timezone.utc
|
|
137
138
|
>>> dt_utc = JalaliDateTime.now(utc_tz)
|
|
138
139
|
>>> dt_tehran = dt_utc.astimezone(tehran_tz)
|
|
139
140
|
>>> dt_utc
|
|
140
|
-
JalaliDateTime(1404, 3, 15, 22, 54, 8, 835877, tzinfo
|
|
141
|
+
JalaliDateTime(1404, 3, 15, 22, 54, 8, 835877, tzinfo=datetime.timezone.utc)
|
|
141
142
|
>>> dt_tehran
|
|
142
|
-
JalaliDateTime(1404, 3, 16, 2, 24, 8, 835877, tzinfo
|
|
143
|
+
JalaliDateTime(1404, 3, 16, 2, 24, 8, 835877, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tehran'))
|
|
143
144
|
```
|
|
144
145
|
|
|
145
146
|
#### Attributes and Methods
|
|
146
147
|
|
|
147
148
|
```python
|
|
148
|
-
>>> dt_obj = JalaliDateTime(1367, 2, 14, 14, 30, 15, 123, tzinfo=
|
|
149
|
+
>>> dt_obj = JalaliDateTime(1367, 2, 14, 14, 30, 15, 123, tzinfo=datetime.timezone.utc)
|
|
149
150
|
|
|
150
151
|
>>> dt_obj.year
|
|
151
152
|
1367
|
|
@@ -162,7 +163,7 @@ JalaliDateTime(1404, 3, 16, 2, 24, 8, 835877, tzinfo=<DstTzInfo 'Asia/Tehran' +0
|
|
|
162
163
|
>>> dt_obj.microsecond
|
|
163
164
|
123
|
|
164
165
|
>>> dt_obj.tzinfo
|
|
165
|
-
|
|
166
|
+
datetime.timezone.utc
|
|
166
167
|
|
|
167
168
|
# Date part as datetime.date (Gregorian)
|
|
168
169
|
>>> dt_obj.date()
|
|
@@ -183,9 +184,9 @@ Based on python `strftime()` behavior
|
|
|
183
184
|
|
|
184
185
|
```python
|
|
185
186
|
>>> from persiantools.jdatetime import JalaliDateTime
|
|
186
|
-
>>> import
|
|
187
|
+
>>> from zoneinfo import ZoneInfo
|
|
187
188
|
|
|
188
|
-
>>> dt = JalaliDateTime(1367, 2, 14, 14, 30, 0, tzinfo=
|
|
189
|
+
>>> dt = JalaliDateTime(1367, 2, 14, 14, 30, 0, tzinfo=ZoneInfo("Asia/Tehran"))
|
|
189
190
|
|
|
190
191
|
>>> dt.strftime("%Y/%m/%d %H:%M:%S")
|
|
191
192
|
'1367/02/14 14:30:00'
|
|
@@ -302,6 +303,7 @@ JalaliDate(1367, 2, 14, Chaharshanbeh)
|
|
|
302
303
|
If you find this project helpful and would like to support its continued development, please consider donating.
|
|
303
304
|
|
|
304
305
|
* **Bitcoin (BTC):** `bc1qg5rp7ymznc98wmhltzvpwl2dvfuvjr33m4hy77`
|
|
306
|
+
* **Ethereum (ETH):** `0xC7D6bf306E456632764D0aD111C8dBBb43a3B9ad`
|
|
305
307
|
* **Tron (TRX):** `TDd63bVWZDBHmwVNFgJ6T2WdWmk9z7PBLg`
|
|
306
308
|
* **Stellar (XLM):** `GDSFPPLY34QSAOTOP4DQDXAI2YDRNRIADZHTN3HCGMQXRLIGPYOEH7L5`
|
|
307
309
|
* **Solana (SOL):** `CXHKgCBqBYy1hbZKGqaSmMzQoTC4Wx2v8QfL9Z7JBo3A`
|
|
@@ -5,8 +5,7 @@ from datetime import datetime as dt
|
|
|
5
5
|
from datetime import time as _time
|
|
6
6
|
from datetime import timedelta, timezone, tzinfo
|
|
7
7
|
from re import escape as re_escape
|
|
8
|
-
|
|
9
|
-
import pytz
|
|
8
|
+
from zoneinfo import ZoneInfo
|
|
10
9
|
|
|
11
10
|
from persiantools import digits, utils
|
|
12
11
|
|
|
@@ -633,12 +632,13 @@ class JalaliDate:
|
|
|
633
632
|
raise ValueError(f"Invalid date separator: {dtstr[4]}")
|
|
634
633
|
|
|
635
634
|
month = int(dtstr[5:7])
|
|
636
|
-
|
|
637
635
|
if dtstr[7] != "-":
|
|
638
636
|
raise ValueError("Invalid date separator")
|
|
639
637
|
|
|
640
638
|
day = int(dtstr[8:10])
|
|
641
639
|
|
|
640
|
+
cls._check_date_fields(year, month, day, "en")
|
|
641
|
+
|
|
642
642
|
return [year, month, day]
|
|
643
643
|
|
|
644
644
|
def __hash__(self):
|
|
@@ -941,8 +941,122 @@ class JalaliDate:
|
|
|
941
941
|
raise NotImplementedError
|
|
942
942
|
|
|
943
943
|
@classmethod
|
|
944
|
-
def strptime(cls, data_string, fmt):
|
|
945
|
-
|
|
944
|
+
def strptime(cls, data_string, fmt, locale="en"):
|
|
945
|
+
if locale not in ["en", "fa"]:
|
|
946
|
+
raise ValueError("locale must be 'en' or 'fa'")
|
|
947
|
+
|
|
948
|
+
if locale == "fa":
|
|
949
|
+
data_string = digits.fa_to_en(data_string)
|
|
950
|
+
|
|
951
|
+
month_names_list = MONTH_NAMES_EN[1:] if locale == "en" else MONTH_NAMES_FA[1:]
|
|
952
|
+
month_names_abbr_list = MONTH_NAMES_ABBR_EN[1:] if locale == "en" else MONTH_NAMES_ABBR_FA[1:]
|
|
953
|
+
weekday_names_list = WEEKDAY_NAMES_EN if locale == "en" else WEEKDAY_NAMES_FA
|
|
954
|
+
weekday_names_abbr_list = WEEKDAY_NAMES_ABBR_EN if locale == "en" else WEEKDAY_NAMES_ABBR_FA
|
|
955
|
+
|
|
956
|
+
directives_regex_pattern = {
|
|
957
|
+
"%Y": r"(?P<Y>\d{4})",
|
|
958
|
+
"%y": r"(?P<y>\d{2})",
|
|
959
|
+
"%m": r"(?P<m>1[0-2]|0?[1-9])",
|
|
960
|
+
"%d": r"(?P<d>\d{1,2})",
|
|
961
|
+
"%b": cls._seqToRE(month_names_abbr_list, "b"),
|
|
962
|
+
"%B": cls._seqToRE(month_names_list, "B"),
|
|
963
|
+
"%a": cls._seqToRE(weekday_names_abbr_list, "a"),
|
|
964
|
+
"%A": cls._seqToRE(weekday_names_list, "A"),
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
fmt = utils.replace(
|
|
968
|
+
fmt,
|
|
969
|
+
{
|
|
970
|
+
"%x": "%Y/%m/%d",
|
|
971
|
+
"%c": "%a %b %d %Y",
|
|
972
|
+
},
|
|
973
|
+
)
|
|
974
|
+
|
|
975
|
+
data_string_regex = utils.replace(fmt, directives_regex_pattern)
|
|
976
|
+
full_pattern = f"^{data_string_regex}$"
|
|
977
|
+
|
|
978
|
+
match = re.match(full_pattern, data_string, re.IGNORECASE)
|
|
979
|
+
if not match:
|
|
980
|
+
match_strict = re.match(f"^{data_string_regex}$", data_string, re.IGNORECASE)
|
|
981
|
+
if not match_strict:
|
|
982
|
+
raise ValueError(f"Date string '{data_string}' does not match format '{fmt}'")
|
|
983
|
+
directives = match_strict.groupdict()
|
|
984
|
+
else:
|
|
985
|
+
directives = match.groupdict()
|
|
986
|
+
|
|
987
|
+
parsed_components = {}
|
|
988
|
+
for k, v in directives.items():
|
|
989
|
+
if v is not None:
|
|
990
|
+
if k not in ["a", "A", "b", "B"] and v.isdigit():
|
|
991
|
+
parsed_components[k] = int(v)
|
|
992
|
+
else:
|
|
993
|
+
parsed_components[k] = v
|
|
994
|
+
|
|
995
|
+
year = parsed_components.get("Y")
|
|
996
|
+
yy = parsed_components.get("y")
|
|
997
|
+
|
|
998
|
+
if year is None and yy is not None:
|
|
999
|
+
if not (0 <= yy <= 99):
|
|
1000
|
+
raise ValueError(f"Year without century (yy) '{yy}' out of range 00-99.")
|
|
1001
|
+
# Heuristic: if yy > 70, assume 13yy, else 14yy.
|
|
1002
|
+
year = (
|
|
1003
|
+
(1300 + yy) if yy > (2070 - 2000) else (1400 + yy)
|
|
1004
|
+
) # Adjusted heuristic to be roughly 70 for 1300 century.
|
|
1005
|
+
# Current Jalali year is around 140x. So values like 01, 02.. up to e.g. 70 => 14xx.
|
|
1006
|
+
# values like 71, 72 .. 99 => 13xx.
|
|
1007
|
+
elif year is None:
|
|
1008
|
+
raise ValueError("Year information is missing from the date string or format.")
|
|
1009
|
+
|
|
1010
|
+
month = parsed_components.get("m")
|
|
1011
|
+
if month is None:
|
|
1012
|
+
month_name_abbr = parsed_components.get("b")
|
|
1013
|
+
month_name_full = parsed_components.get("B")
|
|
1014
|
+
|
|
1015
|
+
found_month = False
|
|
1016
|
+
if month_name_abbr is not None:
|
|
1017
|
+
normalized_month_abbr = month_name_abbr.capitalize() if locale == "en" else month_name_abbr
|
|
1018
|
+
try:
|
|
1019
|
+
month = month_names_abbr_list.index(normalized_month_abbr) + 1
|
|
1020
|
+
found_month = True
|
|
1021
|
+
except ValueError:
|
|
1022
|
+
try:
|
|
1023
|
+
month = month_names_list.index(normalized_month_abbr) + 1
|
|
1024
|
+
found_month = True
|
|
1025
|
+
except ValueError:
|
|
1026
|
+
pass
|
|
1027
|
+
|
|
1028
|
+
if not found_month and month_name_full is not None:
|
|
1029
|
+
normalized_month_full = month_name_full.capitalize() if locale == "en" else month_name_full
|
|
1030
|
+
try:
|
|
1031
|
+
month = month_names_list.index(normalized_month_full) + 1
|
|
1032
|
+
found_month = True
|
|
1033
|
+
except ValueError:
|
|
1034
|
+
pass
|
|
1035
|
+
|
|
1036
|
+
if not found_month:
|
|
1037
|
+
raise ValueError(
|
|
1038
|
+
f"Month name not recognized from '{month_name_abbr or month_name_full}' for locale '{locale}'."
|
|
1039
|
+
)
|
|
1040
|
+
|
|
1041
|
+
day = parsed_components.get("d")
|
|
1042
|
+
if day is None:
|
|
1043
|
+
raise ValueError("Day information is missing from the date string or format.")
|
|
1044
|
+
|
|
1045
|
+
cls._check_date_fields(year, month, day, locale)
|
|
1046
|
+
|
|
1047
|
+
return cls(year, month, day, locale=locale)
|
|
1048
|
+
|
|
1049
|
+
@staticmethod
|
|
1050
|
+
def _seqToRE(to_convert, directive):
|
|
1051
|
+
to_convert = sorted(to_convert, key=len, reverse=True)
|
|
1052
|
+
for value in to_convert:
|
|
1053
|
+
if value != "":
|
|
1054
|
+
break
|
|
1055
|
+
else:
|
|
1056
|
+
return ""
|
|
1057
|
+
regex = "|".join(re_escape(stuff) for stuff in to_convert)
|
|
1058
|
+
regex = f"(?P<{directive}>{regex}"
|
|
1059
|
+
return "%s)" % regex
|
|
946
1060
|
|
|
947
1061
|
|
|
948
1062
|
_tzinfo_class = tzinfo
|
|
@@ -1452,28 +1566,58 @@ class JalaliDateTime(JalaliDate):
|
|
|
1452
1566
|
if self._tzinfo is None:
|
|
1453
1567
|
return None
|
|
1454
1568
|
|
|
1455
|
-
|
|
1569
|
+
g = self.to_gregorian()
|
|
1570
|
+
if g.tzinfo is None:
|
|
1571
|
+
g = g.replace(tzinfo=self._tzinfo)
|
|
1572
|
+
try:
|
|
1573
|
+
offset = self._tzinfo.utcoffset(g)
|
|
1574
|
+
except Exception:
|
|
1575
|
+
offset = self._tzinfo.utcoffset(None)
|
|
1456
1576
|
|
|
1577
|
+
self.check_utc_offset("utcoffset", offset)
|
|
1457
1578
|
return offset
|
|
1458
1579
|
|
|
1459
1580
|
def tzname(self):
|
|
1460
1581
|
if self._tzinfo is None:
|
|
1461
1582
|
return None
|
|
1462
1583
|
|
|
1463
|
-
|
|
1584
|
+
g = self.to_gregorian()
|
|
1585
|
+
if g.tzinfo is None:
|
|
1586
|
+
g = g.replace(tzinfo=self._tzinfo)
|
|
1587
|
+
try:
|
|
1588
|
+
name = self._tzinfo.tzname(g)
|
|
1589
|
+
except Exception:
|
|
1590
|
+
name = self._tzinfo.tzname(None)
|
|
1464
1591
|
|
|
1465
1592
|
if name is not None and not isinstance(name, str):
|
|
1466
|
-
raise TypeError("tzinfo.tzname() must return None or string,
|
|
1593
|
+
raise TypeError("tzinfo.tzname() must return None or string, not '%s'" % type(name))
|
|
1467
1594
|
|
|
1468
1595
|
return name
|
|
1469
1596
|
|
|
1470
1597
|
def dst(self):
|
|
1598
|
+
"""Return DST offset as timedelta or None.
|
|
1599
|
+
|
|
1600
|
+
For stdlib fixed-offset timezones (datetime.timezone, including UTC) this must be timedelta(0).
|
|
1601
|
+
"""
|
|
1471
1602
|
if self._tzinfo is None:
|
|
1472
1603
|
return None
|
|
1473
1604
|
|
|
1474
|
-
|
|
1475
|
-
|
|
1605
|
+
from datetime import timedelta as _td
|
|
1606
|
+
from datetime import timezone as _tz
|
|
1476
1607
|
|
|
1608
|
+
# datetime.timezone instances (including timezone.utc) never have DST
|
|
1609
|
+
if self._tzinfo is _tz.utc or isinstance(self._tzinfo, type(_tz.utc)):
|
|
1610
|
+
return _td(0)
|
|
1611
|
+
|
|
1612
|
+
g = self.to_gregorian()
|
|
1613
|
+
if g.tzinfo is None:
|
|
1614
|
+
g = g.replace(tzinfo=self._tzinfo)
|
|
1615
|
+
try:
|
|
1616
|
+
offset = self._tzinfo.dst(g)
|
|
1617
|
+
except Exception:
|
|
1618
|
+
offset = self._tzinfo.dst(None)
|
|
1619
|
+
|
|
1620
|
+
self.check_utc_offset("dst", offset)
|
|
1477
1621
|
return offset
|
|
1478
1622
|
|
|
1479
1623
|
@staticmethod
|
|
@@ -1604,21 +1748,26 @@ class JalaliDateTime(JalaliDate):
|
|
|
1604
1748
|
look for the table under "strftime() and strptime() Format Codes" section.
|
|
1605
1749
|
"""
|
|
1606
1750
|
directives_regex_pattern = {
|
|
1607
|
-
"%Y": r"(?P<Y>\d
|
|
1751
|
+
"%Y": r"(?P<Y>\d{4})",
|
|
1608
1752
|
"%m": r"(?P<m>1[0-2]|0[1-9]|[1-9])",
|
|
1609
1753
|
"%d": r"(?P<d>3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])",
|
|
1610
|
-
"%a": cls.
|
|
1611
|
-
"%A": cls.
|
|
1612
|
-
"%b": cls.
|
|
1613
|
-
"%B": cls.
|
|
1754
|
+
"%a": cls._seqToRE(weekday_names_abbr, "a"),
|
|
1755
|
+
"%A": cls._seqToRE(weekday_names, "A"),
|
|
1756
|
+
"%b": cls._seqToRE(month_names_abbr, "b"),
|
|
1757
|
+
"%B": cls._seqToRE(month_names, "B"),
|
|
1614
1758
|
"%H": r"(?P<H>2[0-3]|[0-1]\d|\d)",
|
|
1615
1759
|
"%I": r"(?P<I>1[0-2]|0[1-9]|[1-9])",
|
|
1616
|
-
"%p": cls.
|
|
1760
|
+
"%p": cls._seqToRE(periods, "p"),
|
|
1617
1761
|
"%M": r"(?P<M>[0-5]\d|\d)",
|
|
1618
1762
|
"%S": r"(?P<S>6[0-1]|[0-5]\d|\d)",
|
|
1619
1763
|
"%f": r"(?P<f>\d{1,6})",
|
|
1620
|
-
"%z":
|
|
1621
|
-
|
|
1764
|
+
"%z": (
|
|
1765
|
+
r"(?P<z>[-+](?P<zH>2[0-3]|[0-1]\d)"
|
|
1766
|
+
r"(?:[:]?)(?P<zM>[0-5]\d)"
|
|
1767
|
+
r"(?:[:]?(?P<zS>[0-5]\d))?"
|
|
1768
|
+
r"(?:\.(?P<zf>\d{1,6}))?)"
|
|
1769
|
+
),
|
|
1770
|
+
"%Z": r"(?P<Z>[A-Za-z_/\-]+)",
|
|
1622
1771
|
}
|
|
1623
1772
|
|
|
1624
1773
|
fmt = utils.replace(
|
|
@@ -1631,8 +1780,9 @@ class JalaliDateTime(JalaliDate):
|
|
|
1631
1780
|
)
|
|
1632
1781
|
|
|
1633
1782
|
data_string_regex = utils.replace(fmt, directives_regex_pattern)
|
|
1783
|
+
full_pattern = f"^{data_string_regex}$"
|
|
1634
1784
|
|
|
1635
|
-
if re.match(
|
|
1785
|
+
if re.match(full_pattern, data_string, re.IGNORECASE):
|
|
1636
1786
|
directives = re.search(data_string_regex, data_string, re.IGNORECASE).groupdict()
|
|
1637
1787
|
|
|
1638
1788
|
if "Y" in directives.keys() and len(directives.get("Y")) < 4:
|
|
@@ -1666,7 +1816,10 @@ class JalaliDateTime(JalaliDate):
|
|
|
1666
1816
|
)
|
|
1667
1817
|
tz = timezone(delta)
|
|
1668
1818
|
elif "Z" in directives.keys():
|
|
1669
|
-
|
|
1819
|
+
try:
|
|
1820
|
+
tz = ZoneInfo(directives.get("Z"))
|
|
1821
|
+
except Exception:
|
|
1822
|
+
raise ValueError(f"Unknown time zone name: {directives.get('Z')}")
|
|
1670
1823
|
|
|
1671
1824
|
cls_attrs = {
|
|
1672
1825
|
"year": directives.get("Y", 1),
|
|
@@ -1684,17 +1837,6 @@ class JalaliDateTime(JalaliDate):
|
|
|
1684
1837
|
else:
|
|
1685
1838
|
raise ValueError("data string and format are not matched")
|
|
1686
1839
|
|
|
1687
|
-
def __seqToRE(self, to_convert, directive):
|
|
1688
|
-
to_convert = sorted(to_convert, key=len, reverse=True)
|
|
1689
|
-
for value in to_convert:
|
|
1690
|
-
if value != "":
|
|
1691
|
-
break
|
|
1692
|
-
else:
|
|
1693
|
-
return ""
|
|
1694
|
-
regex = "|".join(re_escape(stuff) for stuff in to_convert)
|
|
1695
|
-
regex = f"(?P<{directive}>{regex}"
|
|
1696
|
-
return "%s)" % regex
|
|
1697
|
-
|
|
1698
1840
|
def __repr__(self):
|
|
1699
1841
|
"""Convert to formal string, for repr()."""
|
|
1700
1842
|
d_datetime = [
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: persiantools
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.4.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
|
|
7
7
|
Author-email: majid.hajiloo@gmail.com
|
|
8
8
|
License: MIT
|
|
9
|
+
Project-URL: Source, https://github.com/majiidd/persiantools
|
|
10
|
+
Project-URL: Issues, https://github.com/majiidd/persiantools/issues
|
|
9
11
|
Keywords: jalali shamsi persian digits characters converter jalalidate jalalidatetime date datetime jdate jdatetime farsi
|
|
10
12
|
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
13
|
Classifier: Natural Language :: Persian
|
|
13
14
|
Classifier: Operating System :: OS Independent
|
|
14
15
|
Classifier: Programming Language :: Python
|
|
@@ -28,7 +29,7 @@ Classifier: Topic :: Utilities
|
|
|
28
29
|
Requires-Python: >=3.9
|
|
29
30
|
Description-Content-Type: text/markdown
|
|
30
31
|
License-File: LICENSE
|
|
31
|
-
Requires-Dist:
|
|
32
|
+
Requires-Dist: tzdata; platform_system == "Windows"
|
|
32
33
|
Dynamic: author
|
|
33
34
|
Dynamic: author-email
|
|
34
35
|
Dynamic: classifier
|
|
@@ -38,6 +39,7 @@ Dynamic: home-page
|
|
|
38
39
|
Dynamic: keywords
|
|
39
40
|
Dynamic: license
|
|
40
41
|
Dynamic: license-file
|
|
42
|
+
Dynamic: project-url
|
|
41
43
|
Dynamic: requires-dist
|
|
42
44
|
Dynamic: requires-python
|
|
43
45
|
Dynamic: summary
|
|
@@ -148,15 +150,15 @@ The `JalaliDateTime` object represents a date and time in the Jalali calendar.
|
|
|
148
150
|
|
|
149
151
|
```python
|
|
150
152
|
>>> from persiantools.jdatetime import JalaliDateTime
|
|
151
|
-
>>> import datetime
|
|
153
|
+
>>> import datetime
|
|
152
154
|
|
|
153
155
|
# Current Jalali datetime
|
|
154
156
|
>>> JalaliDateTime.now()
|
|
155
157
|
JalaliDateTime(1404, 3, 16, 2, 17, 14, 907909)
|
|
156
158
|
|
|
157
|
-
|
|
158
|
-
>>> JalaliDateTime.now(
|
|
159
|
-
JalaliDateTime(1404, 3, 16, 2, 17, 14, 907909, tzinfo
|
|
159
|
+
>>> from zoneinfo import ZoneInfo
|
|
160
|
+
>>> JalaliDateTime.now(ZoneInfo("Asia/Tehran"))
|
|
161
|
+
JalaliDateTime(1404, 3, 16, 2, 17, 14, 907909, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tehran'))
|
|
160
162
|
|
|
161
163
|
# Current UTC Jalali datetime
|
|
162
164
|
>>> JalaliDateTime.utcnow()
|
|
@@ -176,20 +178,21 @@ JalaliDateTime(1367, 2, 14, 14, 30, 15)
|
|
|
176
178
|
JalaliDateTime(1400, 1, 1, 15, 30, 0, 10)
|
|
177
179
|
|
|
178
180
|
# Timezone conversion
|
|
179
|
-
>>>
|
|
180
|
-
>>>
|
|
181
|
+
>>> from zoneinfo import ZoneInfo
|
|
182
|
+
>>> tehran_tz = ZoneInfo("Asia/Tehran")
|
|
183
|
+
>>> utc_tz = datetime.timezone.utc
|
|
181
184
|
>>> dt_utc = JalaliDateTime.now(utc_tz)
|
|
182
185
|
>>> dt_tehran = dt_utc.astimezone(tehran_tz)
|
|
183
186
|
>>> dt_utc
|
|
184
|
-
JalaliDateTime(1404, 3, 15, 22, 54, 8, 835877, tzinfo
|
|
187
|
+
JalaliDateTime(1404, 3, 15, 22, 54, 8, 835877, tzinfo=datetime.timezone.utc)
|
|
185
188
|
>>> dt_tehran
|
|
186
|
-
JalaliDateTime(1404, 3, 16, 2, 24, 8, 835877, tzinfo
|
|
189
|
+
JalaliDateTime(1404, 3, 16, 2, 24, 8, 835877, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tehran'))
|
|
187
190
|
```
|
|
188
191
|
|
|
189
192
|
#### Attributes and Methods
|
|
190
193
|
|
|
191
194
|
```python
|
|
192
|
-
>>> dt_obj = JalaliDateTime(1367, 2, 14, 14, 30, 15, 123, tzinfo=
|
|
195
|
+
>>> dt_obj = JalaliDateTime(1367, 2, 14, 14, 30, 15, 123, tzinfo=datetime.timezone.utc)
|
|
193
196
|
|
|
194
197
|
>>> dt_obj.year
|
|
195
198
|
1367
|
|
@@ -206,7 +209,7 @@ JalaliDateTime(1404, 3, 16, 2, 24, 8, 835877, tzinfo=<DstTzInfo 'Asia/Tehran' +0
|
|
|
206
209
|
>>> dt_obj.microsecond
|
|
207
210
|
123
|
|
208
211
|
>>> dt_obj.tzinfo
|
|
209
|
-
|
|
212
|
+
datetime.timezone.utc
|
|
210
213
|
|
|
211
214
|
# Date part as datetime.date (Gregorian)
|
|
212
215
|
>>> dt_obj.date()
|
|
@@ -227,9 +230,9 @@ Based on python `strftime()` behavior
|
|
|
227
230
|
|
|
228
231
|
```python
|
|
229
232
|
>>> from persiantools.jdatetime import JalaliDateTime
|
|
230
|
-
>>> import
|
|
233
|
+
>>> from zoneinfo import ZoneInfo
|
|
231
234
|
|
|
232
|
-
>>> dt = JalaliDateTime(1367, 2, 14, 14, 30, 0, tzinfo=
|
|
235
|
+
>>> dt = JalaliDateTime(1367, 2, 14, 14, 30, 0, tzinfo=ZoneInfo("Asia/Tehran"))
|
|
233
236
|
|
|
234
237
|
>>> dt.strftime("%Y/%m/%d %H:%M:%S")
|
|
235
238
|
'1367/02/14 14:30:00'
|
|
@@ -346,6 +349,7 @@ JalaliDate(1367, 2, 14, Chaharshanbeh)
|
|
|
346
349
|
If you find this project helpful and would like to support its continued development, please consider donating.
|
|
347
350
|
|
|
348
351
|
* **Bitcoin (BTC):** `bc1qg5rp7ymznc98wmhltzvpwl2dvfuvjr33m4hy77`
|
|
352
|
+
* **Ethereum (ETH):** `0xC7D6bf306E456632764D0aD111C8dBBb43a3B9ad`
|
|
349
353
|
* **Tron (TRX):** `TDd63bVWZDBHmwVNFgJ6T2WdWmk9z7PBLg`
|
|
350
354
|
* **Stellar (XLM):** `GDSFPPLY34QSAOTOP4DQDXAI2YDRNRIADZHTN3HCGMQXRLIGPYOEH7L5`
|
|
351
355
|
* **Solana (SOL):** `CXHKgCBqBYy1hbZKGqaSmMzQoTC4Wx2v8QfL9Z7JBo3A`
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[tool.black]
|
|
2
|
+
line-length = 120
|
|
3
|
+
target-version = ["py39", "py310", "py311", "py312", "py313"]
|
|
4
|
+
include = '\.pyi?$'
|
|
5
|
+
exclude = '''
|
|
6
|
+
(
|
|
7
|
+
/(
|
|
8
|
+
\.eggs
|
|
9
|
+
| \.git
|
|
10
|
+
| \.hg
|
|
11
|
+
| \.mypy_cache
|
|
12
|
+
| \.tox
|
|
13
|
+
| \.venv
|
|
14
|
+
| _build
|
|
15
|
+
| buck-out
|
|
16
|
+
| build
|
|
17
|
+
| dist
|
|
18
|
+
)/
|
|
19
|
+
)
|
|
20
|
+
'''
|
|
21
|
+
|
|
22
|
+
[tool.isort]
|
|
23
|
+
profile = "black"
|
|
24
|
+
line_length = 120
|
|
25
|
+
|
|
26
|
+
[tool.pytest.ini_options]
|
|
27
|
+
addopts = "-ra"
|
|
28
|
+
testpaths = ["tests"]
|
|
29
|
+
python_files = ["test_*.py"]
|
|
30
|
+
python_functions = ["test_*"]
|
|
31
|
+
|
|
32
|
+
[tool.mypy]
|
|
33
|
+
python_version = "3.9"
|
|
34
|
+
warn_unused_ignores = true
|
|
35
|
+
warn_redundant_casts = true
|
|
36
|
+
disallow_incomplete_defs = false
|
|
37
|
+
no_implicit_optional = true
|
|
38
|
+
check_untyped_defs = false
|
|
39
|
+
ignore_missing_imports = false
|