From fc16f3c268a9d35f889f5770331d11e87ab16af3 Mon Sep 17 00:00:00 2001 From: Sukanya Pandey Date: Tue, 23 Jun 2020 22:54:27 +0530 Subject: Add timezone to profile settings page - Users will have two options to select a timezone. - UTC and browser offset timezone are the two options for the application. - date-fns and date-fns-tz is used for date and time manipulations because:- - The package size of library is smaller. - It allows for importing functions to work with the native date object rather than having to create a moment instance that carries a larger payload. Signed-off-by: Sukanya Pandey Change-Id: I581803f230f501c0d34d0b53e7c2d89e8466ee60 --- package-lock.json | 20 ++++-- package.json | 2 + src/locales/en-US.json | 12 +++- src/main.js | 39 +++++++++--- src/store/modules/GlobalStore.js | 7 ++- src/views/ProfileSettings/ProfileSettings.vue | 89 +++++++++++++++++++++------ 6 files changed, 134 insertions(+), 35 deletions(-) diff --git a/package-lock.json b/package-lock.json index 03fb44fd..98709b26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6004,10 +6004,14 @@ } }, "date-fns": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", - "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", - "dev": true + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.14.0.tgz", + "integrity": "sha512-1zD+68jhFgDIM0rF05rcwYO8cExdNqxjq4xP1QKM60Q45mnO6zaMWB4tOzrIr4M4GSLntsKeE4c9Bdl2jhL/yw==" + }, + "date-fns-tz": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-1.0.10.tgz", + "integrity": "sha512-cHQAz0/9uDABaUNDM80Mj1FL4ODlxs1xEY4b0DQuAooO2UdNKvDkNbV8ogLnxLbv02Ru1HXFcot0pVvDRBgptg==" }, "de-indent": { "version": "1.0.2", @@ -11802,6 +11806,14 @@ "cli-cursor": "^2.1.0", "date-fns": "^1.27.2", "figures": "^2.0.0" + }, + "dependencies": { + "date-fns": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", + "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", + "dev": true + } } }, "load-json-file": { diff --git a/package.json b/package.json index 115937bf..ae920659 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,8 @@ "bootstrap": "4.4.1", "bootstrap-vue": "2.12.0", "core-js": "3.3.2", + "date-fns": "2.14.0", + "date-fns-tz": "1.0.10", "js-cookie": "2.2.1", "lodash": "4.17.19", "vue": "2.6.11", diff --git a/src/locales/en-US.json b/src/locales/en-US.json index 023efa55..63c75367 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -367,14 +367,22 @@ "solConsole": "Serial over LAN console" } }, - "profileSettings": { + "pageProfileSettings": { + "browserOffset": "Browser offset (%{timezone})", "changePassword": "Change password", "confirmPassword": "Confirm new password", + "defaultUTC": "Default (UTC)", "newPassword": "New password", "newPassLabelTextInfo": "Password must be between %{min} - %{max} characters", "passwordsDoNotMatch": "Passwords do not match", "profileInfoTitle": "Profile information", - "username": "Username" + "timezone": "Timezone", + "timezoneDisplay": "Timezone display preference", + "timezoneDisplayDesc": "Select how time is displayed throughout the application", + "username": "Username", + "toast": { + "successSaveSettings": "Successfully saved account settings." + } }, "pageManagePowerUsage": { "description": "Set a power cap to keep power consumption at or below the specified value in watts", diff --git a/src/main.js b/src/main.js index 0c6d5b07..8336cb3d 100644 --- a/src/main.js +++ b/src/main.js @@ -33,23 +33,46 @@ import { } from 'bootstrap-vue'; import Vuelidate from 'vuelidate'; import i18n from './i18n'; +import { format } from 'date-fns-tz'; // Filters +Vue.filter('shortTimeZone', function(value) { + const longTZ = value + .toString() + .match(/\((.*)\)/) + .pop(); + const regexNotUpper = /[*a-z ]/g; + return longTZ.replace(regexNotUpper, ''); +}); + Vue.filter('formatDate', function(value) { + const isUtcDisplay = store.getters['global/isUtcDisplay']; + if (value instanceof Date) { - return value.toISOString().substring(0, 10); + if (isUtcDisplay) { + return value.toISOString().substring(0, 10); + } + const pattern = `yyyy-MM-dd`; + const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + return format(value, pattern, { timezone }); } }); Vue.filter('formatTime', function(value) { - const timeOptions = { - hour: 'numeric', - minute: 'numeric', - second: 'numeric', - timeZoneName: 'short' - }; + const isUtcDisplay = store.getters['global/isUtcDisplay']; + if (value instanceof Date) { - return value.toLocaleTimeString('default', timeOptions); + if (isUtcDisplay) { + let timeOptions = { + timeZone: 'UTC', + hour12: false + }; + return `${value.toLocaleTimeString('default', timeOptions)} UTC`; + } + const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + const shortTz = Vue.filter('shortTimeZone')(value); + const pattern = `HH:mm:ss ('${shortTz}' O)`; + return format(value, pattern, { timezone }).replace('GMT', 'UTC'); } }); diff --git a/src/store/modules/GlobalStore.js b/src/store/modules/GlobalStore.js index 55b07965..39f3d1d4 100644 --- a/src/store/modules/GlobalStore.js +++ b/src/store/modules/GlobalStore.js @@ -32,12 +32,16 @@ const GlobalStore = { bmcTime: null, hostStatus: 'unreachable', languagePreference: localStorage.getItem('storedLanguage') || 'en-US', + isUtcDisplay: localStorage.getItem('storedUtcDisplay') + ? JSON.parse(localStorage.getItem('storedUtcDisplay')) + : true, username: localStorage.getItem('storedUsername') }, getters: { hostStatus: state => state.hostStatus, bmcTime: state => state.bmcTime, languagePreference: state => state.languagePreference, + isUtcDisplay: state => state.isUtcDisplay, username: state => state.username }, mutations: { @@ -46,7 +50,8 @@ const GlobalStore = { (state.hostStatus = hostStateMapper(hostState)), setLanguagePreference: (state, language) => (state.languagePreference = language), - setUsername: (state, username) => (state.username = username) + setUsername: (state, username) => (state.username = username), + setUtcTime: (state, isUtcDisplay) => (state.isUtcDisplay = isUtcDisplay) }, actions: { async getBmcTime({ commit }) { diff --git a/src/views/ProfileSettings/ProfileSettings.vue b/src/views/ProfileSettings/ProfileSettings.vue index 07aac198..2abee633 100644 --- a/src/views/ProfileSettings/ProfileSettings.vue +++ b/src/views/ProfileSettings/ProfileSettings.vue @@ -4,9 +4,11 @@ - +
-
{{ $t('profileSettings.username') }}
+
{{ $t('pageProfileSettings.username') }}
{{ username }}
@@ -18,10 +20,12 @@ - + @@ -42,9 +46,6 @@ @input="$v.form.newPassword.$touch()" /> -