Skip to content

Commit

Permalink
Merge branch 'fix/date-field-manage-timezone' into doryphore-dev
Browse files Browse the repository at this point in the history
  • Loading branch information
J9rem committed Sep 13, 2023
2 parents 70815a4 + 2f43e97 commit d86f51a
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 36 deletions.
7 changes: 5 additions & 2 deletions includes/YesWikiInit.php
Expand Up @@ -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
Expand Down
45 changes: 45 additions & 0 deletions includes/services/DateService.php
@@ -0,0 +1,45 @@
<?php

namespace YesWiki\Core\Service;

use DateInterval;
use DateTimeImmutable;
use DateTimeZone;
use Exception;

class DateService
{
public function __construct(
) {
}


public function getDateTimeWithRightTimeZone(string $date): DateTimeImmutable
{
$dateObj = new DateTimeImmutable($date);
if (!$dateObj){
throw new Exception("date '$date' can not be converted to DateImmutable !");
}
// retrieve right TimeZone from parameters
$defaultTimeZone = new DateTimeZone(date_default_timezone_get());
if (!$defaultTimeZone){
$defaultTimeZone = new DateTimeZone('GMT');
}
$newDate = $dateObj->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;
}
}
49 changes: 24 additions & 25 deletions tools/bazar/controllers/IcalFormatter.php
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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 [];
Expand Down Expand Up @@ -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');

Expand Down
13 changes: 8 additions & 5 deletions tools/bazar/fields/DateField.php
Expand Up @@ -2,6 +2,8 @@

namespace YesWiki\Bazar\Field;

use YesWiki\Core\Service\DateService;

/**
* @Field({"jour", "listedatedeb", "listedatefin"})
*/
Expand All @@ -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
Expand All @@ -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']];
Expand All @@ -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));
}
Expand Down
47 changes: 43 additions & 4 deletions tools/bazar/presentation/javascripts/components/BazarCalendar.js
Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -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)
Expand Down Expand Up @@ -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),
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions tools/templates/actions/linkjavascript.php
Expand Up @@ -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'),
Expand Down

3 comments on commit d86f51a

@J9rem
Copy link
Contributor Author

@J9rem J9rem commented on d86f51a Sep 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mrflos je reviens sur ce commit qui corrige un souci introduit par 5a0cefa

Avant 5a0cefa, la valeur par défaut était GMT, ce qui faisait que le fuseau horaire utilisé était celui du serveur php (même si le serveur était en Europe/Paris).
Après ce commit, si l'usager mettait Europe/Paris alors le fuseau horaire utilisé était celui du serveur (par exemple GMT).

Le présent commit ne fait que corriger ceci est aussi prendre en compte que certaines dates pourraient avoir été enregistrées avec le fuseau horaire +00:00 (GMT) alors que le besoin est qu'elles devraient être enregistrées avec le fuseau horaire du site ex. : +02:00 (Europe/Paris).
Ceci pose en particulier un souci pour le template dynamique calendar.twig et ceci a été corrigé pour que les heures affichées correspondent bien à celles du fuseau horaire du serveur (comportement attendu par défaut lors de l'édition d'une fiche)

Rien n'a été ajouté pour faire référence à une éventuelle extension externe.

Le service DateService a été créé pour partager la fonction de correction du fuseau horaire avec le IcalFormatter car celui-ci aussi a besoin de faire attention aux décalages introduits par erreur.

Juste que le fuseau horaire du serveur est passé au javascript via wiki.timezone pour que le template calendar sache dans quel fuseau horaire il faut afficher les heures.

@mrflos
Copy link
Contributor

@mrflos mrflos commented on d86f51a Sep 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, merci pour la précision, en tout cas mon commit initial avait pour objectif de mettre la valeur par défaut des dates dans YesWiki sur Europe/Paris car souvent les serveurs ne sont pas configuré, et que la communauté s'attendait a avoir les dates saisie sur le créneau francais, sauf changement dans la conf.

Si cela respecte ce fonctionnement, ca va, si cela remet GMT par defaut, c'est pas bon.

@J9rem
Copy link
Contributor Author

@J9rem J9rem commented on d86f51a Sep 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avec le correctif que j'ai fait, tout est rentré dans l'ordre normalement (aux effets de bord près, mais je vais rester vigilant aux remarques sur le sujet, comme pour les serveurs qui étaient explicitement en UTC et qui changeraient volontairement de fuseau à Europe/Paris, il est possible que les évènements déjà enregistrés soient décalées de 2h d'un coup, ... ).

Please sign in to comment.