Skip to content

Commit 3f781d7

Browse files
odzhychkoAndyScherzinger
authored andcommitted
fix(settings): display birthday correctly regardless of browsers timezone
This also avoid shifting dates after user input. Resolves #49828 Signed-off-by: Oleksandr Dzhychko <hey@oleks.dev>
1 parent 004f5df commit 3f781d7

4 files changed

Lines changed: 87 additions & 22 deletions

File tree

apps/settings/src/components/PersonalInfo/BirthdaySection.spec.js

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,17 @@ vi.mock('../../service/PersonalInfo/PersonalInfoService.js', () => ({
2626
savePrimaryAccountProperty,
2727
}))
2828

29+
async function mountBirthdaySection() {
30+
const BirthdaySection = await import('./BirthdaySection.vue')
31+
return mount(BirthdaySection.default, {
32+
mocks: {
33+
t: (_app, text) => text,
34+
},
35+
})
36+
}
37+
2938
afterEach(() => {
39+
vi.unstubAllEnvs()
3040
personalInfoParameters = undefined
3141
vi.resetModules()
3242
})
@@ -42,12 +52,45 @@ describe('BirthdaySection', () => {
4252
savePrimaryAccountProperty.mockReturnValue(Promise.resolve({
4353
ocs: { meta: { status: 'ok' } },
4454
}))
45-
const BirthdaySection = await import('./BirthdaySection.vue')
46-
const wrapper = mount(BirthdaySection.default, {
47-
mocks: {
48-
t: (_app, text) => text,
55+
const wrapper = await mountBirthdaySection()
56+
57+
const input = wrapper.find('input')
58+
await input.setValue('1987-12-01')
59+
60+
await expect.poll(() => savePrimaryAccountProperty.mock.calls.length).toBe(1)
61+
expect(savePrimaryAccountProperty).toHaveBeenCalledWith(
62+
'birthdate',
63+
'1987-12-01T00:00:00.000Z',
64+
)
65+
expect(input.element.value).toBe('1987-12-01')
66+
})
67+
68+
it('displays value when browser timezone is set', async () => {
69+
vi.stubEnv('TZ', 'US/Pacific')
70+
personalInfoParameters = {
71+
birthdate: {
72+
name: 'birthdate',
73+
value: '1987-12-15T00:00:00.000Z',
4974
},
50-
})
75+
}
76+
77+
const wrapper = await mountBirthdaySection()
78+
79+
expect(wrapper.find('input').element.value).toBe('1987-12-15')
80+
})
81+
82+
it('saves value when browser timezone is set', async () => {
83+
vi.stubEnv('TZ', 'US/Pacific')
84+
personalInfoParameters = {
85+
birthdate: {
86+
name: 'birthdate',
87+
value: null,
88+
},
89+
}
90+
savePrimaryAccountProperty.mockReturnValue(Promise.resolve({
91+
ocs: { meta: { status: 'ok' } },
92+
}))
93+
const wrapper = await mountBirthdaySection()
5194

5295
const input = wrapper.find('input')
5396
await input.setValue('1987-12-01')

apps/settings/src/components/PersonalInfo/BirthdaySection.vue

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
:id="inputId"
1414
type="date"
1515
label=""
16-
:model-value="value"
16+
:model-value="timezoneAdjustedValue"
1717
@input="onInput" />
1818

1919
<p class="property__helper-text-message">
@@ -33,6 +33,16 @@ import { handleError } from '../../utils/handlers.js'
3333
3434
const { birthdate } = loadState('settings', 'personalInfoParameters', {})
3535
36+
/**
37+
* Convert a birthdate string value into a Date.
38+
*
39+
* @param {string } birthdateValue - e.g. "1987-12-01" or "1987-12-01T00:00:00.000Z"
40+
* @return {Date}
41+
*/
42+
function birthdateValueToDate(birthdateValue) {
43+
return new Date(birthdateValue)
44+
}
45+
3646
export default {
3747
name: 'BirthdaySection',
3848
@@ -44,7 +54,7 @@ export default {
4454
data() {
4555
let initialValue = null
4656
if (birthdate.value) {
47-
initialValue = new Date(birthdate.value)
57+
initialValue = birthdateValueToDate(birthdate.value)
4858
}
4959
5060
return {
@@ -62,24 +72,36 @@ export default {
6272
return `account-property-${birthdate.name}`
6373
},
6474
65-
value: {
66-
get() {
67-
return new Date(this.birthdate.value)
68-
},
75+
value() {
76+
return birthdateValueToDate(this.birthdate.value)
77+
},
6978
70-
/** @param {Date} value The date to set */
71-
set(value) {
72-
const day = value.getDate().toString().padStart(2, '0')
73-
const month = (value.getMonth() + 1).toString().padStart(2, '0')
74-
const year = value.getFullYear()
75-
this.birthdate.value = `${year}-${month}-${day}`
76-
},
79+
/**
80+
* Adjusted value for usage with `NcDateTimePickerNative` (internally `<input="date">`)
81+
* The saved value is is UTC and we want to show it the same regardless of the browsers/OSs timezone.
82+
* When the adjusted value is displayed and the users timezone is applied, this adjusted value then looks like the UTC value.
83+
*/
84+
timezoneAdjustedValue() {
85+
// example: this.birthdate.value === '1987-12-01T00:00:00.000Z' or '1987-12-01'
86+
87+
// example: Mon Nov 30 1987 16:00:00 GMT-0800 (Pacific Standard Time)
88+
// `NcDateTimePickerNative` would show this as 11/30/1987
89+
const date = this.value
90+
const timezoneOffsetMilliseconds = date.getTimezoneOffset() * 60 * 1000
91+
const adjustedDate = new Date(date.getTime() + timezoneOffsetMilliseconds)
92+
93+
// example: Tue Dec 01 1987 00:00:00 GMT-0800 (Pacific Standard Time)
94+
// `NcDateTimePickerNative` will show this as 12/01/1987
95+
return adjustedDate
7796
},
7897
},
7998
8099
methods: {
81100
onInput(e) {
82-
this.value = e
101+
const day = e.getDate().toString().padStart(2, '0')
102+
const month = (e.getMonth() + 1).toString().padStart(2, '0')
103+
const year = e.getFullYear()
104+
this.birthdate.value = `${year}-${month}-${day}`
83105
this.debouncePropertyChange(this.value)
84106
},
85107

dist/settings-vue-settings-personal-info.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/settings-vue-settings-personal-info.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)