From f54c4998efa9ea3183bdb0984934b0f9e26e07e2 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Wed, 24 Dec 2025 23:09:33 +0800 Subject: [PATCH] support IANA time zone names when importing DSV files using column mapping --- ...ustom_transaction_plain_text_data_table.go | 15 +++++ src/core/timezone.ts | 9 ++- src/locales/de.json | 1 + src/locales/en.json | 1 + src/locales/es.json | 1 + src/locales/fr.json | 1 + src/locales/it.json | 1 + src/locales/ja.json | 1 + src/locales/kn.json | 1 + src/locales/ko.json | 1 + src/locales/nl.json | 1 + src/locales/pt_BR.json | 1 + src/locales/ru.json | 1 + src/locales/th.json | 1 + src/locales/tr.json | 1 + src/locales/uk.json | 1 + src/locales/vi.json | 1 + src/locales/zh_Hans.json | 1 + src/locales/zh_Hant.json | 1 + .../tabs/ImportTransactionDefineColumnTab.vue | 56 ++++++++++++++++--- 20 files changed, 86 insertions(+), 11 deletions(-) diff --git a/pkg/converters/dsv/custom_transaction_plain_text_data_table.go b/pkg/converters/dsv/custom_transaction_plain_text_data_table.go index 4d4fdc41..b7f69945 100644 --- a/pkg/converters/dsv/custom_transaction_plain_text_data_table.go +++ b/pkg/converters/dsv/custom_transaction_plain_text_data_table.go @@ -107,6 +107,7 @@ func (t *customPlainTextDataRowIterator) Next(ctx core.Context, user *models.Use func (t *customPlainTextDataRowIterator) parseTransaction(ctx core.Context, user *models.User, row datatable.BasicDataTableRow) (map[datatable.TransactionDataTableColumn]string, bool, error) { rowData := make(map[datatable.TransactionDataTableColumn]string, len(t.transactionDataTable.columnIndexMapping)) + var transactionTime *time.Time = nil for column, columnIndex := range t.transactionDataTable.columnIndexMapping { if columnIndex < 0 || columnIndex >= row.ColumnCount() { @@ -144,6 +145,7 @@ func (t *customPlainTextDataRowIterator) parseTransaction(ctx core.Context, user return nil, false, errs.ErrTransactionTimeInvalid } + transactionTime = &dateTime rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIME] = utils.FormatUnixTimeToLongDateTime(dateTime.Unix(), dateTime.Location()) if t.transactionDataTable.timeFormatIncludeTimezone { @@ -164,6 +166,19 @@ func (t *customPlainTextDataRowIterator) parseTransaction(ctx core.Context, user timezone = timezone[:3] + ":" + timezone[3:] rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = timezone + } else if t.transactionDataTable.timezoneFormat == "zzz" { // IANA Timezone Name + timezoneName := rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] + timezone, err := time.LoadLocation(timezoneName) + + if err != nil { + return nil, false, errs.ErrTransactionTimeZoneInvalid + } + + if transactionTime == nil { + return nil, false, errs.ErrTransactionTimeInvalid + } + + rowData[datatable.TRANSACTION_DATA_TABLE_TRANSACTION_TIMEZONE] = utils.FormatTimezoneOffset(transactionTime.Unix(), timezone) } else { return nil, false, errs.ErrImportFileTransactionTimezoneFormatInvalid } diff --git a/src/core/timezone.ts b/src/core/timezone.ts index a429a942..be2a1db5 100644 --- a/src/core/timezone.ts +++ b/src/core/timezone.ts @@ -17,15 +17,18 @@ export class KnownDateTimezoneFormat implements NameValue { private static readonly allInstances: KnownDateTimezoneFormat[] = []; private static readonly allInstancesByValue: Record = {}; - public static readonly HHColonMM = new KnownDateTimezoneFormat('±HH:mm', 'Z', /^[+-]?([0-1][0-9]|2[0-3]):[0-5][0-9]$/); - public static readonly HHMM = new KnownDateTimezoneFormat('±HHmm', 'ZZ', /^[+-]?([0-1][0-9]|2[0-3])[0-5][0-9]$/); + public static readonly HHColonMM = new KnownDateTimezoneFormat('±HH:mm', false, 'Z', /^[+-]?([0-1][0-9]|2[0-3]):[0-5][0-9]$/); + public static readonly HHMM = new KnownDateTimezoneFormat('±HHmm', false, 'ZZ', /^[+-]?([0-1][0-9]|2[0-3])[0-5][0-9]$/); + public static readonly IANAName = new KnownDateTimezoneFormat('IANA Time Zone Name', true, 'zzz', /^[A-Za-z_]+\/[A-Za-z_]+(\/[A-Za-z_]+)?$/); public readonly name: string; + public readonly nameSupportedI18n: boolean; public readonly value: string; private readonly regex: RegExp; - private constructor(name: string, value: string, regex: RegExp) { + private constructor(name: string, nameSupportedI18n: boolean, value: string, regex: RegExp) { this.name = name; + this.nameSupportedI18n = nameSupportedI18n; this.value = value; this.regex = regex; diff --git a/src/locales/de.json b/src/locales/de.json index 42a620ab..a2ca92a8 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -1937,6 +1937,7 @@ "Time Format": "Time Format", "Transaction Type Mapping": "Transaction Type Mapping", "Timezone Format": "Timezone Format", + "IANA Time Zone Name": "IANA Time Zone Name", "Amount Format": "Amount Format", "Geographic Location Separator": "Geographic Location Separator", "Transaction Tags Separator": "Transaction Tags Separator", diff --git a/src/locales/en.json b/src/locales/en.json index 21e26c81..5b6cf21c 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1937,6 +1937,7 @@ "Time Format": "Time Format", "Transaction Type Mapping": "Transaction Type Mapping", "Timezone Format": "Timezone Format", + "IANA Time Zone Name": "IANA Time Zone Name", "Amount Format": "Amount Format", "Geographic Location Separator": "Geographic Location Separator", "Transaction Tags Separator": "Transaction Tags Separator", diff --git a/src/locales/es.json b/src/locales/es.json index a9ba5673..b15d15bf 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -1937,6 +1937,7 @@ "Time Format": "Formato del Tiempo", "Transaction Type Mapping": "Asignación de Tipos de Transacciones", "Timezone Format": "Formato de la Zona Horaria", + "IANA Time Zone Name": "IANA Time Zone Name", "Amount Format": "Formato de la Cantidad", "Geographic Location Separator": "Separador de Ubicación Geográfica", "Transaction Tags Separator": "Separador de Etiquetas de Transacciones", diff --git a/src/locales/fr.json b/src/locales/fr.json index 2b8d28d5..d7d05e7c 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -1937,6 +1937,7 @@ "Time Format": "Format d'heure", "Transaction Type Mapping": "Mappage du type de transaction", "Timezone Format": "Format de fuseau horaire", + "IANA Time Zone Name": "IANA Time Zone Name", "Amount Format": "Format de montant", "Geographic Location Separator": "Séparateur de localisation géographique", "Transaction Tags Separator": "Séparateur d'étiquettes de transaction", diff --git a/src/locales/it.json b/src/locales/it.json index 78f67eb8..a91fb45e 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -1937,6 +1937,7 @@ "Time Format": "Formato ora", "Transaction Type Mapping": "Mappatura tipo transazione", "Timezone Format": "Formato fuso orario", + "IANA Time Zone Name": "IANA Time Zone Name", "Amount Format": "Formato importo", "Geographic Location Separator": "Separatore posizione geografica", "Transaction Tags Separator": "Separatore tag transazione", diff --git a/src/locales/ja.json b/src/locales/ja.json index d587c13c..d0c3e34f 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -1937,6 +1937,7 @@ "Time Format": "時刻形式", "Transaction Type Mapping": "取引タイプのマッピング", "Timezone Format": "タイムゾーン形式", + "IANA Time Zone Name": "IANA Time Zone Name", "Amount Format": "Amount Format", "Geographic Location Separator": "地理座標の区切り", "Transaction Tags Separator": "取引タグの区切り", diff --git a/src/locales/kn.json b/src/locales/kn.json index 5a414c95..f759423e 100644 --- a/src/locales/kn.json +++ b/src/locales/kn.json @@ -1937,6 +1937,7 @@ "Time Format": "ಸಮಯ ರೂಪ", "Transaction Type Mapping": "ವಹಿವಾಟು ಪ್ರಕಾರ ಮ್ಯಾಪಿಂಗ್", "Timezone Format": "ಸಮಯ ವಲಯದ ರೂಪ", + "IANA Time Zone Name": "IANA Time Zone Name", "Amount Format": "ಮೊತ್ತದ ರೂಪ", "Geographic Location Separator": "ಭೌಗೋಳಿಕ ಸ್ಥಳ ವಿಭಜಕ", "Transaction Tags Separator": "ವಹಿವಾಟು ಟ್ಯಾಗ್‌ಗಳ ವಿಭಜಕ", diff --git a/src/locales/ko.json b/src/locales/ko.json index cb7cb877..3b2b80ae 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -1937,6 +1937,7 @@ "Time Format": "시간 형식", "Transaction Type Mapping": "거래 유형 매핑", "Timezone Format": "시간대 형식", + "IANA Time Zone Name": "IANA Time Zone Name", "Amount Format": "금액 형식", "Geographic Location Separator": "지리적 위치 구분 기호", "Transaction Tags Separator": "거래 태그 구분 기호", diff --git a/src/locales/nl.json b/src/locales/nl.json index 8ff54416..8322bce8 100644 --- a/src/locales/nl.json +++ b/src/locales/nl.json @@ -1937,6 +1937,7 @@ "Time Format": "Tijdsformaat", "Transaction Type Mapping": "Transactietypetoewijzing", "Timezone Format": "Tijdzoneformaat", + "IANA Time Zone Name": "IANA Time Zone Name", "Amount Format": "Bedragformaat", "Geographic Location Separator": "Scheidingsteken geografische locatie", "Transaction Tags Separator": "Scheidingsteken transactietags", diff --git a/src/locales/pt_BR.json b/src/locales/pt_BR.json index 68ba7ded..f1577223 100644 --- a/src/locales/pt_BR.json +++ b/src/locales/pt_BR.json @@ -1937,6 +1937,7 @@ "Time Format": "Formato de Tempo", "Transaction Type Mapping": "Mapeamento de Tipo de Transação", "Timezone Format": "Formato de Fuso Horário", + "IANA Time Zone Name": "IANA Time Zone Name", "Amount Format": "Formato de Quantia", "Geographic Location Separator": "Separador de Localização Geográfica", "Transaction Tags Separator": "Separador de Tags de Transação", diff --git a/src/locales/ru.json b/src/locales/ru.json index aefa83bb..a1f67fec 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -1937,6 +1937,7 @@ "Time Format": "Time Format", "Transaction Type Mapping": "Transaction Type Mapping", "Timezone Format": "Timezone Format", + "IANA Time Zone Name": "IANA Time Zone Name", "Amount Format": "Amount Format", "Geographic Location Separator": "Geographic Location Separator", "Transaction Tags Separator": "Transaction Tags Separator", diff --git a/src/locales/th.json b/src/locales/th.json index 06f41794..914c8ecf 100644 --- a/src/locales/th.json +++ b/src/locales/th.json @@ -1937,6 +1937,7 @@ "Time Format": "รูปแบบเวลา", "Transaction Type Mapping": "การแมปประเภทรายการ", "Timezone Format": "รูปแบบเขตเวลา", + "IANA Time Zone Name": "IANA Time Zone Name", "Amount Format": "รูปแบบจำนวนเงิน", "Geographic Location Separator": "ตัวคั่นตำแหน่งทางภูมิศาสตร์", "Transaction Tags Separator": "ตัวคั่นแท็กรายการ", diff --git a/src/locales/tr.json b/src/locales/tr.json index 594f8b76..9f2beaea 100644 --- a/src/locales/tr.json +++ b/src/locales/tr.json @@ -1937,6 +1937,7 @@ "Time Format": "Zaman Formatı", "Transaction Type Mapping": "İşlem Türü Eşlemesi", "Timezone Format": "Saat Dilimi Formatı", + "IANA Time Zone Name": "IANA Time Zone Name", "Amount Format": "Tutar Formatı", "Geographic Location Separator": "Coğrafi Konum Ayırıcı", "Transaction Tags Separator": "İşlem Etiketleri Ayırıcı", diff --git a/src/locales/uk.json b/src/locales/uk.json index 44904612..5ca586c9 100644 --- a/src/locales/uk.json +++ b/src/locales/uk.json @@ -1937,6 +1937,7 @@ "Time Format": "Формат часу", "Transaction Type Mapping": "Відповідність типів транзакцій", "Timezone Format": "Формат часового поясу", + "IANA Time Zone Name": "IANA Time Zone Name", "Amount Format": "Формат суми", "Geographic Location Separator": "Роздільник геолокацій", "Transaction Tags Separator": "Роздільник тегів транзакцій", diff --git a/src/locales/vi.json b/src/locales/vi.json index ede1a532..c8886d25 100644 --- a/src/locales/vi.json +++ b/src/locales/vi.json @@ -1937,6 +1937,7 @@ "Time Format": "Time Format", "Transaction Type Mapping": "Transaction Type Mapping", "Timezone Format": "Timezone Format", + "IANA Time Zone Name": "IANA Time Zone Name", "Amount Format": "Amount Format", "Geographic Location Separator": "Geographic Location Separator", "Transaction Tags Separator": "Transaction Tags Separator", diff --git a/src/locales/zh_Hans.json b/src/locales/zh_Hans.json index 2994f6b4..25bc8866 100644 --- a/src/locales/zh_Hans.json +++ b/src/locales/zh_Hans.json @@ -1938,6 +1938,7 @@ "Amount Format": "金额格式", "Transaction Type Mapping": "交易类型映射", "Timezone Format": "时区格式", + "IANA Time Zone Name": "IANA 时区名称", "Geographic Location Separator": "地理位置分隔符", "Transaction Tags Separator": "交易标签分隔符", "Lines Per Page": "每页行数", diff --git a/src/locales/zh_Hant.json b/src/locales/zh_Hant.json index bc2b92f0..9030c6c9 100644 --- a/src/locales/zh_Hant.json +++ b/src/locales/zh_Hant.json @@ -1938,6 +1938,7 @@ "Amount Format": "金額格式", "Transaction Type Mapping": "交易類型對應", "Timezone Format": "時區格式", + "IANA Time Zone Name": "IANA 時區名稱", "Geographic Location Separator": "地理位置分隔符號", "Transaction Tags Separator": "交易標籤分隔符號", "Lines Per Page": "每頁行數", diff --git a/src/views/desktop/transactions/import/tabs/ImportTransactionDefineColumnTab.vue b/src/views/desktop/transactions/import/tabs/ImportTransactionDefineColumnTab.vue index 21e2fd47..6dacff34 100644 --- a/src/views/desktop/transactions/import/tabs/ImportTransactionDefineColumnTab.vue +++ b/src/views/desktop/transactions/import/tabs/ImportTransactionDefineColumnTab.vue @@ -85,8 +85,7 @@ @click="parsedFileDataColumnMapping.timeFormat = ''"> {{ tt('Auto detect') }} - ({{ parsedFileAutoDetectedTimeFormat }}) - ({{ tt('Unknown') }}) + ({{ parsedFileAutoDetectedTimeFormat || tt('Unknown') }}) {{ tt('Timezone Format') }} - ({{ KnownDateTimezoneFormat.valueOf(parsedFileDataColumnMapping.timezoneFormat || parsedFileAutoDetectedTimezoneFormat || '')?.name || tt('Unknown') }}) + ({{ displayFileCurrentTimezoneFormat }}) {{ tt('Auto detect') }} - ({{ KnownDateTimezoneFormat.valueOf(parsedFileAutoDetectedTimezoneFormat || '')?.name }}) - ({{ tt('Unknown') }}) + ({{ displayFileAutoDetectedTimezoneFormat }}) - {{ timezoneFormat.name }} + {{ ti(timezoneFormat.name, timezoneFormat.nameSupportedI18n) }} @@ -137,8 +135,7 @@ @click="parsedFileDataColumnMapping.amountFormat = ''"> {{ tt('Auto detect') }} - ({{ KnownAmountFormat.valueOf(parsedFileAutoDetectedAmountFormat || '')?.format }}) - ({{ tt('Unknown') }}) + ({{ displayFileAutoDetectedAmountFormat }}) (() => pars const parsedFileAutoDetectedTimezoneFormat = computed(() => parsedFileDataColumnMapping.value.parseFileAutoDetectedTimezoneFormat(props.parsedFileData)); const parsedFileAutoDetectedAmountFormat = computed(() => parsedFileDataColumnMapping.value.parseFileAutoDetectedAmountFormat(props.parsedFileData)); +const displayFileCurrentTimezoneFormat = computed(() => { + const format = parsedFileDataColumnMapping.value.timezoneFormat || parsedFileAutoDetectedTimezoneFormat.value || ''; + + if (format) { + const knownFormat = KnownDateTimezoneFormat.valueOf(format); + + if (knownFormat) { + return ti(knownFormat.name, knownFormat.nameSupportedI18n); + } + } + + return tt('Unknown'); +}); + +const displayFileAutoDetectedTimezoneFormat = computed(() => { + const format = parsedFileAutoDetectedTimezoneFormat.value; + + if (format) { + const knownFormat = KnownDateTimezoneFormat.valueOf(format); + + if (knownFormat) { + return ti(knownFormat.name, knownFormat.nameSupportedI18n); + } + } + + return tt('Unknown'); +}); + +const displayFileAutoDetectedAmountFormat = computed(() => { + const format = parsedFileAutoDetectedAmountFormat.value; + + if (format) { + const knownFormat = KnownAmountFormat.valueOf(format); + + if (knownFormat) { + return knownFormat.format; + } + } + + return tt('Unknown'); +}); + function getDisplayCount(count: number): string { return numeralSystem.value.formatNumber(count); }