Skip to content

Commit 5ab5d52

Browse files
authored
Add more frequencies (#94)
- [x] Every other month - [x] Every quarter - [x] Every half year - [x] Every year
2 parents 951087d + 9fd57cd commit 5ab5d52

File tree

4 files changed

+71
-67
lines changed

4 files changed

+71
-67
lines changed

README.md

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,14 @@
3636

3737
- **Custom Payday Calculation:**
3838
- Supports various pay frequencies:
39-
- **Monthly:** Options include:
40-
- Last bank day of the month.
41-
- First bank day of the month.
42-
- Specific day of the month.
43-
- **Every 28 days.**
44-
- **Every 14 days.**
45-
- **Weekly.**
39+
- **Monthly**
40+
- **Every 2 months**
41+
- **Quarterly (every 3 months)**
42+
- **Semi-annually (every 6 months)**
43+
- **Annually**
44+
- **Every 28 days**
45+
- **Every 14 days**
46+
- **Weekly**
4647

4748
- **Automatic Adjustment for Holidays and Weekends:**
4849
- Fetches public holidays from the [Nager.Date API](https://date.nager.at).
@@ -87,13 +88,17 @@
8788
- **Label:** Select the payout frequency
8889
- **Options:**
8990
- `monthly`: Monthly
91+
- `bimonthly`: Every 2 months
92+
- `quarterly`: Every 3 months
93+
- `semiannual`: Every 6 months
94+
- `annual`: Every year
9095
- `28_days`: Every 28th day
9196
- `14_days`: Every 14th day
9297
- `weekly`: Weekly
9398

9499
### Step 3: Depending on the Selected Frequency
95100

96-
- **Monthly:**
101+
- **Monthly / Bimonthly / Quarterly / Semiannual / Annual:**
97102
- **Label:** Select day of month
98103
- **Options:**
99104
- `last_bank_day`: Last bank day
@@ -108,7 +113,7 @@
108113
- **Label:** Select weekday
109114
- **Description:** Choose the weekday you receive your payment.
110115

111-
### Additional Configuration for Monthly Frequency
116+
### Additional Configuration for Monthly-Based Frequencies
112117

113118
- **If "Last bank day" is selected:**
114119
- **Label:** Days before last bank day

custom_components/isitpayday/config_flow.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,18 @@
2222

2323

2424
async def async_get_homeassistant_country(hass: HomeAssistant) -> str | None:
25-
"""Get Home Assistant configured country."""
2625
country = getattr(hass.config, "country", None)
27-
2826
if not country:
2927
_LOGGER.warning("Home Assistant country is not set.")
3028
return None
31-
3229
supported_countries = await async_fetch_supported_countries()
33-
3430
if country not in supported_countries:
3531
_LOGGER.warning("Country '%s' is not supported.", country)
3632
return None
37-
3833
return country
3934

4035

4136
async def async_fetch_supported_countries() -> dict[str, str]:
42-
"""Fetch supported countries from Nager.Date API."""
4337
async with aiohttp.ClientSession() as session:
4438
async with session.get(API_COUNTRIES) as response:
4539
data = await response.json()
@@ -61,9 +55,7 @@ def __init__(self) -> None:
6155
self.reconfig_entry = None
6256

6357
async def async_step_reconfigure(self, user_input=None):
64-
"""Start reconfiguration flow."""
6558
_LOGGER.info("Starting reconfiguration flow")
66-
6759
entry_id = self.context.get("entry_id")
6860
if not entry_id:
6961
_LOGGER.error("Reconfiguration started without valid entry_id in context.")
@@ -84,7 +76,6 @@ async def async_step_reconfigure(self, user_input=None):
8476
self.last_pay_date = data.get(CONF_LAST_PAY_DATE)
8577
self.bank_offset = data.get(CONF_BANK_OFFSET, 0)
8678
self.weekday = data.get(CONF_WEEKDAY)
87-
8879
self.country_list = await async_fetch_supported_countries()
8980

9081
return await self.async_step_user()
@@ -93,7 +84,6 @@ async def async_step_user(self, user_input=None):
9384
if user_input is None:
9485
self.country_list = await async_fetch_supported_countries()
9586
current_country = self.country or await async_get_homeassistant_country(self.hass) or "DK"
96-
9787
return self.async_show_form(
9888
step_id="user",
9989
data_schema=self._create_user_schema(current_country)
@@ -112,7 +102,13 @@ async def async_step_frequency(self, user_input=None):
112102

113103
self.pay_frequency = user_input[CONF_PAY_FREQ]
114104

115-
if self.pay_frequency == PAY_FREQ_MONTHLY:
105+
if self.pay_frequency in [
106+
PAY_FREQ_MONTHLY,
107+
PAY_FREQ_BIMONTHLY,
108+
PAY_FREQ_QUARTERLY,
109+
PAY_FREQ_SEMIANNUAL,
110+
PAY_FREQ_ANNUAL
111+
]:
116112
return await self.async_step_monthly_day()
117113
elif self.pay_frequency in [PAY_FREQ_28_DAYS, PAY_FREQ_14_DAYS]:
118114
return await self.async_step_cycle_last_paydate()
@@ -194,7 +190,6 @@ def _create_entry(self) -> FlowResult:
194190
self.hass.config_entries.async_update_entry(self.reconfig_entry, data=data)
195191
self.hass.async_create_task(self.hass.config_entries.async_reload(self.reconfig_entry.entry_id))
196192

197-
# Use service call instead of direct import
198193
self.hass.async_create_task(self.hass.services.async_call(
199194
"persistent_notification",
200195
"create",

custom_components/isitpayday/const.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,20 @@
2929
PAY_FREQ_28_DAYS = "28_days"
3030
PAY_FREQ_14_DAYS = "14_days"
3131
PAY_FREQ_WEEKLY = "weekly"
32+
PAY_FREQ_BIMONTHLY = "bimonthly"
33+
PAY_FREQ_QUARTERLY = "quarterly"
34+
PAY_FREQ_SEMIANNUAL = "semiannual"
35+
PAY_FREQ_ANNUAL = "annual"
3236

3337
PAY_FREQ_OPTIONS = {
3438
PAY_FREQ_MONTHLY: "Monthly",
3539
PAY_FREQ_28_DAYS: "Every 28th day",
3640
PAY_FREQ_14_DAYS: "Every 14th day",
3741
PAY_FREQ_WEEKLY: "Weekly",
42+
PAY_FREQ_BIMONTHLY: "Every 2 months",
43+
PAY_FREQ_QUARTERLY: "Every 3 months",
44+
PAY_FREQ_SEMIANNUAL: "Every 6 months",
45+
PAY_FREQ_ANNUAL: "Every year",
3846
}
3947

4048
# Monthly pay day options (only for monthly frequency)

custom_components/isitpayday/payday_calculator.py

Lines changed: 42 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010

1111
async def async_get_bank_holidays(country: str, year: int) -> list:
12-
"""Hent banklukkedage fra Nager.Date API for et givent land og aar."""
1312
url = API_HOLIDAYS.format(year=year, country=country)
1413
_LOGGER.debug("Henter banklukkedage fra: %s", url)
1514

@@ -27,16 +26,25 @@ async def async_get_bank_holidays(country: str, year: int) -> list:
2726

2827

2928
async def async_calculate_next_payday(country: str, pay_frequency: str, pay_day=None, last_pay_date=None, weekday=None, bank_offset=0):
30-
"""Udregn naeste loenningsdag baseret paa konfiguration."""
3129
_LOGGER.info("Starter beregning af naeste loenningsdag for %s med frekvens: %s", country, pay_frequency)
3230

3331
today = date.today()
3432
year = today.year
35-
3633
bank_holidays = await async_get_bank_holidays(country, year)
3734

38-
if pay_frequency == PAY_FREQ_MONTHLY:
39-
payday = await async_calculate_monthly(pay_day, bank_holidays, today, bank_offset)
35+
if pay_frequency in (
36+
PAY_FREQ_MONTHLY, PAY_FREQ_BIMONTHLY,
37+
PAY_FREQ_QUARTERLY, PAY_FREQ_SEMIANNUAL,
38+
PAY_FREQ_ANNUAL
39+
):
40+
months = {
41+
PAY_FREQ_MONTHLY: 1,
42+
PAY_FREQ_BIMONTHLY: 2,
43+
PAY_FREQ_QUARTERLY: 3,
44+
PAY_FREQ_SEMIANNUAL: 6,
45+
PAY_FREQ_ANNUAL: 12,
46+
}[pay_frequency]
47+
payday = await async_calculate_month_based(today, months, pay_day, bank_offset, bank_holidays)
4048

4149
elif pay_frequency in (PAY_FREQ_28_DAYS, PAY_FREQ_14_DAYS):
4250
interval = 28 if pay_frequency == PAY_FREQ_28_DAYS else 14
@@ -52,37 +60,33 @@ async def async_calculate_next_payday(country: str, pay_frequency: str, pay_day=
5260
return None
5361

5462
payday = await async_adjust_for_bank_holidays_and_weekends(payday, bank_holidays)
55-
5663
_LOGGER.info("Naeste loenningsdag efter justering: %s", payday)
5764
return payday
5865

5966

60-
async def async_calculate_monthly(pay_day, bank_holidays, today, bank_offset):
61-
"""Beregner naeste maanedlige loenningsdag baseret paa pay_day type."""
67+
async def async_calculate_month_based(today, month_interval, pay_day, bank_offset, bank_holidays):
6268
year, month = today.year, today.month
6369

64-
if pay_day == PAY_DAY_LAST_BANK_DAY:
65-
payday = await async_find_last_bank_day(year, month, bank_holidays, bank_offset)
66-
elif pay_day == PAY_DAY_FIRST_BANK_DAY:
67-
payday = await async_find_first_bank_day(year, month, bank_holidays)
68-
elif isinstance(pay_day, int):
69-
payday = await async_find_specific_day(year, month, pay_day, bank_holidays)
70-
else:
71-
_LOGGER.error("Ugyldig pay_day vaerdi: %s", pay_day)
72-
return None
73-
74-
if payday < today: # Rettet fra <= til <
75-
month += 1
76-
if month > 12:
77-
month = 1
78-
year += 1
79-
return await async_calculate_monthly(pay_day, bank_holidays, date(year, month, 1), bank_offset)
70+
while True:
71+
if pay_day == PAY_DAY_LAST_BANK_DAY:
72+
payday = await async_find_last_bank_day(year, month, bank_holidays, bank_offset)
73+
elif pay_day == PAY_DAY_FIRST_BANK_DAY:
74+
payday = await async_find_first_bank_day(year, month, bank_holidays)
75+
elif isinstance(pay_day, int):
76+
payday = await async_find_specific_day(year, month, pay_day, bank_holidays)
77+
else:
78+
_LOGGER.error("Ugyldig pay_day vaerdi: %s", pay_day)
79+
return None
80+
81+
if payday >= today:
82+
return payday
8083

81-
return payday
84+
month += month_interval
85+
year += (month - 1) // 12
86+
month = (month - 1) % 12 + 1
8287

8388

8489
async def async_calculate_recurring(last_pay_date, interval, bank_holidays):
85-
"""Beregner naeste loenningsdag for 14- eller 28-dages interval."""
8690
if not last_pay_date:
8791
_LOGGER.error("Mangler sidste loenningsdato for tilbagevendende betaling.")
8892
return None
@@ -91,51 +95,45 @@ async def async_calculate_recurring(last_pay_date, interval, bank_holidays):
9195
payday = last_date + timedelta(days=interval)
9296

9397
today = date.today()
94-
while payday < today: # Rettet fra <= til <
98+
while payday < today:
9599
payday += timedelta(days=interval)
96100

97101
_LOGGER.info("Naeste tilbagevendende loenningsdag beregnet til: %s", payday)
98102
return payday
99103

100104

101105
async def async_calculate_weekly(today, weekday, bank_holidays):
102-
"""Beregner naeste ugentlige loenningsdag."""
103106
days_ahead = (weekday - today.weekday()) % 7
104107
payday = today + timedelta(days=days_ahead)
105-
106-
payday = await async_adjust_for_bank_holidays_and_weekends(payday, bank_holidays)
107-
108-
_LOGGER.info("Naeste ugentlige loenningsdag beregnet til: %s", payday)
109108
return payday
110109

111110

112111
async def async_find_last_bank_day(year, month, bank_holidays, bank_offset):
113-
"""Finder sidste bankdag i maaneden."""
114112
day = 31
115-
while True:
113+
while day > 0:
116114
try:
117115
payday = date(year, month, day)
118116
if payday.weekday() < 5 and payday not in bank_holidays:
119-
payday -= timedelta(days=bank_offset)
120-
return payday
117+
return payday - timedelta(days=bank_offset)
121118
day -= 1
122119
except ValueError:
123120
day -= 1
124121

125122

126123
async def async_find_first_bank_day(year, month, bank_holidays):
127-
"""Finder foerste bankdag i maaneden."""
128124
day = 1
129-
while True:
130-
payday = date(year, month, day)
131-
if payday.weekday() < 5 and payday not in bank_holidays:
132-
return payday
133-
day += 1
125+
while day <= 31:
126+
try:
127+
payday = date(year, month, day)
128+
if payday.weekday() < 5 and payday not in bank_holidays:
129+
return payday
130+
day += 1
131+
except ValueError:
132+
day += 1
134133

135134

136135
async def async_find_specific_day(year, month, day, bank_holidays):
137-
"""Finder specifik dag i maaneden og flytter bagud ved helligdag/weekend."""
138-
while True:
136+
while day > 0:
139137
try:
140138
payday = date(year, month, day)
141139
if payday.weekday() < 5 and payday not in bank_holidays:
@@ -146,8 +144,6 @@ async def async_find_specific_day(year, month, day, bank_holidays):
146144

147145

148146
async def async_adjust_for_bank_holidays_and_weekends(payday, bank_holidays):
149-
"""Flytter dato bagud hvis den falder paa weekend eller banklukket dag."""
150147
while payday.weekday() >= 5 or payday in bank_holidays:
151148
payday -= timedelta(days=1)
152-
153149
return payday

0 commit comments

Comments
 (0)