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