DateTime : nouvelle mais pas encore meilleure

programmation PHP

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com
Le calcul des dates avec strtotime, les opérations mathématiques avec des jours composés de 24 heures, 1440 minutes et 86400... Tout cela, c'est fini avec l'arrivée de la classe DateTime. Intégrée depuis la version 5.2 du PHP, elle permet d'effectuer les opérations sur le temps avec une facilité étonnante.

Dans la première partie, l'article présentera la classe et ses méthodes. Dans un deuxième temps on passera aux exemples. Chaque exemple sera illustré par deux solutions. La première sera réalisée sans utilisation de la classe DateTime. La seconde va être conçue avec l'emploi de cette classe. La dernière partie va traiter des tests de performance.

DateTime - présentation
Le constructeur de la classe peut prendre deux attributs (les deux sont facultatifs) :
- le temps ou la date (string)
- la zone du temps (objet de la classe DateTimeZone)

L'initialisation de la classe se déroule ainsi :

$dateObj = new DateTime();

var_dump($dateObj);

/* dump returns :
object(DateTime)[1]
public 'date' => string '2011-06-25 19:27:04' (length=19)
public 'timezone_type' => int 3
public 'timezone' => string 'Europe/Paris' (length=12)
*/

Si l'on exploite plus les possibilités de la classe, on aperçoit, entre autres :
- la gestion des erreurs rencontrées lors de l'utilisation de la classe (méthode getLastErrors()),
- le calcul de la différence entre deux objets DateTime, un peu à la DATEDIFF du MySQL : méthode diff()
- le calcul (la soustraction et l'addition) des dates de la manière simplifiée (avec l'utilisation du nombre des jours chiffré, plus besoin de les calculer à partir des heures et minutes) : méthode add() ou sub()

Les exemples : la sagesse procédurale contre la fraîcheur objet
1) Addition
Selon la méthode traditionnelle (procédurale), afin de rajouter 3 jours à une date, on doit tout d'abord convertir la date en timestamp. Ensuite on est obligé de convertir 3 jours en secondes. A la fin on est censé d’additionner deux chiffres afin de recevoir un résultat attendu.

Avec la nouvelle méthode on a seulement besoin d'initier la classe DateTime et d'indiquer le nombre de jours qu'on veut additionner. Voici deux méthodes. A vous de juger laquelle vous préférez adapter :

Méthode traditionnelle:

$yesterday = "03-05-2011";
// now we add 3 days to yesterday date
$newDate = strtotime($yesterday) + (3*24*60*60);
echo "New date is : ".(date('d-m-Y', $newDate));


Méthode nouvelle :

$date = new DateTime("03-05-2011");
$date->add(new DateInterval('P3D'));
echo "New date is ".$date->format('d-m-Y');


2) Soustraction
La soustraction dans la méthode traditionnelle se déroule de la même manière que l'addition. La seule différence consiste dans le fait qu'on soustrait le nombre de jours au lieu de l'additionner.

Quant à la nouvelle méthode, là également on n'a pas besoin de changer grande chose. On doit seulement appeler la méthode sub() au lieu d'add().

Méthode traditionnelle :

$yesterday = "03-05-2011";
// now we add 3 days to yesterday date
$newDate = strtotime($yesterday) - (3*24*60*60);
echo "New date is : ".(date('d-m-Y', $newDate));


Méthode nouvelle :

$date = new DateTime("03-05-2011");
$date->sub(new DateInterval('P3D'));
echo "New date is ".$date->format('d-m-Y');


Les performances
Pour le tests des performances on utilisera l'exemple avec les dates de la première guerre mondiale.

$dates = array(
"05-08-1914", "07-08-1914", "12-08-1914", "14-08-1914", "17-08-1914", "20-08-1914", "21-08-1914", "23-08-1914", "25-08-1914",
"26-08-1914", "28-08-1914", "29-08-1914", "02-09-1914", "06-09-1914", "09-09-1914", "11-09-1914", "12-09-1914", "25-09-1914",
"26-09-1914", "28-09-1914", "01-10-1914", "14-10-1914", "18-10-1914", "01-11-1914", "03-11-1914", "05-11-1914",
"03-12-1914", "08-12-1914", "16-12-1914", "18-12-1914", "20-12-1914", "24-01-1915", "31-01-1915", "03-02-1915",
"07-02-1915", "19-02-1915", "10-03-1915", "18-03-1915", "11-04-1915", "22-04-1915", "25-04-1915", "28-04-1915",
"01-05-1915", "06-05-1915", "15-05-1915", "19-05-1915", "31-05-1915", "04-06-1915", "23-06-1915", "27-06-1915",
"28-06-1915", "12-07-1915", "18-07-1915", "06-08-1915", "21-08-1915", "25-09-1915", "28-09-1915", "18-10-1915",
"02-11-1915", "10-11-1915", "07-12-1915", "18-12-1915", "06-01-1916", "13-01-1916", "21-01-1916", "21-02-1916",
"08-03-1916", "09-03-1916", "18-03-1916", "05-04-1916", "15-05-1916", "04-06-1916", "01-07-1916", "14-07-1916",
"15-07-1916", "23-07-1916", "03-08-1916", "06-08-1916", "03-09-1916", "14-09-1916", "15-09-1916", "10-10-1916",
"01-11-1916", "13-12-1916", "09-01-1917", "26-02-1917", "11-03-1917", "13-03-1917", "19-03-1917", "25-03-1917",
"26-03-1917", "09-04-1917", "11-04-1917", "16-04-1917", "17-04-1917", "21-04-1917", "30-04-1917", "12-05-1917",
"14-05-1917", "07-06-1917", "31-07-1917", "19-08-1917", "28-09-1917", "24-10-1917", "31-10-1917", "05-11-1917",
"13-11-1917", "20-11-1917", "08-12-1917", "23-04-1918", "27-05-1918", "28-05-1918", "03-06-1918", "06-06-1918",
"15-06-1918", "04-07-1918", "15-07-1918", "12-09-1918", "18-09-1918", "23-10-1918", "29-10-1918"
);
// source : http://www.firstworldwar.com/battles/all.htm


Dans un premier temps on voudra voir les intervalles entre les batailles.

Avec l'utilisation de la classe DateTime, 10 fois sur 10 on obtient le résultat égal à 0.01 sec. Voici le code utilisé pour ce test :

$timeStart = microtime();
$dates = array(
"05-08-1914", "07-08-1914", "12-08-1914", "14-08-1914", "17-08-1914", "20-08-1914", "21-08-1914", "23-08-1914", "25-08-1914",
"26-08-1914", "28-08-1914", "29-08-1914", "02-09-1914", "06-09-1914", "09-09-1914", "11-09-1914", "12-09-1914", "25-09-1914",
"26-09-1914", "28-09-1914", "01-10-1914", "14-10-1914", "18-10-1914", "01-11-1914", "03-11-1914", "05-11-1914",
"03-12-1914", "08-12-1914", "16-12-1914", "18-12-1914", "20-12-1914", "24-01-1915", "31-01-1915", "03-02-1915",
"07-02-1915", "19-02-1915", "10-03-1915", "18-03-1915", "11-04-1915", "22-04-1915", "25-04-1915", "28-04-1915",
"01-05-1915", "06-05-1915", "15-05-1915", "19-05-1915", "31-05-1915", "04-06-1915", "23-06-1915", "27-06-1915",
"28-06-1915", "12-07-1915", "18-07-1915", "06-08-1915", "21-08-1915", "25-09-1915", "28-09-1915", "18-10-1915",
"02-11-1915", "10-11-1915", "07-12-1915", "18-12-1915", "06-01-1916", "13-01-1916", "21-01-1916", "21-02-1916",
"08-03-1916", "09-03-1916", "18-03-1916", "05-04-1916", "15-05-1916", "04-06-1916", "01-07-1916", "14-07-1916",
"15-07-1916", "23-07-1916", "03-08-1916", "06-08-1916", "03-09-1916", "14-09-1916", "15-09-1916", "10-10-1916",
"01-11-1916", "13-12-1916", "09-01-1917", "26-02-1917", "11-03-1917", "13-03-1917", "19-03-1917", "25-03-1917",
"26-03-1917", "09-04-1917", "11-04-1917", "16-04-1917", "17-04-1917", "21-04-1917", "30-04-1917", "12-05-1917",
"14-05-1917", "07-06-1917", "31-07-1917", "19-08-1917", "28-09-1917", "24-10-1917", "31-10-1917", "05-11-1917",
"13-11-1917", "20-11-1917", "08-12-1917", "23-04-1918", "27-05-1918", "28-05-1918", "03-06-1918", "06-06-1918",
"15-06-1918", "04-07-1918", "15-07-1918", "12-09-1918", "18-09-1918", "23-10-1918", "29-10-1918"
);

for($d = count($dates)-1; $d >= 1; $d--)
{
$date1 = new DateTime($dates[$d]);
$date2 = new DateTime($dates[$d-1]);
$interval = $date1->diff($date2);
echo '

The difference between '.$dates[$d].' and '.$dates[$d-1].' is '.$interval->format('%a').' day(s).

';
// Windows system returns 6015 days (issue known here : http://bugs.php.net/bug.php?id=51184)
}
$timeEnd = microtime();

$executed = $timeEnd - $timeStart;

echo '
Executed in '.$executed.' seconds';


Le temps d'exécution pour le script utilisant la méthode traditionnelle diffère très peu de celui de la classe DateTime. En gros, on reçoit les résultats de 0.01 (8 fois sur 10) à 0.02 sécondes (2 fois sur 10). Voici le code utilisé pour les tests :

$timeStart = microtime();
$dates = array(
"05-08-1914", "07-08-1914", "12-08-1914", "14-08-1914", "17-08-1914", "20-08-1914", "21-08-1914", "23-08-1914", "25-08-1914",
"26-08-1914", "28-08-1914", "29-08-1914", "02-09-1914", "06-09-1914", "09-09-1914", "11-09-1914", "12-09-1914", "25-09-1914",
"26-09-1914", "28-09-1914", "01-10-1914", "14-10-1914", "18-10-1914", "01-11-1914", "03-11-1914", "05-11-1914",
"03-12-1914", "08-12-1914", "16-12-1914", "18-12-1914", "20-12-1914", "24-01-1915", "31-01-1915", "03-02-1915",
"07-02-1915", "19-02-1915", "10-03-1915", "18-03-1915", "11-04-1915", "22-04-1915", "25-04-1915", "28-04-1915",
"01-05-1915", "06-05-1915", "15-05-1915", "19-05-1915", "31-05-1915", "04-06-1915", "23-06-1915", "27-06-1915",
"28-06-1915", "12-07-1915", "18-07-1915", "06-08-1915", "21-08-1915", "25-09-1915", "28-09-1915", "18-10-1915",
"02-11-1915", "10-11-1915", "07-12-1915", "18-12-1915", "06-01-1916", "13-01-1916", "21-01-1916", "21-02-1916",
"08-03-1916", "09-03-1916", "18-03-1916", "05-04-1916", "15-05-1916", "04-06-1916", "01-07-1916", "14-07-1916",
"15-07-1916", "23-07-1916", "03-08-1916", "06-08-1916", "03-09-1916", "14-09-1916", "15-09-1916", "10-10-1916",
"01-11-1916", "13-12-1916", "09-01-1917", "26-02-1917", "11-03-1917", "13-03-1917", "19-03-1917", "25-03-1917",
"26-03-1917", "09-04-1917", "11-04-1917", "16-04-1917", "17-04-1917", "21-04-1917", "30-04-1917", "12-05-1917",
"14-05-1917", "07-06-1917", "31-07-1917", "19-08-1917", "28-09-1917", "24-10-1917", "31-10-1917", "05-11-1917",
"13-11-1917", "20-11-1917", "08-12-1917", "23-04-1918", "27-05-1918", "28-05-1918", "03-06-1918", "06-06-1918",
"15-06-1918", "04-07-1918", "15-07-1918", "12-09-1918", "18-09-1918", "23-10-1918", "29-10-1918"
);

for($d = count($dates)-1; $d >= 1; $d--)
{
$date1 = strtotime($dates[$d]);
$date2 = strtotime($dates[$d-1]);
$interval = $date1 - $date2;
echo '

The difference between '.$dates[$d].' and '.$dates[$d-1].' is '.round($interval/(24*60*60)).' day(s).

';
}
$timeEnd = microtime();

$executed = $timeEnd - $timeStart;

echo '
Executed in '.$executed.' seconds';


La différence est très peu significative. Peut-être celle retournée par le prochain test sera plus parlante. Le deuxième test va calculer les intervalles du temps entre les 50 premières dates, ce qui devrait nous donner au final 1225 calculs.

Selon la méthode traditionnelle on reçoit les résultats allant de 0.08 à 0.09 secondes. Voici le code utilisé :

$timeStart = microtime();
$dates = array(
"05-08-1914", "07-08-1914", "12-08-1914", "14-08-1914", "17-08-1914", "20-08-1914", "21-08-1914", "23-08-1914", "25-08-1914",
"26-08-1914", "28-08-1914", "29-08-1914", "02-09-1914", "06-09-1914", "09-09-1914", "11-09-1914", "12-09-1914", "25-09-1914",
"26-09-1914", "28-09-1914", "01-10-1914", "14-10-1914", "18-10-1914", "01-11-1914", "03-11-1914", "05-11-1914",
"03-12-1914", "08-12-1914", "16-12-1914", "18-12-1914", "20-12-1914", "24-01-1915", "31-01-1915", "03-02-1915",
"07-02-1915", "19-02-1915", "10-03-1915", "18-03-1915", "11-04-1915", "22-04-1915", "25-04-1915", "28-04-1915",
"01-05-1915", "06-05-1915", "15-05-1915", "19-05-1915", "31-05-1915", "04-06-1915", "23-06-1915", "27-06-1915"
);

$counter = 1;
for($d = count($dates)-1; $d >= 1; $d--)
{
$date1 = strtotime($dates[$d]);
for($e = $d-1; $e >= 0; $e--)
{
$date2 = strtotime($dates[$e]);
$interval = $date1 - $date2;
echo '

'.($counter++).'. The difference between '.$dates[$d].' and '.$dates[$e].' is '.round($interval/(24*60*60)).' day(s).

';
}
echo '<hr /><br />';
}
$timeEnd = microtime();

$executed = $timeEnd - $timeStart;

echo '
Executed in '.$executed.' seconds';


Le calcul avec l'utilisation de la classe DateTime s'est avéré plus long. Il prenait en moyenne 0.11 - 0.13 secondes. La différence est ainsi plus marquante. Le code utilisé pour ces tests :

$timeStart = microtime();
$dates = array(
"05-08-1914", "07-08-1914", "12-08-1914", "14-08-1914", "17-08-1914", "20-08-1914", "21-08-1914", "23-08-1914", "25-08-1914",
"26-08-1914", "28-08-1914", "29-08-1914", "02-09-1914", "06-09-1914", "09-09-1914", "11-09-1914", "12-09-1914", "25-09-1914",
"26-09-1914", "28-09-1914", "01-10-1914", "14-10-1914", "18-10-1914", "01-11-1914", "03-11-1914", "05-11-1914",
"03-12-1914", "08-12-1914", "16-12-1914", "18-12-1914", "20-12-1914", "24-01-1915", "31-01-1915", "03-02-1915",
"07-02-1915", "19-02-1915", "10-03-1915", "18-03-1915", "11-04-1915", "22-04-1915", "25-04-1915", "28-04-1915",
"01-05-1915", "06-05-1915", "15-05-1915", "19-05-1915", "31-05-1915", "04-06-1915", "23-06-1915", "27-06-1915"
);

$counter = 1;
for($d = count($dates)-1; $d >= 1; $d--)
{
$date1 = new DateTime($dates[$d]);
for($e = $d-1; $e >= 0; $e--)
{
$date2 = new DateTime($dates[$d-1]);
$interval = $date1->diff($date2);
echo '

'.($counter++).'. The difference between '.$dates[$d].' and '.$dates[$e].' is '.$interval->format('%a').' day(s).

';
}
echo '<hr /><br />';
}
$timeEnd = microtime();

$executed = $timeEnd - $timeStart;

echo '
Executed in '.$executed.' seconds';

La classe DateTime est une nouvelle bonne solution proposée par l'équipe de développement PHP. Très intuitive, aussi rapide que strtotime() pour les opérations simples, elle va aider à accélérer le temps de travail. Néanmoins, elle reste encore très fraîche, pas toujours accessible sur les serveurs mutualisés, et donc pas encore opérationnelle pour l'utiliser dans les projets dont l'environnement de travail reste inconnu. A utiliser avec précaution, jusque l'âgé de sa maturité.

L'article écrit en rythme de:
Slaï - Va s'y Doucement
Bartosz KONIECZNY 26-06-2011 09:19 PHP
Moi

Développeur d'applications Internet et journaliste passionné par l'adjectif français. Un aigle polonais orienté vers la progression, volant très haut et écoutant du zouk après les matches du foot français.

Vous appréciez mon travail ?

Pour contribuer au développement de ce site, ou pour remercier pour des articles rédigés, vous pouvez faire un don.

Un conseil Symfony2

Comment générer un url absolu sous Symfony2 ?

La génération des urls sous Symfony2 se déroule via la méthode generateUrl() de la classe Symfony\Bundle\FrameworkBundle\Controller\Controller.

Cette fonction doit prendre un paramètre obligatoire - le premier qui indique le nom de la route à générer.

Les deux paramètres suivants sont facultatifs. Le premier parmi eux indique les attributs de l'url (par exemple le numéro de la page). Le deuxième prend pour valeur soit true, soit false. S'il est mis en true, cela veut dire que Symfony2 va générer l'url absolu. Cela s'illustre avec cet exemple :

$this->generateUrl('registerConfirm', array('code' => sha1('test')), true);