IntervalUnit.class.php

Go to the documentation of this file.
00001 <?php
00002 /***************************************************************************
00003  *   Copyright (C) 2008-2009 by Ivan Y. Khvostishkov                       *
00004  *                                                                         *
00005  *   This program is free software; you can redistribute it and/or modify  *
00006  *   it under the terms of the GNU Lesser General Public License as        *
00007  *   published by the Free Software Foundation; either version 3 of the    *
00008  *   License, or (at your option) any later version.                       *
00009  *                                                                         *
00010  ***************************************************************************/
00011 
00015     final class IntervalUnit
00016     {
00017         private $name       = null;
00018         
00019         private $months     = null;
00020         private $days       = null;
00021         private $seconds    = null;
00022         
00023         public static function create($name)
00024         {
00025             return self::getInstance($name);
00026         }
00027         
00028         public function getName()
00029         {
00030             return $this->name;
00031         }
00032         
00039         public function truncate(Date $time, $ceil = false)
00040         {
00041             $time = $time->toTimestamp();
00042             
00043             $function = $ceil ? 'ceil' : 'floor';
00044             
00045             if ($this->seconds) {
00046                 
00047                 if ($this->seconds < 1)
00048                     return $time->spawn();
00049                 
00050                 $truncated = (int) (
00051                     $function($time->toStamp() / $this->seconds) * $this->seconds
00052                 );
00053                 
00054                 return Timestamp::create($truncated);
00055                 
00056             } elseif ($this->days) {
00057                 
00058                 $epochStartTruncated = Date::create('1970-01-05');
00059                 
00060                 $truncatedDate = Date::create($time->toDate());
00061                 
00062                 if ($ceil && $truncatedDate->toStamp() < $time->toStamp())
00063                     $truncatedDate->modify('+1 day');
00064                 
00065                 $difference = Date::dayDifference(
00066                     $epochStartTruncated, $truncatedDate
00067                 );
00068                 
00069                 $truncated = (int) (
00070                     $function($difference / $this->days) * $this->days
00071                 );
00072                 
00073                 return Timestamp::create(
00074                     $epochStartTruncated->spawn($truncated.' days')->toStamp()
00075                 );
00076                 
00077             } elseif ($this->months) {
00078                 
00079                 $monthsCount = $time->getYear() * 12 + ($time->getMonth() - 1);
00080                 
00081                 if (
00082                     $ceil
00083                     && (
00084                         ($time->getDay() - 1) + $time->getHour()
00085                         + $time->getMinute() + $time->getSecond() > 0
00086                     )
00087                 )
00088                     $monthsCount += 0.1; // delta
00089                 
00090                 $truncated = (int) (
00091                     $function($monthsCount / $this->months) *
00092                         ($this->months)
00093                 );
00094                 
00095                 $months = $truncated % 12;
00096                 
00097                 $years = ($truncated - $months) / 12;
00098                 
00099                 Assert::isEqual($years, (int) $years);
00100                 
00101                 $years = (int) $years;
00102                 
00103                 $months = $months + 1;
00104                 
00105                 return Timestamp::create("{$years}-{$months}-01 00:00:00");
00106             }
00107             
00108             Assert::isUnreachable();
00109         }
00110         
00111         public function countInRange(
00112             DateRange $range,
00113             $overlappedBounds = true
00114         )
00115         {
00116             $range = $range->toTimestampRange();
00117             
00118             $start = $this->truncate(
00119                 $range->getStart(), !$overlappedBounds
00120             );
00121             
00122             $end = $this->truncate(
00123                 $range->getEnd(), $overlappedBounds
00124             );
00125             
00126             if ($this->seconds) {
00127                 
00128                 $result =
00129                     ($end->toStamp() - $start->toStamp())
00130                     / $this->seconds;
00131                 
00132             } elseif ($this->days) {
00133                 
00134                 $epochStartTruncated = Date::create('1970-01-05');
00135                 
00136                 $startDifference = Date::dayDifference(
00137                     $epochStartTruncated, Date::create($start->toDate())
00138                 );
00139                 
00140                 $endDifference = Date::dayDifference(
00141                     $epochStartTruncated, Date::create($end->toDate())
00142                 );
00143                 
00144                 $result = ($endDifference - $startDifference) / $this->days;
00145                 
00146                 
00147             } elseif ($this->months) {
00148                 
00149                 $startMonthsCount = $start->getYear() * 12 + ($start->getMonth() - 1);
00150                 $endMonthsCount = $end->getYear() * 12 + ($end->getMonth() - 1);
00151                 
00152                 $result = ($endMonthsCount - $startMonthsCount) / $this->months;
00153             }
00154             
00155             Assert::isEqual(
00156                 $result, (int) $result,
00157                 'floating point mistake, arguments: '
00158                 .$this->name.', '
00159                 .$start->toStamp().', '.$end->toStamp().', '
00160                 .'result: '.var_export($result, true)
00161             );
00162             
00163             return (int) $result;
00164         }
00165 
00166         public function compareTo(IntervalUnit $unit)
00167         {
00168             $monthsDiffer = $this->months - $unit->months;
00169             
00170             if ($monthsDiffer)
00171                 return $monthsDiffer;
00172             
00173             $daysDiffer = $this->days - $unit->days;
00174 
00175             if ($daysDiffer)
00176                 return $daysDiffer;
00177             
00178             $secondsDiffer = $this->seconds - $unit->seconds;
00179 
00180             if ($secondsDiffer)
00181                 return $secondsDiffer;
00182             
00183             return 0;
00184         }
00185         
00186         private function __construct($name)
00187         {
00188             $units = self::getUnits();
00189             
00190             if (!isset($units[$name]))
00191                 throw new WrongArgumentException(
00192                     "know nothing about unit '$name'"
00193                 );
00194             
00195             if (!$units[$name])
00196                 throw new UnimplementedFeatureException(
00197                     'need for complex logic, see manual'
00198                 );
00199             
00200             $this->name = $name;
00201             
00202             $this->months = $units[$name][0];
00203             $this->days = $units[$name][1];
00204             $this->seconds = $units[$name][2];
00205             
00206             $notNulls = 0;
00207             
00208             if ($this->months > 0)
00209                 ++$notNulls;
00210             
00211             if ($this->days > 0)
00212                 ++$notNulls;
00213             
00214             if ($this->seconds > 0)
00215                 ++$notNulls;
00216             
00217             Assert::isEqual($notNulls, 1, "broken unit '$name'");
00218         }
00219         
00220         private static function getUnits()
00221         {
00222             static $result = null;
00223             
00224             if (!$result)
00225                 $result = array(
00226                     // name         => array(months,    days,   seconds)
00227                     'microsecond'   => array(0,         0,      0.000001),
00228                     'millisecond'   => array(0,         0,      0.001),
00229                     'second'        => array(0,         0,      1),
00230                     'minute'        => array(0,         0,      60),
00231                     'hour'          => array(0,         0,      3600),
00232                     'day'           => array(0,         1,      0),
00233                     'week'          => array(0,         7,      0),
00234                     'month'         => array(1,         0,      0),
00235                     'year'          => array(12,        0,      0),
00236                     'decade'        => array(120,       0,      0),
00237                     'century'       => array(),
00238                     'millennium'    => array()
00239                 );
00240             
00241             return $result;
00242         }
00243         
00244         private static function getInstance($id)
00245         {
00246             static $instances = array();
00247             
00248             if (!isset($instances[$id]))
00249                 $instances[$id] = new self($id);
00250             
00251             return $instances[$id];
00252         }
00253     }
00254 ?>