human_readable_duration, ou pas ?

human_readable_duration, ou pas ?

Ceci est un post réaction suite à un tweet de notre ami Pierre (on adore tes tweets WPro-Tips !!)

deux points

Alors déjà ma question est : quand/où obtient-on une donnée de temps sous ce format ?

49:18:27

Je vais vous le dire : dans les meta données des fichiers vidéos ou audio, c’est tout.

Donc autant c’est sympa, autant je trouve que son usage est plutôt réduit.

human

Et si on voulait l’utiliser pour autre chose justement car c’est ça le but, pouvoir utiliser l’existant ailleurs !

Disons que je souhaite afficher une longue durée, au delà de 24 heures ?

echo human_readable_duration( '70:50:30' );
// 70 heures, 50 minutes, 30 secondes

Perso, j’aurais aimé que ça passe en jours, ça c’est humain :/ Et aussi que la dernière virgule soit plutôt un “et”. Non je ne suis pas tatillon, c’est du natif WordPress si on utilise wp_sprintf_l() ! Je trouve ça plus propre.

seconde

Mais surtout, quand je dis “ailleurs”, eh bien je récupère 99% du temps des secondes, le fameux 314638200 par exemple.

Mais cette fonction ne prend QUE un format bien précis, avec des secondes et minutes qui ne dépassent pas 59, et les heures illimitées mais pas de jours.

Je souhaite alors obtenir le même résultat que human_readable_duration() mais en lui passant juste des secondes.

bonus x5

Car j’aime rajouter des options, je souhaite :

  1. ne pas afficher si besoin les valeurs qui sont à 0,
  2. afficher les majuscules sur les jours ou pas,
  3. ne pas recréer de traductions, juste du natif,
  4. garder une compat avec le format vidéo/audio même s’il dépasse les limites comme “99:99:99:99:99”,
  5. pouvoir dépasser jusque 999 ans !

Le tout en natif WordPress, code optimisé et propre bien entendu.

code commenté

full options air bag à l’arrière

function julio_readable_duration( $entry, $with_zeros = 'all', $first_caps = false ) {
	// If we use the human_readable_duration format with ":"
	if ( ! is_numeric( $entry ) ) {
		// Create coefficients to get the real seconds count since 1970
		$coeff    = [ 1, MINUTE_IN_SECONDS, HOUR_IN_SECONDS, DAY_IN_SECONDS, MONTH_IN_SECONDS, YEAR_IN_SECONDS ];
		// we start from seconds to years and keep only integer values
		$data     = array_reverse( array_map( 'intval', explode( ':', $entry ) ) );
		// we reset the $entry param
		$entry    = 0;
		// Now for each of our data, we multiply using the next coeff at each iteration
		foreach ( $data as $index => $time ) {
			$entry += $time * $coeff[ $index ];
		}
		// Now we should have a number of second, or… you were using a text! -> notice and stop
		if ( ! $entry ) {
			trigger_error( 'Entry data must be numeric or respect format dd:hh:mm:ss' );
			return;
		}
	}
	// Transform the seconds into a full param string from seconds to years (max 999)
	$from   = new \DateTime( '@0' );
	$to     = new \DateTime( "@$entry" );
	$data   = explode( ':', $from->diff( $to )->format('%s:%i:%h:%d:%m:%y') );
	// This is where we will add our results to be returned
	$return = [];
	// Native labels from WP, yipikaye!
	$labels = [ _n_noop( '%s second', '%s seconds' ),
				_n_noop( '%s minute', '%s minutes' ),
				_n_noop( '%s hour', '%s hours' ),
				_n_noop( '%s day', '%s days' ),
				_n_noop( '%s month', '%s months' ),
				_n_noop( '%s year', '%s years' ),
	];
	// Now again for each value, we get the correct plural form
	foreach( $data as $i => $time ) {
		// We can display the zeros or not, ot just the last ones (see examples)
		if ( '0' === $time && ( 'none' === $with_zeros || ( 'last' === $with_zeros && ! empty( array_filter( $return, 'intval' ) ) ) ) ) {
			continue;
		}
		// The translated string
		$tmp      = sprintf( translate_nooped_plural( $labels[ $i ], $time ), $time );
		// We can get first letter of day in caps (like in english)
		if ( $first_caps ) {
			$tmp  = ucwords( $tmp );
		}
		// Add this in our results…
		$return[] = $tmp;
	}
	// … but we need it from years to seconds now
	$return = array_reverse( $return );
	// And use the magic wp_sprintf_l to get the ',' and ', and' !
	$text   = wp_sprintf( '%l', $return );

	return $text;
}

code plus light

sans les caps, sans les 0, sans commentaires

function julio_readable_duration( $entry ) {
	if ( ! is_numeric( $entry ) ) {
		$coeff    = [ 1, MINUTE_IN_SECONDS, HOUR_IN_SECONDS, DAY_IN_SECONDS, MONTH_IN_SECONDS, YEAR_IN_SECONDS ];
		$data     = array_reverse( array_map( 'intval', explode( ':', $entry ) ) );
		$entry    = 0;
		foreach ( $data as $index => $time ) {
			$entry += $time * $coeff[ $index ];
		}
		if ( ! $entry ) {
			trigger_error( 'Entry data must be numeric or respect format dd:hh:mm:ss' );
			return;
		}
	}
	$from   = new \DateTime( '@0' );
	$to     = new \DateTime( "@$entry" );
	$data   = explode( ':', $from->diff( $to )->format('%s:%i:%h:%d:%m:%y') );
	$return = [];
	$labels = [ _n_noop( '%s second', '%s seconds' ),
				_n_noop( '%s minute', '%s minutes' ),
				_n_noop( '%s hour', '%s hours' ),
				_n_noop( '%s day', '%s days' ),
				_n_noop( '%s month', '%s months' ),
				_n_noop( '%s year', '%s years' ),
	];

	foreach( $data as $i => $time ) {
		$return[] = sprintf( translate_nooped_plural( $labels[ $i ], $time ), $time );
	}

	$return = array_reverse( $return );
	$text   = wp_sprintf( '%l', $return );

	return $text;
}

Quelques explications

Avant les exemples.

  • J’utilise is_numeric() et non is_int() ou intval() comme ça on peut aussi bien passer une chaine de chiffres ou un chiffre (voir les exemples).
  • Les coefficients sont les constantes de WordPress, rien à déclarer !
  • Je fais un explode avec : car c’est le format attendu, je force là un intval() pour mettre à 0 les possibles chaines de texte et éviter des erreurs par la suite (je ne patche pas la connerie), et je reverse le tout car je vais commencer à compter par les secondes (je ne peux pas commencer par les années, il n’y en a peut-être pas !) et remonter.
  • Je calcule donc mon nombre de secondes en additionnant en boucle le produit de mes coefficients. j’obtient alors naturellement un nombre de secondes depuis une chaine comme 49:18:27 !
  • Si jamais on a 0 dans $entry, alors c’est qu’on a passé en paramètre du texte… Si vous passez un array ça fera surement pire, mais encore une fois, je ne patche pas la connerie !
  • Maintenant qu’on a un entier, on peut commencer le vrai travail, créer une chaine qui se découpe toute seule, pas de math à faire avec des divisions par 60, 30, 24, 365 pour savoir si on a des jours, mois, années, minutes… alors que PHP fait ça nativement !
  • On a donc ce format : '%s:%i:%h:%d:%m:%y' qui va des secondes au années. S’il n’y a pas d’années, ça vaut alors 0 tout simplement !
  • Je déclare des labels qui proviennent de WP lui même, rien à retraduire, magiiiiie. Et j’utilise _n_noop() car je dois gérer le cas des pluriels selon la langue locale, et je ne sais pas encore combien est la valeur, donc je ne peux pas décider.
  • Là selon les paramètres, on laisse passer ou pas pour avoir les valeurs à 0 (voir les exemples). Ce qui est intéressant est le check avec array_map( $return, 'intval' ), le but est de n’ajouter les valeurs que si ce n’est pas vide, sans compter les 0 qui sont à la fin, pour le param “last” donc. Le intval sur les valeurs comme “0 secondes” va retourner “0” et donc va supprimer le temps du test car 0 == false !
  • Puis pour chaque de nos données de temps, on va enfin chercher la bonne trad avec le bon pluriel. C’est là qu’on utilise translate_nooped_plural() pour enfin mettre le compteur qui va alors nous donner le singulier ou pluriel.
  • Rapidement on fait un ucwords() sur notre valeur pour la majuscule (on aurait aussi pu le faire à la fin avec un array_map()).
  • Nous voila à la fin, on reverse de nouveau le tableau pour lire des années vers les secondes, humain quoi.
  • Et là, la grande magie opère avec l’appel à wp_sprintf_l() qui va nous retourner une chaine concaténée avec des virgules comme un implode( ', ' ) mais avec la petite particularité d’avoir une virgule de série (aussi appelée virgule d’Oxford ou Oxford comma) avec son “et” tout beau (voir les exemples).

example.com

Comparons d’abord des appels que human_readable_duration sait gérer :

echo human_readable_duration( '49:18:00' );
// 49 heures, 18 minutes, 0 seconde

echo julio_readable_duration( '49:18:00' );
// 0 an, 0 mois, 2 jours, 1 heure, 18 minutes, et 0 seconde

echo julio_readable_duration( '49:18:00', 'none' );
// 2 jours, 1 heure, et 18 minutes

Bon, c’est le seul cas qu’il sait gérer, et il ne gère pas les jours…

À nous, voici ce qu’on peut obtenir :

echo julio_readable_duration( 198999662 );
// 6 ans, 3 mois, 21 jours, 5 heures, 41 minutes, et 2 secondes

echo julio_readable_duration( '255030' );
echo julio_readable_duration( 255030 );
// 0 an, 0 mois, 2 jours, 22 heures, 50 minutes, et 30 secondes

echo julio_readable_duration( YEAR_IN_SECONDS * 1000 );
// 999 ans, 4 mois, 2 jours, 0 heure, 0 minute, et 0 seconde

echo julio_readable_duration( '2:5:15:12:55:30' );
// 2 ans, 5 mois, 13 jours, 12 heures, 55 minutes, et 30 secondes

echo julio_readable_duration( '99:99:99:99:99:99' );
// 107 ans, 4 mois, 6 jours, 4 heures, 40 minutes, et 39 secondes

echo julio_readable_duration( '999:999:999:999:999' );
// 84 ans, 10 mois, 26 jours, 7 heures, 55 minutes, et 39 secondes

echo julio_readable_duration( time() );
// 50 ans, 1 mois, 26 jours, 16 heures, 9 minutes, et 23 secondes

echo julio_readable_duration( time(), 'all', true );
// 50 Ans, 1 Mois, 26 Jours, 16 Heures, 10 Minutes, et 29 Secondes

Voilà, ça c’est utilisable ! Qu’on y passe des valeurs entières, des chaines de chiffres, du format de durée même avec de grands nombres, tout va bien !

D’ailleurs, on devrait réécrire human_readable_duration pour remettre ça au clair, car rien que le début, je ne comprends pas :

function human_readable_duration( $duration = '' ) {
    if ( ( empty( $duration ) || ! is_string( $duration ) ) ) {
        return false;
    }
…

Mettre un paramètre avec une valeur par défaut vide et devoir gérer le cas où on a rien mis en paramètre alors qu’il suffisait de ne pas mettre de valeur par défaut pour que PHP fasse lui même l’erreur !? WHAT?

La gestion du tiret négatif devrait se faire en amont et pas à elle de faire ça.

Ça fait du preg_match pour patcher la connerie au lieu de balancer du 0 avec intval.

1 IF pour chaque entrée, sinon, des boucles ? non ok…

BREF j’aime retoucher du code 😉

Vous aimez ? Partagez !


Réagir à cet article

220 caractères maximum