Voici une classe PHP5 pour la gestion de feuille de temps en vue d'effectuer par exemple une estimation de date de fin en jours ouvrés.
Calcul exact des jours ouvrés grâce à 1 Calcul des jours fériés.
Calcul de la date et heure de fin à partir d'une durée de charge en j/h avec nombre à virgules flottantes (optimisation réélle à 1 chiffre après la virgule en paramétrant l'interval minimale [iMinIntervalPeriod] à une valeur > 0.1 ).
Possibilité de paramétrer les jours de repos par défaut samedi dimanche.
Methode de calcul par paramétrage du plus petit interval en j/h par défaut 0.01
La valeur 0.01 ne convient pour des performances optimales => valeur choisie uniquement pour exemple.
[addInterval]
Derniière MAJ Modification de l'init:
Ajout de contrôles pour ne pas saisir n'importe quoi.
Optimisations ajout de methodes privées
En attendant de trouver une autre méthode qui n'utilise pas l'ajout successif d'intervalles pour le calcul
Source / Exemple :
<?php
class WorkingTimeSheet
{
/**
@property :int
@desc :temps écoulé depuis le début de la journée en s
private $iElapsedTime;
/**
@property :int
@desc : timestamp en cours
private $iCurrentTimestamp;
/**
@property :array
@desc :Les jours fériés de l'année en cours
private $aBankHolydays;
/**
@property :double
@desc :Dernière Periode ajoutée
private $dPeriod;
/**
@property :array
@desc : les jours de la semaine
en francophone
public static $aWDays=array(0=>'Dimanche',
1=>'Lundi',
2=>'Mardi',
3=>'Mercredi',
4=>'Jeudi',
5=>'Vendredi',
6=>'Samedi');
/**
@property :array
@desc :
Les options de configuration
à passer dans le constructeur
ou à configurer par la methode configureProperties
iStartingTimestamp:
Timestamp pour la date heure de début
définissant également l' Horaire
d'ouverture quotidienne.
iTimePerDay:
duree d'une journée travaillée en seconde
sFormat:
Format de date pour affichage
iEffectiveStartTimestamp :
prend en compte l'heure de début effective
du calcul dans le cas ou une tache ne commencerait
pas au moment de l'ouverture quotidienne inclus
dans iStartingTimestamp.
bClosedOnClosedDays :
Booléen à true si les jours fermés ne sont pas
travaillés => true par défaut
false sinon
aWeeklyClosedDays :
Les jours chomés de la semaine
Defaut Samedi 6 Dimanche 0
protected $aOptions=array(
'iStartingTimestamp'=>0,
'iEffectiveStartingTimestamp'=>NULL,
'iTimePerDay'=>28800,
'sFormat'=>'d/m/Y H:i:s',
'bClosedOnClosedDays'=>true,
'aWeeklyClosedDays'=>array(6,0),
'dMinIntervalPeriod'=>0.01);
/**
@param :mixed
@desc :
$prop est la propriété à récupérer
possible uniquement à l'interieur de la classe
protected function __get($prop)
{
if (property_exists(get_class($this),$prop))
return $this->$prop;
if (!array_key_exists($prop,$this->aOptions))
throw new Exception(__CLASS__.'::'.__FUNCTION__.
' clé option inexistante '.$prop);
return $this->aOptions[$prop];
}
/**
@param :mixed $prop
@param :mixed $value
@desc :
$prop est la propriété à définir
possible uniquement à l'interieur de la classe
protected function __set($prop,$value)
{
if (property_exists(get_class($this),$prop))
$this->$prop=$value;
if (!array_key_exists($prop,$this->aOptions))
throw new Exception(__CLASS__.'::'.__FUNCTION__.
' clé option inexistante '.$prop);
$this->aOptions[$prop]=$value;
}
/**
@param :array $aProps
@desc :
Configuration des options
Possibilité de passer un tableau incomplet
à condition que chaque clé existe
Les valeurs du tableau non renseignées
sont celles configurés par défaut en définition
public function configureProperties($aProps)
{
if (!is_array($aProps))
throw new Exception(__CLASS__.'::'.__FUNCTION__.
' paramettre invalide ');
foreach($aProps as $key=>$val)
{
if (!array_key_exists($key,$this->aOptions))
{
throw new Exception(__CLASS__.'::'.__FUNCTION__.
' clé option inexistante '.$key);
}
$this->aOptions[$key]=$val;
}
}
/**
@desc :
Validation et Initialisation des Propriétés privées
ou des propriétés non définies.
private function validProperties()
{
//echo '<br/>avant validProp'.(date('H',$this->iEffectiveStartingTimestamp));
if (!is_int($this->iStartingTimestamp))
throw new Exception (__CLASS__.' :: '.__FUNCTION__.
' : Type int attendu L '.__LINE__);
if (!is_int($this->iEffectiveStartingTimestamp)&&
$this->iEffectiveStartingTimestamp!==NULL)
throw new Exception (__CLASS__.' :: '.__FUNCTION__.
' : Type int attendu L '.__LINE__);
if (!is_int($this->iTimePerDay))
throw new Exception (__CLASS__.' :: '.__FUNCTION__.
' : Type int attendu L '.__LINE__);
if (!is_string($this->sFormat))
throw new Exception (__CLASS__.' :: '.__FUNCTION__.
' : Type string attendu L '.__LINE__);
if (!is_bool($this->bClosedOnClosedDays))
throw new Exception (__CLASS__.' :: '.__FUNCTION__.
' : Type bool attendu L '.__LINE__);
if (!is_double($this->dMinIntervalPeriod))
throw new Exception (__CLASS__.' :: '.__FUNCTION__.
' : Type double attendu L '.__LINE__);
if ($this->dMinIntervalPeriod==0)
throw new Exception (__CLASS__.' :: '.__FUNCTION__.
' : 0 interdit '.__LINE__);
if ($this->isClosed($this->iCurrentTimestamp))
$this->nextOpenedTimestamp();
if ($this->iEffectiveStartingTimestamp===NULL)
$this->iEffectiveStartingTimestamp=$this->iCurrentTimestamp;
else
$this->iEffectiveStartingTimestamp=mktime(
date('H',$this->iEffectiveStartingTimestamp),
date('i',$this->iEffectiveStartingTimestamp),
date('s',$this->iEffectiveStartingTimestamp),
date('m',$this->iCurrentTimestamp),
date('d',$this->iCurrentTimestamp),
date('Y',$this->iCurrentTimestamp));
//echo '<br/>après validProp'.(date('H',$this->iEffectiveStartingTimestamp));
$this->iElapsedTime=$this->iEffectiveStartingTimestamp-$this->iCurrentTimestamp;
$this->iCurrentTimestamp=$this->iEffectiveStartingTimestamp;
}
/**
@param :mixed $mProps
optionnel par défaut le timestamp actuel est récupéré
@desc :
Constructeur
Configuration des options
public function __construct($mProps=NULL)
{
if ($mProps===NULL)
$this->iStartingTimestamp=mktime();
if (is_array($mProps))
{
$this->configureProperties($mProps);
}
else
if (is_int($mProps))
$this->iStartingTimestamp=$mProps;
$this->iCurrentTimestamp=$this->iStartingTimestamp;
$this->buildBankHolidays();
$this->validProperties();
}
/**
@desc :
Affiichage
public function __toString()
{
return '<br/>DEBUT SAISI : '.
self::$aWDays[date('w',$this->iStartingTimestamp)].
' '.date($this->sFormat,$this->iStartingTimestamp).
'<br/>DEBUT EFFECTIF : '.
self::$aWDays[date('w',$this->iEffectiveStartingTimestamp)].
' '.date($this->sFormat,$this->iEffectiveStartingTimestamp).
'<br/>FIN : '.
self::$aWDays[date('w',$this->iCurrentTimestamp)].
' '.date($this->sFormat,$this->iCurrentTimestamp).
'<br/>Durée Ecoulée en jour ouvrés: '.$this->dPeriod.'<br/>';
}
/**
@param int
@return int
@desc :
Donne le temps écoulé depuis la date de début
de la WorkTimeSheet
Ne pas utiliser
private function getElapsedTimestampFrom($Timestamp)
{
return ($Timestamp-$this->iStartingTimestamp);
}
/**
@param int optionnel
@desc :
Construit le tableau de jours feries
private function buildBankHolidays($ts=NULL)
{
if ($ts===NULL)
$year=date('Y',$this->iStartingTimestamp);
else
$year=date('Y',$ts);
$this->aBankHolydays=array('Jour de l\'an'=>mktime(0,0,0,1,1,$year),
'Fete du Travail'=>mktime(0,0,0,5,1,$year),
'Armist. 1945'=>mktime(0,0,0,5,8,$year),
'Fete nat'=>mktime(0,0,0,7,14,$year),
'Assomption'=>mktime(0,0,0,8,15,$year),
'Toussaint'=>mktime(0,0,0,11,1,$year),
'Armist. 1918'=>mktime(0,0,0,11,11,$year),
'Noel'=>mktime(0,0,0,12,25,$year));
//pâques
$G=$year%19;
$C=floor($year/100);
$C_4=floor($C/4);
$E=floor((8*$C+13)/25);
$H=(19*$G+$C-$C_4-$E+15)%30;
$K=floor($H/28);
$P=floor(29/($H+1));
$Q=floor((21-$G)/11);
$I=($K*$P*$Q -1)*$K+$H;
$B=floor($year/4)+$year;
$J1=$B+$I+2+$C_4-$C;
$J2=$J1%7;
$R=28+$I-$J2;
$day=(int)date('d',(mktime(0,0,0,3,1+$R,$year)));
$mth=(int)date('m',(mktime(0,0,0,3,1+$R,$year)));
$this->aBankHolydays['Lundi de paques']=mktime(0,0,0,$mth,$day,$year);
//ascension
$dasc=(int)date('d',(mktime(0,0,0,$mth,$day+38,$year)));
$masc=(int)date('m',(mktime(0,0,0,$mth,$day+38,$year)));
$this->aBankHolydays['ascension']=mktime(0,0,0,$masc,$dasc,$year);
//pentecôte
$dasc=(int)date('d',(mktime(0,0,0,$mth,$day+49,$year)));
$masc=(int)date('m',(mktime(0,0,0,$mth,$day+49,$year)));
$this->aBankHolydays['Lundi de Pentecote']=mktime(0,0,0,
$masc,$dasc,$year);
}
/**
@param int
@return bool
@desc :
Indique si un timestamp est un jour fermé
public function isClosed($ts)
{
$wdts=(int)date('w',$ts);
$ts=mktime(0,0,0,date('m',$ts),date('d',$ts),date('Y',$ts));
$isBankH=in_array($ts,$this->aBankHolydays);
$isWklyClosed=in_array($wdts,$this->aWeeklyClosedDays);
if(!$isBankH)
if ($isWklyClosed && $this->bClosedOnClosedDays===true)
return true;
if ($this->bClosedOnClosedDays===false && ($isBankH||$isWklyClosed))
return false;
if ($isBankH)
return true;
if (!$isWklyClosed)
return false;
return true;
}
/**
@param int
@desc :
Définit l'heure de début d'une tache
dans la même journée
que la propriété iStartingTimestamp
si une autre date est incluse elle n'est pas prise en
compte seule l'heure importe.
public function beginWorkSheetAt($timestamp)
{
$this->iEffectiveStartingTimestamp=$timestamp;
$this->validProperties();
}
/**
@desc :
Positionne le timestamp courant sur la prochaine
date d'ouverture sans considérer la date courante
private function nextOpenedTimestamp()
{
do
{
$lastYear = date('Y',$this->iCurrentTimestamp);
$this->iCurrentTimestamp=mktime(date('H',$this->iStartingTimestamp),
date('i',$this->iStartingTimestamp),
date('s',$this->iStartingTimestamp),
date('m',$this->iCurrentTimestamp),
date('d',$this->iCurrentTimestamp)+1,
date('Y',$this->iCurrentTimestamp));
if (date('Y',$this->iCurrentTimestamp)
!=$lastYear)
$this->buildBankHolidays($this->iCurrentTimestamp);
}
while ($this->isClosed($this->iCurrentTimestamp));
}
/**
@desc :
Ajoute le plus petit interval de temps possible
au timestamp courant
private function addInterval()
{
if ($this->iElapsedTime >= $this->iTimePerDay )
{
$this->iElapsedTime=0;
$this->nextOpenedTimestamp();
}
$this->iElapsedTime+=(int)($this->iTimePerDay*$this->dMinIntervalPeriod);
$this->iCurrentTimestamp+=(int)($this->iTimePerDay*$this->dMinIntervalPeriod);
}
/**
@param double $days
@desc :
Ajout du nombre de jours ouvrés en décimal
au timestamp courant procède par ajout d'intervalles
public function addFloatingDays($dDays)
{
$this->dPeriod=$dDays;
$nbOfIntervals=$dDays/$this->dMinIntervalPeriod;
while ($nbOfIntervals>0)
{
$this->addInterval();
$nbOfIntervals--;
}
}
/**
@desc :
retourne les jours feries
public function getBankHolydays()
{
return $this->aBankHolydays;
}
/**
@desc :
retourne les jours de repos de la semaine
public function getWeeklyClosedDays()
{
return $this->aWeeklyClosedDays;
}
/**
@return int
@desc :
retourne le timestamp courant
public function getTimestamp()
{
return $this->iCurrentTimestamp;
}
}
try
{
// Exemple de feuille de temps commençant tous les jours à 10:00:00:
// l'heure de cette date passée en paramètre est gardée comme
// heure d'ouverture quotidienne.
// On peut commencer le calcul d'une durée ultérieurement dans cette
// journée de debut en passant par la methode beginWorkSheetAt($timestamp)
// ou par la clé iEffectiveStartingTimestamp du tableau d'options en parametre
$Options=array('iStartingTimestamp'=>mktime(10,0,0,12,25,2007),
'iEffectiveStartingTimestamp'=>mktime(14,0,0),
'iTimePerDay'=>28800,
'sFormat'=>'d/m/Y H:i:s',
'bClosedOnClosedDays'=>true,
'aWeeklyClosedDays'=>array(6,0),
'dMinIntervalPeriod'=>0.1);
$WorkTime=new WorkingTimeSheet($Options);
// Ex Si La premiere feuille commence à 14 heure le premier jour
// configuration possible par le constructeur ou par
// configureProperties()
//$WorkTime->beginWorkSheetAt(mktime(14,0,0));
// calcul la date de fin
$WorkTime->addFloatingDays(5.5);
// récuperation des jours fériés
$Feries = $WorkTime->getBankHolydays();
$Fermes= $WorkTime->getWeeklyClosedDays();
// récupération du timestamp de fin si besoin.
$endTimestamp=$WorkTime->getTimestamp();
// écriture de la date de fin par appel implicite à __toString()
echo $WorkTime;
foreach ($Feries as $key=> $val)
{
echo '<br/>'.$key.' : '.WorkingTimeSheet::$aWDays[date('w',$val)].
' '.date('d/m/Y',$val);
}
foreach ($Fermes as $val)
{
echo '<br/> : '.WorkingTimeSheet::$aWDays[$val];
}
}
catch (Exception $e)
{
echo $e->getMessage();
}
?>
Conclusion :
Voilà N'hésitez pas à me faire part de possibles bugs
Vous n'êtes pas encore membre ?
inscrivez-vous, c'est gratuit et ça prend moins d'une minute !
Les membres obtiennent plus de réponses que les utilisateurs anonymes.
Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.
Le fait d'être membre vous permet d'avoir des options supplémentaires.