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 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.
@@ -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
+ [![PyPI version](https://badge.fury.io/py/ethioqen.svg)](https://badge.fury.io/py/ethioqen)
31
+ [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
32
+ [![Build Status](https://github.com/beabzk/ethioqen/actions/workflows/main.yml/badge.svg)](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,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
+ [![PyPI version](https://badge.fury.io/py/ethioqen.svg)](https://badge.fury.io/py/ethioqen)
6
+ [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![Build Status](https://github.com/beabzk/ethioqen/actions/workflows/main.yml/badge.svg)](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,4 @@
1
+ """
2
+ Package initialization for ethioqen.
3
+ """
4
+ __version__ = "0.1.0"
@@ -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,5 @@
1
+ class InvalidDateException(Exception):
2
+ pass
3
+
4
+ class InvalidTimeException(Exception):
5
+ pass
@@ -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
+ [![PyPI version](https://badge.fury.io/py/ethioqen.svg)](https://badge.fury.io/py/ethioqen)
31
+ [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
32
+ [![Build Status](https://github.com/beabzk/ethioqen/actions/workflows/main.yml/badge.svg)](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
+ 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"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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