1<?php 2namespace Amenadiel\JpGraph\Util; 3 4//============================================================================= 5// CLASS DateScaleUtils 6// Description: Help to create a manual date scale 7//============================================================================= 8define('DSUTILS_MONTH', 1); // Major and minor ticks on a monthly basis 9define('DSUTILS_MONTH1', 1); // Major and minor ticks on a monthly basis 10define('DSUTILS_MONTH2', 2); // Major ticks on a bi-monthly basis 11define('DSUTILS_MONTH3', 3); // Major icks on a tri-monthly basis 12define('DSUTILS_MONTH6', 4); // Major on a six-monthly basis 13define('DSUTILS_WEEK1', 5); // Major ticks on a weekly basis 14define('DSUTILS_WEEK2', 6); // Major ticks on a bi-weekly basis 15define('DSUTILS_WEEK4', 7); // Major ticks on a quod-weekly basis 16define('DSUTILS_DAY1', 8); // Major ticks on a daily basis 17define('DSUTILS_DAY2', 9); // Major ticks on a bi-daily basis 18define('DSUTILS_DAY4', 10); // Major ticks on a qoud-daily basis 19define('DSUTILS_YEAR1', 11); // Major ticks on a yearly basis 20define('DSUTILS_YEAR2', 12); // Major ticks on a bi-yearly basis 21define('DSUTILS_YEAR5', 13); // Major ticks on a five-yearly basis 22 23class DateScaleUtils 24{ 25 public static $iMin = 0; 26 public static $iMax = 0; 27 28 private static $starthour; 29 private static $startmonth; 30 private static $startday; 31 private static $startyear; 32 private static $endmonth; 33 private static $endyear; 34 private static $endday; 35 private static $tickPositions = array(); 36 private static $minTickPositions = array(); 37 private static $iUseWeeks = true; 38 39 public static function UseWeekFormat($aFlg) 40 { 41 self::$iUseWeeks = $aFlg; 42 } 43 44 public static function doYearly($aType, $aMinor = false) 45 { 46 $i = 0; 47 $j = 0; 48 $m = self::$startmonth; 49 $y = self::$startyear; 50 51 if (self::$startday == 1) { 52 self::$tickPositions[$i++] = mktime(0, 0, 0, $m, 1, $y); 53 } 54 ++$m; 55 56 switch ($aType) { 57 case DSUTILS_YEAR1: 58 for ($y = self::$startyear; $y <= self::$endyear; ++$y) { 59 if ($aMinor) { 60 while ($m <= 12) { 61 if (!($y == self::$endyear && $m > self::$endmonth)) { 62 self::$minTickPositions[$j++] = mktime(0, 0, 0, $m, 1, $y); 63 } 64 ++$m; 65 } 66 $m = 1; 67 } 68 self::$tickPositions[$i++] = mktime(0, 0, 0, 1, 1, $y); 69 } 70 break; 71 case DSUTILS_YEAR2: 72 $y = self::$startyear; 73 while ($y <= self::$endyear) { 74 self::$tickPositions[$i++] = mktime(0, 0, 0, 1, 1, $y); 75 for ($k = 0; $k < 1; ++$k) { 76 ++$y; 77 if ($aMinor) { 78 self::$minTickPositions[$j++] = mktime(0, 0, 0, 1, 1, $y); 79 } 80 } 81 ++$y; 82 } 83 break; 84 case DSUTILS_YEAR5: 85 $y = self::$startyear; 86 while ($y <= self::$endyear) { 87 self::$tickPositions[$i++] = mktime(0, 0, 0, 1, 1, $y); 88 for ($k = 0; $k < 4; ++$k) { 89 ++$y; 90 if ($aMinor) { 91 self::$minTickPositions[$j++] = mktime(0, 0, 0, 1, 1, $y); 92 } 93 } 94 ++$y; 95 } 96 break; 97 } 98 } 99 100 public static function doDaily($aType, $aMinor = false) 101 { 102 $m = self::$startmonth; 103 $y = self::$startyear; 104 $d = self::$startday; 105 $h = self::$starthour; 106 $i = 0; 107 $j = 0; 108 109 if ($h == 0) { 110 self::$tickPositions[$i++] = mktime(0, 0, 0, $m, $d, $y); 111 } 112 $t = mktime(0, 0, 0, $m, $d, $y); 113 114 switch ($aType) { 115 case DSUTILS_DAY1: 116 while ($t <= self::$iMax) { 117 $t = strtotime('+1 day', $t); 118 self::$tickPositions[$i++] = $t; 119 if ($aMinor) { 120 self::$minTickPositions[$j++] = strtotime('+12 hours', $t); 121 } 122 } 123 break; 124 case DSUTILS_DAY2: 125 while ($t <= self::$iMax) { 126 $t = strtotime('+1 day', $t); 127 if ($aMinor) { 128 self::$minTickPositions[$j++] = $t; 129 } 130 $t = strtotime('+1 day', $t); 131 self::$tickPositions[$i++] = $t; 132 } 133 break; 134 case DSUTILS_DAY4: 135 while ($t <= self::$iMax) { 136 for ($k = 0; $k < 3; ++$k) { 137 $t = strtotime('+1 day', $t); 138 if ($aMinor) { 139 self::$minTickPositions[$j++] = $t; 140 } 141 } 142 $t = strtotime('+1 day', $t); 143 self::$tickPositions[$i++] = $t; 144 } 145 break; 146 } 147 } 148 149 public static function doWeekly($aType, $aMinor = false) 150 { 151 $hpd = 3600 * 24; 152 $hpw = 3600 * 24 * 7; 153 // Find out week number of min date 154 $thursday = self::$iMin + $hpd * (3 - (date('w', self::$iMin) + 6) % 7); 155 $week = 1 + (date('z', $thursday) - (11 - date('w', mktime(0, 0, 0, 1, 1, date('Y', $thursday)))) % 7) / 7; 156 $daynumber = date('w', self::$iMin); 157 if ($daynumber == 0) { 158 $daynumber = 7; 159 } 160 161 $m = self::$startmonth; 162 $y = self::$startyear; 163 $d = self::$startday; 164 $i = 0; 165 $j = 0; 166 // The assumption is that the weeks start on Monday. If the first day 167 // is later in the week then the first week tick has to be on the following 168 // week. 169 if ($daynumber == 1) { 170 self::$tickPositions[$i++] = mktime(0, 0, 0, $m, $d, $y); 171 $t = mktime(0, 0, 0, $m, $d, $y) + $hpw; 172 } else { 173 $t = mktime(0, 0, 0, $m, $d, $y) + $hpd * (8 - $daynumber); 174 } 175 176 switch ($aType) { 177 case DSUTILS_WEEK1: 178 $cnt = 0; 179 break; 180 case DSUTILS_WEEK2: 181 $cnt = 1; 182 break; 183 case DSUTILS_WEEK4: 184 $cnt = 3; 185 break; 186 } 187 while ($t <= self::$iMax) { 188 self::$tickPositions[$i++] = $t; 189 for ($k = 0; $k < $cnt; ++$k) { 190 $t += $hpw; 191 if ($aMinor) { 192 self::$minTickPositions[$j++] = $t; 193 } 194 } 195 $t += $hpw; 196 } 197 } 198 199 public static function doMonthly($aType, $aMinor = false) 200 { 201 $monthcount = 0; 202 $m = self::$startmonth; 203 $y = self::$startyear; 204 $i = 0; 205 $j = 0; 206 207 // Skip the first month label if it is before the startdate 208 if (self::$startday == 1) { 209 self::$tickPositions[$i++] = mktime(0, 0, 0, $m, 1, $y); 210 $monthcount = 1; 211 } 212 if ($aType == 1) { 213 if (self::$startday < 15) { 214 self::$minTickPositions[$j++] = mktime(0, 0, 0, $m, 15, $y); 215 } 216 } 217 ++$m; 218 219 // Loop through all the years included in the scale 220 for ($y = self::$startyear; $y <= self::$endyear; ++$y) { 221 // Loop through all the months. There are three cases to consider: 222 // 1. We are in the first year and must start with the startmonth 223 // 2. We are in the end year and we must stop at last month of the scale 224 // 3. A year in between where we run through all the 12 months 225 $stopmonth = $y == self::$endyear ? self::$endmonth : 12; 226 while ($m <= $stopmonth) { 227 switch ($aType) { 228 case DSUTILS_MONTH1: 229 // Set minor tick at the middle of the month 230 if ($aMinor) { 231 if ($m <= $stopmonth) { 232 if (!($y == self::$endyear && $m == $stopmonth && self::$endday < 15)) { 233 self::$minTickPositions[$j++] = mktime(0, 0, 0, $m, 15, $y); 234 } 235 } 236 } 237 // Major at month 238 // Get timestamp of first hour of first day in each month 239 self::$tickPositions[$i++] = mktime(0, 0, 0, $m, 1, $y); 240 241 break; 242 case DSUTILS_MONTH2: 243 if ($aMinor) { 244 // Set minor tick at start of each month 245 self::$minTickPositions[$j++] = mktime(0, 0, 0, $m, 1, $y); 246 } 247 248 // Major at every second month 249 // Get timestamp of first hour of first day in each month 250 if ($monthcount % 2 == 0) { 251 self::$tickPositions[$i++] = mktime(0, 0, 0, $m, 1, $y); 252 } 253 break; 254 case DSUTILS_MONTH3: 255 if ($aMinor) { 256 // Set minor tick at start of each month 257 self::$minTickPositions[$j++] = mktime(0, 0, 0, $m, 1, $y); 258 } 259 // Major at every third month 260 // Get timestamp of first hour of first day in each month 261 if ($monthcount % 3 == 0) { 262 self::$tickPositions[$i++] = mktime(0, 0, 0, $m, 1, $y); 263 } 264 break; 265 case DSUTILS_MONTH6: 266 if ($aMinor) { 267 // Set minor tick at start of each month 268 self::$minTickPositions[$j++] = mktime(0, 0, 0, $m, 1, $y); 269 } 270 // Major at every third month 271 // Get timestamp of first hour of first day in each month 272 if ($monthcount % 6 == 0) { 273 self::$tickPositions[$i++] = mktime(0, 0, 0, $m, 1, $y); 274 } 275 break; 276 } 277 ++$m; 278 ++$monthcount; 279 } 280 $m = 1; 281 } 282 283 // For the case where all dates are within the same month 284 // we want to make sure we have at least two ticks on the scale 285 // since the scale want work properly otherwise 286 if (self::$startmonth == self::$endmonth && self::$startyear == self::$endyear && $aType == 1) { 287 self::$tickPositions[$i++] = mktime(0, 0, 0, self::$startmonth + 1, 1, self::$startyear); 288 } 289 290 return array(self::$tickPositions, self::$minTickPositions); 291 } 292 293 public static function GetTicks($aData, $aType = 1, $aMinor = false, $aEndPoints = false) 294 { 295 $n = count($aData); 296 return self::GetTicksFromMinMax($aData[0], $aData[$n - 1], $aType, $aMinor, $aEndPoints); 297 } 298 299 public static function GetAutoTicks($aMin, $aMax, $aMaxTicks = 10, $aMinor = false) 300 { 301 $diff = $aMax - $aMin; 302 $spd = 3600 * 24; 303 $spw = $spd * 7; 304 $spm = $spd * 30; 305 $spy = $spd * 352; 306 307 if (self::$iUseWeeks) { 308 $w = 'W'; 309 } else { 310 $w = 'd M'; 311 } 312 313 // Decision table for suitable scales 314 // First value: Main decision point 315 // Second value: Array of formatting depending on divisor for wanted max number of ticks. <divisor><formatting><format-string>,.. 316 $tt = array( 317 array($spw, array(1, DSUTILS_DAY1, 'd M', 2, DSUTILS_DAY2, 'd M', -1, DSUTILS_DAY4, 'd M')), 318 array($spm, array(1, DSUTILS_DAY1, 'd M', 2, DSUTILS_DAY2, 'd M', 4, DSUTILS_DAY4, 'd M', 7, DSUTILS_WEEK1, $w, -1, DSUTILS_WEEK2, $w)), 319 array($spy, array(1, DSUTILS_DAY1, 'd M', 2, DSUTILS_DAY2, 'd M', 4, DSUTILS_DAY4, 'd M', 7, DSUTILS_WEEK1, $w, 14, DSUTILS_WEEK2, $w, 30, DSUTILS_MONTH1, 'M', 60, DSUTILS_MONTH2, 'M', -1, DSUTILS_MONTH3, 'M')), 320 array(-1, array(30, DSUTILS_MONTH1, 'M-Y', 60, DSUTILS_MONTH2, 'M-Y', 90, DSUTILS_MONTH3, 'M-Y', 180, DSUTILS_MONTH6, 'M-Y', 352, DSUTILS_YEAR1, 'Y', 704, DSUTILS_YEAR2, 'Y', -1, DSUTILS_YEAR5, 'Y'))); 321 322 $ntt = count($tt); 323 $nd = floor($diff / $spd); 324 for ($i = 0; $i < $ntt; ++$i) { 325 if ($diff <= $tt[$i][0] || $i == $ntt - 1) { 326 $t = $tt[$i][1]; 327 $n = count($t) / 3; 328 for ($j = 0; $j < $n; ++$j) { 329 if ($nd / $t[3 * $j] <= $aMaxTicks || $j == $n - 1) { 330 $type = $t[3 * $j + 1]; 331 $fs = $t[3 * $j + 2]; 332 list($tickPositions, $minTickPositions) = self::GetTicksFromMinMax($aMin, $aMax, $type, $aMinor); 333 return array($fs, $tickPositions, $minTickPositions, $type); 334 } 335 } 336 } 337 } 338 } 339 340 public static function GetTicksFromMinMax($aMin, $aMax, $aType, $aMinor = false, $aEndPoints = false) 341 { 342 self::$starthour = date('G', $aMin); 343 self::$startmonth = date('n', $aMin); 344 self::$startday = date('j', $aMin); 345 self::$startyear = date('Y', $aMin); 346 self::$endmonth = date('n', $aMax); 347 self::$endyear = date('Y', $aMax); 348 self::$endday = date('j', $aMax); 349 self::$iMin = $aMin; 350 self::$iMax = $aMax; 351 352 if ($aType <= DSUTILS_MONTH6) { 353 self::doMonthly($aType, $aMinor); 354 } elseif ($aType <= DSUTILS_WEEK4) { 355 self::doWeekly($aType, $aMinor); 356 } elseif ($aType <= DSUTILS_DAY4) { 357 self::doDaily($aType, $aMinor); 358 } elseif ($aType <= DSUTILS_YEAR5) { 359 self::doYearly($aType, $aMinor); 360 } else { 361 JpGraphError::RaiseL(24003); 362 } 363 // put a label at the very left data pos 364 if ($aEndPoints) { 365 $tickPositions[$i++] = $aData[0]; 366 } 367 368 // put a label at the very right data pos 369 if ($aEndPoints) { 370 $tickPositions[$i] = $aData[$n - 1]; 371 } 372 373 return array(self::$tickPositions, self::$minTickPositions); 374 } 375} 376