# 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 ?>