00001 <?php
00002
00003
00004
00005
00006
00007
00008
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;
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
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 ?>