calcom / cal.com

Scheduling infrastructure for absolutely everyone.
https://cal.com
Other
29.76k stars 6.98k forks source link

[CAL-3981] Two hours less time difference in Nextcloud Web #15368

Open aukfood opened 3 weeks ago

aukfood commented 3 weeks ago

Found a bug? Please fill out the sections below. 👍

Issue Summary

When we create an event with cal.com integrated with Nextcloud agenda, we have two hours less time difference.

Steps to Reproduce

  1. create an event with cal.com installed in docker environment.
  2. see informations in Thunderbird ok
  3. see informations in Nextcloud Agenda not ok

Any other relevant information. For example, why do you consider this a bug and what did you expect to happen instead?

Actual Results

In Nextcloud nextcloud-agenda

In Thunderbird thunderbird-agenda

Technical details

Thanks

CAL-3981

dosubot[bot] commented 3 weeks ago

Bonjour @aukfood! I'm here to help you with any bugs, questions, or contributions you have regarding the repository. Let's work together to resolve the issue you're experiencing.

To resolve the two-hour time discrepancy in Nextcloud Agenda when creating events using cal.com integrated with the Nextcloud agenda, you need to ensure that the time zones are correctly set and synchronized between the two systems. Here are the key points to check and adjust:

  1. Ensure Time Zone Consistency: Make sure that the time zone settings in both Nextcloud and cal.com are consistent. The setNewTimeZone function in cal.com updates the user's time zone and their default schedule's time zone.

  2. Update Time Zones: Ensure that the travel schedules are correctly set and that the time zone changes are applied as expected.

  3. Check for Time Zone Offsets: The code calculates the UTC offset for the user's time zone and adjusts the start and end dates of travel schedules accordingly. Ensure that these calculations are correct and that the time zone offsets are properly applied.

Additionally, the buildSlots function in packages/lib/slots.ts handles the creation of time slots and takes into account the time zone differences between the organizer and the invitee. It calculates the differences in UTC offsets due to Daylight Saving Time (DST) and adjusts the slot times accordingly.

Here is the relevant part of the code that updates the time zone:

const setNewTimeZone = async (timeZone: string, user: { id: number; defaultScheduleId: number | null }) => {
  await prisma.user.update({
    where: {
      id: user.id,
    },
    data: {
      timeZone: timeZone,
    },
  });

  const defaultScheduleId = await getDefaultScheduleId(user.id, prisma);

  if (!user.defaultScheduleId) {
    // set default schedule if not already set
    await prisma.user.update({
      where: {
        id: user.id,
      },
      data: {
        defaultScheduleId,
      },
    });
  }

  await prisma.schedule.updateMany({
    where: {
      id: defaultScheduleId,
    },
    data: {
      timeZone: timeZone,
    },
  });
  timeZonesChanged++;
};

And here is the relevant part of the buildSlots function that handles time zone differences:

const organizerDSTDiff =
    dayjs().tz(organizerTimeZone).utcOffset() - startOfInviteeDay.tz(organizerTimeZone).utcOffset();
const inviteeDSTDiff =
    dayjs().tz(inviteeTimeZone).utcOffset() - startOfInviteeDay.tz(inviteeTimeZone).utcOffset();
const slots: { time: Dayjs; userIds?: number[] }[] = [];
const getTime = (time: number) => {
    const minutes = time + organizerDSTDiff - inviteeDSTDiff;
    return startOfInviteeDay.tz(inviteeTimeZone).add(minutes, "minutes");
};

Additionally, the useTimezone hook in useTimezone.ts ensures that the user's preferred time zone is used and updated correctly:

import { useEffect } from "react";

import dayjs from "@calcom/dayjs";

import { useMe } from "./useMe";

export const useTimezone = (
  onTimeZoneChange?: (currentTimezone: string) => void,
  currentTimezone: string = dayjs.tz.guess()
) => {
  const { data: me, isLoading } = useMe();
  const preferredTimezone = me?.data?.timeZone ?? currentTimezone;

  useEffect(() => {
    if (!isLoading && preferredTimezone && onTimeZoneChange && preferredTimezone !== currentTimezone) {
      onTimeZoneChange(currentTimezone);
    }
  }, [currentTimezone, preferredTimezone, onTimeZoneChange, isLoading]);
};

To resolve the discrepancy, ensure that:

Additionally, ensure that the dayjs library used in cal.com is correctly patched to handle time zones accurately. The patch provided in .yarn/patches/dayjs-npm-1.11.2-644b12fe04.patch includes updates to the timezone.js plugin to improve time zone handling:

diff --git a/plugin/timezone.js b/plugin/timezone.js
index fb6112a96f03f53ba78162ac323dce17f635161d..991e4e5410a32f5a69cb6e0e9476a9c529657aae 100644
--- a/plugin/timezone.js
+++ b/plugin/timezone.js
@@ -1 +1 @@
-!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).dayjs_plugin_timezone=e()}(this,(function(){"use strict";var t={year:0,month:1,day:2,hour:3,minute:4,second:5},e={};return function(n,i,o){var r,a=function(t,n,i){void 0===i&&(i={});var o=new Date(t),r=function(t,n){void 0===n&&(n={});var i=n.timeZoneName||"short",o=t+"|"+i,r=e[o];return r||(r=new Intl.DateTimeFormat("en-US",{hour12:!1,timeZone:t,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:i}),e[o]=r),r}(n,i);return r.formatToParts(o)},u=function(e,n){for(var i=a(e,n),r=[],u=0;u<i.length;u+=1){var f=i[u],s=f.type,m=f.value,c=t[s];c>=0&&(r[c]=parseInt(m,10))}var d=r[3],l=24===d?0:d,v=r[0]+"-"+r[1]+"-"+r[2]+" "+l+":"+r[4]+":"+r[5]+":000",h=+e;return(o.utc(v).valueOf()-(h-=h%1e3))/6e4},f=i.prototype;f.tz=function(t,e){void 0===t&&(t=r);var n=this.utcOffset(),i=this.toDate(),a=i.toLocaleString("en-US",{timeZone:t}),u=Math.round((i-new Date(a))/1e3/60),f=o(a).$set("millisecond",this.$ms).utcOffset(15*-Math.round(i.getTimezoneOffset()/15)-u,!0);if(e){var s=f.utcOffset();f=f.add(n-s,"minute")}return f.$x.$timezone=t,f},f.offsetName=function(t){var e=this.$x.$timezone||o.tz.guess(),n=a(this.valueOf(),e,{timeZoneName:t}).find((function(t){return"timezonename"===t.type.toLowerCase()}));return n&&n.value};var s=f.startOf;f.startOf=function(t,e){if(!this.$x||!this.$x.$timezone)return s.call(this,t,e);var n=o(this.format("YYYY-MM-DD HH:mm:ss:SSS"));return s.call(n,t,e).tz(this.$x.$timezone,!0)},o.tz=function(t,e,n){var i=n&&e,a=n||e||r,f=u(+o(),a);if("string"!=typeof t)return o(t).tz(a);var s=function(t,e,n){var i=t-60*e*1e3,o=u(i,n);if(e===o)return[i,e];var r=u(i-=60*(o-e)*1e3,n);return o===r?[i,o]:[t-60*Math.min(o,r)*1e3,Math.max(o,r)]}(o.utc(t,i).valueOf(),f,a),m=s[0],c=s[1],d=o(m).utcOffset(c);return d.$x.$timezone=a,d},o.tz.guess=function(){return
Intl.DateTimeFormat().resolvedOptions().timeZone},o.tz.setDefault=function(t){r=t}}}));
\ No newline at end of file
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).dayjs_plugin_timezone=e()}(this,(function(){"use strict";function t(){return t=Object.assign?Object.assign.bind():function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(t[i]=n[i])}return t},t.apply(this,arguments)}var e={year:0,month:1,day:2,hour:3,minute:4,second:5},n={year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"},i={},r={};return function(o,a,u){var f,s=function(e,r,o){void 0===o&&(o={});var a=new Date(e),u=function(e,r){void 0===r&&(r={});var o=r.timeZoneName||"short",a=e+"|"+o,u=i[a];return u||(u=new Intl.DateTimeFormat("en-US",t({},n,{hour12:!1,timeZone:e,timeZoneName:o})),i[a]=u),u}(r,o);return u.formatToParts(a)},m=function(t,n){for(var i=s(t,n),r=[],o=0;o<i.length;o+=1){var a=i[o],f=a.type,m=a.value,c=e[f];c>=0&&(r[c]=parseInt(m,10))}var l=r[3],d=24===l?0:l,h=r[0]+"-"+r[1]+"-"+r[2]+" "+d+":"+r[4]+":"+r[5]+":000",v=+t;return(u.utc(h).valueOf()-(v-=v%1e3))/6e4},c=a.prototype;c.tz=function(e,i){void 0===e&&(e=f);var o=this.utcOffset(),a=this.toDate(),s=function(e){var i=r[e];return i||(i=new Intl.DateTimeFormat("en-US",t({},n,{timeZone:e})),r[e]=i),i}(e).format(a),m=Math.round((a-new Date(s))/1e3/60),c=u(s,{locale:this.$L}).$set("millisecond",this.$ms).utcOffset(15*-Math.round(a.getTimezoneOffset()/15)-m,!0);if(i){var l=c.utcOffset();c=c.add(o-l,"minute")}return c.$x.$timezone=e,c},c.offsetName=function(t){var e=this.$x.$timezone||u.tz.guess(),n=s(this.valueOf(),e,{timeZoneName:t}).find((function(t){return"timezonename"===t.type.toLowerCase()}));return n&&n.value};var l=c.startOf;c.startOf=function(t,e){if(!this.$x||!this.$x.$timezone)return l.call(this,t,e);var n=u(this.format("YYYY-MM-DD HH:mm:ss:SSS"),{locale:this.$L});return l.call(n,t,e).tz(this.$x.$timezone,!0)},u.tz=function(t,e,n){var
i=n&&e,r=n||e||f,o=m(+u(),r);if("string"!=typeof t)return u(t).tz(r);var a=function(t,e,n){var i=t-60*e*1e3,r=m(i,n);if(e===r)return[i,e];var o=m(i-=60*(r-e)*1e3,n);return r===o?[i,r]:[t-60*Math.min(r,o)*1e3,Math.max(r,o)]}(u.utc(t,i).valueOf(),o,r),s=a[0],c=a[1],l=u(s).utcOffset(c);return l.$x.$timezone=r,l},u.tz.guess=function(){return Intl.DateTimeFormat().resolvedOptions().timeZone},u.tz.setDefault=function(t){f=t}}}));

If the discrepancy persists, you may need to debug the time zone settings and offsets in both systems to identify any inconsistencies [1][2][3][4].

To continue talking to Dosu, mention @dosu.

aukfood commented 2 weeks ago

What I see is that information in Nextcloud database is not registred with the good hour. For example I create an event for 20240614 at 10h30 am and in Nextcloud database is registred at DTSTART:20240614T083000Z

So if I create an event in Netcloud at 20240613 at 13h30 event is like that in database : DTSTART;TZID=Europe/Paris:20240613T133000

So I think that cal.com is not registred correctly time in nextcloud database. How to see timezone in cal.com container ?

Regards

aukfood commented 2 weeks ago

Date in container is not good

b71d7c2b9a7f:/calcom# date
Wed Jun 12 16:27:48  2024

I try to add this in docker-compose

    environment:
      - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DATABASE_HOST}/${POSTGRES_DB}
      - TZ="Europe/Paris"
    depends_on:
      - database
    volumes:
      - "/etc/timezone:/etc/timezone:ro" 
aukfood commented 2 weeks ago

I set allo container with

 volumes:
      - "/etc/timezone:/etc/timezone:ro" 

But same problem date is ok but creation in nextcloud is not goot