mirror of https://github.com/nextcloud/calendar
Merge pull request #1864 from nextcloud/bugfix/1841/timezone_alias
Fix resolving timezone alias
This commit is contained in:
commit
270ced9b5c
|
@ -21,6 +21,7 @@
|
|||
*/
|
||||
import { getDurationValueFromFullCalendarDuration } from '../fullcalendar/duration'
|
||||
import getTimezoneManager from '../services/timezoneDataProviderService'
|
||||
import logger from '../utils/logger.js'
|
||||
|
||||
/**
|
||||
* Returns a function to drop an event at a different position
|
||||
|
@ -35,7 +36,11 @@ export default function(store, fcAPI) {
|
|||
const defaultAllDayDuration = getDurationValueFromFullCalendarDuration(fcAPI.getOption('defaultAllDayEventDuration'))
|
||||
const defaultTimedDuration = getDurationValueFromFullCalendarDuration(fcAPI.getOption('defaultTimedEventDuration'))
|
||||
const timezoneId = fcAPI.getOption('timeZone')
|
||||
const timezone = getTimezoneManager().getTimezoneForId(timezoneId)
|
||||
let timezone = getTimezoneManager().getTimezoneForId(timezoneId)
|
||||
if (!timezone) {
|
||||
timezone = getTimezoneManager().getTimezoneForId('UTC')
|
||||
logger.error(`EventDrop: Timezone ${timezoneId} not found, falling back to UTC.`)
|
||||
}
|
||||
|
||||
if (!deltaDuration || !defaultAllDayDuration || !defaultTimedDuration) {
|
||||
revert()
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
import getTimezoneManager from '../services/timezoneDataProviderService'
|
||||
import { getUnixTimestampFromDate } from '../utils/date.js'
|
||||
import { eventSourceFunction } from './eventSourceFunction.js'
|
||||
import logger from '../utils/logger.js'
|
||||
|
||||
/**
|
||||
* Returns a function to generate a FullCalendar event-source based on the Vuex calendar model
|
||||
|
@ -42,7 +43,12 @@ export default function(store) {
|
|||
textColor: generateTextColorForHex(calendar.color),
|
||||
// html foo
|
||||
events: async({ start, end, timeZone }, successCallback, failureCallback) => {
|
||||
const timezoneObject = getTimezoneManager().getTimezoneForId(timeZone)
|
||||
let timezoneObject = getTimezoneManager().getTimezoneForId(timeZone)
|
||||
if (!timezoneObject) {
|
||||
timezoneObject = getTimezoneManager().getTimezoneForId('UTC')
|
||||
logger.error(`EventSource: Timezone ${timeZone} not found, falling back to UTC.`)
|
||||
}
|
||||
|
||||
const timeRange = store.getters.getTimeRangeForCalendarCoveringRange(calendar.id, getUnixTimestampFromDate(start), getUnixTimestampFromDate(end))
|
||||
if (!timeRange) {
|
||||
let timeRangeId
|
||||
|
|
|
@ -24,6 +24,7 @@ import { createFreeBusyRequest } from 'calendar-js'
|
|||
import DateTimeValue from 'calendar-js/src/values/dateTimeValue.js'
|
||||
import client from '../services/caldavService.js'
|
||||
import freeBusyEventSourceFunction from './freeBusyEventSourceFunction.js'
|
||||
import logger from '../utils/logger.js'
|
||||
// import AttendeeProperty from 'calendar-js/src/properties/attendeeProperty.js'
|
||||
|
||||
/**
|
||||
|
@ -44,7 +45,11 @@ export default function(id, organizer, attendees) {
|
|||
events: async({ start, end, timeZone }, successCallback, failureCallback) => {
|
||||
console.debug(start, end, timeZone)
|
||||
|
||||
const timezoneObject = getTimezoneManager().getTimezoneForId(timeZone)
|
||||
let timezoneObject = getTimezoneManager().getTimezoneForId(timeZone)
|
||||
if (!timezoneObject) {
|
||||
timezoneObject = getTimezoneManager().getTimezoneForId('UTC')
|
||||
logger.error(`FreeBusyEventSource: Timezone ${timeZone} not found, falling back to UTC.`)
|
||||
}
|
||||
|
||||
const startDateTime = DateTimeValue.fromJSDate(start, true)
|
||||
const endDateTime = DateTimeValue.fromJSDate(end, true)
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
createPlugin,
|
||||
} from '@fullcalendar/core'
|
||||
import getTimezoneManager from '../services/timezoneDataProviderService'
|
||||
import logger from '../utils/logger.js'
|
||||
|
||||
/**
|
||||
* Our own FullCalendar Timezone implementation based on the VTimezones we ship
|
||||
|
@ -37,7 +38,11 @@ class VTimezoneNamedTimezone extends NamedTimeZoneImpl {
|
|||
* @returns {Number} offset in minutes
|
||||
*/
|
||||
offsetForArray([year, month, day, hour, minute, second]) {
|
||||
const timezone = getTimezoneManager().getTimezoneForId(this.timeZoneName)
|
||||
let timezone = getTimezoneManager().getTimezoneForId(this.timeZoneName)
|
||||
if (!timezone) {
|
||||
timezone = getTimezoneManager().getTimezoneForId('UTC')
|
||||
logger.error(`VTimezoneNamedTimezoneImpl: Timezone ${this.timeZoneName} not found, falling back to UTC.`)
|
||||
}
|
||||
// calendar-js works with natural month numbers,
|
||||
// not the javascript 0-based ones
|
||||
month += 1
|
||||
|
@ -52,8 +57,13 @@ class VTimezoneNamedTimezone extends NamedTimeZoneImpl {
|
|||
* @returns {Number[]}
|
||||
*/
|
||||
timestampToArray(ms) {
|
||||
const timezone = getTimezoneManager().getTimezoneForId(this.timeZoneName)
|
||||
let timezone = getTimezoneManager().getTimezoneForId(this.timeZoneName)
|
||||
if (!timezone) {
|
||||
timezone = getTimezoneManager().getTimezoneForId('UTC')
|
||||
logger.error(`VTimezoneNamedTimezoneImpl: Timezone ${this.timeZoneName} not found, falling back to UTC.`)
|
||||
}
|
||||
const timestampArray = timezone.timestampToArray(ms)
|
||||
|
||||
// calendar-js works with natural month numbers,
|
||||
// not the javascript 0-based ones
|
||||
timestampArray[1]--
|
||||
|
|
|
@ -58,5 +58,11 @@ function initialize() {
|
|||
}
|
||||
}
|
||||
|
||||
for (const tzid in tzData.aliases) {
|
||||
if (Object.prototype.hasOwnProperty.call(tzData.aliases, [tzid])) {
|
||||
timezoneManager.registerAlias(tzid, tzData.aliases[tzid].aliasTo)
|
||||
}
|
||||
}
|
||||
|
||||
initialized = true
|
||||
}
|
||||
|
|
|
@ -116,6 +116,7 @@ import VTimezoneNamedTimezone from '../fullcalendar/vtimezoneNamedTimezoneImpl'
|
|||
import AppNavigationHeader from '../components/AppNavigation/AppNavigationHeader.vue'
|
||||
import CalendarList from '../components/AppNavigation/CalendarList.vue'
|
||||
import Settings from '../components/AppNavigation/Settings.vue'
|
||||
import getTimezoneManager from '../services/timezoneDataProviderService'
|
||||
import {
|
||||
mapGetters,
|
||||
mapState,
|
||||
|
@ -347,6 +348,12 @@ export default {
|
|||
|
||||
toastElement.classList.add('toast-calendar-multiline')
|
||||
}
|
||||
if (getTimezoneManager().getTimezoneForId(this.timezoneId) === null) {
|
||||
const { toastElement }
|
||||
= this.$toast.warning(this.$t('calendar', 'Your configured timezone ({timezoneId}) was not found. Falling back to UTC.\nPlease change your timezone in the settings and report this issue.', { timezoneId: this.timezoneId }), { timeout: 60 })
|
||||
|
||||
toastElement.classList.add('toast-calendar-multiline')
|
||||
}
|
||||
|
||||
this.loadFullCalendarLocale()
|
||||
this.loadMomentLocale()
|
||||
|
|
|
@ -108,6 +108,86 @@ describe('fullcalendar/eventDrop test suite', () => {
|
|||
expect(revert).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it('should properly drop a non-recurring event - unknown timezone', async () => {
|
||||
const store = {
|
||||
dispatch: jest.fn()
|
||||
}
|
||||
const fcAPI = {
|
||||
getOption: jest.fn()
|
||||
.mockReturnValueOnce({ days: 1 })
|
||||
.mockReturnValueOnce({ hours: 2 })
|
||||
.mockReturnValueOnce('America/New_York'),
|
||||
}
|
||||
|
||||
const event = {
|
||||
allDay: false,
|
||||
extendedProps: {
|
||||
objectId: 'object123',
|
||||
recurrenceId: '1573554842'
|
||||
}
|
||||
}
|
||||
const delta = {
|
||||
hours: 5
|
||||
}
|
||||
const revert = jest.fn()
|
||||
|
||||
getDurationValueFromFullCalendarDuration
|
||||
.mockReturnValueOnce({ calendarJsDurationValue: true, hours: 5 })
|
||||
.mockReturnValueOnce({ calendarJsDurationValue: true, days: 1 })
|
||||
.mockReturnValueOnce({ calendarJsDurationValue: true, hours: 2 })
|
||||
|
||||
const getTimezoneForId = jest.fn()
|
||||
.mockReturnValueOnce(null)
|
||||
.mockReturnValueOnce({ calendarJsTimezone: true, tzid: 'UTC' })
|
||||
getTimezoneManager
|
||||
.mockReturnValue({
|
||||
getTimezoneForId
|
||||
})
|
||||
|
||||
const eventComponent = {
|
||||
shiftByDuration: jest.fn(),
|
||||
canCreateRecurrenceExceptions: jest.fn().mockReturnValue(false),
|
||||
createRecurrenceException: jest.fn(),
|
||||
}
|
||||
const calendarObject = {
|
||||
getObjectAtRecurrenceId: jest.fn().mockReturnValueOnce(eventComponent),
|
||||
resetToDav: jest.fn()
|
||||
}
|
||||
|
||||
store.dispatch.mockResolvedValueOnce(calendarObject) // getEventByObjectId
|
||||
store.dispatch.mockResolvedValueOnce() // updateCalendarObject
|
||||
|
||||
const eventDropFunction = eventDrop(store, fcAPI)
|
||||
await eventDropFunction({ event, delta, revert })
|
||||
|
||||
expect(getTimezoneForId).toHaveBeenCalledTimes(2)
|
||||
expect(getTimezoneForId).toHaveBeenNthCalledWith(1, 'America/New_York')
|
||||
expect(getTimezoneForId).toHaveBeenNthCalledWith(2, 'UTC')
|
||||
|
||||
expect(fcAPI.getOption).toHaveBeenCalledTimes(3)
|
||||
expect(fcAPI.getOption).toHaveBeenNthCalledWith(1, 'defaultAllDayEventDuration')
|
||||
expect(fcAPI.getOption).toHaveBeenNthCalledWith(2, 'defaultTimedEventDuration')
|
||||
expect(fcAPI.getOption).toHaveBeenNthCalledWith(3, 'timeZone')
|
||||
|
||||
expect(getDurationValueFromFullCalendarDuration).toHaveBeenCalledTimes(3)
|
||||
expect(getDurationValueFromFullCalendarDuration).toHaveBeenNthCalledWith(1, delta)
|
||||
expect(getDurationValueFromFullCalendarDuration).toHaveBeenNthCalledWith(2, { days: 1})
|
||||
expect(getDurationValueFromFullCalendarDuration).toHaveBeenNthCalledWith(3, { hours: 2 })
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledTimes(2)
|
||||
expect(store.dispatch).toHaveBeenNthCalledWith(1, 'getEventByObjectId', { objectId: 'object123' })
|
||||
expect(store.dispatch).toHaveBeenNthCalledWith(2, 'updateCalendarObject', { calendarObject })
|
||||
|
||||
expect(eventComponent.shiftByDuration).toHaveBeenCalledTimes(1)
|
||||
expect(eventComponent.shiftByDuration).toHaveBeenNthCalledWith(1, { calendarJsDurationValue: true, hours: 5 }, false, { calendarJsTimezone: true, tzid: 'UTC' }, { calendarJsDurationValue: true, days: 1 }, { calendarJsDurationValue: true, hours: 2 })
|
||||
|
||||
expect(eventComponent.canCreateRecurrenceExceptions).toHaveBeenCalledTimes(1)
|
||||
expect(eventComponent.createRecurrenceException).toHaveBeenCalledTimes(0)
|
||||
|
||||
expect(calendarObject.resetToDav).toHaveBeenCalledTimes(0)
|
||||
expect(revert).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it('should properly drop a recurring event', async () => {
|
||||
const store = {
|
||||
dispatch: jest.fn()
|
||||
|
|
|
@ -222,6 +222,76 @@ describe('fullcalendar/eventSource test suite', () => {
|
|||
expect(failureCallback).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it('should provide an eventSource function to provide events - existing timerange - unknown timezone', async () => {
|
||||
const store = {
|
||||
dispatch: jest.fn()
|
||||
.mockReturnValueOnce(42),
|
||||
getters: {
|
||||
getTimeRangeForCalendarCoveringRange: jest.fn()
|
||||
.mockReturnValueOnce({
|
||||
id: 42
|
||||
}),
|
||||
getCalendarObjectsByTimeRangeId: jest.fn()
|
||||
.mockReturnValueOnce([{ calendarObjectId: 1 }, { calendarObjectId: 2 }])
|
||||
}
|
||||
}
|
||||
|
||||
const calendar = {
|
||||
id: 'calendar-id-123',
|
||||
color: '#ff00ff',
|
||||
isReadOnly: true
|
||||
}
|
||||
|
||||
const getTimezoneForId = jest.fn()
|
||||
.mockReturnValueOnce(null)
|
||||
.mockReturnValueOnce({ calendarJsTimezone: true, tzid: 'UTC' })
|
||||
getTimezoneManager
|
||||
.mockReturnValue({
|
||||
getTimezoneForId
|
||||
})
|
||||
|
||||
getUnixTimestampFromDate
|
||||
.mockReturnValueOnce(1234)
|
||||
.mockReturnValueOnce(5678)
|
||||
|
||||
generateTextColorForHex
|
||||
.mockReturnValue('#00ff00')
|
||||
|
||||
eventSourceFunction
|
||||
.mockReturnValueOnce([{ fcEventId: 1 }, { fcEventId: 2 }])
|
||||
|
||||
const start = new Date(Date.UTC(2019, 0, 1, 0, 0, 0, 0))
|
||||
const end = new Date(Date.UTC(2019, 0, 31, 59, 59, 59, 999))
|
||||
const timeZone = 'America/New_York'
|
||||
|
||||
const successCallback = jest.fn()
|
||||
const failureCallback = jest.fn()
|
||||
|
||||
const eventSourceFn = eventSource(store)
|
||||
const { events } = eventSourceFn(calendar)
|
||||
await events({ start, end, timeZone }, successCallback, failureCallback)
|
||||
|
||||
expect(getTimezoneForId).toHaveBeenCalledTimes(2)
|
||||
expect(getTimezoneForId).toHaveBeenNthCalledWith(1, 'America/New_York')
|
||||
expect(getTimezoneForId).toHaveBeenNthCalledWith(2, 'UTC')
|
||||
|
||||
expect(store.getters.getTimeRangeForCalendarCoveringRange).toHaveBeenCalledTimes(1)
|
||||
expect(store.getters.getTimeRangeForCalendarCoveringRange).toHaveBeenNthCalledWith(1, 'calendar-id-123', 1234, 5678)
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledTimes(0)
|
||||
|
||||
expect(store.getters.getCalendarObjectsByTimeRangeId).toHaveBeenCalledTimes(1)
|
||||
expect(store.getters.getCalendarObjectsByTimeRangeId).toHaveBeenNthCalledWith(1, 42)
|
||||
|
||||
expect(eventSourceFunction).toHaveBeenCalledTimes(1)
|
||||
expect(eventSourceFunction).toHaveBeenNthCalledWith(1, [{ calendarObjectId: 1 }, { calendarObjectId: 2 }], calendar, start, end, { calendarJsTimezone: true, tzid: 'UTC' })
|
||||
|
||||
expect(successCallback).toHaveBeenCalledTimes(1)
|
||||
expect(successCallback).toHaveBeenNthCalledWith(1, [{ fcEventId: 1 }, { fcEventId: 2 }])
|
||||
|
||||
expect(failureCallback).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it('should provide an eventSource function that catches errors while fetching', async () => {
|
||||
const store = {
|
||||
dispatch: jest.fn()
|
||||
|
|
|
@ -27,7 +27,7 @@ import getTimezoneManager from '../../../../src/services/timezoneDataProviderSer
|
|||
jest.mock('../../../../src/services/timezoneDataProviderService.js')
|
||||
jest.mock('@fullcalendar/core')
|
||||
|
||||
import vtimezoneNamedTimezoneImpl from "../../../../src/fullcalendar/vtimezoneNamedTimezoneImpl.js";
|
||||
import '../../../../src/fullcalendar/vtimezoneNamedTimezoneImpl.js'
|
||||
|
||||
describe('fullcalendar/vtimezoneNamedTimezoneImpl test suite', () => {
|
||||
|
||||
|
@ -59,12 +59,45 @@ describe('fullcalendar/vtimezoneNamedTimezoneImpl test suite', () => {
|
|||
|
||||
const VTimezoneNamedTimezone = createPlugin.mock.calls[0][0].namedTimeZonedImpl
|
||||
const instance = new VTimezoneNamedTimezone('America/New_York')
|
||||
instance.timeZoneName = 'America/New_York'
|
||||
|
||||
const result = instance.offsetForArray([2019, 0, 1, 14, 30, 0])
|
||||
|
||||
expect(result).toEqual(1337)
|
||||
|
||||
expect(getTimezoneForId).toHaveBeenCalledTimes(1)
|
||||
expect(getTimezoneForId).toHaveBeenNthCalledWith(1, 'America/New_York')
|
||||
expect(timezone.offsetForArray).toHaveBeenCalledTimes(1)
|
||||
expect(timezone.offsetForArray).toHaveBeenNthCalledWith(1, 2019, 1, 1, 14, 30, 0)
|
||||
})
|
||||
|
||||
it('should properly implement the offsetForArray method - unknown timezone', () => {
|
||||
const timezone = {
|
||||
calendarJsTimezone: true,
|
||||
tzid: 'UTC',
|
||||
offsetForArray: jest.fn().mockReturnValue(1337 * 60)
|
||||
}
|
||||
|
||||
const getTimezoneForId = jest.fn()
|
||||
.mockReturnValueOnce(null)
|
||||
.mockReturnValue(timezone)
|
||||
|
||||
getTimezoneManager
|
||||
.mockReturnValue({
|
||||
getTimezoneForId
|
||||
})
|
||||
|
||||
const VTimezoneNamedTimezone = createPlugin.mock.calls[0][0].namedTimeZonedImpl
|
||||
const instance = new VTimezoneNamedTimezone('America/New_York')
|
||||
instance.timeZoneName = 'America/New_York'
|
||||
|
||||
const result = instance.offsetForArray([2019, 0, 1, 14, 30, 0])
|
||||
|
||||
expect(result).toEqual(1337)
|
||||
|
||||
expect(getTimezoneForId).toHaveBeenCalledTimes(2)
|
||||
expect(getTimezoneForId).toHaveBeenNthCalledWith(1, 'America/New_York')
|
||||
expect(getTimezoneForId).toHaveBeenNthCalledWith(2, 'UTC')
|
||||
expect(timezone.offsetForArray).toHaveBeenCalledTimes(1)
|
||||
expect(timezone.offsetForArray).toHaveBeenNthCalledWith(1, 2019, 1, 1, 14, 30, 0)
|
||||
})
|
||||
|
@ -86,12 +119,45 @@ describe('fullcalendar/vtimezoneNamedTimezoneImpl test suite', () => {
|
|||
|
||||
const VTimezoneNamedTimezone = createPlugin.mock.calls[0][0].namedTimeZonedImpl
|
||||
const instance = new VTimezoneNamedTimezone('America/New_York')
|
||||
instance.timeZoneName = 'America/New_York'
|
||||
|
||||
const result = instance.timestampToArray(1337)
|
||||
|
||||
expect(result).toEqual([2019, 0, 1, 14, 30, 0])
|
||||
|
||||
expect(getTimezoneForId).toHaveBeenCalledTimes(1)
|
||||
expect(getTimezoneForId).toHaveBeenNthCalledWith(1, 'America/New_York')
|
||||
expect(timezone.timestampToArray).toHaveBeenCalledTimes(1)
|
||||
expect(timezone.timestampToArray).toHaveBeenNthCalledWith(1, 1337)
|
||||
})
|
||||
|
||||
it('should properly implement the timestampToArray method - unknown timezone', () => {
|
||||
const timezone = {
|
||||
calendarJsTimezone: true,
|
||||
tzid: 'America/New_York',
|
||||
timestampToArray: jest.fn().mockReturnValue([2019, 1, 1, 14, 30, 0])
|
||||
}
|
||||
|
||||
const getTimezoneForId = jest.fn()
|
||||
.mockReturnValueOnce(null)
|
||||
.mockReturnValue(timezone)
|
||||
|
||||
getTimezoneManager
|
||||
.mockReturnValue({
|
||||
getTimezoneForId
|
||||
})
|
||||
|
||||
const VTimezoneNamedTimezone = createPlugin.mock.calls[0][0].namedTimeZonedImpl
|
||||
const instance = new VTimezoneNamedTimezone('America/New_York')
|
||||
instance.timeZoneName = 'America/New_York'
|
||||
|
||||
const result = instance.timestampToArray(1337)
|
||||
|
||||
expect(result).toEqual([2019, 0, 1, 14, 30, 0])
|
||||
|
||||
expect(getTimezoneForId).toHaveBeenCalledTimes(2)
|
||||
expect(getTimezoneForId).toHaveBeenNthCalledWith(1, 'America/New_York')
|
||||
expect(getTimezoneForId).toHaveBeenNthCalledWith(2, 'UTC')
|
||||
expect(timezone.timestampToArray).toHaveBeenCalledTimes(1)
|
||||
expect(timezone.timestampToArray).toHaveBeenNthCalledWith(1, 1337)
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue