diff --git a/package-lock.json b/package-lock.json
index f835e031..1879df7a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9017,6 +9017,14 @@
"lodash.difference": "^4.5.0"
}
},
+ "moment-timezone": {
+ "version": "0.5.33",
+ "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.33.tgz",
+ "integrity": "sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w==",
+ "requires": {
+ "moment": ">= 2.9.0"
+ }
+ },
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
diff --git a/package.json b/package.json
index 292c2fcf..1178ff70 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"js-cookie": "^2.2.1",
"line-awesome": "^1.3.0",
"moment": "^2.29.1",
+ "moment-timezone": "^0.5.33",
"register-service-worker": "^1.7.2",
"ua-parser-js": "^0.7.24",
"vue": "^2.6.12",
diff --git a/pkg/exchangerates/euro_central_bank_datasource.go b/pkg/exchangerates/euro_central_bank_datasource.go
index 9f4b325d..9483d1a4 100644
--- a/pkg/exchangerates/euro_central_bank_datasource.go
+++ b/pkg/exchangerates/euro_central_bank_datasource.go
@@ -2,6 +2,7 @@ package exchangerates
import (
"encoding/xml"
+ "time"
"github.com/mayswind/lab/pkg/core"
"github.com/mayswind/lab/pkg/errs"
@@ -14,6 +15,9 @@ const euroCentralBankExchangeRateReferenceUrl = "https://www.ecb.europa.eu/stats
const euroCentralBankDataSource = "European Central Bank"
const euroCentralBankBaseCurrency = "EUR"
+const euroCentralBankDataUpdateDateFormat = "2006-01-02 15"
+const euroCentralBankDataUpdateDateTimezone = "Etc/GMT-1" // UTC+01:00
+
// EuroCentralBankDataSource defines the structure of exchange rates data source of euro central bank
type EuroCentralBankDataSource struct {
ExchangeRatesDataSource
@@ -55,10 +59,23 @@ func (e *EuroCentralBankExchangeRateData) ToLatestExchangeRateResponse() *models
exchangeRates[i] = latestEuroCentralBankExchangeRate.ExchangeRates[i].ToLatestExchangeRate()
}
+ timezone, err := time.LoadLocation(euroCentralBankDataUpdateDateTimezone)
+
+ if err != nil {
+ return nil
+ }
+
+ updateDateTime := latestEuroCentralBankExchangeRate.Date + " 16" // The reference rates are usually updated around 16:00 CET on every working day
+ updateTime, err := time.ParseInLocation(euroCentralBankDataUpdateDateFormat, updateDateTime, timezone)
+
+ if err != nil {
+ return nil
+ }
+
latestExchangeRateResp := &models.LatestExchangeRateResponse{
DataSource: euroCentralBankDataSource,
ReferenceUrl: euroCentralBankExchangeRateReferenceUrl,
- Date: latestEuroCentralBankExchangeRate.Date,
+ UpdateTime: updateTime.Unix(),
BaseCurrency: euroCentralBankBaseCurrency,
ExchangeRates: exchangeRates,
}
diff --git a/pkg/models/exchange_rate.go b/pkg/models/exchange_rate.go
index 328b3359..28319fbd 100644
--- a/pkg/models/exchange_rate.go
+++ b/pkg/models/exchange_rate.go
@@ -4,7 +4,7 @@ package models
type LatestExchangeRateResponse struct {
DataSource string `json:"dataSource"`
ReferenceUrl string `json:"referenceUrl"`
- Date string `json:"date"`
+ UpdateTime int64 `json:"updateTime"`
BaseCurrency string `json:"baseCurrency"`
ExchangeRates []*LatestExchangeRate `json:"exchangeRates"`
}
diff --git a/src/components/mobile/DateRangeSelectionSheet.vue b/src/components/mobile/DateRangeSelectionSheet.vue
index c7f4578a..0dd75448 100644
--- a/src/components/mobile/DateRangeSelectionSheet.vue
+++ b/src/components/mobile/DateRangeSelectionSheet.vue
@@ -52,7 +52,7 @@ export default {
data() {
const self = this;
let minDate = self.$utilities.getTodayFirstUnixTime();
- let maxDate = self.$utilities.getUnixTime(new Date());
+ let maxDate = self.$utilities.getCurrentUnixTime();
if (self.minTime) {
minDate = self.minTime;
@@ -70,12 +70,12 @@ export default {
watch: {
'currentMinDate': function (newValue) {
if (!newValue) {
- this.currentMinDate = this.$utilities.formatDate(new Date(), 'YYYY-MM-DDTHH:mm');
+ this.currentMinDate = this.$utilities.formatUnixTime(this.$utilities.getCurrentUnixTime(), 'YYYY-MM-DDTHH:mm');
}
},
'currentMaxDate': function (newValue) {
if (!newValue) {
- this.currentMaxDate = this.$utilities.formatDate(new Date(), 'YYYY-MM-DDTHH:mm');
+ this.currentMaxDate = this.$utilities.formatUnixTime(this.$utilities.getCurrentUnixTime(), 'YYYY-MM-DDTHH:mm');
}
}
},
diff --git a/src/lib/settings.js b/src/lib/settings.js
index b189bd08..0d086339 100644
--- a/src/lib/settings.js
+++ b/src/lib/settings.js
@@ -8,6 +8,7 @@ const serverSettingsCookieKey = 'lab_server_settings';
const defaultSettings = {
lang: 'en',
+ timeZone: '',
debug: false,
applicationLock: false,
applicationLockWebAuthn: false,
@@ -128,6 +129,8 @@ export default {
isProduction: () => process.env.NODE_ENV === 'production',
getLanguage: () => getOriginalOption('lang'),
setLanguage: value => setOption('lang', value),
+ getTimezone: () => getOption('timeZone'),
+ setTimezone: value => setOption('timeZone', value),
isEnableDebug: () => getOption('debug'),
setEnableDebug: value => setOption('debug', value),
isEnableApplicationLock: () => getOption('applicationLock'),
diff --git a/src/lib/utils.js b/src/lib/utils.js
index abfdda06..b7c7e992 100644
--- a/src/lib/utils.js
+++ b/src/lib/utils.js
@@ -34,12 +34,20 @@ function isBoolean(val) {
return typeof(val) === 'boolean';
}
-function parseDateFromUnixTime(unixTime) {
- return moment.unix(unixTime);
+function getTimezoneOffset(timezone) {
+ if (timezone) {
+ return moment().tz(timezone).format('Z');
+ } else {
+ return moment().format('Z');
+ }
}
-function formatDate(date, format) {
- return moment(date).format(format);
+function getCurrentUnixTime() {
+ return moment().unix();
+}
+
+function parseDateFromUnixTime(unixTime) {
+ return moment.unix(unixTime);
}
function formatUnixTime(unixTime, format) {
@@ -107,7 +115,7 @@ function getMinuteLastUnixTime(date) {
}
function getTodayFirstUnixTime() {
- return moment({ hour: 0, minute: 0, second: 0, millisecond: 0 }).unix();
+ return moment().set({ hour: 0, minute: 0, second: 0, millisecond: 0 }).unix();
}
function getTodayLastUnixTime() {
@@ -562,8 +570,9 @@ export default {
isString,
isNumber,
isBoolean,
+ getTimezoneOffset,
+ getCurrentUnixTime,
parseDateFromUnixTime,
- formatDate,
formatUnixTime,
getUnixTime,
getYear,
diff --git a/src/locales/en.js b/src/locales/en.js
index 247a9c5f..41150fa1 100644
--- a/src/locales/en.js
+++ b/src/locales/en.js
@@ -680,6 +680,8 @@ export default {
'Filter Transaction Categories': 'Filter Transaction Categories',
'User Profile': 'User Profile',
'Language': 'Language',
+ 'Timezone': 'Timezone',
+ 'System Default': 'System Default',
'Auto Update Exchange Rates Data': 'Auto Update Exchange Rates Data',
'Enable Thousands Separator': 'Enable Thousands Separator',
'Currency Display Mode': 'Currency Display Mode',
diff --git a/src/locales/zh_Hans.js b/src/locales/zh_Hans.js
index eaaafb35..1a068867 100644
--- a/src/locales/zh_Hans.js
+++ b/src/locales/zh_Hans.js
@@ -680,6 +680,8 @@ export default {
'Filter Transaction Categories': '过滤交易类型',
'User Profile': '用户信息',
'Language': '语言',
+ 'Timezone': '时区',
+ 'System Default': '系统默认',
'Auto Update Exchange Rates Data': '自动更新汇率数据',
'Enable Thousands Separator': '启用千位分隔符',
'Currency Display Mode': '货币显示模式',
diff --git a/src/mobile-main.js b/src/mobile-main.js
index 0db26ec9..ecd0937e 100644
--- a/src/mobile-main.js
+++ b/src/mobile-main.js
@@ -6,7 +6,7 @@ import PincodeInput from 'vue-pincode-input';
import VueMoment from 'vue-moment';
import VueClipboard from 'vue-clipboard2';
-import moment from 'moment';
+import moment from 'moment-timezone';
import Framework7 from 'framework7/framework7-lite.esm.js';
import Framework7Dialog from 'framework7/components/dialog/dialog';
@@ -186,6 +186,7 @@ Vue.prototype.$logger = logger;
Vue.prototype.$webauthn = webauthn;
Vue.prototype.$settings = settings;
Vue.prototype.$locale = {
+ defaultTimezoneOffset: utils.getTimezoneOffset(),
getDefaultLanguage: getDefaultLanguage,
getAllLanguages: getAllLanguages,
getLanguage: getLanguage,
@@ -200,6 +201,31 @@ Vue.prototype.$locale = {
document.querySelector('html').setAttribute('lang', locale);
return locale;
},
+ getTimezone: function () {
+ return settings.getTimezone();
+ },
+ setTimezone: function (timezone) {
+ if (timezone) {
+ settings.setTimezone(timezone);
+ moment.tz.setDefault(timezone);
+ } else {
+ settings.setTimezone('');
+ moment.tz.setDefault();
+ }
+ },
+ getAllTimezones: function () {
+ const allTimezoneNames = moment.tz.names();
+ const allTimezoneInfos = [];
+
+ for (let i = 0; i < allTimezoneNames.length; i++) {
+ allTimezoneInfos.push({
+ name: allTimezoneNames[i],
+ displayName: `(UTC${utils.getTimezoneOffset(allTimezoneNames[i])}) ${allTimezoneNames[i]}`
+ });
+ }
+
+ return allTimezoneInfos;
+ },
getAllCurrencies: function () {
const allCurrencyCodes = currency.all;
const allCurrencies = [];
@@ -229,6 +255,13 @@ Vue.prototype.$locale = {
logger.info(`No language is set, use browser default ${getDefaultLanguage()}`);
this.setLanguage(getDefaultLanguage());
}
+
+ if (settings.getTimezone()) {
+ logger.info(`Current timezone is ${settings.getTimezone()}`);
+ this.setTimezone(settings.getTimezone());
+ } else {
+ logger.info(`No timezone is set, use browser default ${utils.getTimezoneOffset()} (maybe ${moment.tz.guess(true)})`);
+ }
}
};
diff --git a/src/store/exchangeRates.js b/src/store/exchangeRates.js
index e72ca5b1..37583a8c 100644
--- a/src/store/exchangeRates.js
+++ b/src/store/exchangeRates.js
@@ -10,16 +10,16 @@ const exchangeRatesLocalStorageKey = 'lab_app_exchange_rates';
export function getLatestExchangeRates(context, { silent, force }) {
const currentExchangeRateData = context.state.latestExchangeRates;
- const now = new Date();
+ const now = utils.getCurrentUnixTime();
if (!force) {
if (currentExchangeRateData && currentExchangeRateData.time && currentExchangeRateData.data &&
- currentExchangeRateData.data.date === utils.formatDate(now, 'YYYY-MM-DD')) {
+ utils.formatUnixTime(currentExchangeRateData.data.updateTime, 'YYYY-MM-DD') === utils.formatUnixTime(now, 'YYYY-MM-DD')) {
return currentExchangeRateData.data;
}
if (currentExchangeRateData && currentExchangeRateData.time && currentExchangeRateData.data &&
- utils.formatUnixTime(currentExchangeRateData.time, 'YYYY-MM-DD HH') === utils.formatDate(now, 'YYYY-MM-DD HH')) {
+ utils.formatUnixTime(currentExchangeRateData.time, 'YYYY-MM-DD HH') === utils.formatUnixTime(now, 'YYYY-MM-DD HH')) {
return currentExchangeRateData.data;
}
}
@@ -36,7 +36,7 @@ export function getLatestExchangeRates(context, { silent, force }) {
}
context.commit(STORE_LATEST_EXCHANGE_RATES, {
- time: utils.getUnixTime(now),
+ time: now,
data: data.result
});
@@ -55,9 +55,9 @@ export function getLatestExchangeRates(context, { silent, force }) {
});
}
-export function exchangeRatesLastUpdateDate(state) {
+export function exchangeRatesLastUpdateTime(state) {
const exchangeRates = state.latestExchangeRates || {};
- return exchangeRates && exchangeRates.data ? exchangeRates.data.date : null;
+ return exchangeRates && exchangeRates.data ? exchangeRates.data.updateTime : null;
}
export function getExchangedAmount(state) {
diff --git a/src/store/index.js b/src/store/index.js
index 70716a12..7cbb0e61 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -85,7 +85,7 @@ import {
import {
getLatestExchangeRates,
- exchangeRatesLastUpdateDate,
+ exchangeRatesLastUpdateTime,
getExchangedAmount,
getExchangeRatesFromLocalStorage,
setExchangeRatesToLocalStorage,
@@ -198,7 +198,7 @@ const stores = {
currentUserFirstDayOfWeek,
// exchange rates
- exchangeRatesLastUpdateDate,
+ exchangeRatesLastUpdateTime,
getExchangedAmount,
// account
diff --git a/src/views/mobile/Settings.vue b/src/views/mobile/Settings.vue
index dfc77cad..522d6319 100644
--- a/src/views/mobile/Settings.vue
+++ b/src/views/mobile/Settings.vue
@@ -24,7 +24,7 @@
+ smart-select :smart-select-params="{ openIn: 'sheet', closeOnSelect: true, sheetCloseLinkText: $t('Done'), scrollToSelectedItem: true }">
+
+
+
+
@@ -104,6 +115,12 @@ export default {
allLanguages() {
return this.$locale.getAllLanguages();
},
+ allTimezones() {
+ return this.$locale.getAllTimezones();
+ },
+ defaultTimezoneOffset() {
+ return this.$locale.defaultTimezoneOffset;
+ },
currentLocale: {
get: function () {
return this.$i18n.locale;
@@ -112,6 +129,14 @@ export default {
this.$locale.setLanguage(value);
}
},
+ currentTimezone: {
+ get: function () {
+ return this.$locale.getTimezone();
+ },
+ set: function (value) {
+ this.$locale.setTimezone(value);
+ }
+ },
currentNickName() {
return this.$store.getters.currentUserNickname || this.$t('User');
},
@@ -119,8 +144,8 @@ export default {
return this.$settings.isDataExportingEnabled();
},
exchangeRatesLastUpdateDate() {
- const exchangeRatesLastUpdateDate = this.$store.getters.exchangeRatesLastUpdateDate;
- return exchangeRatesLastUpdateDate ? this.$utilities.formatDate(exchangeRatesLastUpdateDate, this.$t('format.date.long')) : '';
+ const exchangeRatesLastUpdateTime = this.$store.getters.exchangeRatesLastUpdateTime;
+ return exchangeRatesLastUpdateTime ? this.$utilities.formatUnixTime(exchangeRatesLastUpdateTime, this.$t('format.date.long')) : '';
},
isAutoUpdateExchangeRatesData: {
get: function () {
diff --git a/src/views/mobile/transactions/Edit.vue b/src/views/mobile/transactions/Edit.vue
index ffb8eec2..692ded41 100644
--- a/src/views/mobile/transactions/Edit.vue
+++ b/src/views/mobile/transactions/Edit.vue
@@ -250,7 +250,7 @@ export default {
data() {
const self = this;
const query = self.$f7route.query;
- const now = new Date();
+ const now = self.$utilities.getCurrentUnixTime();
let defaultType = self.$constants.transaction.allTransactionTypes.Expense;
@@ -265,8 +265,8 @@ export default {
editTransactionId: null,
transaction: {
type: defaultType,
- unixTime: self.$utilities.getUnixTime(now),
- time: self.$utilities.formatDate(now, 'YYYY-MM-DDTHH:mm'),
+ unixTime: now,
+ time: self.$utilities.formatUnixTime(now, 'YYYY-MM-DDTHH:mm'),
expenseCategory: '',
incomeCategory: '',
transferCategory: '',
@@ -488,7 +488,7 @@ export default {
},
'transaction.time': function (newValue) {
if (!newValue) {
- newValue = this.$utilities.formatDate(new Date(), 'YYYY-MM-DDTHH:mm');
+ newValue = this.$utilities.formatUnixTime(this.$utilities.getCurrentUnixTime(), 'YYYY-MM-DDTHH:mm');
this.transaction.time = newValue;
}