Merge pull request #1864 from nextcloud/bugfix/1841/timezone_alias

Fix resolving timezone alias
This commit is contained in:
Georg Ehrke 2020-01-20 14:52:07 +01:00 committed by GitHub
commit 270ced9b5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 261 additions and 6 deletions

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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]--

View File

@ -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
}

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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)
})