fix the dates in Statistics & Analysis page does not be processed for daylight saving time

This commit is contained in:
MaysWind
2025-12-21 02:35:11 +08:00
parent a09d7b57f9
commit d95e34a597
14 changed files with 171 additions and 94 deletions
+2 -2
View File
@@ -150,7 +150,7 @@ func (a *AccountsApi) AccountCreateHandler(c *core.WebContext) (any, *errs.Error
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
} }
utcOffset, err := c.GetClientTimezoneOffset() _, utcOffset, err := c.GetClientTimezone()
if err != nil { if err != nil {
log.Warnf(c, "[accounts.AccountCreateHandler] cannot get client timezone offset, because %s", err.Error()) log.Warnf(c, "[accounts.AccountCreateHandler] cannot get client timezone offset, because %s", err.Error())
@@ -315,7 +315,7 @@ func (a *AccountsApi) AccountModifyHandler(c *core.WebContext) (any, *errs.Error
return nil, errs.ErrAccountIdInvalid return nil, errs.ErrAccountIdInvalid
} }
utcOffset, err := c.GetClientTimezoneOffset() _, utcOffset, err := c.GetClientTimezone()
if err != nil { if err != nil {
log.Warnf(c, "[accounts.AccountModifyHandler] cannot get client timezone offset, because %s", err.Error()) log.Warnf(c, "[accounts.AccountModifyHandler] cannot get client timezone offset, because %s", err.Error())
+2 -4
View File
@@ -302,13 +302,11 @@ func (a *DataManagementsApi) getExportedFileContent(c *core.WebContext, fileType
return nil, "", errs.NewIncompleteOrIncorrectSubmissionError(err) return nil, "", errs.NewIncompleteOrIncorrectSubmissionError(err)
} }
timezone := time.Local timezone, _, err := c.GetClientTimezone()
utcOffset, err := c.GetClientTimezoneOffset()
if err != nil { if err != nil {
log.Warnf(c, "[data_managements.getExportedFileContent] cannot get client timezone offset, because %s", err.Error()) log.Warnf(c, "[data_managements.getExportedFileContent] cannot get client timezone offset, because %s", err.Error())
} else { timezone = time.Local
timezone = time.FixedZone("Client Timezone", int(utcOffset)*60)
} }
uid := c.GetCurrentUid() uid := c.GetCurrentUid()
+3 -4
View File
@@ -47,14 +47,13 @@ func (a *LargeLanguageModelsApi) RecognizeReceiptImageHandler(c *core.WebContext
return nil, errs.ErrLargeLanguageModelProviderNotEnabled return nil, errs.ErrLargeLanguageModelProviderNotEnabled
} }
utcOffset, err := c.GetClientTimezoneOffset() clientTimezone, utcOffset, err := c.GetClientTimezone()
if err != nil { if err != nil {
log.Warnf(c, "[large_language_models.RecognizeReceiptImageHandler] cannot get client timezone offset, because %s", err.Error()) log.Warnf(c, "[large_language_models.RecognizeReceiptImageHandler] cannot get client timezone, because %s", err.Error())
return nil, errs.ErrClientTimezoneOffsetInvalid return nil, errs.ErrClientTimezoneOffsetInvalid
} }
timezone := time.FixedZone("Client Timezone", int(utcOffset)*60)
uid := c.GetCurrentUid() uid := c.GetCurrentUid()
user, err := a.users.GetUserById(c, uid) user, err := a.users.GetUserById(c, uid)
@@ -197,7 +196,7 @@ func (a *LargeLanguageModelsApi) RecognizeReceiptImageHandler(c *core.WebContext
} }
systemPromptParams := map[string]any{ systemPromptParams := map[string]any{
"CurrentDateTime": utils.FormatUnixTimeToLongDateTime(time.Now().Unix(), timezone), "CurrentDateTime": utils.FormatUnixTimeToLongDateTime(time.Now().Unix(), clientTimezone),
"AllExpenseCategoryNames": strings.Join(expenseCategoryNames, "\n"), "AllExpenseCategoryNames": strings.Join(expenseCategoryNames, "\n"),
"AllIncomeCategoryNames": strings.Join(incomeCategoryNames, "\n"), "AllIncomeCategoryNames": strings.Join(incomeCategoryNames, "\n"),
"AllTransferCategoryNames": strings.Join(transferCategoryNames, "\n"), "AllTransferCategoryNames": strings.Join(transferCategoryNames, "\n"),
+59 -37
View File
@@ -7,6 +7,7 @@ import (
"math" "math"
"sort" "sort"
"strings" "strings"
"time"
orderedmap "github.com/wk8/go-ordered-map/v2" orderedmap "github.com/wk8/go-ordered-map/v2"
@@ -120,10 +121,10 @@ func (a *TransactionsApi) TransactionListHandler(c *core.WebContext) (any, *errs
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
} }
utcOffset, err := c.GetClientTimezoneOffset() clientTimezone, _, err := c.GetClientTimezone()
if err != nil { if err != nil {
log.Warnf(c, "[transactions.TransactionListHandler] cannot get client timezone offset, because %s", err.Error()) log.Warnf(c, "[transactions.TransactionListHandler] cannot get client timezone, because %s", err.Error())
return nil, errs.ErrClientTimezoneOffsetInvalid return nil, errs.ErrClientTimezoneOffsetInvalid
} }
@@ -191,7 +192,7 @@ func (a *TransactionsApi) TransactionListHandler(c *core.WebContext) (any, *errs
transactions = transactions[:transactionListReq.Count] transactions = transactions[:transactionListReq.Count]
} }
transactionResult, err := a.getTransactionResponseListResult(c, user, transactions, utcOffset, transactionListReq.WithPictures, transactionListReq.TrimAccount, transactionListReq.TrimCategory, transactionListReq.TrimTag) transactionResult, err := a.getTransactionResponseListResult(c, user, transactions, clientTimezone, transactionListReq.WithPictures, transactionListReq.TrimAccount, transactionListReq.TrimCategory, transactionListReq.TrimTag)
if err != nil { if err != nil {
log.Errorf(c, "[transactions.TransactionListHandler] failed to assemble transaction result for user \"uid:%d\", because %s", uid, err.Error()) log.Errorf(c, "[transactions.TransactionListHandler] failed to assemble transaction result for user \"uid:%d\", because %s", uid, err.Error())
@@ -223,10 +224,10 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.WebContext) (any,
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
} }
utcOffset, err := c.GetClientTimezoneOffset() clientTimezone, _, err := c.GetClientTimezone()
if err != nil { if err != nil {
log.Warnf(c, "[transactions.TransactionMonthListHandler] cannot get client timezone offset, because %s", err.Error()) log.Warnf(c, "[transactions.TransactionMonthListHandler] cannot get client timezone, because %s", err.Error())
return nil, errs.ErrClientTimezoneOffsetInvalid return nil, errs.ErrClientTimezoneOffsetInvalid
} }
@@ -274,7 +275,7 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.WebContext) (any,
return nil, errs.Or(err, errs.ErrOperationFailed) return nil, errs.Or(err, errs.ErrOperationFailed)
} }
transactionResult, err := a.getTransactionResponseListResult(c, user, transactions, utcOffset, transactionListReq.WithPictures, transactionListReq.TrimAccount, transactionListReq.TrimCategory, transactionListReq.TrimTag) transactionResult, err := a.getTransactionResponseListResult(c, user, transactions, clientTimezone, transactionListReq.WithPictures, transactionListReq.TrimAccount, transactionListReq.TrimCategory, transactionListReq.TrimTag)
if err != nil { if err != nil {
log.Errorf(c, "[transactions.TransactionMonthListHandler] failed to assemble transaction result for user \"uid:%d\", because %s", uid, err.Error()) log.Errorf(c, "[transactions.TransactionMonthListHandler] failed to assemble transaction result for user \"uid:%d\", because %s", uid, err.Error())
@@ -299,10 +300,10 @@ func (a *TransactionsApi) TransactionListAllHandler(c *core.WebContext) (any, *e
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
} }
utcOffset, err := c.GetClientTimezoneOffset() clientTimezone, _, err := c.GetClientTimezone()
if err != nil { if err != nil {
log.Warnf(c, "[transactions.TransactionListAllHandler] cannot get client timezone offset, because %s", err.Error()) log.Warnf(c, "[transactions.TransactionListAllHandler] cannot get client timezone, because %s", err.Error())
return nil, errs.ErrClientTimezoneOffsetInvalid return nil, errs.ErrClientTimezoneOffsetInvalid
} }
@@ -361,7 +362,7 @@ func (a *TransactionsApi) TransactionListAllHandler(c *core.WebContext) (any, *e
return nil, errs.Or(err, errs.ErrOperationFailed) return nil, errs.Or(err, errs.ErrOperationFailed)
} }
transactionResult, err := a.getTransactionResponseListResult(c, user, allTransactions, utcOffset, transactionAllListReq.WithPictures, transactionAllListReq.TrimAccount, transactionAllListReq.TrimCategory, transactionAllListReq.TrimTag) transactionResult, err := a.getTransactionResponseListResult(c, user, allTransactions, clientTimezone, transactionAllListReq.WithPictures, transactionAllListReq.TrimAccount, transactionAllListReq.TrimCategory, transactionAllListReq.TrimTag)
if err != nil { if err != nil {
log.Errorf(c, "[transactions.TransactionListAllHandler] failed to assemble transaction result for user \"uid:%d\", because %s", uid, err.Error()) log.Errorf(c, "[transactions.TransactionListAllHandler] failed to assemble transaction result for user \"uid:%d\", because %s", uid, err.Error())
@@ -381,10 +382,10 @@ func (a *TransactionsApi) TransactionReconciliationStatementHandler(c *core.WebC
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
} }
utcOffset, err := c.GetClientTimezoneOffset() clientTimezone, _, err := c.GetClientTimezone()
if err != nil { if err != nil {
log.Warnf(c, "[transactions.TransactionReconciliationStatementHandler] cannot get client timezone offset, because %s", err.Error()) log.Warnf(c, "[transactions.TransactionReconciliationStatementHandler] cannot get client timezone, because %s", err.Error())
return nil, errs.ErrClientTimezoneOffsetInvalid return nil, errs.ErrClientTimezoneOffsetInvalid
} }
@@ -440,7 +441,7 @@ func (a *TransactionsApi) TransactionReconciliationStatementHandler(c *core.WebC
transactionAccountBalanceMap[transactionWithBalance.RelatedId] = transactionWithBalance transactionAccountBalanceMap[transactionWithBalance.RelatedId] = transactionWithBalance
} }
transactionResult, err := a.getTransactionResponseListResult(c, user, transactions, utcOffset, false, true, true, true) transactionResult, err := a.getTransactionResponseListResult(c, user, transactions, clientTimezone, false, true, true, true)
if err != nil { if err != nil {
log.Errorf(c, "[transactions.TransactionReconciliationStatementHandler] failed to assemble transaction result for user \"uid:%d\", because %s", uid, err.Error()) log.Errorf(c, "[transactions.TransactionReconciliationStatementHandler] failed to assemble transaction result for user \"uid:%d\", because %s", uid, err.Error())
@@ -489,10 +490,10 @@ func (a *TransactionsApi) TransactionStatisticsHandler(c *core.WebContext) (any,
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
} }
utcOffset, err := c.GetClientTimezoneOffset() clientTimezone, utfOffset, err := c.GetClientTimezone()
if err != nil { if err != nil {
log.Warnf(c, "[transactions.TransactionStatisticsHandler] cannot get client timezone offset, because %s", err.Error()) log.Warnf(c, "[transactions.TransactionStatisticsHandler] cannot get client timezone, because %s", err.Error())
return nil, errs.ErrClientTimezoneOffsetInvalid return nil, errs.ErrClientTimezoneOffsetInvalid
} }
@@ -509,7 +510,7 @@ func (a *TransactionsApi) TransactionStatisticsHandler(c *core.WebContext) (any,
} }
uid := c.GetCurrentUid() uid := c.GetCurrentUid()
totalAmounts, err := a.transactions.GetAccountsAndCategoriesTotalInflowAndOutflow(c, uid, statisticReq.StartTime, statisticReq.EndTime, tagFilters, noTags, statisticReq.Keyword, utcOffset, statisticReq.UseTransactionTimezone) totalAmounts, err := a.transactions.GetAccountsAndCategoriesTotalInflowAndOutflow(c, uid, statisticReq.StartTime, statisticReq.EndTime, tagFilters, noTags, statisticReq.Keyword, utfOffset, clientTimezone, statisticReq.UseTransactionTimezone)
if err != nil { if err != nil {
log.Errorf(c, "[transactions.TransactionStatisticsHandler] failed to get accounts and categories total income and expense for user \"uid:%d\", because %s", uid, err.Error()) log.Errorf(c, "[transactions.TransactionStatisticsHandler] failed to get accounts and categories total income and expense for user \"uid:%d\", because %s", uid, err.Error())
@@ -550,10 +551,10 @@ func (a *TransactionsApi) TransactionStatisticsTrendsHandler(c *core.WebContext)
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
} }
utcOffset, err := c.GetClientTimezoneOffset() clientTimezone, _, err := c.GetClientTimezone()
if err != nil { if err != nil {
log.Warnf(c, "[transactions.TransactionStatisticsTrendsHandler] cannot get client timezone offset, because %s", err.Error()) log.Warnf(c, "[transactions.TransactionStatisticsTrendsHandler] cannot get client timezone, because %s", err.Error())
return nil, errs.ErrClientTimezoneOffsetInvalid return nil, errs.ErrClientTimezoneOffsetInvalid
} }
@@ -577,7 +578,7 @@ func (a *TransactionsApi) TransactionStatisticsTrendsHandler(c *core.WebContext)
} }
uid := c.GetCurrentUid() uid := c.GetCurrentUid()
allMonthlyTotalAmounts, err := a.transactions.GetAccountsAndCategoriesMonthlyInflowAndOutflow(c, uid, startYear, startMonth, endYear, endMonth, tagFilters, noTags, statisticTrendsReq.Keyword, utcOffset, statisticTrendsReq.UseTransactionTimezone) allMonthlyTotalAmounts, err := a.transactions.GetAccountsAndCategoriesMonthlyInflowAndOutflow(c, uid, startYear, startMonth, endYear, endMonth, tagFilters, noTags, statisticTrendsReq.Keyword, clientTimezone, statisticTrendsReq.UseTransactionTimezone)
if err != nil { if err != nil {
log.Errorf(c, "[transactions.TransactionStatisticsTrendsHandler] failed to get accounts and categories total income and expense for user \"uid:%d\", because %s", uid, err.Error()) log.Errorf(c, "[transactions.TransactionStatisticsTrendsHandler] failed to get accounts and categories total income and expense for user \"uid:%d\", because %s", uid, err.Error())
@@ -625,10 +626,10 @@ func (a *TransactionsApi) TransactionStatisticsAssetTrendsHandler(c *core.WebCon
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
} }
utcOffset, err := c.GetClientTimezoneOffset() clientTimezone, _, err := c.GetClientTimezone()
if err != nil { if err != nil {
log.Warnf(c, "[transactions.TransactionStatisticsAssetTrendsHandler] cannot get client timezone offset, because %s", err.Error()) log.Warnf(c, "[transactions.TransactionStatisticsAssetTrendsHandler] cannot get client timezone, because %s", err.Error())
return nil, errs.ErrClientTimezoneOffsetInvalid return nil, errs.ErrClientTimezoneOffsetInvalid
} }
@@ -646,7 +647,7 @@ func (a *TransactionsApi) TransactionStatisticsAssetTrendsHandler(c *core.WebCon
minTransactionTime = utils.GetMinTransactionTimeFromUnixTime(statisticAssetTrendsReq.StartTime) minTransactionTime = utils.GetMinTransactionTimeFromUnixTime(statisticAssetTrendsReq.StartTime)
} }
accountDailyBalances, err := a.transactions.GetAllAccountsDailyOpeningAndClosingBalance(c, uid, maxTransactionTime, minTransactionTime, utcOffset) accountDailyBalances, err := a.transactions.GetAllAccountsDailyOpeningAndClosingBalance(c, uid, maxTransactionTime, minTransactionTime, clientTimezone)
if err != nil { if err != nil {
log.Errorf(c, "[transactions.TransactionStatisticsAssetTrendsHandler] failed to get transactions from \"%d\" to \"%d\" for user \"uid:%d\", because %s", statisticAssetTrendsReq.StartTime, statisticAssetTrendsReq.EndTime, uid, err.Error()) log.Errorf(c, "[transactions.TransactionStatisticsAssetTrendsHandler] failed to get transactions from \"%d\" to \"%d\" for user \"uid:%d\", because %s", statisticAssetTrendsReq.StartTime, statisticAssetTrendsReq.EndTime, uid, err.Error())
@@ -726,10 +727,10 @@ func (a *TransactionsApi) TransactionAmountsHandler(c *core.WebContext) (any, *e
} }
} }
utcOffset, err := c.GetClientTimezoneOffset() clientTimezone, utfOffset, err := c.GetClientTimezone()
if err != nil { if err != nil {
log.Warnf(c, "[transactions.TransactionAmountsHandler] cannot get client timezone offset, because %s", err.Error()) log.Warnf(c, "[transactions.TransactionAmountsHandler] cannot get client timezone, because %s", err.Error())
return nil, errs.ErrClientTimezoneOffsetInvalid return nil, errs.ErrClientTimezoneOffsetInvalid
} }
@@ -748,7 +749,7 @@ func (a *TransactionsApi) TransactionAmountsHandler(c *core.WebContext) (any, *e
for i := 0; i < len(requestItems); i++ { for i := 0; i < len(requestItems); i++ {
requestItem := requestItems[i] requestItem := requestItems[i]
incomeAmounts, expenseAmounts, err := a.transactions.GetAccountsTotalIncomeAndExpense(c, uid, requestItem.StartTime, requestItem.EndTime, excludeAccountIds, excludeCategoryIds, utcOffset, transactionAmountsReq.UseTransactionTimezone) incomeAmounts, expenseAmounts, err := a.transactions.GetAccountsTotalIncomeAndExpense(c, uid, requestItem.StartTime, requestItem.EndTime, excludeAccountIds, excludeCategoryIds, utfOffset, clientTimezone, transactionAmountsReq.UseTransactionTimezone)
if err != nil { if err != nil {
log.Errorf(c, "[transactions.TransactionAmountsHandler] failed to get transaction amounts item for user \"uid:%d\", because %s", uid, err.Error()) log.Errorf(c, "[transactions.TransactionAmountsHandler] failed to get transaction amounts item for user \"uid:%d\", because %s", uid, err.Error())
@@ -829,10 +830,10 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.WebContext) (any, *errs.
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
} }
utcOffset, err := c.GetClientTimezoneOffset() clientTimezone, _, err := c.GetClientTimezone()
if err != nil { if err != nil {
log.Warnf(c, "[transactions.TransactionGetHandler] cannot get client timezone offset, because %s", err.Error()) log.Warnf(c, "[transactions.TransactionGetHandler] cannot get client timezone, because %s", err.Error())
return nil, errs.ErrClientTimezoneOffsetInvalid return nil, errs.ErrClientTimezoneOffsetInvalid
} }
@@ -918,7 +919,7 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.WebContext) (any, *errs.
} }
} }
transactionEditable := transaction.IsEditable(user, utcOffset, accountMap[transaction.AccountId], accountMap[transaction.RelatedAccountId]) transactionEditable := transaction.IsEditable(user, clientTimezone, accountMap[transaction.AccountId], accountMap[transaction.RelatedAccountId])
transactionTagIds := allTransactionTagIds[transaction.TransactionId] transactionTagIds := allTransactionTagIds[transaction.TransactionId]
transactionResp := transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable) transactionResp := transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable)
@@ -959,6 +960,13 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.WebContext) (any, *er
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
} }
clientTimezone, _, err := c.GetClientTimezone()
if err != nil {
log.Warnf(c, "[transactions.TransactionCreateHandler] cannot get client timezone, because %s", err.Error())
return nil, errs.ErrClientTimezoneOffsetInvalid
}
tagIds, err := utils.StringArrayToInt64Array(transactionCreateReq.TagIds) tagIds, err := utils.StringArrayToInt64Array(transactionCreateReq.TagIds)
if err != nil { if err != nil {
@@ -1016,7 +1024,7 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.WebContext) (any, *er
} }
transaction := a.createNewTransactionModel(uid, &transactionCreateReq, c.ClientIP()) transaction := a.createNewTransactionModel(uid, &transactionCreateReq, c.ClientIP())
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, transactionCreateReq.UtcOffset) transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone)
if !transactionEditable { if !transactionEditable {
return nil, errs.ErrCannotCreateTransactionWithThisTransactionTime return nil, errs.ErrCannotCreateTransactionWithThisTransactionTime
@@ -1089,6 +1097,13 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.WebContext) (any, *er
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
} }
clientTimezone, _, err := c.GetClientTimezone()
if err != nil {
log.Warnf(c, "[transactions.TransactionModifyHandler] cannot get client timezone, because %s", err.Error())
return nil, errs.ErrClientTimezoneOffsetInvalid
}
tagIds, err := utils.StringArrayToInt64Array(transactionModifyReq.TagIds) tagIds, err := utils.StringArrayToInt64Array(transactionModifyReq.TagIds)
if err != nil { if err != nil {
@@ -1202,8 +1217,8 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.WebContext) (any, *er
return nil, errs.ErrNothingWillBeUpdated return nil, errs.ErrNothingWillBeUpdated
} }
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, transaction.TimezoneUtcOffset) transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone)
newTransactionEditable := user.CanEditTransactionByTransactionTime(newTransaction.TransactionTime, transactionModifyReq.UtcOffset) newTransactionEditable := user.CanEditTransactionByTransactionTime(newTransaction.TransactionTime, clientTimezone)
if !transactionEditable || !newTransactionEditable { if !transactionEditable || !newTransactionEditable {
return nil, errs.ErrCannotModifyTransactionWithThisTransactionTime return nil, errs.ErrCannotModifyTransactionWithThisTransactionTime
@@ -1339,10 +1354,10 @@ func (a *TransactionsApi) TransactionDeleteHandler(c *core.WebContext) (any, *er
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
} }
utcOffset, err := c.GetClientTimezoneOffset() clientTimezone, _, err := c.GetClientTimezone()
if err != nil { if err != nil {
log.Warnf(c, "[transactions.TransactionDeleteHandler] cannot get client timezone offset, because %s", err.Error()) log.Warnf(c, "[transactions.TransactionDeleteHandler] cannot get client timezone, because %s", err.Error())
return nil, errs.ErrClientTimezoneOffsetInvalid return nil, errs.ErrClientTimezoneOffsetInvalid
} }
@@ -1369,7 +1384,7 @@ func (a *TransactionsApi) TransactionDeleteHandler(c *core.WebContext) (any, *er
return nil, errs.ErrTransactionTypeInvalid return nil, errs.ErrTransactionTypeInvalid
} }
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, utcOffset) transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone)
if !transactionEditable { if !transactionEditable {
return nil, errs.ErrCannotDeleteTransactionWithThisTransactionTime return nil, errs.ErrCannotDeleteTransactionWithThisTransactionTime
@@ -1473,7 +1488,7 @@ func (a *TransactionsApi) TransactionParseImportFileHandler(c *core.WebContext)
return nil, errs.ErrParameterInvalid return nil, errs.ErrParameterInvalid
} }
utcOffset, err := c.GetClientTimezoneOffset() _, utcOffset, err := c.GetClientTimezone()
if err != nil { if err != nil {
log.Warnf(c, "[transactions.TransactionParseImportFileHandler] cannot get client timezone offset, because %s", err.Error()) log.Warnf(c, "[transactions.TransactionParseImportFileHandler] cannot get client timezone offset, because %s", err.Error())
@@ -1704,6 +1719,13 @@ func (a *TransactionsApi) TransactionImportHandler(c *core.WebContext) (any, *er
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
} }
clientTimezone, _, err := c.GetClientTimezone()
if err != nil {
log.Warnf(c, "[transactions.TransactionImportHandler] cannot get client timezone, because %s", err.Error())
return nil, errs.ErrClientTimezoneOffsetInvalid
}
uid := c.GetCurrentUid() uid := c.GetCurrentUid()
if a.CurrentConfig().EnableDuplicateSubmissionsCheck && transactionImportReq.ClientSessionId != "" { if a.CurrentConfig().EnableDuplicateSubmissionsCheck && transactionImportReq.ClientSessionId != "" {
@@ -1789,7 +1811,7 @@ func (a *TransactionsApi) TransactionImportHandler(c *core.WebContext) (any, *er
for i := 0; i < len(transactionImportReq.Transactions); i++ { for i := 0; i < len(transactionImportReq.Transactions); i++ {
transactionCreateReq := transactionImportReq.Transactions[i] transactionCreateReq := transactionImportReq.Transactions[i]
transaction := a.createNewTransactionModel(uid, transactionCreateReq, c.ClientIP()) transaction := a.createNewTransactionModel(uid, transactionCreateReq, c.ClientIP())
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, transactionCreateReq.UtcOffset) transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone)
if !transactionEditable { if !transactionEditable {
return nil, errs.ErrCannotCreateTransactionWithThisTransactionTime return nil, errs.ErrCannotCreateTransactionWithThisTransactionTime
@@ -1906,7 +1928,7 @@ func (a *TransactionsApi) getTransactionTagInfoResponses(tagIds []int64, allTran
return allTags return allTags
} }
func (a *TransactionsApi) getTransactionResponseListResult(c *core.WebContext, user *models.User, transactions []*models.Transaction, utcOffset int16, withPictures bool, trimAccount bool, trimCategory bool, trimTag bool) (models.TransactionInfoResponseSlice, error) { func (a *TransactionsApi) getTransactionResponseListResult(c *core.WebContext, user *models.User, transactions []*models.Transaction, clientTimezone *time.Location, withPictures bool, trimAccount bool, trimCategory bool, trimTag bool) (models.TransactionInfoResponseSlice, error) {
uid := user.Uid uid := user.Uid
transactionIds := make([]int64, len(transactions)) transactionIds := make([]int64, len(transactions))
accountIds := make([]int64, 0, len(transactions)*2) accountIds := make([]int64, 0, len(transactions)*2)
@@ -1985,7 +2007,7 @@ func (a *TransactionsApi) getTransactionResponseListResult(c *core.WebContext, u
transaction = a.transactions.GetRelatedTransferTransaction(transaction) transaction = a.transactions.GetRelatedTransferTransaction(transaction)
} }
transactionEditable := transaction.IsEditable(user, utcOffset, allAccounts[transaction.AccountId], allAccounts[transaction.RelatedAccountId]) transactionEditable := transaction.IsEditable(user, clientTimezone, allAccounts[transaction.AccountId], allAccounts[transaction.RelatedAccountId])
transactionTagIds := allTransactionTagIds[transaction.TransactionId] transactionTagIds := allTransactionTagIds[transaction.TransactionId]
result[i] = transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable) result[i] = transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable)
+37 -6
View File
@@ -4,6 +4,7 @@ import (
"net" "net"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -25,6 +26,9 @@ const RemoteClientPortHeader = "X-Real-Port"
// ClientTimezoneOffsetHeaderName represents the header name of client timezone offset // ClientTimezoneOffsetHeaderName represents the header name of client timezone offset
const ClientTimezoneOffsetHeaderName = "X-Timezone-Offset" const ClientTimezoneOffsetHeaderName = "X-Timezone-Offset"
// ClientTimezoneNameHeaderName represents the header name of client timezone name
const ClientTimezoneNameHeaderName = "X-Timezone-Name"
const tokenHeaderName = "Authorization" const tokenHeaderName = "Authorization"
const tokenHeaderValuePrefix = "bearer " const tokenHeaderValuePrefix = "bearer "
const tokenQueryStringParam = "token" const tokenQueryStringParam = "token"
@@ -183,16 +187,24 @@ func (c *WebContext) GetClientLocale() string {
return value return value
} }
// GetClientTimezoneOffset returns the client timezone offset func (c *WebContext) GetClientTimezone() (*time.Location, int16, error) {
func (c *WebContext) GetClientTimezoneOffset() (int16, error) { utcOffset, err := c.getClientTimezoneOffset()
value := c.GetHeader(ClientTimezoneOffsetHeaderName)
offset, err := strconv.Atoi(value)
if err != nil { if err != nil {
return 0, err return nil, 0, err
} }
return int16(offset), nil timezoneName := c.getClientTimezoneName()
if timezoneName != "" {
location, err := time.LoadLocation(timezoneName)
if err == nil && location != nil {
return location, utcOffset, nil
}
}
return time.FixedZone("Client Fixed Timezone", int(utcOffset)*60), utcOffset, nil
} }
// SetResponseError sets the response error // SetResponseError sets the response error
@@ -211,6 +223,25 @@ func (c *WebContext) GetResponseError() *errs.Error {
return err.(*errs.Error) return err.(*errs.Error)
} }
// GetClientTimezoneOffset returns the client timezone offset
func (c *WebContext) getClientTimezoneOffset() (int16, error) {
value := c.GetHeader(ClientTimezoneOffsetHeaderName)
offset, err := strconv.Atoi(value)
if err != nil {
return 0, err
}
return int16(offset), nil
}
// GetClientTimezoneName returns the client timezone name
func (c *WebContext) getClientTimezoneName() string {
value := c.GetHeader(ClientTimezoneNameHeaderName)
return value
}
// WrapWebContext returns a context wrapped by this file // WrapWebContext returns a context wrapped by this file
func WrapWebContext(ginCtx *gin.Context) *WebContext { func WrapWebContext(ginCtx *gin.Context) *WebContext {
return &WebContext{ return &WebContext{
+2 -1
View File
@@ -3,6 +3,7 @@ package mcp
import ( import (
"encoding/json" "encoding/json"
"reflect" "reflect"
"time"
"github.com/mayswind/ezbookkeeping/pkg/core" "github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/errs" "github.com/mayswind/ezbookkeeping/pkg/errs"
@@ -177,7 +178,7 @@ func (h *mcpAddTransactionToolHandler) Handle(c *core.WebContext, callToolReq *M
return nil, nil, err return nil, nil, err
} }
transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, transaction.TimezoneUtcOffset) transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60))
if !transactionEditable { if !transactionEditable {
return nil, nil, errs.ErrCannotCreateTransactionWithThisTransactionTime return nil, nil, errs.ErrCannotCreateTransactionWithThisTransactionTime
+3 -2
View File
@@ -3,6 +3,7 @@ package models
import ( import (
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/mayswind/ezbookkeeping/pkg/errs" "github.com/mayswind/ezbookkeeping/pkg/errs"
"github.com/mayswind/ezbookkeeping/pkg/utils" "github.com/mayswind/ezbookkeeping/pkg/utils"
@@ -511,8 +512,8 @@ func ParseTransactionTagFilter(tagFilterStr string) ([]*TransactionTagFilter, er
} }
// IsEditable returns whether this transaction can be edited // IsEditable returns whether this transaction can be edited
func (t *Transaction) IsEditable(currentUser *User, utcOffset int16, account *Account, relatedAccount *Account) bool { func (t *Transaction) IsEditable(currentUser *User, clientTimezone *time.Location, account *Account, relatedAccount *Account) bool {
if currentUser == nil || !currentUser.CanEditTransactionByTransactionTime(t.TransactionTime, utcOffset) { if currentUser == nil || !currentUser.CanEditTransactionByTransactionTime(t.TransactionTime, clientTimezone) {
return false return false
} }
+2 -3
View File
@@ -230,7 +230,7 @@ type UserProfileResponse struct {
} }
// CanEditTransactionByTransactionTime returns whether this user can edit transaction with specified transaction time // CanEditTransactionByTransactionTime returns whether this user can edit transaction with specified transaction time
func (u *User) CanEditTransactionByTransactionTime(transactionTime int64, utcOffset int16) bool { func (u *User) CanEditTransactionByTransactionTime(transactionTime int64, clientTimezone *time.Location) bool {
if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_NONE { if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_NONE {
return false return false
} else if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_ALL { } else if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_ALL {
@@ -245,8 +245,7 @@ func (u *User) CanEditTransactionByTransactionTime(transactionTime int64, utcOff
return transactionUnixTime >= now.Unix()-24*60*60 return transactionUnixTime >= now.Unix()-24*60*60
} }
clientLocation := time.FixedZone("Client Timezone", int(utcOffset)*60) clientNow := now.In(clientTimezone)
clientNow := now.In(clientLocation)
clientTodayFirstUnixTime := clientNow.Unix() - int64(clientNow.Hour()*60*60+clientNow.Minute()*60+clientNow.Second()) clientTodayFirstUnixTime := clientNow.Unix() - int64(clientNow.Hour()*60*60+clientNow.Minute()*60+clientNow.Second())
if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_TODAY_OR_LATER { if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_TODAY_OR_LATER {
+23 -16
View File
@@ -15,7 +15,8 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsNone(t *testing.T) {
TransactionEditScope: TRANSACTION_EDIT_SCOPE_NONE, TransactionEditScope: TRANSACTION_EDIT_SCOPE_NONE,
} }
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(time.Now().Unix()), utils.GetServerTimezoneOffsetMinutes())) timezone := time.FixedZone("Timezone", int(utils.GetServerTimezoneOffsetMinutes())*60)
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(time.Now().Unix()), timezone))
} }
func TestUserCanEditTransactionByTransactionTime_ScopeIsAll(t *testing.T) { func TestUserCanEditTransactionByTransactionTime_ScopeIsAll(t *testing.T) {
@@ -23,7 +24,8 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsAll(t *testing.T) {
TransactionEditScope: TRANSACTION_EDIT_SCOPE_ALL, TransactionEditScope: TRANSACTION_EDIT_SCOPE_ALL,
} }
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(time.Now().Unix()), utils.GetServerTimezoneOffsetMinutes())) timezone := time.FixedZone("Timezone", int(utils.GetServerTimezoneOffsetMinutes())*60)
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(time.Now().Unix()), timezone))
} }
func TestUserCanEditTransactionByTransactionTime_ScopeIsTodayOrLater(t *testing.T) { func TestUserCanEditTransactionByTransactionTime_ScopeIsTodayOrLater(t *testing.T) {
@@ -32,13 +34,14 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsTodayOrLater(t *testing.
} }
now := time.Now() now := time.Now()
timezone := time.FixedZone("Timezone", int(utils.GetServerTimezoneOffsetMinutes())*60)
todayFirstDatetime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local) todayFirstDatetime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
yesterdayLastDatetime := todayFirstDatetime.Add(-1 * time.Second) yesterdayLastDatetime := todayFirstDatetime.Add(-1 * time.Second)
todayLastDatetime := yesterdayLastDatetime.Add(24 * time.Hour) todayLastDatetime := yesterdayLastDatetime.Add(24 * time.Hour)
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(todayFirstDatetime.Unix()), utils.GetServerTimezoneOffsetMinutes())) assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(todayFirstDatetime.Unix()), timezone))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(todayLastDatetime.Unix()), utils.GetServerTimezoneOffsetMinutes())) assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(todayLastDatetime.Unix()), timezone))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(yesterdayLastDatetime.Unix()), utils.GetServerTimezoneOffsetMinutes())) assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(yesterdayLastDatetime.Unix()), timezone))
} }
func TestUserCanEditTransactionByTransactionTime_ScopeIsLast24HourOrLater(t *testing.T) { func TestUserCanEditTransactionByTransactionTime_ScopeIsLast24HourOrLater(t *testing.T) {
@@ -47,10 +50,11 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsLast24HourOrLater(t *tes
} }
now := time.Now() now := time.Now()
timezone := time.FixedZone("Timezone", int(utils.GetServerTimezoneOffsetMinutes())*60)
twentyfourHourBeforeDatetime := now.Add(-24 * time.Hour).Add(-1 * time.Second) twentyfourHourBeforeDatetime := now.Add(-24 * time.Hour).Add(-1 * time.Second)
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(twentyfourHourBeforeDatetime.Unix()), utils.GetServerTimezoneOffsetMinutes())) assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(twentyfourHourBeforeDatetime.Unix()), timezone))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(twentyfourHourBeforeDatetime.Add(1*time.Second).Unix()), utils.GetServerTimezoneOffsetMinutes())) assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(twentyfourHourBeforeDatetime.Add(1*time.Second).Unix()), timezone))
} }
func TestUserCanEditTransactionByTransactionTime_ScopeIsThisWeekOrLater(t *testing.T) { func TestUserCanEditTransactionByTransactionTime_ScopeIsThisWeekOrLater(t *testing.T) {
@@ -60,6 +64,7 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsThisWeekOrLater(t *testi
} }
now := time.Now() now := time.Now()
timezone := time.FixedZone("Timezone", int(utils.GetServerTimezoneOffsetMinutes())*60)
thisWeekFirstDatetime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local) thisWeekFirstDatetime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
if thisWeekFirstDatetime.Weekday() == time.Sunday { if thisWeekFirstDatetime.Weekday() == time.Sunday {
@@ -71,9 +76,9 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsThisWeekOrLater(t *testi
lastWeekLastDatetime := thisWeekFirstDatetime.Add(-1 * time.Second) lastWeekLastDatetime := thisWeekFirstDatetime.Add(-1 * time.Second)
thisWeekLastDatetime := lastWeekLastDatetime.Add(24 * time.Hour) thisWeekLastDatetime := lastWeekLastDatetime.Add(24 * time.Hour)
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisWeekFirstDatetime.Unix()), utils.GetServerTimezoneOffsetMinutes())) assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisWeekFirstDatetime.Unix()), timezone))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisWeekLastDatetime.Unix()), utils.GetServerTimezoneOffsetMinutes())) assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisWeekLastDatetime.Unix()), timezone))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(lastWeekLastDatetime.Unix()), utils.GetServerTimezoneOffsetMinutes())) assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(lastWeekLastDatetime.Unix()), timezone))
} }
func TestUserCanEditTransactionByTransactionTime_ScopeIsThisMonthOrLater(t *testing.T) { func TestUserCanEditTransactionByTransactionTime_ScopeIsThisMonthOrLater(t *testing.T) {
@@ -82,13 +87,14 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsThisMonthOrLater(t *test
} }
now := time.Now() now := time.Now()
timezone := time.FixedZone("Timezone", int(utils.GetServerTimezoneOffsetMinutes())*60)
thisMonthFirstDatetime := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.Local) thisMonthFirstDatetime := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.Local)
lastMonthLastDatetime := thisMonthFirstDatetime.Add(-1 * time.Second) lastMonthLastDatetime := thisMonthFirstDatetime.Add(-1 * time.Second)
thisMonthLastDatetime := lastMonthLastDatetime.Add(24 * time.Hour) thisMonthLastDatetime := lastMonthLastDatetime.Add(24 * time.Hour)
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisMonthFirstDatetime.Unix()), utils.GetServerTimezoneOffsetMinutes())) assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisMonthFirstDatetime.Unix()), timezone))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisMonthLastDatetime.Unix()), utils.GetServerTimezoneOffsetMinutes())) assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisMonthLastDatetime.Unix()), timezone))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(lastMonthLastDatetime.Unix()), utils.GetServerTimezoneOffsetMinutes())) assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(lastMonthLastDatetime.Unix()), timezone))
} }
func TestUserCanEditTransactionByTransactionTime_ScopeIsThisYearOrLater(t *testing.T) { func TestUserCanEditTransactionByTransactionTime_ScopeIsThisYearOrLater(t *testing.T) {
@@ -97,11 +103,12 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsThisYearOrLater(t *testi
} }
now := time.Now() now := time.Now()
timezone := time.FixedZone("Timezone", int(utils.GetServerTimezoneOffsetMinutes())*60)
thisYearFirstDatetime := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.Local) thisYearFirstDatetime := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.Local)
lastYearLastDatetime := thisYearFirstDatetime.Add(-1 * time.Second) lastYearLastDatetime := thisYearFirstDatetime.Add(-1 * time.Second)
thisYearLastDatetime := lastYearLastDatetime.Add(24 * time.Hour) thisYearLastDatetime := lastYearLastDatetime.Add(24 * time.Hour)
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisYearFirstDatetime.Unix()), utils.GetServerTimezoneOffsetMinutes())) assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisYearFirstDatetime.Unix()), timezone))
assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisYearLastDatetime.Unix()), utils.GetServerTimezoneOffsetMinutes())) assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisYearLastDatetime.Unix()), timezone))
assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(lastYearLastDatetime.Unix()), utils.GetServerTimezoneOffsetMinutes())) assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(lastYearLastDatetime.Unix()), timezone))
} }
+13 -17
View File
@@ -198,12 +198,11 @@ func (s *TransactionService) GetAllTransactionsInOneAccountWithAccountBalanceByM
} }
// GetAllAccountsDailyOpeningAndClosingBalance returns daily opening and closing balance of all accounts within time range // GetAllAccountsDailyOpeningAndClosingBalance returns daily opening and closing balance of all accounts within time range
func (s *TransactionService) GetAllAccountsDailyOpeningAndClosingBalance(c core.Context, uid int64, maxTransactionTime int64, minTransactionTime int64, utcOffset int16) (map[int32][]*models.TransactionWithAccountBalance, error) { func (s *TransactionService) GetAllAccountsDailyOpeningAndClosingBalance(c core.Context, uid int64, maxTransactionTime int64, minTransactionTime int64, clientTimezone *time.Location) (map[int32][]*models.TransactionWithAccountBalance, error) {
if maxTransactionTime <= 0 { if maxTransactionTime <= 0 {
maxTransactionTime = utils.GetMaxTransactionTimeFromUnixTime(time.Now().Unix()) maxTransactionTime = utils.GetMaxTransactionTimeFromUnixTime(time.Now().Unix())
} }
clientLocation := time.FixedZone("Client Timezone", int(utcOffset)*60)
var allTransactions []*models.Transaction var allTransactions []*models.Transaction
for maxTransactionTime > 0 { for maxTransactionTime > 0 {
@@ -260,7 +259,7 @@ func (s *TransactionService) GetAllAccountsDailyOpeningAndClosingBalance(c core.
continue continue
} }
yearMonthDay := utils.FormatUnixTimeToNumericYearMonthDay(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime), clientLocation) yearMonthDay := utils.FormatUnixTimeToNumericYearMonthDay(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime), clientTimezone)
groupKey := fmt.Sprintf("%d_%d", yearMonthDay, transaction.AccountId) groupKey := fmt.Sprintf("%d_%d", yearMonthDay, transaction.AccountId)
dailyAccountBalance, exists := accountDailyLastBalances[groupKey] dailyAccountBalance, exists := accountDailyLastBalances[groupKey]
@@ -284,7 +283,7 @@ func (s *TransactionService) GetAllAccountsDailyOpeningAndClosingBalance(c core.
firstTransactionTime = minTransactionTime firstTransactionTime = minTransactionTime
} }
firstYearMonthDay := utils.FormatUnixTimeToNumericYearMonthDay(utils.GetUnixTimeFromTransactionTime(firstTransactionTime), clientLocation) firstYearMonthDay := utils.FormatUnixTimeToNumericYearMonthDay(utils.GetUnixTimeFromTransactionTime(firstTransactionTime), clientTimezone)
// fill in the opening balance for accounts that do not have transactions on the first day // fill in the opening balance for accounts that do not have transactions on the first day
for accountId, accumulatedBalance := range accumulatedBalancesBeforeStartTime { for accountId, accumulatedBalance := range accumulatedBalancesBeforeStartTime {
@@ -1798,14 +1797,13 @@ func (s *TransactionService) GetRelatedTransferTransaction(originalTransaction *
} }
// GetAccountsTotalIncomeAndExpense returns the every accounts total income and expense amount by specific date range // GetAccountsTotalIncomeAndExpense returns the every accounts total income and expense amount by specific date range
func (s *TransactionService) GetAccountsTotalIncomeAndExpense(c core.Context, uid int64, startUnixTime int64, endUnixTime int64, excludeAccountIds []int64, excludeCategoryIds []int64, utcOffset int16, useTransactionTimezone bool) (map[int64]int64, map[int64]int64, error) { func (s *TransactionService) GetAccountsTotalIncomeAndExpense(c core.Context, uid int64, startUnixTime int64, endUnixTime int64, excludeAccountIds []int64, excludeCategoryIds []int64, utcOffset int16, clientTimezone *time.Location, useTransactionTimezone bool) (map[int64]int64, map[int64]int64, error) {
if uid <= 0 { if uid <= 0 {
return nil, nil, errs.ErrUserIdInvalid return nil, nil, errs.ErrUserIdInvalid
} }
clientLocation := time.FixedZone("Client Timezone", int(utcOffset)*60) startLocalDateTime := utils.FormatUnixTimeToNumericLocalDateTime(startUnixTime, clientTimezone)
startLocalDateTime := utils.FormatUnixTimeToNumericLocalDateTime(startUnixTime, clientLocation) endLocalDateTime := utils.FormatUnixTimeToNumericLocalDateTime(endUnixTime, clientTimezone)
endLocalDateTime := utils.FormatUnixTimeToNumericLocalDateTime(endUnixTime, clientLocation)
startUnixTime = utils.GetMinUnixTimeWithSameLocalDateTime(startUnixTime, utcOffset) startUnixTime = utils.GetMinUnixTimeWithSameLocalDateTime(startUnixTime, utcOffset)
endUnixTime = utils.GetMaxUnixTimeWithSameLocalDateTime(endUnixTime, utcOffset) endUnixTime = utils.GetMaxUnixTimeWithSameLocalDateTime(endUnixTime, utcOffset)
@@ -1889,7 +1887,7 @@ func (s *TransactionService) GetAccountsTotalIncomeAndExpense(c core.Context, ui
for i := 0; i < len(allTransactions); i++ { for i := 0; i < len(allTransactions); i++ {
transaction := allTransactions[i] transaction := allTransactions[i]
timeZone := clientLocation timeZone := clientTimezone
if useTransactionTimezone { if useTransactionTimezone {
timeZone = time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60) timeZone = time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
@@ -1923,22 +1921,21 @@ func (s *TransactionService) GetAccountsTotalIncomeAndExpense(c core.Context, ui
} }
// GetAccountsAndCategoriesTotalInflowAndOutflow returns the every accounts and categories total inflows and outflows amount by specific date range // GetAccountsAndCategoriesTotalInflowAndOutflow returns the every accounts and categories total inflows and outflows amount by specific date range
func (s *TransactionService) GetAccountsAndCategoriesTotalInflowAndOutflow(c core.Context, uid int64, startUnixTime int64, endUnixTime int64, tagFilters []*models.TransactionTagFilter, noTags bool, keyword string, utcOffset int16, useTransactionTimezone bool) ([]*models.Transaction, error) { func (s *TransactionService) GetAccountsAndCategoriesTotalInflowAndOutflow(c core.Context, uid int64, startUnixTime int64, endUnixTime int64, tagFilters []*models.TransactionTagFilter, noTags bool, keyword string, utcOffset int16, clientTimezone *time.Location, useTransactionTimezone bool) ([]*models.Transaction, error) {
if uid <= 0 { if uid <= 0 {
return nil, errs.ErrUserIdInvalid return nil, errs.ErrUserIdInvalid
} }
clientLocation := time.FixedZone("Client Timezone", int(utcOffset)*60)
var startLocalDateTime, endLocalDateTime, startTransactionTime, endTransactionTime int64 var startLocalDateTime, endLocalDateTime, startTransactionTime, endTransactionTime int64
if startUnixTime > 0 { if startUnixTime > 0 {
startLocalDateTime = utils.FormatUnixTimeToNumericLocalDateTime(startUnixTime, clientLocation) startLocalDateTime = utils.FormatUnixTimeToNumericLocalDateTime(startUnixTime, clientTimezone)
startUnixTime = utils.GetMinUnixTimeWithSameLocalDateTime(startUnixTime, utcOffset) startUnixTime = utils.GetMinUnixTimeWithSameLocalDateTime(startUnixTime, utcOffset)
startTransactionTime = utils.GetMinTransactionTimeFromUnixTime(startUnixTime) startTransactionTime = utils.GetMinTransactionTimeFromUnixTime(startUnixTime)
} }
if endUnixTime > 0 { if endUnixTime > 0 {
endLocalDateTime = utils.FormatUnixTimeToNumericLocalDateTime(endUnixTime, clientLocation) endLocalDateTime = utils.FormatUnixTimeToNumericLocalDateTime(endUnixTime, clientTimezone)
endUnixTime = utils.GetMaxUnixTimeWithSameLocalDateTime(endUnixTime, utcOffset) endUnixTime = utils.GetMaxUnixTimeWithSameLocalDateTime(endUnixTime, utcOffset)
endTransactionTime = utils.GetMaxTransactionTimeFromUnixTime(endUnixTime) endTransactionTime = utils.GetMaxTransactionTimeFromUnixTime(endUnixTime)
} }
@@ -2001,7 +1998,7 @@ func (s *TransactionService) GetAccountsAndCategoriesTotalInflowAndOutflow(c cor
for i := 0; i < len(allTransactions); i++ { for i := 0; i < len(allTransactions); i++ {
transaction := allTransactions[i] transaction := allTransactions[i]
timeZone := clientLocation timeZone := clientTimezone
if useTransactionTimezone { if useTransactionTimezone {
timeZone = time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60) timeZone = time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
@@ -2046,12 +2043,11 @@ func (s *TransactionService) GetAccountsAndCategoriesTotalInflowAndOutflow(c cor
} }
// GetAccountsAndCategoriesMonthlyInflowAndOutflow returns the every accounts monthly inflows and outflows amount by specific date range // GetAccountsAndCategoriesMonthlyInflowAndOutflow returns the every accounts monthly inflows and outflows amount by specific date range
func (s *TransactionService) GetAccountsAndCategoriesMonthlyInflowAndOutflow(c core.Context, uid int64, startYear int32, startMonth int32, endYear int32, endMonth int32, tagFilters []*models.TransactionTagFilter, noTags bool, keyword string, utcOffset int16, useTransactionTimezone bool) (map[int32][]*models.Transaction, error) { func (s *TransactionService) GetAccountsAndCategoriesMonthlyInflowAndOutflow(c core.Context, uid int64, startYear int32, startMonth int32, endYear int32, endMonth int32, tagFilters []*models.TransactionTagFilter, noTags bool, keyword string, clientTimezone *time.Location, useTransactionTimezone bool) (map[int32][]*models.Transaction, error) {
if uid <= 0 { if uid <= 0 {
return nil, errs.ErrUserIdInvalid return nil, errs.ErrUserIdInvalid
} }
clientLocation := time.FixedZone("Client Timezone", int(utcOffset)*60)
var startTransactionTime, endTransactionTime int64 var startTransactionTime, endTransactionTime int64
var err error var err error
@@ -2132,7 +2128,7 @@ func (s *TransactionService) GetAccountsAndCategoriesMonthlyInflowAndOutflow(c c
for i := 0; i < len(allTransactions); i++ { for i := 0; i < len(allTransactions); i++ {
transaction := allTransactions[i] transaction := allTransactions[i]
timeZone := clientLocation timeZone := clientTimezone
if useTransactionTimezone { if useTransactionTimezone {
timeZone = time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60) timeZone = time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60)
+4
View File
@@ -521,6 +521,10 @@ export function getBrowserTimezoneOffsetMinutes(): number {
return -new Date().getTimezoneOffset(); return -new Date().getTimezoneOffset();
} }
export function guessTimezoneName(): string {
return moment.tz.guess(true);
}
export function getLocalDatetimeFromUnixTime(unixTime: number): Date { export function getLocalDatetimeFromUnixTime(unixTime: number): Date {
return new Date(unixTime * 1000); return new Date(unixTime * 1000);
} }
+15 -1
View File
@@ -167,13 +167,19 @@ import {
isBoolean, isBoolean,
objectFieldWithValueToArrayItem objectFieldWithValueToArrayItem
} from './common.ts'; } from './common.ts';
import {
getTimeZone
} from './settings.ts';
import { import {
getGoogleMapAPIKey, getGoogleMapAPIKey,
getBaiduMapAK, getBaiduMapAK,
getAmapApplicationKey, getAmapApplicationKey,
getExchangeRatesRequestTimeout getExchangeRatesRequestTimeout
} from './server_settings.ts'; } from './server_settings.ts';
import { getTimezoneOffsetMinutes } from './datetime.ts'; import {
getTimezoneOffsetMinutes,
guessTimezoneName
} from './datetime.ts';
import { generateRandomUUID } from './misc.ts'; import { generateRandomUUID } from './misc.ts';
import { getBasePath } from './web.ts'; import { getBasePath } from './web.ts';
import logger from './logger.ts'; import logger from './logger.ts';
@@ -204,6 +210,14 @@ axios.interceptors.request.use((config: ApiRequestConfig) => {
config.headers['X-Timezone-Offset'] = getTimezoneOffsetMinutes(); config.headers['X-Timezone-Offset'] = getTimezoneOffsetMinutes();
let timezoneName = getTimeZone();
if (!timezoneName || timezoneName.trim().length < 1) {
timezoneName = guessTimezoneName();
}
config.headers['X-Timezone-Name'] = timezoneName;
if (needBlockRequest && !config.ignoreBlocked) { if (needBlockRequest && !config.ignoreBlocked) {
return new Promise(resolve => { return new Promise(resolve => {
blockedRequests.push(newToken => { blockedRequests.push(newToken => {
+4
View File
@@ -89,6 +89,10 @@ export function getTheme(): string {
return getApplicationSettings().theme; return getApplicationSettings().theme;
} }
export function getTimeZone(): string {
return getApplicationSettings().timeZone;
}
export function isEnableApplicationLock(): boolean { export function isEnableApplicationLock(): boolean {
return getApplicationSettings().applicationLock; return getApplicationSettings().applicationLock;
} }
+2 -1
View File
@@ -202,6 +202,7 @@ import {
getTimeDifferenceHoursAndMinutes, getTimeDifferenceHoursAndMinutes,
getTimezoneOffset, getTimezoneOffset,
getTimezoneOffsetMinutes, getTimezoneOffsetMinutes,
guessTimezoneName,
isDateRangeMatchFullMonths, isDateRangeMatchFullMonths,
isDateRangeMatchFullYears, isDateRangeMatchFullYears,
isPM isPM
@@ -2316,7 +2317,7 @@ export function useI18n() {
logger.info(`Current timezone is ${timezone}`); logger.info(`Current timezone is ${timezone}`);
setTimeZone(timezone); setTimeZone(timezone);
} else { } else {
logger.info(`No timezone is set, use browser default ${getTimezoneOffset()} (maybe ${moment.tz.guess(true)})`); logger.info(`No timezone is set, use browser default ${getTimezoneOffset()} (maybe ${guessTimezoneName()})`);
setTimeZone(''); setTimeZone('');
} }