diff --git a/pkg/api/accounts.go b/pkg/api/accounts.go index 06eb7500..ebb22ff1 100644 --- a/pkg/api/accounts.go +++ b/pkg/api/accounts.go @@ -150,7 +150,7 @@ func (a *AccountsApi) AccountCreateHandler(c *core.WebContext) (any, *errs.Error return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) } - utcOffset, err := c.GetClientTimezoneOffset() + _, utcOffset, err := c.GetClientTimezone() if err != nil { 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 } - utcOffset, err := c.GetClientTimezoneOffset() + _, utcOffset, err := c.GetClientTimezone() if err != nil { log.Warnf(c, "[accounts.AccountModifyHandler] cannot get client timezone offset, because %s", err.Error()) diff --git a/pkg/api/data_managements.go b/pkg/api/data_managements.go index 6b1b2151..c269510f 100644 --- a/pkg/api/data_managements.go +++ b/pkg/api/data_managements.go @@ -302,13 +302,11 @@ func (a *DataManagementsApi) getExportedFileContent(c *core.WebContext, fileType return nil, "", errs.NewIncompleteOrIncorrectSubmissionError(err) } - timezone := time.Local - utcOffset, err := c.GetClientTimezoneOffset() + timezone, _, err := c.GetClientTimezone() if err != nil { log.Warnf(c, "[data_managements.getExportedFileContent] cannot get client timezone offset, because %s", err.Error()) - } else { - timezone = time.FixedZone("Client Timezone", int(utcOffset)*60) + timezone = time.Local } uid := c.GetCurrentUid() diff --git a/pkg/api/large_language_models.go b/pkg/api/large_language_models.go index fc22d4aa..26450878 100644 --- a/pkg/api/large_language_models.go +++ b/pkg/api/large_language_models.go @@ -47,14 +47,13 @@ func (a *LargeLanguageModelsApi) RecognizeReceiptImageHandler(c *core.WebContext return nil, errs.ErrLargeLanguageModelProviderNotEnabled } - utcOffset, err := c.GetClientTimezoneOffset() + clientTimezone, utcOffset, err := c.GetClientTimezone() 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 } - timezone := time.FixedZone("Client Timezone", int(utcOffset)*60) uid := c.GetCurrentUid() user, err := a.users.GetUserById(c, uid) @@ -197,7 +196,7 @@ func (a *LargeLanguageModelsApi) RecognizeReceiptImageHandler(c *core.WebContext } systemPromptParams := map[string]any{ - "CurrentDateTime": utils.FormatUnixTimeToLongDateTime(time.Now().Unix(), timezone), + "CurrentDateTime": utils.FormatUnixTimeToLongDateTime(time.Now().Unix(), clientTimezone), "AllExpenseCategoryNames": strings.Join(expenseCategoryNames, "\n"), "AllIncomeCategoryNames": strings.Join(incomeCategoryNames, "\n"), "AllTransferCategoryNames": strings.Join(transferCategoryNames, "\n"), diff --git a/pkg/api/transactions.go b/pkg/api/transactions.go index cc6e7ed3..1d0cd808 100644 --- a/pkg/api/transactions.go +++ b/pkg/api/transactions.go @@ -7,6 +7,7 @@ import ( "math" "sort" "strings" + "time" 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) } - utcOffset, err := c.GetClientTimezoneOffset() + clientTimezone, _, err := c.GetClientTimezone() 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 } @@ -191,7 +192,7 @@ func (a *TransactionsApi) TransactionListHandler(c *core.WebContext) (any, *errs 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 { 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) } - utcOffset, err := c.GetClientTimezoneOffset() + clientTimezone, _, err := c.GetClientTimezone() 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 } @@ -274,7 +275,7 @@ func (a *TransactionsApi) TransactionMonthListHandler(c *core.WebContext) (any, 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 { 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) } - utcOffset, err := c.GetClientTimezoneOffset() + clientTimezone, _, err := c.GetClientTimezone() 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 } @@ -361,7 +362,7 @@ func (a *TransactionsApi) TransactionListAllHandler(c *core.WebContext) (any, *e 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 { 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) } - utcOffset, err := c.GetClientTimezoneOffset() + clientTimezone, _, err := c.GetClientTimezone() 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 } @@ -440,7 +441,7 @@ func (a *TransactionsApi) TransactionReconciliationStatementHandler(c *core.WebC 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 { 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) } - utcOffset, err := c.GetClientTimezoneOffset() + clientTimezone, utfOffset, err := c.GetClientTimezone() 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 } @@ -509,7 +510,7 @@ func (a *TransactionsApi) TransactionStatisticsHandler(c *core.WebContext) (any, } 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 { 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) } - utcOffset, err := c.GetClientTimezoneOffset() + clientTimezone, _, err := c.GetClientTimezone() 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 } @@ -577,7 +578,7 @@ func (a *TransactionsApi) TransactionStatisticsTrendsHandler(c *core.WebContext) } 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 { 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) } - utcOffset, err := c.GetClientTimezoneOffset() + clientTimezone, _, err := c.GetClientTimezone() 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 } @@ -646,7 +647,7 @@ func (a *TransactionsApi) TransactionStatisticsAssetTrendsHandler(c *core.WebCon 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 { 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 { - 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 } @@ -748,7 +749,7 @@ func (a *TransactionsApi) TransactionAmountsHandler(c *core.WebContext) (any, *e for i := 0; i < len(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 { 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) } - utcOffset, err := c.GetClientTimezoneOffset() + clientTimezone, _, err := c.GetClientTimezone() 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 } @@ -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] transactionResp := transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable) @@ -959,6 +960,13 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.WebContext) (any, *er 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) if err != nil { @@ -1016,7 +1024,7 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.WebContext) (any, *er } transaction := a.createNewTransactionModel(uid, &transactionCreateReq, c.ClientIP()) - transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, transactionCreateReq.UtcOffset) + transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone) if !transactionEditable { return nil, errs.ErrCannotCreateTransactionWithThisTransactionTime @@ -1089,6 +1097,13 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.WebContext) (any, *er 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) if err != nil { @@ -1202,8 +1217,8 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.WebContext) (any, *er return nil, errs.ErrNothingWillBeUpdated } - transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, transaction.TimezoneUtcOffset) - newTransactionEditable := user.CanEditTransactionByTransactionTime(newTransaction.TransactionTime, transactionModifyReq.UtcOffset) + transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone) + newTransactionEditable := user.CanEditTransactionByTransactionTime(newTransaction.TransactionTime, clientTimezone) if !transactionEditable || !newTransactionEditable { return nil, errs.ErrCannotModifyTransactionWithThisTransactionTime @@ -1339,10 +1354,10 @@ func (a *TransactionsApi) TransactionDeleteHandler(c *core.WebContext) (any, *er return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) } - utcOffset, err := c.GetClientTimezoneOffset() + clientTimezone, _, err := c.GetClientTimezone() 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 } @@ -1369,7 +1384,7 @@ func (a *TransactionsApi) TransactionDeleteHandler(c *core.WebContext) (any, *er return nil, errs.ErrTransactionTypeInvalid } - transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, utcOffset) + transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone) if !transactionEditable { return nil, errs.ErrCannotDeleteTransactionWithThisTransactionTime @@ -1473,7 +1488,7 @@ func (a *TransactionsApi) TransactionParseImportFileHandler(c *core.WebContext) return nil, errs.ErrParameterInvalid } - utcOffset, err := c.GetClientTimezoneOffset() + _, utcOffset, err := c.GetClientTimezone() if err != nil { 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) } + 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() 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++ { transactionCreateReq := transactionImportReq.Transactions[i] transaction := a.createNewTransactionModel(uid, transactionCreateReq, c.ClientIP()) - transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, transactionCreateReq.UtcOffset) + transactionEditable := user.CanEditTransactionByTransactionTime(transaction.TransactionTime, clientTimezone) if !transactionEditable { return nil, errs.ErrCannotCreateTransactionWithThisTransactionTime @@ -1906,7 +1928,7 @@ func (a *TransactionsApi) getTransactionTagInfoResponses(tagIds []int64, allTran 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 transactionIds := make([]int64, len(transactions)) accountIds := make([]int64, 0, len(transactions)*2) @@ -1985,7 +2007,7 @@ func (a *TransactionsApi) getTransactionResponseListResult(c *core.WebContext, u 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] result[i] = transaction.ToTransactionInfoResponse(transactionTagIds, transactionEditable) diff --git a/pkg/core/context_web.go b/pkg/core/context_web.go index 60c276eb..a2d1274b 100644 --- a/pkg/core/context_web.go +++ b/pkg/core/context_web.go @@ -4,6 +4,7 @@ import ( "net" "strconv" "strings" + "time" "github.com/gin-gonic/gin" @@ -25,6 +26,9 @@ const RemoteClientPortHeader = "X-Real-Port" // ClientTimezoneOffsetHeaderName represents the header name of client 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 tokenHeaderValuePrefix = "bearer " const tokenQueryStringParam = "token" @@ -183,16 +187,24 @@ func (c *WebContext) GetClientLocale() string { return value } -// GetClientTimezoneOffset returns the client timezone offset -func (c *WebContext) GetClientTimezoneOffset() (int16, error) { - value := c.GetHeader(ClientTimezoneOffsetHeaderName) - offset, err := strconv.Atoi(value) +func (c *WebContext) GetClientTimezone() (*time.Location, int16, error) { + utcOffset, err := c.getClientTimezoneOffset() 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 @@ -211,6 +223,25 @@ func (c *WebContext) GetResponseError() *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 func WrapWebContext(ginCtx *gin.Context) *WebContext { return &WebContext{ diff --git a/pkg/mcp/add_transaction_tool_handler.go b/pkg/mcp/add_transaction_tool_handler.go index 8024f37b..9b16fe9d 100644 --- a/pkg/mcp/add_transaction_tool_handler.go +++ b/pkg/mcp/add_transaction_tool_handler.go @@ -3,6 +3,7 @@ package mcp import ( "encoding/json" "reflect" + "time" "github.com/mayswind/ezbookkeeping/pkg/core" "github.com/mayswind/ezbookkeeping/pkg/errs" @@ -177,7 +178,7 @@ func (h *mcpAddTransactionToolHandler) Handle(c *core.WebContext, callToolReq *M 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 { return nil, nil, errs.ErrCannotCreateTransactionWithThisTransactionTime diff --git a/pkg/models/transaction.go b/pkg/models/transaction.go index 29b7c56d..28e1cf85 100644 --- a/pkg/models/transaction.go +++ b/pkg/models/transaction.go @@ -3,6 +3,7 @@ package models import ( "fmt" "strings" + "time" "github.com/mayswind/ezbookkeeping/pkg/errs" "github.com/mayswind/ezbookkeeping/pkg/utils" @@ -511,8 +512,8 @@ func ParseTransactionTagFilter(tagFilterStr string) ([]*TransactionTagFilter, er } // IsEditable returns whether this transaction can be edited -func (t *Transaction) IsEditable(currentUser *User, utcOffset int16, account *Account, relatedAccount *Account) bool { - if currentUser == nil || !currentUser.CanEditTransactionByTransactionTime(t.TransactionTime, utcOffset) { +func (t *Transaction) IsEditable(currentUser *User, clientTimezone *time.Location, account *Account, relatedAccount *Account) bool { + if currentUser == nil || !currentUser.CanEditTransactionByTransactionTime(t.TransactionTime, clientTimezone) { return false } diff --git a/pkg/models/user.go b/pkg/models/user.go index 2816aa25..e6e165c7 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -230,7 +230,7 @@ type UserProfileResponse struct { } // 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 { return false } 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 } - clientLocation := time.FixedZone("Client Timezone", int(utcOffset)*60) - clientNow := now.In(clientLocation) + clientNow := now.In(clientTimezone) clientTodayFirstUnixTime := clientNow.Unix() - int64(clientNow.Hour()*60*60+clientNow.Minute()*60+clientNow.Second()) if u.TransactionEditScope == TRANSACTION_EDIT_SCOPE_TODAY_OR_LATER { diff --git a/pkg/models/user_test.go b/pkg/models/user_test.go index 65a0f369..e47fcd22 100644 --- a/pkg/models/user_test.go +++ b/pkg/models/user_test.go @@ -15,7 +15,8 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsNone(t *testing.T) { 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) { @@ -23,7 +24,8 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsAll(t *testing.T) { 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) { @@ -32,13 +34,14 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsTodayOrLater(t *testing. } 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) yesterdayLastDatetime := todayFirstDatetime.Add(-1 * time.Second) 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(todayLastDatetime.Unix()), utils.GetServerTimezoneOffsetMinutes())) - assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(yesterdayLastDatetime.Unix()), utils.GetServerTimezoneOffsetMinutes())) + assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(todayFirstDatetime.Unix()), timezone)) + assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(todayLastDatetime.Unix()), timezone)) + assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(yesterdayLastDatetime.Unix()), timezone)) } func TestUserCanEditTransactionByTransactionTime_ScopeIsLast24HourOrLater(t *testing.T) { @@ -47,10 +50,11 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsLast24HourOrLater(t *tes } now := time.Now() + timezone := time.FixedZone("Timezone", int(utils.GetServerTimezoneOffsetMinutes())*60) 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, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(twentyfourHourBeforeDatetime.Add(1*time.Second).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()), timezone)) } func TestUserCanEditTransactionByTransactionTime_ScopeIsThisWeekOrLater(t *testing.T) { @@ -60,6 +64,7 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsThisWeekOrLater(t *testi } 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) if thisWeekFirstDatetime.Weekday() == time.Sunday { @@ -71,9 +76,9 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsThisWeekOrLater(t *testi lastWeekLastDatetime := thisWeekFirstDatetime.Add(-1 * time.Second) 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(thisWeekLastDatetime.Unix()), utils.GetServerTimezoneOffsetMinutes())) - assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(lastWeekLastDatetime.Unix()), utils.GetServerTimezoneOffsetMinutes())) + assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisWeekFirstDatetime.Unix()), timezone)) + assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisWeekLastDatetime.Unix()), timezone)) + assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(lastWeekLastDatetime.Unix()), timezone)) } func TestUserCanEditTransactionByTransactionTime_ScopeIsThisMonthOrLater(t *testing.T) { @@ -82,13 +87,14 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsThisMonthOrLater(t *test } 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) lastMonthLastDatetime := thisMonthFirstDatetime.Add(-1 * time.Second) 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(thisMonthLastDatetime.Unix()), utils.GetServerTimezoneOffsetMinutes())) - assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(lastMonthLastDatetime.Unix()), utils.GetServerTimezoneOffsetMinutes())) + assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisMonthFirstDatetime.Unix()), timezone)) + assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisMonthLastDatetime.Unix()), timezone)) + assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(lastMonthLastDatetime.Unix()), timezone)) } func TestUserCanEditTransactionByTransactionTime_ScopeIsThisYearOrLater(t *testing.T) { @@ -97,11 +103,12 @@ func TestUserCanEditTransactionByTransactionTime_ScopeIsThisYearOrLater(t *testi } now := time.Now() + timezone := time.FixedZone("Timezone", int(utils.GetServerTimezoneOffsetMinutes())*60) thisYearFirstDatetime := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.Local) lastYearLastDatetime := thisYearFirstDatetime.Add(-1 * time.Second) 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(thisYearLastDatetime.Unix()), utils.GetServerTimezoneOffsetMinutes())) - assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(lastYearLastDatetime.Unix()), utils.GetServerTimezoneOffsetMinutes())) + assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisYearFirstDatetime.Unix()), timezone)) + assert.Equal(t, true, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(thisYearLastDatetime.Unix()), timezone)) + assert.Equal(t, false, user.CanEditTransactionByTransactionTime(utils.GetMinTransactionTimeFromUnixTime(lastYearLastDatetime.Unix()), timezone)) } diff --git a/pkg/services/transactions.go b/pkg/services/transactions.go index 68d373c6..9be626b7 100644 --- a/pkg/services/transactions.go +++ b/pkg/services/transactions.go @@ -198,12 +198,11 @@ func (s *TransactionService) GetAllTransactionsInOneAccountWithAccountBalanceByM } // 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 { maxTransactionTime = utils.GetMaxTransactionTimeFromUnixTime(time.Now().Unix()) } - clientLocation := time.FixedZone("Client Timezone", int(utcOffset)*60) var allTransactions []*models.Transaction for maxTransactionTime > 0 { @@ -260,7 +259,7 @@ func (s *TransactionService) GetAllAccountsDailyOpeningAndClosingBalance(c core. 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) dailyAccountBalance, exists := accountDailyLastBalances[groupKey] @@ -284,7 +283,7 @@ func (s *TransactionService) GetAllAccountsDailyOpeningAndClosingBalance(c core. 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 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 -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 { return nil, nil, errs.ErrUserIdInvalid } - clientLocation := time.FixedZone("Client Timezone", int(utcOffset)*60) - startLocalDateTime := utils.FormatUnixTimeToNumericLocalDateTime(startUnixTime, clientLocation) - endLocalDateTime := utils.FormatUnixTimeToNumericLocalDateTime(endUnixTime, clientLocation) + startLocalDateTime := utils.FormatUnixTimeToNumericLocalDateTime(startUnixTime, clientTimezone) + endLocalDateTime := utils.FormatUnixTimeToNumericLocalDateTime(endUnixTime, clientTimezone) startUnixTime = utils.GetMinUnixTimeWithSameLocalDateTime(startUnixTime, 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++ { transaction := allTransactions[i] - timeZone := clientLocation + timeZone := clientTimezone if useTransactionTimezone { 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 -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 { return nil, errs.ErrUserIdInvalid } - clientLocation := time.FixedZone("Client Timezone", int(utcOffset)*60) var startLocalDateTime, endLocalDateTime, startTransactionTime, endTransactionTime int64 if startUnixTime > 0 { - startLocalDateTime = utils.FormatUnixTimeToNumericLocalDateTime(startUnixTime, clientLocation) + startLocalDateTime = utils.FormatUnixTimeToNumericLocalDateTime(startUnixTime, clientTimezone) startUnixTime = utils.GetMinUnixTimeWithSameLocalDateTime(startUnixTime, utcOffset) startTransactionTime = utils.GetMinTransactionTimeFromUnixTime(startUnixTime) } if endUnixTime > 0 { - endLocalDateTime = utils.FormatUnixTimeToNumericLocalDateTime(endUnixTime, clientLocation) + endLocalDateTime = utils.FormatUnixTimeToNumericLocalDateTime(endUnixTime, clientTimezone) endUnixTime = utils.GetMaxUnixTimeWithSameLocalDateTime(endUnixTime, utcOffset) endTransactionTime = utils.GetMaxTransactionTimeFromUnixTime(endUnixTime) } @@ -2001,7 +1998,7 @@ func (s *TransactionService) GetAccountsAndCategoriesTotalInflowAndOutflow(c cor for i := 0; i < len(allTransactions); i++ { transaction := allTransactions[i] - timeZone := clientLocation + timeZone := clientTimezone if useTransactionTimezone { 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 -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 { return nil, errs.ErrUserIdInvalid } - clientLocation := time.FixedZone("Client Timezone", int(utcOffset)*60) var startTransactionTime, endTransactionTime int64 var err error @@ -2132,7 +2128,7 @@ func (s *TransactionService) GetAccountsAndCategoriesMonthlyInflowAndOutflow(c c for i := 0; i < len(allTransactions); i++ { transaction := allTransactions[i] - timeZone := clientLocation + timeZone := clientTimezone if useTransactionTimezone { timeZone = time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60) diff --git a/src/lib/datetime.ts b/src/lib/datetime.ts index ceb5c771..fbfc7050 100644 --- a/src/lib/datetime.ts +++ b/src/lib/datetime.ts @@ -521,6 +521,10 @@ export function getBrowserTimezoneOffsetMinutes(): number { return -new Date().getTimezoneOffset(); } +export function guessTimezoneName(): string { + return moment.tz.guess(true); +} + export function getLocalDatetimeFromUnixTime(unixTime: number): Date { return new Date(unixTime * 1000); } diff --git a/src/lib/services.ts b/src/lib/services.ts index 5b02f1f4..f53d1f7d 100644 --- a/src/lib/services.ts +++ b/src/lib/services.ts @@ -167,13 +167,19 @@ import { isBoolean, objectFieldWithValueToArrayItem } from './common.ts'; +import { + getTimeZone +} from './settings.ts'; import { getGoogleMapAPIKey, getBaiduMapAK, getAmapApplicationKey, getExchangeRatesRequestTimeout } from './server_settings.ts'; -import { getTimezoneOffsetMinutes } from './datetime.ts'; +import { + getTimezoneOffsetMinutes, + guessTimezoneName +} from './datetime.ts'; import { generateRandomUUID } from './misc.ts'; import { getBasePath } from './web.ts'; import logger from './logger.ts'; @@ -204,6 +210,14 @@ axios.interceptors.request.use((config: ApiRequestConfig) => { 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) { return new Promise(resolve => { blockedRequests.push(newToken => { diff --git a/src/lib/settings.ts b/src/lib/settings.ts index 7d23cdfd..4d9b6e31 100644 --- a/src/lib/settings.ts +++ b/src/lib/settings.ts @@ -89,6 +89,10 @@ export function getTheme(): string { return getApplicationSettings().theme; } +export function getTimeZone(): string { + return getApplicationSettings().timeZone; +} + export function isEnableApplicationLock(): boolean { return getApplicationSettings().applicationLock; } diff --git a/src/locales/helpers.ts b/src/locales/helpers.ts index f866943b..b1deaff0 100644 --- a/src/locales/helpers.ts +++ b/src/locales/helpers.ts @@ -202,6 +202,7 @@ import { getTimeDifferenceHoursAndMinutes, getTimezoneOffset, getTimezoneOffsetMinutes, + guessTimezoneName, isDateRangeMatchFullMonths, isDateRangeMatchFullYears, isPM @@ -2316,7 +2317,7 @@ export function useI18n() { logger.info(`Current timezone is ${timezone}`); setTimeZone(timezone); } 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(''); }