Skip to content

Commit e0b9bbc

Browse files
authored
Merge pull request #61744 from nextcloud/backport/61638/stable32
[stable32] fix(settings): in absence form avoid shifting dates to browsers timezone
2 parents e0a0ade + 810462e commit e0b9bbc

4 files changed

Lines changed: 99 additions & 5 deletions

File tree

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { mount } from '@vue/test-utils'
7+
import { afterEach, describe, expect, it, vi } from 'vitest'
8+
import AbsenceForm from './AbsenceForm.vue'
9+
10+
let davAbsence
11+
vi.mock('@nextcloud/initial-state', () => ({
12+
loadState(app, key, fallback) {
13+
if (app === 'dav' && key === 'absence' && davAbsence !== undefined) {
14+
return davAbsence
15+
}
16+
if (fallback !== undefined) {
17+
return fallback
18+
}
19+
20+
console.error('Unexpected loadState call without fallback', { app, key })
21+
throw new Error()
22+
},
23+
}))
24+
25+
afterEach(() => {
26+
vi.unstubAllEnvs()
27+
davAbsence = undefined
28+
vi.resetModules()
29+
})
30+
31+
function mountAbsenceForm() {
32+
return mount(AbsenceForm, {
33+
mocks: {
34+
$t: (_app, text) => text,
35+
},
36+
})
37+
}
38+
39+
function getInputs(wrapper) {
40+
const lables = wrapper.findAll('label')
41+
42+
const firstDayLabel = lables.filter((l) => l.text() === 'First day').at(0)
43+
const firstDayInput = wrapper.get(`#${firstDayLabel.attributes('for')}`)
44+
45+
const lastDayLabel = lables.filter((l) => l.text() === 'Last day (inclusive)').at(0)
46+
const lastDayInput = wrapper.get(`#${lastDayLabel.attributes('for')}`)
47+
48+
return { firstDayInput, lastDayInput }
49+
}
50+
51+
describe('AbsenceForm', () => {
52+
it('displays default state when browser timezone is set', async () => {
53+
vi.setSystemTime(new Date(2026, 5, 29, 5, 0))
54+
vi.stubEnv('TZ', 'US/Pacific')
55+
56+
const wrapper = mountAbsenceForm()
57+
58+
const { firstDayInput } = getInputs(wrapper)
59+
expect(firstDayInput.element.value).toBe('2026-06-29')
60+
})
61+
62+
it('displays state when browser timezone is set', async () => {
63+
vi.stubEnv('TZ', 'US/Pacific')
64+
davAbsence = {
65+
firstDay: '2026-06-29',
66+
lastDay: '2026-06-30',
67+
}
68+
69+
const wrapper = mountAbsenceForm()
70+
71+
const { firstDayInput, lastDayInput } = getInputs(wrapper)
72+
expect(firstDayInput.element.value).toBe('2026-06-29')
73+
expect(lastDayInput.element.value).toBe('2026-06-30')
74+
})
75+
})

‎apps/dav/src/components/AbsenceForm.vue‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,22 @@ import NcTextArea from '@nextcloud/vue/components/NcTextArea'
6666
import NcSelect from '@nextcloud/vue/components/NcSelect'
6767
import NcDateTimePickerNative from '@nextcloud/vue/components/NcDateTimePickerNative'
6868
69+
/**
70+
* Adjusts a date so `NcDateTimePickerNative` shows the same date
71+
* instead of shifting it to the browsers timezone.
72+
*
73+
* @param {Date} date - e.g., new Date("1987-12-01")
74+
* @return {Date}
75+
*/
76+
function inputAdjustDate(date) {
77+
// e.g., date === Mon Nov 30 1987 16:00:00 GMT-0800 (Pacific Standard Time)
78+
const timezoneOffsetMilliseconds = date.getTimezoneOffset() * 60 * 1000
79+
// e.g., Tue Dec 01 1987 00:00:00 GMT-0800 (Pacific Standard Time)
80+
const adjustedDate = new Date(date.getTime() + timezoneOffsetMilliseconds)
81+
// `NcDateTimePickerNative` will display this as 12/01/1987
82+
return adjustedDate
83+
}
84+
6985
export default {
7086
name: 'AbsenceForm',
7187
components: {
@@ -77,12 +93,15 @@ export default {
7793
},
7894
data() {
7995
const { firstDay, lastDay, status, message, replacementUserId, replacementUserDisplayName } = loadState('dav', 'absence', {})
96+
const firstDayDate = firstDay ? new Date(firstDay) : new Date()
97+
const firstDayInputAdjusted = inputAdjustDate(firstDayDate)
98+
const lastDayInputAdjusted = lastDay ? inputAdjustDate(new Date(lastDay)) : null
8099
return {
81100
loading: false,
82101
status: status ?? '',
83102
message: message ?? '',
84-
firstDay: firstDay ? new Date(firstDay) : new Date(),
85-
lastDay: lastDay ? new Date(lastDay) : null,
103+
firstDay: firstDayInputAdjusted,
104+
lastDay: lastDayInputAdjusted,
86105
replacementUserId,
87106
replacementUser: replacementUserId ? { user: replacementUserId, displayName: replacementUserDisplayName } : null,
88107
searchLoading: false,

‎dist/dav-settings-personal-availability.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/dav-settings-personal-availability.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)