ethioqen 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.
- ethioqen-0.1.0/LICENSE +21 -0
- ethioqen-0.1.0/PKG-INFO +152 -0
- ethioqen-0.1.0/README.md +127 -0
- ethioqen-0.1.0/ethioqen/__init__.py +4 -0
- ethioqen-0.1.0/ethioqen/calendar_conversion.py +111 -0
- ethioqen-0.1.0/ethioqen/exceptions.py +5 -0
- ethioqen-0.1.0/ethioqen/time_conversion.py +43 -0
- ethioqen-0.1.0/ethioqen/unix_time_conversion.py +104 -0
- ethioqen-0.1.0/ethioqen/utils.py +49 -0
- ethioqen-0.1.0/ethioqen.egg-info/PKG-INFO +152 -0
- ethioqen-0.1.0/ethioqen.egg-info/SOURCES.txt +17 -0
- ethioqen-0.1.0/ethioqen.egg-info/dependency_links.txt +1 -0
- ethioqen-0.1.0/ethioqen.egg-info/top_level.txt +1 -0
- ethioqen-0.1.0/pyproject.toml +31 -0
- ethioqen-0.1.0/setup.cfg +4 -0
- ethioqen-0.1.0/setup.py +36 -0
- ethioqen-0.1.0/tests/test_calendar_conversion.py +105 -0
- ethioqen-0.1.0/tests/test_time_conversion.py +83 -0
- ethioqen-0.1.0/tests/test_unix_time_conversion.py +108 -0
ethioqen-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Beabfekad Zikie
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|
ethioqen-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ethioqen
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A package for Ethiopian date and time conversions
|
|
5
|
+
Home-page: https://github.com/beabzk/ethioqen
|
|
6
|
+
Author: Beabfekad Zikie
|
|
7
|
+
Author-email: Beabfekad Zikie <beabzk@proton.me>
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Bug Reports, https://github.com/beabzk/ethioqen/issues
|
|
10
|
+
Project-URL: Source, https://github.com/beabzk/ethioqen
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Topic :: Software Development :: Localization
|
|
22
|
+
Requires-Python: >=3.8
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
|
|
26
|
+
# ethioqen: Ethiopian Calendar, Time, and Unix Timestamp Conversion
|
|
27
|
+
|
|
28
|
+
> ⚠️ **Warning**: This library is in very early stages of development and should not be used in production. Contributions are crucial to make this production-ready.
|
|
29
|
+
|
|
30
|
+
[](https://badge.fury.io/py/ethioqen)
|
|
31
|
+
[](https://opensource.org/licenses/MIT)
|
|
32
|
+
[](https://github.com/beabzk/ethioqen/actions)
|
|
33
|
+
|
|
34
|
+
`ethioqen` is a Python library that provides accurate and efficient conversions between the Ethiopian calendar, the Gregorian calendar, Ethiopian local time (12-hour format), standard 24-hour local time, and Unix timestamps.
|
|
35
|
+
|
|
36
|
+
## Introduction
|
|
37
|
+
|
|
38
|
+
The Ethiopian calendar is a solar calendar used in Ethiopia and Eritrea. It differs significantly from the Gregorian calendar, which is the most widely used calendar system today.
|
|
39
|
+
|
|
40
|
+
**Key Differences:**
|
|
41
|
+
|
|
42
|
+
* **Year Offset:** The Ethiopian calendar is typically 7-8 years behind the Gregorian calendar.
|
|
43
|
+
* **Months:** It has 13 months: 12 months of 30 days each and a 13th month called *Pagume*, which has 5 days (6 days in a leap year).
|
|
44
|
+
* **New Year:** The Ethiopian New Year (*Enkutatash*) falls on September 11th (or 12th in a Gregorian leap year).
|
|
45
|
+
* **Leap Years:** Ethiopia follows a simple 4-year leap year cycle without the century exception found in the Gregorian calendar.
|
|
46
|
+
|
|
47
|
+
**Ethiopian Local Time:**
|
|
48
|
+
|
|
49
|
+
Ethiopian time uses a 12-hour clock that starts counting from dawn (around 6:00 AM standard time). This creates a 6-hour offset between Ethiopian and standard time:
|
|
50
|
+
|
|
51
|
+
* 12:00 AM Ethiopian = 6:00 AM standard time
|
|
52
|
+
* 1:00 AM Ethiopian = 7:00 AM standard time
|
|
53
|
+
* 12:00 PM Ethiopian = 6:00 PM standard time
|
|
54
|
+
* 6:00 PM Ethiopian = 12:00 AM standard time (next day)
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install ethioqen
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Usage Examples
|
|
63
|
+
|
|
64
|
+
### Calendar Conversions
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from ethioqen.calendar_conversion import convert_ethiopian_to_gregorian, convert_gregorian_to_ethiopian
|
|
68
|
+
|
|
69
|
+
# Convert from Ethiopian to Gregorian
|
|
70
|
+
greg_year, greg_month, greg_day = convert_ethiopian_to_gregorian(2016, 7, 6)
|
|
71
|
+
print(f"{greg_year}-{greg_month}-{greg_day}") # Output: 2024-3-15
|
|
72
|
+
|
|
73
|
+
# Convert from Gregorian to Ethiopian
|
|
74
|
+
eth_year, eth_month, eth_day = convert_gregorian_to_ethiopian(2024, 3, 15)
|
|
75
|
+
print(f"{eth_year}-{eth_month}-{eth_day}") # Output: 2016-7-6
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Time Conversions
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from ethioqen.time_conversion import convert_to_ethiopian_time, convert_from_ethiopian_time
|
|
82
|
+
|
|
83
|
+
# Convert standard time (14:30 / 2:30 PM) to Ethiopian time
|
|
84
|
+
eth_hour, eth_minute, is_day = convert_to_ethiopian_time(14, 30)
|
|
85
|
+
print(f"{eth_hour}:{eth_minute} {'AM' if is_day else 'PM'}") # Output: 8:30 PM
|
|
86
|
+
|
|
87
|
+
# Convert Ethiopian time (8:30 PM) to standard time
|
|
88
|
+
std_hour, std_minute = convert_from_ethiopian_time(8, 30, is_am=False)
|
|
89
|
+
print(f"{std_hour:02d}:{std_minute:02d}") # Output: 14:30
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Unix Timestamp Conversions
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from ethioqen.unix_time_conversion import ethiopian_to_unix, unix_to_ethiopian
|
|
96
|
+
|
|
97
|
+
# Convert Ethiopian date/time to Unix timestamp (UTC)
|
|
98
|
+
# 1:30 PM Ethiopian time
|
|
99
|
+
timestamp = ethiopian_to_unix(2016, 7, 6, eth_hour=1, minute=30, is_pm=True)
|
|
100
|
+
print(timestamp) # Output: Unix timestamp
|
|
101
|
+
|
|
102
|
+
# Convert Unix timestamp to Ethiopian date/time (UTC)
|
|
103
|
+
eth_year, eth_month, eth_day, hour, minute, is_pm = unix_to_ethiopian(timestamp)
|
|
104
|
+
print(f"{eth_year}-{eth_month}-{eth_day} {hour}:{minute:02d} {'PM' if is_pm else 'AM'}")
|
|
105
|
+
# Output: 2016-7-6 1:30 PM
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Timezone Support
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from ethioqen.unix_time_conversion import ethiopian_to_unix, unix_to_ethiopian
|
|
112
|
+
|
|
113
|
+
# Convert with timezone offset (UTC+3 for Ethiopia)
|
|
114
|
+
# 8:30 AM Ethiopian time
|
|
115
|
+
timestamp = ethiopian_to_unix(2016, 7, 6, eth_hour=8, minute=30, is_pm=False, tz_offset=3)
|
|
116
|
+
print(timestamp) # Output: Unix timestamp adjusted for UTC+3
|
|
117
|
+
|
|
118
|
+
# Convert back with timezone offset
|
|
119
|
+
eth_date = unix_to_ethiopian(timestamp, tz_offset=3)
|
|
120
|
+
print(f"{eth_date[0]}-{eth_date[1]}-{eth_date[2]} {eth_date[3]}:{eth_date[4]:02d} {'PM' if eth_date[5] else 'AM'}")
|
|
121
|
+
# Output: Ethiopian date/time in UTC+3
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Contributing
|
|
125
|
+
|
|
126
|
+
We welcome contributions! Here's how you can help:
|
|
127
|
+
|
|
128
|
+
1. **Report Bugs**
|
|
129
|
+
* Open an issue in the [GitHub issue tracker](https://github.com/beabzk/ethioqen/issues)
|
|
130
|
+
* Include a clear description and steps to reproduce
|
|
131
|
+
|
|
132
|
+
2. **Submit Pull Requests**
|
|
133
|
+
* Fork the repository
|
|
134
|
+
* Create a new branch for your feature (`git checkout -b feature/amazing-feature`)
|
|
135
|
+
* Make your changes
|
|
136
|
+
* Write or update tests as needed
|
|
137
|
+
* Update documentation if necessary
|
|
138
|
+
* Submit a pull request
|
|
139
|
+
|
|
140
|
+
3. **Coding Style**
|
|
141
|
+
* Follow PEP 8 guidelines
|
|
142
|
+
* Include docstrings for new functions/classes
|
|
143
|
+
* Add type hints where possible
|
|
144
|
+
|
|
145
|
+
4. **Testing**
|
|
146
|
+
* Run the test suite: `pytest`
|
|
147
|
+
* Add tests for new features
|
|
148
|
+
* Ensure all tests pass before submitting
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
ethioqen-0.1.0/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# ethioqen: Ethiopian Calendar, Time, and Unix Timestamp Conversion
|
|
2
|
+
|
|
3
|
+
> ⚠️ **Warning**: This library is in very early stages of development and should not be used in production. Contributions are crucial to make this production-ready.
|
|
4
|
+
|
|
5
|
+
[](https://badge.fury.io/py/ethioqen)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://github.com/beabzk/ethioqen/actions)
|
|
8
|
+
|
|
9
|
+
`ethioqen` is a Python library that provides accurate and efficient conversions between the Ethiopian calendar, the Gregorian calendar, Ethiopian local time (12-hour format), standard 24-hour local time, and Unix timestamps.
|
|
10
|
+
|
|
11
|
+
## Introduction
|
|
12
|
+
|
|
13
|
+
The Ethiopian calendar is a solar calendar used in Ethiopia and Eritrea. It differs significantly from the Gregorian calendar, which is the most widely used calendar system today.
|
|
14
|
+
|
|
15
|
+
**Key Differences:**
|
|
16
|
+
|
|
17
|
+
* **Year Offset:** The Ethiopian calendar is typically 7-8 years behind the Gregorian calendar.
|
|
18
|
+
* **Months:** It has 13 months: 12 months of 30 days each and a 13th month called *Pagume*, which has 5 days (6 days in a leap year).
|
|
19
|
+
* **New Year:** The Ethiopian New Year (*Enkutatash*) falls on September 11th (or 12th in a Gregorian leap year).
|
|
20
|
+
* **Leap Years:** Ethiopia follows a simple 4-year leap year cycle without the century exception found in the Gregorian calendar.
|
|
21
|
+
|
|
22
|
+
**Ethiopian Local Time:**
|
|
23
|
+
|
|
24
|
+
Ethiopian time uses a 12-hour clock that starts counting from dawn (around 6:00 AM standard time). This creates a 6-hour offset between Ethiopian and standard time:
|
|
25
|
+
|
|
26
|
+
* 12:00 AM Ethiopian = 6:00 AM standard time
|
|
27
|
+
* 1:00 AM Ethiopian = 7:00 AM standard time
|
|
28
|
+
* 12:00 PM Ethiopian = 6:00 PM standard time
|
|
29
|
+
* 6:00 PM Ethiopian = 12:00 AM standard time (next day)
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install ethioqen
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage Examples
|
|
38
|
+
|
|
39
|
+
### Calendar Conversions
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from ethioqen.calendar_conversion import convert_ethiopian_to_gregorian, convert_gregorian_to_ethiopian
|
|
43
|
+
|
|
44
|
+
# Convert from Ethiopian to Gregorian
|
|
45
|
+
greg_year, greg_month, greg_day = convert_ethiopian_to_gregorian(2016, 7, 6)
|
|
46
|
+
print(f"{greg_year}-{greg_month}-{greg_day}") # Output: 2024-3-15
|
|
47
|
+
|
|
48
|
+
# Convert from Gregorian to Ethiopian
|
|
49
|
+
eth_year, eth_month, eth_day = convert_gregorian_to_ethiopian(2024, 3, 15)
|
|
50
|
+
print(f"{eth_year}-{eth_month}-{eth_day}") # Output: 2016-7-6
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Time Conversions
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from ethioqen.time_conversion import convert_to_ethiopian_time, convert_from_ethiopian_time
|
|
57
|
+
|
|
58
|
+
# Convert standard time (14:30 / 2:30 PM) to Ethiopian time
|
|
59
|
+
eth_hour, eth_minute, is_day = convert_to_ethiopian_time(14, 30)
|
|
60
|
+
print(f"{eth_hour}:{eth_minute} {'AM' if is_day else 'PM'}") # Output: 8:30 PM
|
|
61
|
+
|
|
62
|
+
# Convert Ethiopian time (8:30 PM) to standard time
|
|
63
|
+
std_hour, std_minute = convert_from_ethiopian_time(8, 30, is_am=False)
|
|
64
|
+
print(f"{std_hour:02d}:{std_minute:02d}") # Output: 14:30
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Unix Timestamp Conversions
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from ethioqen.unix_time_conversion import ethiopian_to_unix, unix_to_ethiopian
|
|
71
|
+
|
|
72
|
+
# Convert Ethiopian date/time to Unix timestamp (UTC)
|
|
73
|
+
# 1:30 PM Ethiopian time
|
|
74
|
+
timestamp = ethiopian_to_unix(2016, 7, 6, eth_hour=1, minute=30, is_pm=True)
|
|
75
|
+
print(timestamp) # Output: Unix timestamp
|
|
76
|
+
|
|
77
|
+
# Convert Unix timestamp to Ethiopian date/time (UTC)
|
|
78
|
+
eth_year, eth_month, eth_day, hour, minute, is_pm = unix_to_ethiopian(timestamp)
|
|
79
|
+
print(f"{eth_year}-{eth_month}-{eth_day} {hour}:{minute:02d} {'PM' if is_pm else 'AM'}")
|
|
80
|
+
# Output: 2016-7-6 1:30 PM
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Timezone Support
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from ethioqen.unix_time_conversion import ethiopian_to_unix, unix_to_ethiopian
|
|
87
|
+
|
|
88
|
+
# Convert with timezone offset (UTC+3 for Ethiopia)
|
|
89
|
+
# 8:30 AM Ethiopian time
|
|
90
|
+
timestamp = ethiopian_to_unix(2016, 7, 6, eth_hour=8, minute=30, is_pm=False, tz_offset=3)
|
|
91
|
+
print(timestamp) # Output: Unix timestamp adjusted for UTC+3
|
|
92
|
+
|
|
93
|
+
# Convert back with timezone offset
|
|
94
|
+
eth_date = unix_to_ethiopian(timestamp, tz_offset=3)
|
|
95
|
+
print(f"{eth_date[0]}-{eth_date[1]}-{eth_date[2]} {eth_date[3]}:{eth_date[4]:02d} {'PM' if eth_date[5] else 'AM'}")
|
|
96
|
+
# Output: Ethiopian date/time in UTC+3
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Contributing
|
|
100
|
+
|
|
101
|
+
We welcome contributions! Here's how you can help:
|
|
102
|
+
|
|
103
|
+
1. **Report Bugs**
|
|
104
|
+
* Open an issue in the [GitHub issue tracker](https://github.com/beabzk/ethioqen/issues)
|
|
105
|
+
* Include a clear description and steps to reproduce
|
|
106
|
+
|
|
107
|
+
2. **Submit Pull Requests**
|
|
108
|
+
* Fork the repository
|
|
109
|
+
* Create a new branch for your feature (`git checkout -b feature/amazing-feature`)
|
|
110
|
+
* Make your changes
|
|
111
|
+
* Write or update tests as needed
|
|
112
|
+
* Update documentation if necessary
|
|
113
|
+
* Submit a pull request
|
|
114
|
+
|
|
115
|
+
3. **Coding Style**
|
|
116
|
+
* Follow PEP 8 guidelines
|
|
117
|
+
* Include docstrings for new functions/classes
|
|
118
|
+
* Add type hints where possible
|
|
119
|
+
|
|
120
|
+
4. **Testing**
|
|
121
|
+
* Run the test suite: `pytest`
|
|
122
|
+
* Add tests for new features
|
|
123
|
+
* Ensure all tests pass before submitting
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from .exceptions import InvalidDateException
|
|
2
|
+
from .utils import is_valid_ethiopian_date, DAYS_IN_ETH_MONTH
|
|
3
|
+
|
|
4
|
+
def is_ethiopian_leap_year(year):
|
|
5
|
+
return year % 4 == 3
|
|
6
|
+
|
|
7
|
+
def is_gregorian_leap_year(year):
|
|
8
|
+
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
|
|
9
|
+
|
|
10
|
+
def _ethiopian_to_jdn(year, month, day):
|
|
11
|
+
"""Convert Ethiopian date to Julian Day Number."""
|
|
12
|
+
if not is_valid_ethiopian_date(year, month, day):
|
|
13
|
+
raise InvalidDateException(f"Invalid Ethiopian date: {year}-{month}-{day}")
|
|
14
|
+
|
|
15
|
+
ethiopian_epoch = 1723856
|
|
16
|
+
year_days = (year * 365) + (year // 4)
|
|
17
|
+
month_days = (month - 1) * 30
|
|
18
|
+
|
|
19
|
+
return ethiopian_epoch + year_days + month_days + day - 1
|
|
20
|
+
|
|
21
|
+
def _jdn_to_ethiopian(jdn):
|
|
22
|
+
"""Convert Julian Day Number to Ethiopian date."""
|
|
23
|
+
ethiopian_epoch = 1723856
|
|
24
|
+
days_since_epoch = jdn - ethiopian_epoch
|
|
25
|
+
|
|
26
|
+
# Calculate initial year estimate
|
|
27
|
+
year = int((days_since_epoch - 0.25) // 365.25)
|
|
28
|
+
|
|
29
|
+
# Calculate remaining days
|
|
30
|
+
year_days = year * 365 + year // 4
|
|
31
|
+
remaining_days = days_since_epoch - year_days
|
|
32
|
+
|
|
33
|
+
# If we're past the end of the year, increment
|
|
34
|
+
if remaining_days >= 366 or (remaining_days >= 365 and not is_ethiopian_leap_year(year)):
|
|
35
|
+
year += 1
|
|
36
|
+
year_days = year * 365 + year // 4
|
|
37
|
+
remaining_days = days_since_epoch - year_days
|
|
38
|
+
|
|
39
|
+
# Calculate month and day
|
|
40
|
+
month = int(remaining_days // 30) + 1
|
|
41
|
+
day = int(remaining_days % 30) + 1
|
|
42
|
+
|
|
43
|
+
# Handle pagume
|
|
44
|
+
if month > 13:
|
|
45
|
+
month = 13
|
|
46
|
+
day = remaining_days - 360 + 1
|
|
47
|
+
|
|
48
|
+
# Handle year transitions
|
|
49
|
+
max_pagume_days = 6 if is_ethiopian_leap_year(year) else 5
|
|
50
|
+
if month == 13 and day > max_pagume_days:
|
|
51
|
+
year += 1
|
|
52
|
+
month = 1
|
|
53
|
+
day = day - max_pagume_days
|
|
54
|
+
|
|
55
|
+
return int(year), int(month), int(day)
|
|
56
|
+
|
|
57
|
+
def _gregorian_to_jdn(year, month, day):
|
|
58
|
+
"""Convert Gregorian date to Julian Day Number."""
|
|
59
|
+
if month <= 2:
|
|
60
|
+
year -= 1
|
|
61
|
+
month += 12
|
|
62
|
+
|
|
63
|
+
a = year // 100
|
|
64
|
+
b = 2 - a + (a // 4)
|
|
65
|
+
|
|
66
|
+
jdn = int(365.25 * (year + 4716)) + int(30.6001 * (month + 1)) + day + b - 1524
|
|
67
|
+
return jdn
|
|
68
|
+
|
|
69
|
+
def _jdn_to_gregorian(jdn):
|
|
70
|
+
"""Convert Julian Day Number to Gregorian date."""
|
|
71
|
+
y = 4716
|
|
72
|
+
j = 1401
|
|
73
|
+
m = 2
|
|
74
|
+
n = 12
|
|
75
|
+
r = 4
|
|
76
|
+
p = 1461
|
|
77
|
+
v = 3
|
|
78
|
+
u = 5
|
|
79
|
+
s = 153
|
|
80
|
+
w = 2
|
|
81
|
+
B = 274277
|
|
82
|
+
C = -38
|
|
83
|
+
|
|
84
|
+
f = jdn + j + (((4 * jdn + B) // 146097) * 3) // 4 + C
|
|
85
|
+
e = r * f + v
|
|
86
|
+
g = (e % p) // r
|
|
87
|
+
h = u * g + w
|
|
88
|
+
|
|
89
|
+
day = (h % s) // u + 1
|
|
90
|
+
month = ((h // s + m) % n) + 1
|
|
91
|
+
year = (e // p) - y + (n + m - month) // n
|
|
92
|
+
|
|
93
|
+
return year, month, day
|
|
94
|
+
|
|
95
|
+
def convert_ethiopian_to_gregorian(eth_year, eth_month, eth_day):
|
|
96
|
+
"""Convert an Ethiopian date to Gregorian."""
|
|
97
|
+
if not is_valid_ethiopian_date(eth_year, eth_month, eth_day):
|
|
98
|
+
raise InvalidDateException(f"Invalid Ethiopian date: {eth_year}-{eth_month}-{eth_day}")
|
|
99
|
+
|
|
100
|
+
jdn = _ethiopian_to_jdn(eth_year, eth_month, eth_day)
|
|
101
|
+
return _jdn_to_gregorian(jdn)
|
|
102
|
+
|
|
103
|
+
def convert_gregorian_to_ethiopian(greg_year, greg_month, greg_day):
|
|
104
|
+
"""Convert a Gregorian date to Ethiopian."""
|
|
105
|
+
if greg_month < 1 or greg_month > 12:
|
|
106
|
+
raise InvalidDateException(f"Invalid Gregorian month: {greg_month}")
|
|
107
|
+
if greg_day < 1 or greg_day > 31:
|
|
108
|
+
raise InvalidDateException(f"Invalid Gregorian day: {greg_day}")
|
|
109
|
+
|
|
110
|
+
jdn = _gregorian_to_jdn(greg_year, greg_month, greg_day)
|
|
111
|
+
return _jdn_to_ethiopian(jdn)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from .exceptions import InvalidTimeException
|
|
2
|
+
from .utils import is_valid_eth_time, is_valid_time
|
|
3
|
+
|
|
4
|
+
def convert_to_ethiopian_time(hour, minute, period=None):
|
|
5
|
+
"""Convert 24-hour time to Ethiopian time."""
|
|
6
|
+
if not is_valid_time(hour, minute):
|
|
7
|
+
raise InvalidTimeException(f"Invalid time: {hour}:{minute}")
|
|
8
|
+
|
|
9
|
+
# Handle 12-hour format with AM/PM
|
|
10
|
+
if period:
|
|
11
|
+
if period.upper() not in ["AM", "PM"]:
|
|
12
|
+
raise InvalidTimeException("Period must be 'AM' or 'PM'")
|
|
13
|
+
if period.upper() == "PM" and hour != 12:
|
|
14
|
+
hour += 12
|
|
15
|
+
elif period.upper() == "AM" and hour == 12:
|
|
16
|
+
hour = 0
|
|
17
|
+
|
|
18
|
+
# Ethiopian time starts 6 hours before standard time
|
|
19
|
+
eth_hour = (hour - 6) % 12
|
|
20
|
+
if eth_hour == 0:
|
|
21
|
+
eth_hour = 12
|
|
22
|
+
|
|
23
|
+
# Determine if it's morning/day (AM) or evening/night (PM)
|
|
24
|
+
is_am = 6 <= hour < 18
|
|
25
|
+
|
|
26
|
+
return eth_hour, minute, is_am
|
|
27
|
+
|
|
28
|
+
def convert_from_ethiopian_time(eth_hour, minute, is_am=True):
|
|
29
|
+
"""Convert Ethiopian time to 24-hour time."""
|
|
30
|
+
if not (1 <= eth_hour <= 12) or not (0 <= minute <= 59):
|
|
31
|
+
raise InvalidTimeException(f"Invalid Ethiopian time: {eth_hour}:{minute}")
|
|
32
|
+
|
|
33
|
+
# Convert Ethiopian time to 24-hour format
|
|
34
|
+
std_hour = eth_hour % 12 # Convert 12 to 0
|
|
35
|
+
|
|
36
|
+
# Add 6 hours to align with standard time
|
|
37
|
+
std_hour = (std_hour + 6) % 24
|
|
38
|
+
|
|
39
|
+
# Adjust for AM/PM
|
|
40
|
+
if not is_am:
|
|
41
|
+
std_hour = (std_hour + 12) % 24
|
|
42
|
+
|
|
43
|
+
return std_hour, minute
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from datetime import datetime, timezone, timedelta
|
|
2
|
+
from .calendar_conversion import convert_ethiopian_to_gregorian, convert_gregorian_to_ethiopian
|
|
3
|
+
from .exceptions import InvalidDateException
|
|
4
|
+
from .utils import is_valid_ethiopian_date
|
|
5
|
+
|
|
6
|
+
def _convert_ethiopian_to_24h(hour, is_pm):
|
|
7
|
+
"""Convert Ethiopian 12-hour time to 24-hour format.
|
|
8
|
+
|
|
9
|
+
In Ethiopian time:
|
|
10
|
+
- Day starts at 12:00 AM (6:00 AM standard)
|
|
11
|
+
- 1:00 AM = 7:00 AM standard = 7 in 24h
|
|
12
|
+
- 12:00 PM = 6:00 PM standard = 18 in 24h
|
|
13
|
+
- 1:00 PM = 7:00 PM standard = 19 in 24h
|
|
14
|
+
"""
|
|
15
|
+
if hour < 1 or hour > 12:
|
|
16
|
+
raise InvalidDateException(f"Invalid Ethiopian hour: {hour}")
|
|
17
|
+
|
|
18
|
+
if hour == 12:
|
|
19
|
+
hour = 0
|
|
20
|
+
|
|
21
|
+
if is_pm:
|
|
22
|
+
hour += 12
|
|
23
|
+
|
|
24
|
+
# Add 6 hours to align with standard time
|
|
25
|
+
hour = (hour + 6) % 24
|
|
26
|
+
return hour
|
|
27
|
+
|
|
28
|
+
def _convert_24h_to_ethiopian(hour):
|
|
29
|
+
"""Convert 24-hour time to Ethiopian 12-hour format.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
tuple: (hour in 1-12 format, is_pm boolean)
|
|
33
|
+
"""
|
|
34
|
+
# Subtract 6 hours to align with Ethiopian time
|
|
35
|
+
eth_hour = (hour - 6) % 24
|
|
36
|
+
|
|
37
|
+
# Convert to 12-hour format
|
|
38
|
+
is_pm = eth_hour >= 12
|
|
39
|
+
if is_pm:
|
|
40
|
+
eth_hour -= 12
|
|
41
|
+
|
|
42
|
+
if eth_hour == 0:
|
|
43
|
+
eth_hour = 12
|
|
44
|
+
|
|
45
|
+
return eth_hour, is_pm
|
|
46
|
+
|
|
47
|
+
def ethiopian_to_unix(e_year, e_month, e_day, eth_hour=12, minute=0, is_pm=False, tz_offset=0):
|
|
48
|
+
"""Convert Ethiopian date/time to Unix timestamp.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
e_year (int): Ethiopian year
|
|
52
|
+
e_month (int): Ethiopian month (1-13)
|
|
53
|
+
e_day (int): Ethiopian day
|
|
54
|
+
eth_hour (int, optional): Hour in Ethiopian 12-hour time (1-12). Defaults to 12.
|
|
55
|
+
minute (int, optional): Minutes (0-59). Defaults to 0.
|
|
56
|
+
is_pm (bool, optional): Whether the time is PM. Defaults to False.
|
|
57
|
+
tz_offset (int, optional): Timezone offset in hours. Defaults to 0 (UTC).
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
int: Unix timestamp (seconds since Unix epoch)
|
|
61
|
+
"""
|
|
62
|
+
if not is_valid_ethiopian_date(e_year, e_month, e_day):
|
|
63
|
+
raise InvalidDateException(f"Invalid Ethiopian date: {e_year}-{e_month}-{e_day}")
|
|
64
|
+
if not (0 <= minute <= 59):
|
|
65
|
+
raise InvalidDateException(f"Invalid minute: {minute}")
|
|
66
|
+
|
|
67
|
+
# Convert Ethiopian 12-hour time to 24-hour time
|
|
68
|
+
hour_24 = _convert_ethiopian_to_24h(eth_hour, is_pm)
|
|
69
|
+
|
|
70
|
+
# Convert to Gregorian and create timestamp
|
|
71
|
+
g_year, g_month, g_day = convert_ethiopian_to_gregorian(e_year, e_month, e_day)
|
|
72
|
+
try:
|
|
73
|
+
dt = datetime(g_year, g_month, g_day, hour_24, minute,
|
|
74
|
+
tzinfo=timezone(timedelta(hours=tz_offset)))
|
|
75
|
+
return int(dt.timestamp())
|
|
76
|
+
except ValueError as e:
|
|
77
|
+
raise InvalidDateException(str(e))
|
|
78
|
+
|
|
79
|
+
def unix_to_ethiopian(timestamp, tz_offset=0):
|
|
80
|
+
"""Convert Unix timestamp to Ethiopian date/time.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
timestamp (int): Unix timestamp (seconds since Unix epoch)
|
|
84
|
+
tz_offset (int, optional): Timezone offset in hours. Defaults to 0 (UTC).
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
tuple: (year, month, day, hour, minute, is_pm)
|
|
88
|
+
hour will be in 12-hour format (1-12)
|
|
89
|
+
is_pm indicates whether the time is PM
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
dt = datetime.fromtimestamp(timestamp,
|
|
93
|
+
tz=timezone(timedelta(hours=tz_offset)))
|
|
94
|
+
except (ValueError, OSError) as e:
|
|
95
|
+
raise InvalidDateException(f"Invalid timestamp: {timestamp}")
|
|
96
|
+
|
|
97
|
+
# Convert to Ethiopian date
|
|
98
|
+
e_year, e_month, e_day = convert_gregorian_to_ethiopian(
|
|
99
|
+
dt.year, dt.month, dt.day)
|
|
100
|
+
|
|
101
|
+
# Convert 24-hour time to Ethiopian 12-hour time
|
|
102
|
+
eth_hour, is_pm = _convert_24h_to_ethiopian(dt.hour)
|
|
103
|
+
|
|
104
|
+
return e_year, e_month, e_day, eth_hour, dt.minute, is_pm
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Constants
|
|
2
|
+
DAYS_IN_ETH_MONTH = 30
|
|
3
|
+
MONTHS_IN_ETH_YEAR = 13
|
|
4
|
+
|
|
5
|
+
# Month lengths - most Ethiopian months have 30 days, except Pagume which has 5 or 6
|
|
6
|
+
ETH_MONTH_LENGTHS = {
|
|
7
|
+
1: 30, 2: 30, 3: 30, 4: 30, 5: 30, 6: 30,
|
|
8
|
+
7: 30, 8: 30, 9: 30, 10: 30, 11: 30, 12: 30,
|
|
9
|
+
13: 5 # Will be 6 in leap years
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
# Time constants
|
|
13
|
+
HOURS_IN_DAY = 24
|
|
14
|
+
MINUTES_IN_HOUR = 60
|
|
15
|
+
ETH_HOURS_IN_KENTR = 4 # Ethiopian hours in one quarter day (kentr)
|
|
16
|
+
ETH_HOURS_IN_KENTR = 6 # Ethiopian hours in one sixth day
|
|
17
|
+
|
|
18
|
+
def is_valid_ethiopian_date(year, month, day):
|
|
19
|
+
"""Check if the given Ethiopian date is valid."""
|
|
20
|
+
if month < 1 or month > MONTHS_IN_ETH_YEAR:
|
|
21
|
+
return False
|
|
22
|
+
|
|
23
|
+
if month == 13:
|
|
24
|
+
max_days = 6 if is_ethiopian_leap_year(year) else 5
|
|
25
|
+
if day < 1 or day > max_days:
|
|
26
|
+
return False
|
|
27
|
+
else:
|
|
28
|
+
if day < 1 or day > DAYS_IN_ETH_MONTH:
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
return True
|
|
32
|
+
|
|
33
|
+
def is_ethiopian_leap_year(year):
|
|
34
|
+
"""Determine if the given Ethiopian year is a leap year."""
|
|
35
|
+
return year % 4 == 3
|
|
36
|
+
|
|
37
|
+
def get_ethiopian_month_length(year, month):
|
|
38
|
+
"""Get the length of a given Ethiopian month in a specific year."""
|
|
39
|
+
if month == 13:
|
|
40
|
+
return 6 if is_ethiopian_leap_year(year) else 5
|
|
41
|
+
return DAYS_IN_ETH_MONTH
|
|
42
|
+
|
|
43
|
+
def is_valid_eth_time(hour, minute):
|
|
44
|
+
"""Validate Ethiopian time."""
|
|
45
|
+
return 0 <= hour < HOURS_IN_DAY and 0 <= minute < MINUTES_IN_HOUR
|
|
46
|
+
|
|
47
|
+
def is_valid_time(hour, minute):
|
|
48
|
+
"""Validate standard 24-hour time."""
|
|
49
|
+
return 0 <= hour < HOURS_IN_DAY and 0 <= minute < MINUTES_IN_HOUR
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ethioqen
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A package for Ethiopian date and time conversions
|
|
5
|
+
Home-page: https://github.com/beabzk/ethioqen
|
|
6
|
+
Author: Beabfekad Zikie
|
|
7
|
+
Author-email: Beabfekad Zikie <beabzk@proton.me>
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Bug Reports, https://github.com/beabzk/ethioqen/issues
|
|
10
|
+
Project-URL: Source, https://github.com/beabzk/ethioqen
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Topic :: Software Development :: Localization
|
|
22
|
+
Requires-Python: >=3.8
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
|
|
26
|
+
# ethioqen: Ethiopian Calendar, Time, and Unix Timestamp Conversion
|
|
27
|
+
|
|
28
|
+
> ⚠️ **Warning**: This library is in very early stages of development and should not be used in production. Contributions are crucial to make this production-ready.
|
|
29
|
+
|
|
30
|
+
[](https://badge.fury.io/py/ethioqen)
|
|
31
|
+
[](https://opensource.org/licenses/MIT)
|
|
32
|
+
[](https://github.com/beabzk/ethioqen/actions)
|
|
33
|
+
|
|
34
|
+
`ethioqen` is a Python library that provides accurate and efficient conversions between the Ethiopian calendar, the Gregorian calendar, Ethiopian local time (12-hour format), standard 24-hour local time, and Unix timestamps.
|
|
35
|
+
|
|
36
|
+
## Introduction
|
|
37
|
+
|
|
38
|
+
The Ethiopian calendar is a solar calendar used in Ethiopia and Eritrea. It differs significantly from the Gregorian calendar, which is the most widely used calendar system today.
|
|
39
|
+
|
|
40
|
+
**Key Differences:**
|
|
41
|
+
|
|
42
|
+
* **Year Offset:** The Ethiopian calendar is typically 7-8 years behind the Gregorian calendar.
|
|
43
|
+
* **Months:** It has 13 months: 12 months of 30 days each and a 13th month called *Pagume*, which has 5 days (6 days in a leap year).
|
|
44
|
+
* **New Year:** The Ethiopian New Year (*Enkutatash*) falls on September 11th (or 12th in a Gregorian leap year).
|
|
45
|
+
* **Leap Years:** Ethiopia follows a simple 4-year leap year cycle without the century exception found in the Gregorian calendar.
|
|
46
|
+
|
|
47
|
+
**Ethiopian Local Time:**
|
|
48
|
+
|
|
49
|
+
Ethiopian time uses a 12-hour clock that starts counting from dawn (around 6:00 AM standard time). This creates a 6-hour offset between Ethiopian and standard time:
|
|
50
|
+
|
|
51
|
+
* 12:00 AM Ethiopian = 6:00 AM standard time
|
|
52
|
+
* 1:00 AM Ethiopian = 7:00 AM standard time
|
|
53
|
+
* 12:00 PM Ethiopian = 6:00 PM standard time
|
|
54
|
+
* 6:00 PM Ethiopian = 12:00 AM standard time (next day)
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install ethioqen
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Usage Examples
|
|
63
|
+
|
|
64
|
+
### Calendar Conversions
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from ethioqen.calendar_conversion import convert_ethiopian_to_gregorian, convert_gregorian_to_ethiopian
|
|
68
|
+
|
|
69
|
+
# Convert from Ethiopian to Gregorian
|
|
70
|
+
greg_year, greg_month, greg_day = convert_ethiopian_to_gregorian(2016, 7, 6)
|
|
71
|
+
print(f"{greg_year}-{greg_month}-{greg_day}") # Output: 2024-3-15
|
|
72
|
+
|
|
73
|
+
# Convert from Gregorian to Ethiopian
|
|
74
|
+
eth_year, eth_month, eth_day = convert_gregorian_to_ethiopian(2024, 3, 15)
|
|
75
|
+
print(f"{eth_year}-{eth_month}-{eth_day}") # Output: 2016-7-6
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Time Conversions
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from ethioqen.time_conversion import convert_to_ethiopian_time, convert_from_ethiopian_time
|
|
82
|
+
|
|
83
|
+
# Convert standard time (14:30 / 2:30 PM) to Ethiopian time
|
|
84
|
+
eth_hour, eth_minute, is_day = convert_to_ethiopian_time(14, 30)
|
|
85
|
+
print(f"{eth_hour}:{eth_minute} {'AM' if is_day else 'PM'}") # Output: 8:30 PM
|
|
86
|
+
|
|
87
|
+
# Convert Ethiopian time (8:30 PM) to standard time
|
|
88
|
+
std_hour, std_minute = convert_from_ethiopian_time(8, 30, is_am=False)
|
|
89
|
+
print(f"{std_hour:02d}:{std_minute:02d}") # Output: 14:30
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Unix Timestamp Conversions
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from ethioqen.unix_time_conversion import ethiopian_to_unix, unix_to_ethiopian
|
|
96
|
+
|
|
97
|
+
# Convert Ethiopian date/time to Unix timestamp (UTC)
|
|
98
|
+
# 1:30 PM Ethiopian time
|
|
99
|
+
timestamp = ethiopian_to_unix(2016, 7, 6, eth_hour=1, minute=30, is_pm=True)
|
|
100
|
+
print(timestamp) # Output: Unix timestamp
|
|
101
|
+
|
|
102
|
+
# Convert Unix timestamp to Ethiopian date/time (UTC)
|
|
103
|
+
eth_year, eth_month, eth_day, hour, minute, is_pm = unix_to_ethiopian(timestamp)
|
|
104
|
+
print(f"{eth_year}-{eth_month}-{eth_day} {hour}:{minute:02d} {'PM' if is_pm else 'AM'}")
|
|
105
|
+
# Output: 2016-7-6 1:30 PM
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Timezone Support
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from ethioqen.unix_time_conversion import ethiopian_to_unix, unix_to_ethiopian
|
|
112
|
+
|
|
113
|
+
# Convert with timezone offset (UTC+3 for Ethiopia)
|
|
114
|
+
# 8:30 AM Ethiopian time
|
|
115
|
+
timestamp = ethiopian_to_unix(2016, 7, 6, eth_hour=8, minute=30, is_pm=False, tz_offset=3)
|
|
116
|
+
print(timestamp) # Output: Unix timestamp adjusted for UTC+3
|
|
117
|
+
|
|
118
|
+
# Convert back with timezone offset
|
|
119
|
+
eth_date = unix_to_ethiopian(timestamp, tz_offset=3)
|
|
120
|
+
print(f"{eth_date[0]}-{eth_date[1]}-{eth_date[2]} {eth_date[3]}:{eth_date[4]:02d} {'PM' if eth_date[5] else 'AM'}")
|
|
121
|
+
# Output: Ethiopian date/time in UTC+3
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Contributing
|
|
125
|
+
|
|
126
|
+
We welcome contributions! Here's how you can help:
|
|
127
|
+
|
|
128
|
+
1. **Report Bugs**
|
|
129
|
+
* Open an issue in the [GitHub issue tracker](https://github.com/beabzk/ethioqen/issues)
|
|
130
|
+
* Include a clear description and steps to reproduce
|
|
131
|
+
|
|
132
|
+
2. **Submit Pull Requests**
|
|
133
|
+
* Fork the repository
|
|
134
|
+
* Create a new branch for your feature (`git checkout -b feature/amazing-feature`)
|
|
135
|
+
* Make your changes
|
|
136
|
+
* Write or update tests as needed
|
|
137
|
+
* Update documentation if necessary
|
|
138
|
+
* Submit a pull request
|
|
139
|
+
|
|
140
|
+
3. **Coding Style**
|
|
141
|
+
* Follow PEP 8 guidelines
|
|
142
|
+
* Include docstrings for new functions/classes
|
|
143
|
+
* Add type hints where possible
|
|
144
|
+
|
|
145
|
+
4. **Testing**
|
|
146
|
+
* Run the test suite: `pytest`
|
|
147
|
+
* Add tests for new features
|
|
148
|
+
* Ensure all tests pass before submitting
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
setup.py
|
|
5
|
+
ethioqen/__init__.py
|
|
6
|
+
ethioqen/calendar_conversion.py
|
|
7
|
+
ethioqen/exceptions.py
|
|
8
|
+
ethioqen/time_conversion.py
|
|
9
|
+
ethioqen/unix_time_conversion.py
|
|
10
|
+
ethioqen/utils.py
|
|
11
|
+
ethioqen.egg-info/PKG-INFO
|
|
12
|
+
ethioqen.egg-info/SOURCES.txt
|
|
13
|
+
ethioqen.egg-info/dependency_links.txt
|
|
14
|
+
ethioqen.egg-info/top_level.txt
|
|
15
|
+
tests/test_calendar_conversion.py
|
|
16
|
+
tests/test_time_conversion.py
|
|
17
|
+
tests/test_unix_time_conversion.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ethioqen
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ethioqen"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A package for Ethiopian date and time conversions"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Beabfekad Zikie", email = "beabzk@proton.me"}
|
|
14
|
+
]
|
|
15
|
+
classifiers=[
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.8",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
26
|
+
"Topic :: Software Development :: Localization",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
"Bug Reports" = "https://github.com/beabzk/ethioqen/issues"
|
|
31
|
+
"Source" = "https://github.com/beabzk/ethioqen"
|
ethioqen-0.1.0/setup.cfg
ADDED
ethioqen-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
import pathlib
|
|
3
|
+
|
|
4
|
+
here = pathlib.Path(__file__).parent.resolve()
|
|
5
|
+
long_description = (here / "README.md").read_text(encoding="utf-8")
|
|
6
|
+
|
|
7
|
+
setup(
|
|
8
|
+
name="ethioqen",
|
|
9
|
+
version="0.1.0",
|
|
10
|
+
author="Beabfekad Zikie",
|
|
11
|
+
author_email="beabzk@proton.me",
|
|
12
|
+
description="A package for Ethiopian date and time conversions",
|
|
13
|
+
long_description=long_description,
|
|
14
|
+
long_description_content_type="text/markdown",
|
|
15
|
+
url="https://github.com/beabzk/ethioqen",
|
|
16
|
+
packages=find_packages(),
|
|
17
|
+
license="MIT",
|
|
18
|
+
classifiers=[
|
|
19
|
+
"Development Status :: 3 - Alpha",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"License :: OSI Approved :: MIT License",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.8",
|
|
25
|
+
"Programming Language :: Python :: 3.9",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
29
|
+
"Topic :: Software Development :: Localization",
|
|
30
|
+
],
|
|
31
|
+
python_requires=">=3.8",
|
|
32
|
+
project_urls={
|
|
33
|
+
"Bug Reports": "https://github.com/beabzk/ethioqen/issues",
|
|
34
|
+
"Source": "https://github.com/beabzk/ethioqen",
|
|
35
|
+
},
|
|
36
|
+
)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from ethioqen.calendar_conversion import (
|
|
3
|
+
convert_ethiopian_to_gregorian,
|
|
4
|
+
convert_gregorian_to_ethiopian,
|
|
5
|
+
is_ethiopian_leap_year,
|
|
6
|
+
is_gregorian_leap_year
|
|
7
|
+
)
|
|
8
|
+
from ethioqen.exceptions import InvalidDateException
|
|
9
|
+
|
|
10
|
+
def test_ethiopian_leap_year():
|
|
11
|
+
"""Test Ethiopian leap year calculation."""
|
|
12
|
+
assert is_ethiopian_leap_year(2015) == True # 2015 E.C. is a leap year
|
|
13
|
+
assert is_ethiopian_leap_year(2016) == False # 2016 E.C. is not a leap year
|
|
14
|
+
assert is_ethiopian_leap_year(2019) == True # 2019 E.C. is a leap year
|
|
15
|
+
|
|
16
|
+
def test_gregorian_leap_year():
|
|
17
|
+
"""Test Gregorian leap year calculation."""
|
|
18
|
+
assert is_gregorian_leap_year(2020) == True # 2020-01-01 is start of a leap year
|
|
19
|
+
assert is_gregorian_leap_year(2021) == False # 2021-01-01 is not a leap year
|
|
20
|
+
assert is_gregorian_leap_year(2000) == True # 2000-01-01 is a leap year (divisible by 400)
|
|
21
|
+
assert is_gregorian_leap_year(1900) == False # 1900-01-01 is not a leap year (divisible by 100 but not 400)
|
|
22
|
+
|
|
23
|
+
def test_ethiopian_to_gregorian():
|
|
24
|
+
"""Test conversion from Ethiopian to Gregorian calendar."""
|
|
25
|
+
# Test some known dates
|
|
26
|
+
assert convert_ethiopian_to_gregorian(2015, 1, 1) == (2022, 9, 11) # Ethiopian New Year
|
|
27
|
+
assert convert_ethiopian_to_gregorian(2015, 13, 6) == (2023, 9, 11) # Last day of 2015 (leap year)
|
|
28
|
+
assert convert_ethiopian_to_gregorian(2015, 5, 1) == (2023, 1, 9) # Regular date
|
|
29
|
+
|
|
30
|
+
def test_ethiopian_to_gregorian_year_boundaries():
|
|
31
|
+
"""Test Ethiopian to Gregorian conversion around year boundaries."""
|
|
32
|
+
# Year end tests (Pagume)
|
|
33
|
+
assert convert_ethiopian_to_gregorian(2015, 13, 1) == (2023, 9, 6) # Start of Pagume 2015
|
|
34
|
+
assert convert_ethiopian_to_gregorian(2015, 13, 2) == (2023, 9, 7) # Pagume 2
|
|
35
|
+
assert convert_ethiopian_to_gregorian(2015, 13, 3) == (2023, 9, 8) # Pagume 3
|
|
36
|
+
assert convert_ethiopian_to_gregorian(2015, 13, 4) == (2023, 9, 9) # Pagume 4
|
|
37
|
+
assert convert_ethiopian_to_gregorian(2015, 13, 5) == (2023, 9, 10) # Pagume 5
|
|
38
|
+
assert convert_ethiopian_to_gregorian(2015, 13, 6) == (2023, 9, 11) # Last day of 2015 (leap)
|
|
39
|
+
|
|
40
|
+
# Year start tests (Meskerem)
|
|
41
|
+
assert convert_ethiopian_to_gregorian(2016, 1, 1) == (2023, 9, 12) # New Year 2016
|
|
42
|
+
assert convert_ethiopian_to_gregorian(2016, 1, 2) == (2023, 9, 13) # Meskerem 2
|
|
43
|
+
assert convert_ethiopian_to_gregorian(2016, 1, 3) == (2023, 9, 14) # Meskerem 3
|
|
44
|
+
assert convert_ethiopian_to_gregorian(2016, 1, 4) == (2023, 9, 15) # Meskerem 4
|
|
45
|
+
assert convert_ethiopian_to_gregorian(2016, 1, 5) == (2023, 9, 16) # Meskerem 5
|
|
46
|
+
|
|
47
|
+
# Previous year tests
|
|
48
|
+
assert convert_ethiopian_to_gregorian(2014, 13, 1) == (2022, 9, 6) # Start of Pagume 2014
|
|
49
|
+
assert convert_ethiopian_to_gregorian(2014, 13, 5) == (2022, 9, 10) # Last day of 2014 (non-leap)
|
|
50
|
+
assert convert_ethiopian_to_gregorian(2015, 1, 1) == (2022, 9, 11) # New Year 2015
|
|
51
|
+
|
|
52
|
+
def test_gregorian_to_ethiopian():
|
|
53
|
+
"""Test conversion from Gregorian to Ethiopian calendar."""
|
|
54
|
+
# Year end tests (different from eth_to_greg tests)
|
|
55
|
+
assert convert_gregorian_to_ethiopian(2023, 9, 6) == (2015, 13, 1) # Start of Pagume
|
|
56
|
+
assert convert_gregorian_to_ethiopian(2023, 9, 7) == (2015, 13, 2) # Pagume 2
|
|
57
|
+
assert convert_gregorian_to_ethiopian(2023, 9, 8) == (2015, 13, 3) # Pagume 3
|
|
58
|
+
assert convert_gregorian_to_ethiopian(2023, 9, 9) == (2015, 13, 4) # Pagume 4
|
|
59
|
+
assert convert_gregorian_to_ethiopian(2023, 9, 10) == (2015, 13, 5) # Pagume 5
|
|
60
|
+
assert convert_gregorian_to_ethiopian(2023, 9, 11) == (2015, 13, 6) # Last day of 2015
|
|
61
|
+
|
|
62
|
+
# Year start tests
|
|
63
|
+
assert convert_gregorian_to_ethiopian(2023, 9, 12) == (2016, 1, 1) # Ethiopian New Year
|
|
64
|
+
assert convert_gregorian_to_ethiopian(2023, 9, 13) == (2016, 1, 2) # Meskerem 2
|
|
65
|
+
assert convert_gregorian_to_ethiopian(2023, 9, 14) == (2016, 1, 3) # Meskerem 3
|
|
66
|
+
assert convert_gregorian_to_ethiopian(2023, 9, 15) == (2016, 1, 4) # Meskerem 4
|
|
67
|
+
assert convert_gregorian_to_ethiopian(2023, 9, 16) == (2016, 1, 5) # Meskerem 5
|
|
68
|
+
|
|
69
|
+
# Previous year boundary
|
|
70
|
+
assert convert_gregorian_to_ethiopian(2022, 9, 6) == (2014, 13, 1) # Start of Pagume 2014
|
|
71
|
+
assert convert_gregorian_to_ethiopian(2022, 9, 10) == (2014, 13, 5) # Last day of 2014
|
|
72
|
+
assert convert_gregorian_to_ethiopian(2022, 9, 11) == (2015, 1, 1) # Start of 2015
|
|
73
|
+
|
|
74
|
+
def test_invalid_dates():
|
|
75
|
+
"""Test handling of invalid dates."""
|
|
76
|
+
# Test invalid Ethiopian month
|
|
77
|
+
with pytest.raises(InvalidDateException):
|
|
78
|
+
convert_ethiopian_to_gregorian(2015, 14, 1)
|
|
79
|
+
|
|
80
|
+
# Test invalid Ethiopian day
|
|
81
|
+
with pytest.raises(InvalidDateException):
|
|
82
|
+
convert_ethiopian_to_gregorian(2015, 1, 31)
|
|
83
|
+
|
|
84
|
+
# Test invalid Pagume day in non-leap year
|
|
85
|
+
with pytest.raises(InvalidDateException):
|
|
86
|
+
convert_ethiopian_to_gregorian(2016, 13, 6) # 2016 E.C. is not a leap year
|
|
87
|
+
|
|
88
|
+
def test_round_trip_conversion():
|
|
89
|
+
"""Test converting dates back and forth."""
|
|
90
|
+
# Test several dates to ensure they convert back correctly
|
|
91
|
+
test_dates = [
|
|
92
|
+
(2015, 1, 1), # Ethiopian New Year
|
|
93
|
+
(2015, 7, 15), # Middle of the year
|
|
94
|
+
(2015, 13, 5), # Pagume 5
|
|
95
|
+
(2015, 13, 6), # Pagume 6 (leap year)
|
|
96
|
+
(2016, 1, 1), # New Year after leap year
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
for eth_year, eth_month, eth_day in test_dates:
|
|
100
|
+
# Convert to Gregorian
|
|
101
|
+
greg_year, greg_month, greg_day = convert_ethiopian_to_gregorian(eth_year, eth_month, eth_day)
|
|
102
|
+
# Convert back to Ethiopian
|
|
103
|
+
eth_year2, eth_month2, eth_day2 = convert_gregorian_to_ethiopian(greg_year, greg_month, greg_day)
|
|
104
|
+
# Should get the original date back
|
|
105
|
+
assert (eth_year, eth_month, eth_day) == (eth_year2, eth_month2, eth_day2)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from ethioqen.time_conversion import convert_to_ethiopian_time, convert_from_ethiopian_time
|
|
3
|
+
from ethioqen.exceptions import InvalidTimeException
|
|
4
|
+
|
|
5
|
+
def test_standard_to_ethiopian():
|
|
6
|
+
"""Test key Ethiopian time conversions."""
|
|
7
|
+
# Morning/Day hours
|
|
8
|
+
assert convert_to_ethiopian_time(6, 0) == (12, 0, True) # 6:00 AM -> 12:00 AM ET
|
|
9
|
+
assert convert_to_ethiopian_time(7, 0) == (1, 0, True) # 7:00 AM -> 1:00 AM ET
|
|
10
|
+
assert convert_to_ethiopian_time(12, 0) == (6, 0, True) # 12:00 PM -> 6:00 AM ET
|
|
11
|
+
|
|
12
|
+
# Evening/Night hours
|
|
13
|
+
assert convert_to_ethiopian_time(18, 0) == (12, 0, False) # 6:00 PM -> 12:00 PM ET
|
|
14
|
+
assert convert_to_ethiopian_time(19, 0) == (1, 0, False) # 7:00 PM -> 1:00 PM ET
|
|
15
|
+
assert convert_to_ethiopian_time(0, 0) == (6, 0, False) # 12:00 AM -> 6:00 PM ET
|
|
16
|
+
assert convert_to_ethiopian_time(3, 0) == (9, 0, False) # 3:00 AM -> 9:00 PM ET
|
|
17
|
+
|
|
18
|
+
def test_ethiopian_to_standard():
|
|
19
|
+
"""Test key standard time conversions."""
|
|
20
|
+
# Morning/Day hours (is_am=True)
|
|
21
|
+
assert convert_from_ethiopian_time(12, 0, True) == (6, 0) # 12:00 AM ET -> 6:00 AM
|
|
22
|
+
assert convert_from_ethiopian_time(1, 0, True) == (7, 0) # 1:00 AM ET -> 7:00 AM
|
|
23
|
+
assert convert_from_ethiopian_time(6, 0, True) == (12, 0) # 6:00 AM ET -> 12:00 PM
|
|
24
|
+
|
|
25
|
+
# Evening/Night hours (is_am=False)
|
|
26
|
+
assert convert_from_ethiopian_time(12, 0, False) == (18, 0) # 12:00 PM ET -> 6:00 PM
|
|
27
|
+
assert convert_from_ethiopian_time(1, 0, False) == (19, 0) # 1:00 PM ET -> 7:00 PM
|
|
28
|
+
assert convert_from_ethiopian_time(6, 0, False) == (0, 0) # 6:00 PM ET -> 12:00 AM
|
|
29
|
+
assert convert_from_ethiopian_time(9, 0, False) == (3, 0) # 9:00 PM ET -> 3:00 AM
|
|
30
|
+
|
|
31
|
+
def test_12hour_format_conversion():
|
|
32
|
+
"""Test conversion using 12-hour format with AM/PM."""
|
|
33
|
+
assert convert_to_ethiopian_time(7, 0, "AM") == (1, 0, True) # 7:00 AM -> 1:00 AM ET
|
|
34
|
+
assert convert_to_ethiopian_time(12, 0, "PM") == (6, 0, True) # 12:00 PM -> 6:00 AM ET
|
|
35
|
+
assert convert_to_ethiopian_time(7, 0, "PM") == (1, 0, False) # 7:00 PM -> 1:00 PM ET
|
|
36
|
+
assert convert_to_ethiopian_time(12, 0, "AM") == (6, 0, False) # 12:00 AM -> 6:00 PM ET
|
|
37
|
+
|
|
38
|
+
def test_minutes_preserved():
|
|
39
|
+
"""Test that minutes are preserved in conversion."""
|
|
40
|
+
assert convert_to_ethiopian_time(6, 30) == (12, 30, True)
|
|
41
|
+
assert convert_to_ethiopian_time(18, 45) == (12, 45, False)
|
|
42
|
+
assert convert_from_ethiopian_time(12, 30, True) == (6, 30)
|
|
43
|
+
assert convert_from_ethiopian_time(12, 45, False) == (18, 45)
|
|
44
|
+
|
|
45
|
+
def test_invalid_standard_time():
|
|
46
|
+
"""Test error handling for invalid standard times."""
|
|
47
|
+
with pytest.raises(InvalidTimeException):
|
|
48
|
+
convert_to_ethiopian_time(24, 0)
|
|
49
|
+
with pytest.raises(InvalidTimeException):
|
|
50
|
+
convert_to_ethiopian_time(-1, 0)
|
|
51
|
+
with pytest.raises(InvalidTimeException):
|
|
52
|
+
convert_to_ethiopian_time(12, 60)
|
|
53
|
+
with pytest.raises(InvalidTimeException):
|
|
54
|
+
convert_to_ethiopian_time(12, -1)
|
|
55
|
+
with pytest.raises(InvalidTimeException):
|
|
56
|
+
convert_to_ethiopian_time(7, 0, "invalid")
|
|
57
|
+
|
|
58
|
+
def test_invalid_ethiopian_time():
|
|
59
|
+
"""Test error handling for invalid Ethiopian times."""
|
|
60
|
+
with pytest.raises(InvalidTimeException):
|
|
61
|
+
convert_from_ethiopian_time(13, 0, True)
|
|
62
|
+
with pytest.raises(InvalidTimeException):
|
|
63
|
+
convert_from_ethiopian_time(0, 0, True)
|
|
64
|
+
with pytest.raises(InvalidTimeException):
|
|
65
|
+
convert_from_ethiopian_time(6, 60, True)
|
|
66
|
+
with pytest.raises(InvalidTimeException):
|
|
67
|
+
convert_from_ethiopian_time(6, -1, False)
|
|
68
|
+
|
|
69
|
+
def test_round_trip_conversion():
|
|
70
|
+
"""Test converting times back and forth."""
|
|
71
|
+
test_cases = [
|
|
72
|
+
(6, 0), # 6:00 AM / 12:00 AM ET
|
|
73
|
+
(7, 0), # 7:00 AM / 1:00 AM ET
|
|
74
|
+
(12, 0), # 12:00 PM / 6:00 AM ET
|
|
75
|
+
(18, 0), # 6:00 PM / 12:00 PM ET
|
|
76
|
+
(0, 0), # 12:00 AM / 6:00 PM ET
|
|
77
|
+
(3, 0), # 3:00 AM / 9:00 PM ET
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
for hour, minute in test_cases:
|
|
81
|
+
eth_hour, eth_minute, is_am = convert_to_ethiopian_time(hour, minute)
|
|
82
|
+
std_hour, std_minute = convert_from_ethiopian_time(eth_hour, eth_minute, is_am)
|
|
83
|
+
assert (hour, minute) == (std_hour, std_minute)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from datetime import datetime, timezone, timedelta
|
|
3
|
+
from ethioqen.unix_time_conversion import (
|
|
4
|
+
ethiopian_to_unix,
|
|
5
|
+
unix_to_ethiopian,
|
|
6
|
+
_convert_ethiopian_to_24h,
|
|
7
|
+
_convert_24h_to_ethiopian
|
|
8
|
+
)
|
|
9
|
+
from ethioqen.exceptions import InvalidDateException
|
|
10
|
+
|
|
11
|
+
def test_ethiopian_12h_to_24h_conversion():
|
|
12
|
+
"""Test Ethiopian 12-hour to 24-hour conversion."""
|
|
13
|
+
# Morning times
|
|
14
|
+
assert _convert_ethiopian_to_24h(12, False) == 6 # 12 AM = 6:00
|
|
15
|
+
assert _convert_ethiopian_to_24h(1, False) == 7 # 1 AM = 7:00
|
|
16
|
+
assert _convert_ethiopian_to_24h(6, False) == 12 # 6 AM = 12:00
|
|
17
|
+
|
|
18
|
+
# Afternoon/evening times
|
|
19
|
+
assert _convert_ethiopian_to_24h(12, True) == 18 # 12 PM = 18:00
|
|
20
|
+
assert _convert_ethiopian_to_24h(1, True) == 19 # 1 PM = 19:00
|
|
21
|
+
assert _convert_ethiopian_to_24h(6, True) == 0 # 6 PM = 00:00
|
|
22
|
+
|
|
23
|
+
def test_24h_to_ethiopian_12h_conversion():
|
|
24
|
+
"""Test 24-hour to Ethiopian 12-hour conversion."""
|
|
25
|
+
# Morning times
|
|
26
|
+
assert _convert_24h_to_ethiopian(6) == (12, False) # 6:00 = 12 AM
|
|
27
|
+
assert _convert_24h_to_ethiopian(7) == (1, False) # 7:00 = 1 AM
|
|
28
|
+
assert _convert_24h_to_ethiopian(12) == (6, False) # 12:00 = 6 AM
|
|
29
|
+
|
|
30
|
+
# Afternoon/evening times
|
|
31
|
+
assert _convert_24h_to_ethiopian(18) == (12, True) # 18:00 = 12 PM
|
|
32
|
+
assert _convert_24h_to_ethiopian(19) == (1, True) # 19:00 = 1 PM
|
|
33
|
+
assert _convert_24h_to_ethiopian(0) == (6, True) # 00:00 = 6 PM
|
|
34
|
+
|
|
35
|
+
def test_ethiopian_to_unix_12h_format():
|
|
36
|
+
"""Test Ethiopian to Unix conversion with 12-hour format."""
|
|
37
|
+
# Ethiopian date: 2015-01-01 12:00 AM (6:00 standard)
|
|
38
|
+
eth_timestamp = ethiopian_to_unix(2015, 1, 1, 12, 0, False)
|
|
39
|
+
greg_dt = datetime(2022, 9, 11, 6, 0, tzinfo=timezone.utc)
|
|
40
|
+
assert eth_timestamp == int(greg_dt.timestamp())
|
|
41
|
+
|
|
42
|
+
# Ethiopian date: 2015-01-01 1:00 PM (19:00 standard)
|
|
43
|
+
eth_timestamp = ethiopian_to_unix(2015, 1, 1, 1, 0, True)
|
|
44
|
+
greg_dt = datetime(2022, 9, 11, 19, 0, tzinfo=timezone.utc)
|
|
45
|
+
assert eth_timestamp == int(greg_dt.timestamp())
|
|
46
|
+
|
|
47
|
+
def test_unix_to_ethiopian_12h_format():
|
|
48
|
+
"""Test Unix to Ethiopian conversion with 12-hour format."""
|
|
49
|
+
# 2022-09-11 6:00:00 UTC (Ethiopian 12:00 AM)
|
|
50
|
+
greg_dt = datetime(2022, 9, 11, 6, 0, tzinfo=timezone.utc)
|
|
51
|
+
eth_date = unix_to_ethiopian(int(greg_dt.timestamp()))
|
|
52
|
+
assert eth_date == (2015, 1, 1, 12, 0, False)
|
|
53
|
+
|
|
54
|
+
# 2022-09-11 19:00:00 UTC (Ethiopian 1:00 PM)
|
|
55
|
+
greg_dt = datetime(2022, 9, 11, 19, 0, tzinfo=timezone.utc)
|
|
56
|
+
eth_date = unix_to_ethiopian(int(greg_dt.timestamp()))
|
|
57
|
+
assert eth_date == (2015, 1, 1, 1, 0, True)
|
|
58
|
+
|
|
59
|
+
def test_ethiopian_to_unix_basic():
|
|
60
|
+
"""Test basic Ethiopian to Unix timestamp conversion."""
|
|
61
|
+
eth_timestamp = ethiopian_to_unix(2015, 1, 1)
|
|
62
|
+
greg_dt = datetime(2022, 9, 11, 6, 0, tzinfo=timezone.utc) # Default 12 AM = 6:00
|
|
63
|
+
assert eth_timestamp == int(greg_dt.timestamp())
|
|
64
|
+
|
|
65
|
+
def test_ethiopian_to_unix_with_timezone():
|
|
66
|
+
"""Test conversion with timezone offsets."""
|
|
67
|
+
# Ethiopian date with +3:00 timezone (Addis Ababa)
|
|
68
|
+
eth_timestamp = ethiopian_to_unix(2015, 1, 1, 12, 0, False, 3)
|
|
69
|
+
greg_dt = datetime(2022, 9, 11, 6, 0,
|
|
70
|
+
tzinfo=timezone(timedelta(hours=3)))
|
|
71
|
+
assert eth_timestamp == int(greg_dt.timestamp())
|
|
72
|
+
|
|
73
|
+
def test_invalid_ethiopian_dates():
|
|
74
|
+
"""Test error handling for invalid Ethiopian dates."""
|
|
75
|
+
with pytest.raises(InvalidDateException):
|
|
76
|
+
ethiopian_to_unix(2015, 13, 7) # Invalid Pagume day
|
|
77
|
+
with pytest.raises(InvalidDateException):
|
|
78
|
+
ethiopian_to_unix(2015, 14, 1) # Invalid month
|
|
79
|
+
with pytest.raises(InvalidDateException):
|
|
80
|
+
ethiopian_to_unix(2015, 1, 31) # Invalid day
|
|
81
|
+
|
|
82
|
+
def test_invalid_times():
|
|
83
|
+
"""Test error handling for invalid times."""
|
|
84
|
+
with pytest.raises(InvalidDateException):
|
|
85
|
+
ethiopian_to_unix(2015, 1, 1, 13, 0) # Invalid hour (>12)
|
|
86
|
+
with pytest.raises(InvalidDateException):
|
|
87
|
+
ethiopian_to_unix(2015, 1, 1, 0, 0) # Invalid hour (<1)
|
|
88
|
+
with pytest.raises(InvalidDateException):
|
|
89
|
+
ethiopian_to_unix(2015, 1, 1, 12, 60) # Invalid minute
|
|
90
|
+
|
|
91
|
+
def test_invalid_timestamps():
|
|
92
|
+
"""Test error handling for invalid Unix timestamps."""
|
|
93
|
+
with pytest.raises(InvalidDateException):
|
|
94
|
+
unix_to_ethiopian(-62167219200) # Too early
|
|
95
|
+
with pytest.raises(InvalidDateException):
|
|
96
|
+
unix_to_ethiopian(253402300800) # Too late
|
|
97
|
+
|
|
98
|
+
def test_round_trip_conversion():
|
|
99
|
+
"""Test converting dates back and forth."""
|
|
100
|
+
original_date = (2015, 1, 1, 1, 30, True) # Ethiopian date/time (1:30 PM)
|
|
101
|
+
# Convert to Unix timestamp
|
|
102
|
+
unix_ts = ethiopian_to_unix(
|
|
103
|
+
original_date[0], original_date[1], original_date[2],
|
|
104
|
+
original_date[3], original_date[4], original_date[5]
|
|
105
|
+
)
|
|
106
|
+
# Convert back to Ethiopian
|
|
107
|
+
result_date = unix_to_ethiopian(unix_ts)
|
|
108
|
+
assert original_date == result_date
|