From ffcbca01d9bdabbd61cc9589d5c8b67042495cf3 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Thu, 25 Mar 2021 23:36:52 +0800 Subject: [PATCH] the export file name uses browser time zone, the transaction time in exported file uses time zone in transaction --- cmd/webserver.go | 1 + pkg/api/data_managements.go | 17 +++++++++++++---- pkg/cli/user_data.go | 4 +++- pkg/core/context.go | 4 ++-- pkg/exporters/csv_file.go | 6 ++++-- pkg/exporters/data_exporter.go | 8 ++++++-- pkg/middlewares/header_in_query_string.go | 16 ++++++++++++++++ pkg/utils/datetimes.go | 10 ++++++++-- src/views/mobile/users/DataManagement.vue | 7 ++++++- 9 files changed, 59 insertions(+), 14 deletions(-) create mode 100644 pkg/middlewares/header_in_query_string.go diff --git a/cmd/webserver.go b/cmd/webserver.go index b9a7631f..846008d5 100644 --- a/cmd/webserver.go +++ b/cmd/webserver.go @@ -157,6 +157,7 @@ func startWebServer(c *cli.Context) error { if config.EnableDataExport { dataRoute := apiRoute.Group("/data") + dataRoute.Use(bindMiddleware(middlewares.HeaderInQueryString)) dataRoute.Use(bindMiddleware(middlewares.JWTAuthorizationByQueryString)) { dataRoute.GET("/export.csv", bindCsv(api.DataManagements.ExportDataHandler)) diff --git a/pkg/api/data_managements.go b/pkg/api/data_managements.go index 78845e5e..6be70bc0 100644 --- a/pkg/api/data_managements.go +++ b/pkg/api/data_managements.go @@ -47,6 +47,15 @@ func (a *DataManagementsApi) ExportDataHandler(c *core.Context) ([]byte, string, return nil, "", errs.ErrDataExportNotAllowed } + timezone := time.Local + utcOffset, err := c.GetClientTimezoneOffset() + + if err != nil { + log.WarnfWithRequestId(c, "[data_managements.ExportDataHandler] cannot get client timezone offset, because %s", err.Error()) + } else { + timezone = time.FixedZone("Client Timezone", int(utcOffset)*60) + } + uid := c.GetCurrentUid() accounts, err := a.accounts.GetAllAccountsByUid(uid) @@ -88,14 +97,14 @@ func (a *DataManagementsApi) ExportDataHandler(c *core.Context) ([]byte, string, return nil, "", errs.ErrOperationFailed } - result, err := a.exporter.GetOutputContent(uid, allTransactions, accountMap, categoryMap, tagMap, tagIndexs) + result, err := a.exporter.GetOutputContent(uid, timezone, allTransactions, accountMap, categoryMap, tagMap, tagIndexs) if err != nil { log.ErrorfWithRequestId(c, "[data_managements.ExportDataHandler] failed to get csv format exported data for \"uid:%d\", because %s", uid, err.Error()) return nil, "", errs.Or(err, errs.ErrOperationFailed) } - fileName := a.getFileName() + fileName := a.getFileName(timezone) return result, fileName, nil } @@ -150,8 +159,8 @@ func (a *DataManagementsApi) ClearDataHandler(c *core.Context) (interface{}, *er return true, nil } -func (a *DataManagementsApi) getFileName() string { - currentTime := utils.FormatToLongDateTimeWithoutSecond(time.Now()) +func (a *DataManagementsApi) getFileName(timezone *time.Location) string { + currentTime := utils.FormatUnixTimeToLongDateTimeWithoutSecond(time.Now().Unix(), timezone) currentTime = strings.Replace(currentTime, "-", "_", -1) currentTime = strings.Replace(currentTime, " ", "_", -1) currentTime = strings.Replace(currentTime, ":", "_", -1) diff --git a/pkg/cli/user_data.go b/pkg/cli/user_data.go index c13214f9..c7a70605 100644 --- a/pkg/cli/user_data.go +++ b/pkg/cli/user_data.go @@ -1,6 +1,8 @@ package cli import ( + "time" + "github.com/urfave/cli/v2" "github.com/mayswind/lab/pkg/errs" @@ -176,7 +178,7 @@ func (a *UserDataCli) ExportTransaction(c *cli.Context, uid int64) ([]byte, erro return nil, err } - result, err := a.csvExporter.GetOutputContent(uid, allTransactions, accountMap, categoryMap, tagMap, tagIndexs) + result, err := a.csvExporter.GetOutputContent(uid, time.Local, allTransactions, accountMap, categoryMap, tagMap, tagIndexs) if err != nil { log.BootErrorf("[user_data.ExportTransaction] failed to get csv format exported data for \"uid:%d\", because %s", uid, err.Error()) diff --git a/pkg/core/context.go b/pkg/core/context.go index 053f050d..3eb655dc 100644 --- a/pkg/core/context.go +++ b/pkg/core/context.go @@ -12,7 +12,7 @@ const requestIdFieldKey = "REQUEST_ID" const tokenClaimsFieldKey = "TOKEN_CLAIMS" const responseErrorFieldKey = "RESPONSE_ERROR" -const clientTimezoneOffsetHeaderName = "X-Timezone-Offset" +const ClientTimezoneOffsetHeaderName = "X-Timezone-Offset" // Context represents the request and response context type Context struct { @@ -71,7 +71,7 @@ func (c *Context) GetCurrentUid() int64 { // GetClientTimezoneOffset returns the client timezone offset func (c *Context) GetClientTimezoneOffset() (int16, error) { - value := c.GetHeader(clientTimezoneOffsetHeaderName) + value := c.GetHeader(ClientTimezoneOffsetHeaderName) offset, err := strconv.Atoi(value) if err != nil { diff --git a/pkg/exporters/csv_file.go b/pkg/exporters/csv_file.go index 04270b89..a844e458 100644 --- a/pkg/exporters/csv_file.go +++ b/pkg/exporters/csv_file.go @@ -3,6 +3,7 @@ package exporters import ( "fmt" "strings" + "time" "github.com/mayswind/lab/pkg/models" "github.com/mayswind/lab/pkg/utils" @@ -17,7 +18,7 @@ const csvHeaderLine = "Time,Type,Category,Sub Category,Account,Amount,Account2,A const csvDataLineFormat = "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n" // GetOutputContent returns the exported csv data -func (e *CSVFileExporter) GetOutputContent(uid int64, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexs map[int64][]int64) ([]byte, error) { +func (e *CSVFileExporter) GetOutputContent(uid int64, timezone *time.Location, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexs map[int64][]int64) ([]byte, error) { var ret strings.Builder ret.Grow(len(transactions) * 100) @@ -30,7 +31,8 @@ func (e *CSVFileExporter) GetOutputContent(uid int64, transactions []*models.Tra continue } - transactionTime := utils.FormatToLongDateTimeWithoutSecond(utils.ParseFromUnixTime(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime))) + transactionTimeZone := time.FixedZone("Transaction Timezone", int(transaction.TimezoneUtcOffset)*60) + transactionTime := utils.FormatUnixTimeToLongDateTimeWithoutSecond(utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime), transactionTimeZone) transactionType := e.getTransactionTypeName(transaction.Type) category := e.getTransactionCategoryName(transaction.CategoryId, categoryMap) subCategory := e.getTransactionSubCategoryName(transaction.CategoryId, categoryMap) diff --git a/pkg/exporters/data_exporter.go b/pkg/exporters/data_exporter.go index 0f93a9a9..a22838d5 100644 --- a/pkg/exporters/data_exporter.go +++ b/pkg/exporters/data_exporter.go @@ -1,9 +1,13 @@ package exporters -import "github.com/mayswind/lab/pkg/models" +import ( + "time" + + "github.com/mayswind/lab/pkg/models" +) // DataExporter defines the structure of data exporter type DataExporter interface { // GetOutputContent returns the exported data - GetOutputContent(uid int64, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexs map[int64][]int64) ([]byte, error) + GetOutputContent(uid int64, timezone *time.Location, transactions []*models.Transaction, accountMap map[int64]*models.Account, categoryMap map[int64]*models.TransactionCategory, tagMap map[int64]*models.TransactionTag, allTagIndexs map[int64][]int64) ([]byte, error) } diff --git a/pkg/middlewares/header_in_query_string.go b/pkg/middlewares/header_in_query_string.go new file mode 100644 index 00000000..8fe84459 --- /dev/null +++ b/pkg/middlewares/header_in_query_string.go @@ -0,0 +1,16 @@ +package middlewares + +import ( + "github.com/mayswind/lab/pkg/core" +) + +const utcOffsetQueryStringParam = "utc_offset" + +// HeaderInQueryString puts some headers from query string +func HeaderInQueryString(c *core.Context) { + utcOffset, exists := c.GetQuery(utcOffsetQueryStringParam) + + if exists { + c.Request.Header.Set(core.ClientTimezoneOffsetHeaderName, utcOffset) + } +} diff --git a/pkg/utils/datetimes.go b/pkg/utils/datetimes.go index 66d1ab3f..8e950085 100644 --- a/pkg/utils/datetimes.go +++ b/pkg/utils/datetimes.go @@ -12,8 +12,14 @@ func FormatUnixTimeToLongDateTimeInServerTimezone(unixTime int64) string { return ParseFromUnixTime(unixTime).Format(longDateTimeFormat) } -// FormatToLongDateTimeWithoutSecond returns a textual representation of the time value formatted by long date time format (no second) -func FormatToLongDateTimeWithoutSecond(t time.Time) string { +// FormatUnixTimeToLongDateTimeWithoutSecond returns a textual representation of the unix time formatted by long date time format (no second) +func FormatUnixTimeToLongDateTimeWithoutSecond(unixTime int64, timezone *time.Location) string { + t := ParseFromUnixTime(unixTime) + + if timezone != nil { + t = t.In(timezone) + } + return t.Format(longDateTimeWithoutSecondFormat) } diff --git a/src/views/mobile/users/DataManagement.vue b/src/views/mobile/users/DataManagement.vue index 10c99498..c4e03b96 100644 --- a/src/views/mobile/users/DataManagement.vue +++ b/src/views/mobile/users/DataManagement.vue @@ -5,7 +5,7 @@ - {{ $t('Export Data') }} + {{ $t('Export Data') }} {{ $t('Clear User Data') }} @@ -31,6 +31,11 @@ export default { showInputPasswordSheetForClearData: false, }; }, + computed: { + currentTimezoneOffsetMinutes() { + return this.$utilities.getTimezoneOffsetMinutes(); + } + }, methods: { clearData(password) { const self = this;