Revised the calculation of "today" to use the client's timezone instead of the server's timezone.

This commit is contained in:
依瑪貓 2024-06-04 08:23:15 +08:00
parent 6ee3ee76ea
commit 80ae4bd91c
9 changed files with 113 additions and 22 deletions

View File

@ -1,7 +1,7 @@
# The Mia! Accounting Project. # The Mia! Accounting Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/18 # 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"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with 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.journal_entry_types import JournalEntryType
from accounting.utils.next_uri import inherit_next, or_next from accounting.utils.next_uri import inherit_next, or_next
from accounting.utils.permission import has_permission, can_view, can_edit 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 accounting.utils.user import get_current_user_pk
from .forms import sort_journal_entries_in, JournalEntryReorderForm from .forms import sort_journal_entries_in, JournalEntryReorderForm
from .template_filters import with_type, to_transfer, format_amount_input, \ 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() form.validate()
else: else:
form = journal_entry_op.form() 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) return journal_entry_op.render_create_template(form)

View File

@ -1,7 +1,7 @@
# The Mia! Accounting Project. # The Mia! Accounting Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4 # 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"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with 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 collections.abc import Callable
from accounting.models import JournalEntry from accounting.models import JournalEntry
from accounting.utils.timezone import get_tz_today
from .period import Period from .period import Period
from .shortcuts import ThisMonth, LastMonth, SinceLastMonth, ThisYear, \ from .shortcuts import ThisMonth, LastMonth, SinceLastMonth, ThisYear, \
LastYear, Today, Yesterday, AllTime, TemplatePeriod, YearPeriod LastYear, Today, Yesterday, AllTime, TemplatePeriod, YearPeriod
@ -80,7 +81,7 @@ class PeriodChooser:
"""The available years.""" """The available years."""
if self.has_data: 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_month = start < dt.date(today.year, today.month, 1)
self.has_last_year = start.year < today.year self.has_last_year = start.year < today.year
self.has_yesterday = start < today self.has_yesterday = start < today

View File

@ -1,7 +1,7 @@
# The Mia! Accounting Project. # The Mia! Accounting Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4 # 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"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -20,6 +20,7 @@
import datetime as dt import datetime as dt
from accounting.locale import gettext from accounting.locale import gettext
from accounting.utils.timezone import get_tz_today
from .month_end import month_end from .month_end import month_end
from .period import Period from .period import Period
@ -27,7 +28,7 @@ from .period import Period
class ThisMonth(Period): class ThisMonth(Period):
"""The period of this month.""" """The period of this month."""
def __init__(self): 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) this_month_start: dt.date = dt.date(today.year, today.month, 1)
super().__init__(this_month_start, month_end(today)) super().__init__(this_month_start, month_end(today))
self.is_default = True self.is_default = True
@ -43,7 +44,7 @@ class ThisMonth(Period):
class LastMonth(Period): class LastMonth(Period):
"""The period of this month.""" """The period of this month."""
def __init__(self): def __init__(self):
today: dt.date = dt.date.today() today: dt.date = get_tz_today()
year: int = today.year year: int = today.year
month: int = today.month - 1 month: int = today.month - 1
if month < 1: if month < 1:
@ -63,7 +64,7 @@ class LastMonth(Period):
class SinceLastMonth(Period): class SinceLastMonth(Period):
"""The period of this month.""" """The period of this month."""
def __init__(self): def __init__(self):
today: dt.date = dt.date.today() today: dt.date = get_tz_today()
year: int = today.year year: int = today.year
month: int = today.month - 1 month: int = today.month - 1
if month < 1: if month < 1:
@ -82,7 +83,7 @@ class SinceLastMonth(Period):
class ThisYear(Period): class ThisYear(Period):
"""The period of this year.""" """The period of this year."""
def __init__(self): def __init__(self):
year: int = dt.date.today().year year: int = get_tz_today().year
start: dt.date = dt.date(year, 1, 1) start: dt.date = dt.date(year, 1, 1)
end: dt.date = dt.date(year, 12, 31) end: dt.date = dt.date(year, 12, 31)
super().__init__(start, end) super().__init__(start, end)
@ -97,7 +98,7 @@ class ThisYear(Period):
class LastYear(Period): class LastYear(Period):
"""The period of last year.""" """The period of last year."""
def __init__(self): def __init__(self):
year: int = dt.date.today().year year: int = get_tz_today().year
start: dt.date = dt.date(year - 1, 1, 1) start: dt.date = dt.date(year - 1, 1, 1)
end: dt.date = dt.date(year - 1, 12, 31) end: dt.date = dt.date(year - 1, 12, 31)
super().__init__(start, end) super().__init__(start, end)
@ -112,7 +113,7 @@ class LastYear(Period):
class Today(Period): class Today(Period):
"""The period of today.""" """The period of today."""
def __init__(self): def __init__(self):
today: dt.date = dt.date.today() today: dt.date = get_tz_today()
super().__init__(today, today) super().__init__(today, today)
self.is_today = True self.is_today = True
@ -125,7 +126,7 @@ class Today(Period):
class Yesterday(Period): class Yesterday(Period):
"""The period of yesterday.""" """The period of yesterday."""
def __init__(self): 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) super().__init__(yesterday, yesterday)
self.is_yesterday = True self.is_yesterday = True

View File

@ -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`;
}

View File

@ -1,7 +1,7 @@
# The Mia! Accounting Project. # The Mia! Accounting Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/25 # 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"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with 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 flask_babel import get_locale
from accounting.locale import gettext from accounting.locale import gettext
from accounting.utils.timezone import get_tz_today
def format_amount(value: Decimal | None) -> str | None: def format_amount(value: Decimal | None) -> str | None:
@ -47,7 +48,7 @@ def format_date(value: dt.date) -> str:
:param value: The date. :param value: The date.
:return: The human-friendly date text. :return: The human-friendly date text.
""" """
today: dt.date = dt.date.today() today: dt.date = get_tz_today()
if value == today: if value == today:
return gettext("Today") return gettext("Today")
if value == today - dt.timedelta(days=1): if value == today - dt.timedelta(days=1):

View File

@ -2,7 +2,7 @@
The Mia! Accounting Project The Mia! Accounting Project
base.html: The application-wide base template. 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 scripts %}
<script src="{{ url_for("accounting.babel_catalog") }}"></script> <script src="{{ url_for("accounting.babel_catalog") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/timezone.js") }}"></script>
{% block accounting_scripts %}{% endblock %} {% block accounting_scripts %}{% endblock %}
{% endblock %} {% endblock %}

View File

@ -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()

View File

@ -1,7 +1,7 @@
# The Mia! Accounting Demonstration Website. # The Mia! Accounting Demonstration Website.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/4/13 # 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"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with 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 import sqlalchemy as sa
from flask import Flask from flask import Flask
from accounting.utils.timezone import get_tz_today
from . import db from . import db
from .auth import User from .auth import User
@ -44,6 +45,17 @@ class Accounts:
MEAL: str = "6272-001" 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: class JournalEntryLineItemData:
"""The journal entry line item data.""" """The journal entry line item data."""
@ -183,7 +195,7 @@ class JournalEntryData:
:param is_update: True for an update operation, or False otherwise :param is_update: True for an update operation, or False otherwise
:return: The journal entry as a form. :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, form: dict[str, str] = {"csrf_token": csrf_token,
"next": encoded_next_uri, "next": encoded_next_uri,
"date": date.isoformat()} "date": date.isoformat()}
@ -260,8 +272,7 @@ class BaseTestData(ABC):
existing_j_id: set[int] = {x["id"] for x in self.__journal_entries} 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} existing_l_id: set[int] = {x["id"] for x in self.__line_items}
journal_entry_data.id = self.__new_id(existing_j_id) journal_entry_data.id = self.__new_id(existing_j_id)
date: dt.date \ date: dt.date = get_today() - dt.timedelta(days=journal_entry_data.days)
= dt.date.today() - dt.timedelta(days=journal_entry_data.days)
self.__journal_entries.append( self.__journal_entries.append(
{"id": journal_entry_data.id, {"id": journal_entry_data.id,
"date": date, "date": date,

View File

@ -1,7 +1,7 @@
# The Mia! Accounting Demonstration Website. # The Mia! Accounting Demonstration Website.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/4/12 # 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"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with 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 render_template, current_app
from flask_babel import lazy_gettext from flask_babel import lazy_gettext
from accounting.utils.timezone import get_tz_today
from . import db from . import db
from .auth import admin_required from .auth import admin_required
from .lib import Accounts, JournalEntryLineItemData, JournalEntryData, \ from .lib import Accounts, JournalEntryLineItemData, JournalEntryData, \
@ -117,7 +118,7 @@ class SampleData(BaseTestData):
:return: None. :return: None.
""" """
today: dt.date = dt.date.today() today: dt.date = get_tz_today()
days: int days: int
year: int year: int
month: int month: int
@ -160,7 +161,7 @@ class SampleData(BaseTestData):
:return: None. :return: None.
""" """
today: dt.date = dt.date.today() today: dt.date = get_tz_today()
year: int = today.year - 5 year: int = today.year - 5
month: int = today.month month: int = today.month