Skip to content

Commit

Permalink
feat: add fortnightly interval
Browse files Browse the repository at this point in the history
In some countries (such as Australia), it is very common for wages to be
paid on a fortnightly basis, that is, every 2 weeks. As a result, a
number of other expenses are also paid on a fortnightly basis.

This commit extends the `Interval` enum by adding a `FORTNIGHT` option.
I have added a test for that, and have updated the translations where
possible (not that other than for English and French, the other
translations may be suboptimal).

Resolves: beancount#1939
Signed-off-by: JP-Ellis <[email protected]>
  • Loading branch information
JP-Ellis committed Feb 4, 2025
1 parent ad5412a commit d6733c3
Show file tree
Hide file tree
Showing 18 changed files with 97 additions and 32 deletions.
36 changes: 22 additions & 14 deletions frontend/src/lib/interval.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
import { _ } from "../i18n";

export type Interval = "year" | "quarter" | "month" | "week" | "day";
export type Interval =
| "year"
| "quarter"
| "month"
| "fortnight"
| "week"
| "day";

export const DEFAULT_INTERVAL: Interval = "month";

export const INTERVALS: Interval[] = [
"year",
"quarter",
"month",
"week",
"day",
"year",
"quarter",
"month",
"fortnight",
"week",
"day",
];

export function getInterval(s: string | null): Interval {
return INTERVALS.includes(s as Interval) ? (s as Interval) : DEFAULT_INTERVAL;
return INTERVALS.includes(s as Interval) ? (s as Interval) : DEFAULT_INTERVAL;
}

/** Get the translateable label for an interval. */
export function intervalLabel(s: Interval): string {
return {
year: _("Yearly"),
quarter: _("Quarterly"),
month: _("Monthly"),
week: _("Weekly"),
day: _("Daily"),
}[s];
return {
year: _("Yearly"),
quarter: _("Quarterly"),
month: _("Monthly"),
fortnight: _("Fortnightly"),
week: _("Weekly"),
day: _("Daily"),
}[s];
}
1 change: 1 addition & 0 deletions src/fava/core/budgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ def parse_budgets(
interval_map = {
"daily": Interval.DAY,
"weekly": Interval.WEEK,
"fortnightly": Interval.FORTNIGHT,
"monthly": Interval.MONTH,
"quarterly": Interval.QUARTER,
"yearly": Interval.YEAR,
Expand Down
9 changes: 5 additions & 4 deletions src/fava/help/budgets.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@ Beancount file:
<pre><textarea is="beancount-textarea">
2012-01-01 custom "budget" Expenses:Coffee "daily" 4.00 EUR
2013-01-01 custom "budget" Expenses:Books "weekly" 20.00 EUR
2013-01-01 custom "budget" Expenses:Fuel "fortnightly" 60.00 EUR
2014-02-10 custom "budget" Expenses:Groceries "monthly" 40.00 EUR
2015-05-01 custom "budget" Expenses:Electricity "quarterly" 85.00 EUR
2016-06-01 custom "budget" Expenses:Holiday "yearly" 2500.00 EUR</textarea></pre>

If budgets are specified, Fava's reports and charts will display remaining
budgets and related information.

The budget directives can be specified `daily`, `weekly`, `monthly`, `quarterly`
and `yearly`. The specified budget is valid until another budget directive for
the account is specified. The budget is broken down to a daily budget, and
summed up for a range of dates as needed.
The budget directives can be specified `daily`, `weekly`, `fortnightly`,
`monthly`, `quarterly` and `yearly`. The specified budget is valid until another
budget directive for the account is specified. The budget is broken down to a
daily budget, and summed up for a range of dates as needed.

This makes the budgets very flexible, allowing for a monthly budget, being taken
over by a weekly budget, and so on.
Expand Down
5 changes: 4 additions & 1 deletion src/fava/translations/bg/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,10 @@ msgstr "Тримесечен"
msgid "Monthly"
msgstr "Месечен"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Fortnightly"
msgstr "На всеки две седмици"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Weekly"
msgstr "Седмичен"
Expand Down Expand Up @@ -592,4 +596,3 @@ msgstr "Изтриване..."
#: frontend/src/sidebar/AsideContents.svelte:60
msgid "Add Journal Entry"
msgstr ""

5 changes: 4 additions & 1 deletion src/fava/translations/ca/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,10 @@ msgstr "Per trimestre"
msgid "Monthly"
msgstr "Per mes"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Fortnightly"
msgstr "Per quinzena"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Weekly"
msgstr "Per setmana"
Expand Down Expand Up @@ -592,4 +596,3 @@ msgstr "S'està suprimint..."
#: frontend/src/sidebar/AsideContents.svelte:60
msgid "Add Journal Entry"
msgstr ""

5 changes: 4 additions & 1 deletion src/fava/translations/de/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,10 @@ msgstr "Quartalsweise"
msgid "Monthly"
msgstr "Monatlich"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Fortnightly"
msgstr "Zweiwöchentlich"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Weekly"
msgstr "Wöchentlich"
Expand Down Expand Up @@ -592,4 +596,3 @@ msgstr "Wird gelöscht..."
#: frontend/src/sidebar/AsideContents.svelte:60
msgid "Add Journal Entry"
msgstr "Journal-Eintrag hinzufügen"

5 changes: 4 additions & 1 deletion src/fava/translations/fa/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,10 @@ msgstr "سه‌ماهه"
msgid "Monthly"
msgstr "ماهانه"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Fortnightly"
msgstr "هر دو هفته یکبار"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Weekly"
msgstr "هفتگی"
Expand Down Expand Up @@ -592,4 +596,3 @@ msgstr ""
#: frontend/src/sidebar/AsideContents.svelte:60
msgid "Add Journal Entry"
msgstr ""

5 changes: 4 additions & 1 deletion src/fava/translations/fr/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,10 @@ msgstr "Trimestriel"
msgid "Monthly"
msgstr "Mensuel"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Fortnightly"
msgstr "Bimensuel"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Weekly"
msgstr "Hebdomadaire"
Expand Down Expand Up @@ -592,4 +596,3 @@ msgstr "Suppression..."
#: frontend/src/sidebar/AsideContents.svelte:60
msgid "Add Journal Entry"
msgstr ""

5 changes: 4 additions & 1 deletion src/fava/translations/nl/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,10 @@ msgstr "Per kwartaal"
msgid "Monthly"
msgstr "Maandelijks"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Fortnightly"
msgstr "Tweewekelijks"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Weekly"
msgstr "Wekelijks"
Expand Down Expand Up @@ -592,4 +596,3 @@ msgstr ""
#: frontend/src/sidebar/AsideContents.svelte:60
msgid "Add Journal Entry"
msgstr ""

5 changes: 4 additions & 1 deletion src/fava/translations/pt_BR/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,10 @@ msgstr "Trimestral"
msgid "Monthly"
msgstr "Mensal"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Fortnightly"
msgstr "Quinzenal"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Weekly"
msgstr "Semanal"
Expand Down Expand Up @@ -593,4 +597,3 @@ msgstr "Apagando..."
#: frontend/src/sidebar/AsideContents.svelte:60
msgid "Add Journal Entry"
msgstr "Adicionar Entrada"

5 changes: 4 additions & 1 deletion src/fava/translations/ru/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,10 @@ msgstr "По кварталам"
msgid "Monthly"
msgstr "По месяцам"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Fortnightly"
msgstr "Каждые две недели"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Weekly"
msgstr "По неделям"
Expand Down Expand Up @@ -591,4 +595,3 @@ msgstr ""
#: frontend/src/sidebar/AsideContents.svelte:60
msgid "Add Journal Entry"
msgstr ""

5 changes: 4 additions & 1 deletion src/fava/translations/sk/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,10 @@ msgstr "Kvartálne"
msgid "Monthly"
msgstr "Mesačne"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Fortnightly"
msgstr "Dvotýždenne"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Weekly"
msgstr "Týždenne"
Expand Down Expand Up @@ -597,4 +601,3 @@ msgstr ""
#: frontend/src/sidebar/AsideContents.svelte:60
msgid "Add Journal Entry"
msgstr ""

5 changes: 4 additions & 1 deletion src/fava/translations/sv/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,10 @@ msgstr "Kvartalsvis"
msgid "Monthly"
msgstr "Månatligt"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Fortnightly"
msgstr "Var fjortonde dag"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Weekly"
msgstr "Veckovis"
Expand Down Expand Up @@ -592,4 +596,3 @@ msgstr ""
#: frontend/src/sidebar/AsideContents.svelte:60
msgid "Add Journal Entry"
msgstr ""

5 changes: 4 additions & 1 deletion src/fava/translations/uk/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,10 @@ msgstr "Щоквартально"
msgid "Monthly"
msgstr "Щомісячно"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Fortnightly"
msgstr "двотижневий"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Weekly"
msgstr "Щотижнево"
Expand Down Expand Up @@ -592,4 +596,3 @@ msgstr ""
#: frontend/src/sidebar/AsideContents.svelte:60
msgid "Add Journal Entry"
msgstr ""

5 changes: 4 additions & 1 deletion src/fava/translations/zh/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,10 @@ msgstr "按季"
msgid "Monthly"
msgstr "按月"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Fortnightly"
msgstr "按两周"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Weekly"
msgstr "按周"
Expand Down Expand Up @@ -592,4 +596,3 @@ msgstr "正在删除..."
#: frontend/src/sidebar/AsideContents.svelte:60
msgid "Add Journal Entry"
msgstr "添加日记账条目"

5 changes: 4 additions & 1 deletion src/fava/translations/zh_Hant_TW/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,10 @@ msgstr "季度"
msgid "Monthly"
msgstr "月"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Fortnightly"
msgstr "兩周"

#: frontend/src/lib/interval.ts:25 src/fava/util/date.py:105
msgid "Weekly"
msgstr "週"
Expand Down Expand Up @@ -594,4 +598,3 @@ msgstr "正在删除..."
#: frontend/src/sidebar/AsideContents.svelte:60
msgid "Add Journal Entry"
msgstr ""

6 changes: 5 additions & 1 deletion src/fava/util/date.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class Interval(Enum):
YEAR = "year"
QUARTER = "quarter"
MONTH = "month"
FORTNIGHT = "fortnight"
WEEK = "week"
DAY = "day"

Expand All @@ -103,6 +104,7 @@ def label(self) -> str:
Interval.YEAR: gettext("Yearly"),
Interval.QUARTER: gettext("Quarterly"),
Interval.MONTH: gettext("Monthly"),
Interval.FORTNIGHT: gettext("Fortnightly"),
Interval.WEEK: gettext("Weekly"),
Interval.DAY: gettext("Daily"),
}
Expand Down Expand Up @@ -501,7 +503,7 @@ def days_in_daterange(
yield start_date + timedelta(diff)


def number_of_days_in_period(interval: Interval, date: datetime.date) -> int:
def number_of_days_in_period(interval: Interval, date: datetime.date) -> int: # noqa: PLR0911
"""Get number of days in the surrounding interval.
Args:
Expand All @@ -516,6 +518,8 @@ def number_of_days_in_period(interval: Interval, date: datetime.date) -> int:
return 1
if interval is Interval.WEEK:
return 7
if interval is Interval.FORTNIGHT:
return 14
if interval is Interval.MONTH:
date = datetime.date(date.year, date.month, 1)
return (get_next_interval(date, Interval.MONTH) - date).days
Expand Down
12 changes: 12 additions & 0 deletions tests/test_core_budgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@ def test_budgets_weekly(budgets_doc: BudgetDict) -> None:
assert budget["EUR"] == num


def test_budgets_fortnightly(budgets_doc: BudgetDict) -> None:
"""
2016-05-01 custom "budget" Expenses:Books "fortnightly" 42 EUR"""

for start, end, num in [
(date(2016, 5, 1), date(2016, 5, 2), Decimal(21) / 7),
(date(2016, 9, 1), date(2016, 9, 2), Decimal(21) / 7),
]:
budget = calculate_budget(budgets_doc, "Expenses:Books", start, end)
assert budget["EUR"] == num


def test_budgets_monthly(budgets_doc: BudgetDict) -> None:
"""
2014-05-01 custom "budget" Expenses:Books "monthly" 100 EUR"""
Expand Down

0 comments on commit d6733c3

Please sign in to comment.