diff --git a/src/accounting/journal_entry/views.py b/src/accounting/journal_entry/views.py
index 6258d18..6795152 100644
--- a/src/accounting/journal_entry/views.py
+++ b/src/accounting/journal_entry/views.py
@@ -1,7 +1,7 @@
# The Mia! Accounting Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/18
-# Copyright (c) 2023 imacat.
+# Copyright (c) 2023-2024 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -33,6 +33,7 @@ from accounting.utils.flash_errors import flash_form_errors
from accounting.utils.journal_entry_types import JournalEntryType
from accounting.utils.next_uri import inherit_next, or_next
from accounting.utils.permission import has_permission, can_view, can_edit
+from accounting.utils.timezone import get_tz_today
from accounting.utils.user import get_current_user_pk
from .forms import sort_journal_entries_in, JournalEntryReorderForm
from .template_filters import with_type, to_transfer, format_amount_input, \
@@ -67,7 +68,7 @@ def show_add_journal_entry_form(journal_entry_type: JournalEntryType) -> str:
form.validate()
else:
form = journal_entry_op.form()
- form.date.data = dt.date.today()
+ form.date.data = get_tz_today()
return journal_entry_op.render_create_template(form)
diff --git a/src/accounting/report/period/chooser.py b/src/accounting/report/period/chooser.py
index 4fd4f14..ae300df 100644
--- a/src/accounting/report/period/chooser.py
+++ b/src/accounting/report/period/chooser.py
@@ -1,7 +1,7 @@
# The Mia! Accounting Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
-# Copyright (c) 2023 imacat.
+# Copyright (c) 2023-2024 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ import datetime as dt
from collections.abc import Callable
from accounting.models import JournalEntry
+from accounting.utils.timezone import get_tz_today
from .period import Period
from .shortcuts import ThisMonth, LastMonth, SinceLastMonth, ThisYear, \
LastYear, Today, Yesterday, AllTime, TemplatePeriod, YearPeriod
@@ -80,7 +81,7 @@ class PeriodChooser:
"""The available years."""
if self.has_data:
- today: dt.date = dt.date.today()
+ today: dt.date = get_tz_today()
self.has_last_month = start < dt.date(today.year, today.month, 1)
self.has_last_year = start.year < today.year
self.has_yesterday = start < today
diff --git a/src/accounting/report/period/shortcuts.py b/src/accounting/report/period/shortcuts.py
index b08ac77..827cead 100644
--- a/src/accounting/report/period/shortcuts.py
+++ b/src/accounting/report/period/shortcuts.py
@@ -1,7 +1,7 @@
# The Mia! Accounting Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
-# Copyright (c) 2023 imacat.
+# Copyright (c) 2023-2024 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
import datetime as dt
from accounting.locale import gettext
+from accounting.utils.timezone import get_tz_today
from .month_end import month_end
from .period import Period
@@ -27,7 +28,7 @@ from .period import Period
class ThisMonth(Period):
"""The period of this month."""
def __init__(self):
- today: dt.date = dt.date.today()
+ today: dt.date = get_tz_today()
this_month_start: dt.date = dt.date(today.year, today.month, 1)
super().__init__(this_month_start, month_end(today))
self.is_default = True
@@ -43,7 +44,7 @@ class ThisMonth(Period):
class LastMonth(Period):
"""The period of this month."""
def __init__(self):
- today: dt.date = dt.date.today()
+ today: dt.date = get_tz_today()
year: int = today.year
month: int = today.month - 1
if month < 1:
@@ -63,7 +64,7 @@ class LastMonth(Period):
class SinceLastMonth(Period):
"""The period of this month."""
def __init__(self):
- today: dt.date = dt.date.today()
+ today: dt.date = get_tz_today()
year: int = today.year
month: int = today.month - 1
if month < 1:
@@ -82,7 +83,7 @@ class SinceLastMonth(Period):
class ThisYear(Period):
"""The period of this year."""
def __init__(self):
- year: int = dt.date.today().year
+ year: int = get_tz_today().year
start: dt.date = dt.date(year, 1, 1)
end: dt.date = dt.date(year, 12, 31)
super().__init__(start, end)
@@ -97,7 +98,7 @@ class ThisYear(Period):
class LastYear(Period):
"""The period of last year."""
def __init__(self):
- year: int = dt.date.today().year
+ year: int = get_tz_today().year
start: dt.date = dt.date(year - 1, 1, 1)
end: dt.date = dt.date(year - 1, 12, 31)
super().__init__(start, end)
@@ -112,7 +113,7 @@ class LastYear(Period):
class Today(Period):
"""The period of today."""
def __init__(self):
- today: dt.date = dt.date.today()
+ today: dt.date = get_tz_today()
super().__init__(today, today)
self.is_today = True
@@ -125,7 +126,7 @@ class Today(Period):
class Yesterday(Period):
"""The period of yesterday."""
def __init__(self):
- yesterday: dt.date = dt.date.today() - dt.timedelta(days=1)
+ yesterday: dt.date = get_tz_today() - dt.timedelta(days=1)
super().__init__(yesterday, yesterday)
self.is_yesterday = True
diff --git a/src/accounting/static/js/timezone.js b/src/accounting/static/js/timezone.js
new file mode 100644
index 0000000..3c00489
--- /dev/null
+++ b/src/accounting/static/js/timezone.js
@@ -0,0 +1,37 @@
+/* The Mia! Accounting Project
+ * timezone.js: The JavaScript for the timezone
+ */
+
+/* Copyright (c) 2024 imacat.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Author: imacat@mail.imacat.idv.tw (imacat)
+ * First written: 2024/6/4
+ */
+"use strict";
+
+// Initializes the page JavaScript.
+document.addEventListener("DOMContentLoaded", () => {
+ setTimeZone();
+});
+
+/**
+ * Sets the time zone.
+ *
+ * @private
+ */
+function setTimeZone() {
+ document.cookie = `accounting-tz=${Intl.DateTimeFormat().resolvedOptions().timeZone}; SameSite=Strict`;
+}
diff --git a/src/accounting/template_filters.py b/src/accounting/template_filters.py
index 8005911..1faa62d 100644
--- a/src/accounting/template_filters.py
+++ b/src/accounting/template_filters.py
@@ -1,7 +1,7 @@
# The Mia! Accounting Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/25
-# Copyright (c) 2023 imacat.
+# Copyright (c) 2023-2024 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ from typing import Any
from flask_babel import get_locale
from accounting.locale import gettext
+from accounting.utils.timezone import get_tz_today
def format_amount(value: Decimal | None) -> str | None:
@@ -47,7 +48,7 @@ def format_date(value: dt.date) -> str:
:param value: The date.
:return: The human-friendly date text.
"""
- today: dt.date = dt.date.today()
+ today: dt.date = get_tz_today()
if value == today:
return gettext("Today")
if value == today - dt.timedelta(days=1):
diff --git a/src/accounting/templates/accounting/base.html b/src/accounting/templates/accounting/base.html
index 6aff78a..cad63af 100644
--- a/src/accounting/templates/accounting/base.html
+++ b/src/accounting/templates/accounting/base.html
@@ -2,7 +2,7 @@
The Mia! Accounting Project
base.html: The application-wide base template.
- Copyright (c) 2023 imacat.
+ Copyright (c) 2023-2024 imacat.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -27,5 +27,6 @@ First written: 2023/1/27
{% block scripts %}
+
{% block accounting_scripts %}{% endblock %}
{% endblock %}
diff --git a/src/accounting/utils/timezone.py b/src/accounting/utils/timezone.py
new file mode 100644
index 0000000..8cdda19
--- /dev/null
+++ b/src/accounting/utils/timezone.py
@@ -0,0 +1,37 @@
+# The Mia! Accounting Project.
+# Author: imacat@mail.imacat.idv.tw (imacat), 2024/6/4
+
+# Copyright (c) 2024 imacat.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""The timezone utility.
+
+This module should not import any other module from the application.
+
+"""
+
+import datetime as dt
+
+import pytz
+from flask import request
+
+
+def get_tz_today() -> dt.date:
+ """Returns today in the client timezone.
+
+ :return: today in the client timezone.
+ """
+ tz_name: str | None = request.cookies.get("accounting-tz")
+ if tz_name is None:
+ return dt.date.today()
+ return dt.datetime.now(tz=pytz.timezone(tz_name)).date()
diff --git a/tests/test_site/lib.py b/tests/test_site/lib.py
index ea460d2..821132a 100644
--- a/tests/test_site/lib.py
+++ b/tests/test_site/lib.py
@@ -1,7 +1,7 @@
# The Mia! Accounting Demonstration Website.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/4/13
-# Copyright (c) 2023 imacat.
+# Copyright (c) 2023-2024 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@ from typing import Any
import sqlalchemy as sa
from flask import Flask
+from accounting.utils.timezone import get_tz_today
from . import db
from .auth import User
@@ -44,6 +45,17 @@ class Accounts:
MEAL: str = "6272-001"
+def get_today() -> dt.date:
+ """Returns today, based on the context.
+
+ :return: Today.
+ """
+ try:
+ return get_tz_today()
+ except RuntimeError:
+ return dt.date.today()
+
+
class JournalEntryLineItemData:
"""The journal entry line item data."""
@@ -183,7 +195,7 @@ class JournalEntryData:
:param is_update: True for an update operation, or False otherwise
:return: The journal entry as a form.
"""
- date: dt.date = dt.date.today() - dt.timedelta(days=self.days)
+ date: dt.date = get_today() - dt.timedelta(days=self.days)
form: dict[str, str] = {"csrf_token": csrf_token,
"next": encoded_next_uri,
"date": date.isoformat()}
@@ -260,8 +272,7 @@ class BaseTestData(ABC):
existing_j_id: set[int] = {x["id"] for x in self.__journal_entries}
existing_l_id: set[int] = {x["id"] for x in self.__line_items}
journal_entry_data.id = self.__new_id(existing_j_id)
- date: dt.date \
- = dt.date.today() - dt.timedelta(days=journal_entry_data.days)
+ date: dt.date = get_today() - dt.timedelta(days=journal_entry_data.days)
self.__journal_entries.append(
{"id": journal_entry_data.id,
"date": date,
diff --git a/tests/test_site/reset.py b/tests/test_site/reset.py
index 72c7512..85ec4c3 100644
--- a/tests/test_site/reset.py
+++ b/tests/test_site/reset.py
@@ -1,7 +1,7 @@
# The Mia! Accounting Demonstration Website.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/4/12
-# Copyright (c) 2023 imacat.
+# Copyright (c) 2023-2024 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ from flask import Flask, Blueprint, url_for, flash, redirect, session, \
render_template, current_app
from flask_babel import lazy_gettext
+from accounting.utils.timezone import get_tz_today
from . import db
from .auth import admin_required
from .lib import Accounts, JournalEntryLineItemData, JournalEntryData, \
@@ -117,7 +118,7 @@ class SampleData(BaseTestData):
:return: None.
"""
- today: dt.date = dt.date.today()
+ today: dt.date = get_tz_today()
days: int
year: int
month: int
@@ -160,7 +161,7 @@ class SampleData(BaseTestData):
:return: None.
"""
- today: dt.date = dt.date.today()
+ today: dt.date = get_tz_today()
year: int = today.year - 5
month: int = today.month