From 020fa5662ad0d077fa98558fc6bad7d903287734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Wed, 13 Sep 2023 22:44:16 +0200 Subject: [PATCH 1/5] fix(YesWikiInit): set default timezone event if wakka.config = default --- includes/YesWikiInit.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/includes/YesWikiInit.php b/includes/YesWikiInit.php index 6f4ec483f..9f4862622 100644 --- a/includes/YesWikiInit.php +++ b/includes/YesWikiInit.php @@ -254,10 +254,13 @@ public function getConfig($wakkaConfig = array()) $wakkaConfig = $this->array_merge_recursive_distinct($yeswikiDefaultConfig, $wakkaConfig); // give a default timezone to avoid error - if (!empty($wakkaConfig['timezone']) && $wakkaConfig['timezone'] != $yeswikiDefaultConfig['timezone']) { + if (!empty($wakkaConfig['timezone'])) { date_default_timezone_set($wakkaConfig['timezone']); - } elseif (!ini_get('date.timezone')) { + } elseif (!empty($yeswikiDefaultConfig['timezone'])) { date_default_timezone_set($yeswikiDefaultConfig['timezone']); + } elseif (!ini_get('date.timezone')) { + // backup in last case + date_default_timezone_set('GMT'); } // check for locking From 1ba40e514f4acf2013d63ee939f6aeeb169b7614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Wed, 13 Sep 2023 22:44:54 +0200 Subject: [PATCH 2/5] feat(DateService): create to be able to repair wrong timezone --- includes/services/DateService.php | 45 +++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 includes/services/DateService.php diff --git a/includes/services/DateService.php b/includes/services/DateService.php new file mode 100644 index 000000000..1fc27dca3 --- /dev/null +++ b/includes/services/DateService.php @@ -0,0 +1,45 @@ +setTimeZone($defaultTimeZone); + $anchor = '+00:00'; + if (substr($date,-strlen($anchor)) == $anchor){ + // it could be an error + $offsetToGmt = $defaultTimeZone->getOffset($newDate); + // be careful to offset time because time is changed by setTimeZone + $offSetAbs = abs($offsetToGmt); + return ($offsetToGmt == 0) + ? $newDate + : ( + $offsetToGmt > 0 + ? $newDate->sub(new DateInterval("PT{$offSetAbs}S")) + : $newDate->add(new DateInterval("PT{$offSetAbs}S")) + ); + } + return $newDate; + } +} From c1c4a11685446b1337423b73b4cb8e8cb7a7a4b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Wed, 13 Sep 2023 22:45:29 +0200 Subject: [PATCH 3/5] fix(IcalFormatter): repair export with right timezone --- tools/bazar/controllers/IcalFormatter.php | 49 +++++++++++------------ 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/tools/bazar/controllers/IcalFormatter.php b/tools/bazar/controllers/IcalFormatter.php index acf528dc0..4d4388e63 100644 --- a/tools/bazar/controllers/IcalFormatter.php +++ b/tools/bazar/controllers/IcalFormatter.php @@ -6,35 +6,39 @@ namespace YesWiki\Bazar\Controller; +use DateInterval; +use DateTimeImmutable; +use DateTimeZone; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\HttpFoundation\Response; use YesWiki\Bazar\Field\DateField; use YesWiki\Bazar\Controller\EntryController; use YesWiki\Bazar\Controller\GeoJSONFormatter; +use YesWiki\Core\Service\DateService; use YesWiki\Core\Service\Performer; use YesWiki\Core\YesWikiController; -use \DateInterval; -use \DateTime; -use \DateTimeZone; class IcalFormatter extends YesWikiController { public const MAX_CHARS_BY_LINE = 74; - protected $params; - protected $geoJSONFormatter; + protected $dateService; protected $entryController; + protected $geoJSONFormatter; + protected $params; protected $performer; public function __construct( - ParameterBagInterface $params, - GeoJSONFormatter $geoJSONFormatter, + DateService $dateService, EntryController $entryController, + GeoJSONFormatter $geoJSONFormatter, + ParameterBagInterface $params, Performer $performer ) { - $this->params = $params; - $this->geoJSONFormatter = $geoJSONFormatter; + $this->dateService = $padateServicerams; $this->entryController = $entryController; + $this->geoJSONFormatter = $geoJSONFormatter; + $this->params = $params; $this->performer = $performer; } @@ -145,27 +149,25 @@ public function formatToICAL(array $entries, $formId = null): string private function getICALData(array $entry):array { if (!empty($entry['bf_date_debut_evenement']) && !empty($entry['bf_date_fin_evenement'])) { - $startDate = new DateTime($entry['bf_date_debut_evenement']); + $startDate = $this->dateService->getDateTimeWithRightTimeZone($entry['bf_date_debut_evenement']); if (is_null($startDate)){ return []; } - $endData = $entry['bf_date_fin_evenement']; - $endDataObject = new DateTime($endData); - if (is_null($endDataObject)){ + $endDate = $this->dateService->getDateTimeWithRightTimeZone($entry['bf_date_fin_evenement']); + if (is_null($endDate)){ return []; } // 24 h for end date if all day - if ($this->isAllDay(strval($endData))) { - $endData = $endDataObject->add(new DateInterval('P1D'))->format('Y-m-d H:i:s'); - $endDataObject = new DateTime($endData); + if ($this->isAllDay(strval($entry['bf_date_fin_evenement']))) { + $endDate = $endDate->add(new DateInterval('P1D')); } - if ($startDate->diff($endDataObject)->invert > 0){ + if ($startDate->diff($endDate)->invert > 0){ // end date before start date not possible in ical : use start time + 1 hour - $endData = $startDate->add(new DateInterval('PT1H'))->format('Y-m-d H:i:s'); + $endDate = $startDate->add(new DateInterval('PT1H')); } return [ - 'startDate' => $entry['bf_date_debut_evenement'], - 'endDate' => $endData, + 'startDate' => $startDate->format('Y-m-d H:i:s'), + 'endDate' => $endDate->format('Y-m-d H:i:s'), ]; } return []; @@ -262,11 +264,8 @@ private function formatEvent(array $entry, array $icalData, array &$cache): stri */ private function formatDate(string $date): string { - if (empty($date)) { - $date = null; - } - $dateObject = new DateTime($date); - $dateObject->setTimezone(new DateTimeZone('UTC')); + $dateObject = empty($date) ? new DateTimeImmutable() : new DateTimeImmutable($date); + $dateObject = $dateObject->setTimezone(new DateTimeZone('UTC')); $localFormattedDate = $dateObject->format('Ymd'); $localFormattedTime = $dateObject->format('His'); From ac5962220d21987c927d9242256790e8b22ed5ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Wed, 13 Sep 2023 22:46:04 +0200 Subject: [PATCH 4/5] fix(DateField): use DateService to repair wrong timezone --- tools/bazar/fields/DateField.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tools/bazar/fields/DateField.php b/tools/bazar/fields/DateField.php index 7ed68b974..f58d77d82 100644 --- a/tools/bazar/fields/DateField.php +++ b/tools/bazar/fields/DateField.php @@ -2,6 +2,8 @@ namespace YesWiki\Bazar\Field; +use YesWiki\Core\Service\DateService; + /** * @Field({"jour", "listedatedeb", "listedatefin"}) */ @@ -17,11 +19,12 @@ protected function renderInput($entry) if (!empty($value)) { // Default value when entry exist - $day = date("Y-m-d", strtotime($value)); - $hasTime = (strlen($value) > 10); + $day = $this->getService(DateService::class)->getDateTimeWithRightTimeZone($value)->format('Y-m-d H:i'); + $hasTime = (strlen($day) > 10); if ($hasTime) { - $result = explode('T', $value); + $result = explode(' ', $day); list($hour, $minute) = array_map('intval', explode(':', $result[1])); + $day = $result[0]; } } elseif (!empty($this->default)) { // Default value when new entry @@ -47,7 +50,7 @@ public function formatValuesBeforeSave($entry) $value = $this->getValue($entry); if (!empty($value) && isset($entry[$this->propertyName . '_allday']) && $entry[$this->propertyName . '_allday'] == 0 && isset($entry[$this->propertyName . '_hour']) && isset($entry[$this->propertyName . '_minutes'])) { - $value = date("c", strtotime($value . ' ' . $entry[$this->propertyName . '_hour'] . ':' . $entry[$this->propertyName . '_minutes'])); + $value = $this->getService(DateService::class)->getDateTimeWithRightTimeZone("$value {$entry[$this->propertyName . '_hour']}:{$entry[$this->propertyName . '_minutes']}")->format('c'); } return [$this->propertyName => $value, 'fields-to-remove' =>[$this->propertyName . '_allday',$this->propertyName . '_hour',$this->propertyName . '_minutes']]; @@ -61,7 +64,7 @@ protected function renderStatic($entry) } if (strlen($value) > 10) { - $value = date('d.m.Y - H:i', strtotime($value)); + $value = $this->getService(DateService::class)->getDateTimeWithRightTimeZone($value)->format('d.m.Y - H:i'); } else { $value = date('d.m.Y', strtotime($value)); } From 2f43e979d45bd6b1ff0254f573a3c19454859c47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Wed, 13 Sep 2023 22:46:38 +0200 Subject: [PATCH 5/5] fix(BazarCalendar): display right hours in server timezone --- .../javascripts/components/BazarCalendar.js | 47 +++++++++++++++++-- tools/templates/actions/linkjavascript.php | 1 + 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/tools/bazar/presentation/javascripts/components/BazarCalendar.js b/tools/bazar/presentation/javascripts/components/BazarCalendar.js index 8a4761d94..121d29dc9 100644 --- a/tools/bazar/presentation/javascripts/components/BazarCalendar.js +++ b/tools/bazar/presentation/javascripts/components/BazarCalendar.js @@ -34,6 +34,22 @@ Vue.component('BazarCalendar', { } return true }, + getDateInServerTimeZone(date){ + const newDate = new Date(this.getDateStringInServerTimeZone(date)) + if (newDate){ + return newDate.toISOString() + } + return date + }, + getDateStringInServerTimeZone(date){ + let exportableDate = new Date(date) + if (exportableDate){ + return exportableDate + .toLocaleString('en-GB',{timeZone:wiki.timezone}) + .replace(/^([0-9]{2})\/([0-9]{2})\/([0-9]{4}), ([0-9]{2}):([0-9]{2}):([0-9]{2})$/,'$3-$2-$1T$4:$5:$6') + } + return date + }, displaySideBar(info) { info.jsEvent.preventDefault() const { entries } = this @@ -57,10 +73,10 @@ Vue.component('BazarCalendar', { formatEndDate(entry) { // Fixs bug, when no time is specified, is the event is on multiple day, calendJs show it like // it end one day earlier - const startDate = entry.bf_date_debut_evenement + const startDate = this.retrieveTimeZone(entry.bf_date_debut_evenement) let endDate = null if (entry.bf_date_fin_evenement != undefined) { - endDate = entry.bf_date_fin_evenement + endDate = this.retrieveTimeZone(entry.bf_date_fin_evenement) try { endDate = new Date(endDate) } catch (error) { @@ -79,7 +95,7 @@ Vue.component('BazarCalendar', { endDate.setMilliseconds(0) return endDate.toISOString() } - let endDateRaw = entry.bf_date_fin_evenement + let endDateRaw = this.retrieveTimeZone(entry.bf_date_fin_evenement) if (endDateRaw.length <= 10) { endDate.setDate(endDate.getDate() + 1) // +1 day endDate.setHours(0) @@ -125,7 +141,7 @@ Vue.component('BazarCalendar', { const newEvent = { id: entryId, title: entry.bf_titre, - start: entry.bf_date_debut_evenement, + start: this.retrieveTimeZone(entry.bf_date_debut_evenement), end: this.formatEndDate(entry), url: entry.url + (this.isModalDisplay() ? '/iframe' : ''), allDay: this.isAllDayDate(entry.bf_date_debut_evenement), @@ -150,6 +166,29 @@ Vue.component('BazarCalendar', { existingEvent.remove() } }, + retrieveTimeZone(dateAsString){ + let exportableDate = dateAsString + if (typeof exportableDate === 'string' + && exportableDate?.length > 10){ + if (exportableDate.match(/\+00:00$/)){ + // could be an error + const dateObj = new Date(exportableDate) + if (dateObj){ + const browserTimezoneOffset = dateObj.getTimezoneOffset() + const dateNoTimeZone = exportableDate.replace(/\+00:00$/,'') + const dateObjNoTimezone = new Date(dateNoTimeZone) + const dateStringInServerTimeZone = this.getDateStringInServerTimeZone(dateNoTimeZone) + const diffBetweenServerAndBrowserTimeZone_ms = (new Date(dateStringInServerTimeZone)).getTime() + - dateObjNoTimezone.getTime() + dateObj.setTime(dateObj.getTime()+browserTimezoneOffset*60000-diffBetweenServerAndBrowserTimeZone_ms) + exportableDate = dateObj.toISOString() + } + } + // fake date in browser timezone to be sure to be sync with server's timezone + exportableDate = this.getDateInServerTimeZone(exportableDate) + } + return exportableDate + }, updateEventData(arg) { const { event } = arg const htmlAttributes = event.extendedProps.htmlattributes diff --git a/tools/templates/actions/linkjavascript.php b/tools/templates/actions/linkjavascript.php index 922434954..1d5521c9e 100755 --- a/tools/templates/actions/linkjavascript.php +++ b/tools/templates/actions/linkjavascript.php @@ -86,6 +86,7 @@ $wikiprops = [ 'locale' => $GLOBALS['prefered_language'], + 'timezone' => date_default_timezone_get(), 'baseUrl' => $this->config['base_url'], 'pageTag' => $this->getPageTag(), 'isDebugEnabled' => ($this->GetConfigValue('debug') =='yes' ? 'true' : 'false'),