adds directory scanning for apps as fallback
[tine20] / tine20 / Tinebase / DateTime.php
1 <?php
2 /**
3  * Tine 2.0
4  *
5  * @package     Tinebase
6  * @license     http://www.gnu.org/licenses/agpl.html AGPL3
7  * @copyright   Copyright (c) 2010 Metaways Infosystems GmbH (http://www.metaways.de)
8  * @author      Goekmen Ciyiltepe <g.ciyiltepe@metaways.de>
9  */
10
11 /**
12  * Tinebase_DateTime - Extensions around native php DateTime object
13  * 
14  * @package     Tinebase
15  * 
16  * @TODO: recheck addDay/Week ... rework dst fixes
17  * @TODO: recheck 32 bit issues create / compare
18  * 
19  * NOTE: Tinebase_DateTime was introduced as replacement for Zend_Date in normal
20  *       datetime operations for perfomance reasons. Most of the functions in this
21  *       class are modeled after the Zend_Date class
22  * 
23  * NOTE: This class has nothing to do with localisation! If you need localisation
24  *       use Zend_Date!
25  */
26 class Tinebase_DateTime extends DateTime
27 {
28     const TIMEZONE_UTC = "UTC";
29     
30     const MODIFIER_SECOND   = 'sec';
31     const MODIFIER_MINUTE   = 'min';
32     const MODIFIER_HOUR     = 'hour';
33     const MODIFIER_DAY      = 'day';
34     const MODIFIER_WEEK     = 'week';
35     const MODIFIER_MONTH    = 'month';
36     const MODIFIER_YEAR     = 'year';
37     
38     /**
39      * holds datetime string when being serialised (needed for php < 5.3)
40      * @see http://bugs.php.net/bug.php?id=46891
41      * 
42      * @var string
43      */
44     private $__sDT;
45     
46     /**
47      * holds datetimezone string when being serialised (needed for php < 5.3)
48      * @see http://bugs.php.net/bug.php?id=46891
49      * 
50      * @var string
51      */
52     private $__sDTZ;
53     
54     /**
55      * this datetime represents a date only
56      * 
57      * @var bool
58      */
59     protected $_hasTime = TRUE;
60     
61     /**
62      * @see http://bugs.php.net/bug.php?id=46891
63      */
64     public function __sleep(){
65         $this->__sDT = $this->format('Y-m-d H:i:s');
66         $tz = $this->getTimezone();
67         $this->__sDTZ = $tz? $tz->getName() : 'UTC';
68         return array('__sDT', '__sDTZ');
69     }
70
71     /**
72      * @see http://bugs.php.net/bug.php?id=46891
73      */
74     public function __wakeup() {
75         $this->__construct($this->__sDT, new DateTimeZone($this->__sDTZ ? $this->__sDTZ : 'UTC'));
76         $this->__sDT = $this->__sDTZ = NULL;
77     }
78     
79     /**
80      * Returns new DateTime object
81      * 
82      * @param string|int|DateTime $_time
83      * @param DateTimeZone $_timezone
84      */
85     public function __construct($_time = "now", $_timezone = NULL)
86     {
87         // allow to pass instanceof DateTime
88         if ($_time instanceof DateTime) {
89             if (! $_timezone) {
90                 $_timezone = $_time->getTimezone();
91             } else {
92                 $_time = clone $_time;
93                 $_time->setTimezone($_timezone);
94             }
95             
96             $time = $_time->format("Y-m-d H:i:s");
97         } else {
98             $time = (is_numeric($_time)) ? "@" . floor($_time) : $_time;
99         }
100         
101         if ($_timezone) {
102             if (! $_timezone instanceof DateTimeZone) {
103                 $_timezone = new DateTimeZone($_timezone);
104             }
105             
106             parent::__construct($time, $_timezone);
107         } else {
108             parent::__construct($time);
109         }
110
111         // Normalize Timezonename, as sometimes +00:00 is taken
112         if (is_numeric($_time)) {
113             $this->setTimezone('UTC');
114         }
115     }
116     
117     /**
118      * returns clone to be used with right hand operators
119      * 
120      * @return Tinebase_DateTime
121      */
122     public function getClone()
123     {
124         return clone $this;
125     }
126     
127     /**
128      * call interceptor
129      * 
130      * @param string $name
131      * @param array $arguments
132      */
133     public function __call($name, $arguments)
134     {
135         if (strpos($name, 'php52compat_') === FALSE) {
136             return call_user_func_array(array($this, "php52compat_$name"), $arguments);
137         }
138         throw new Tinebase_Exception_InvalidArgument('unknown method: ' . str_replace('php52compat_', '', $name));
139     }
140     
141     /**
142      * Adds a date or datepart to the existing date. For PHP < 5.3 supported parts are:
143      *  sec, min, hour, day, week, month, year. For later versions check DateTime::add docu.
144      *  Returns the modified DateTime object or FALSE on failure.
145      *  
146      *  For compability with Zend_Date, if $_interval is a DateTime, the timestamp of this datetime 
147      *  will be added
148      *
149      * @param  integer|DateTime|DateInterval    $_interval    Date or datepart to add
150      * @param  string                           $part    OPTIONAL Part of the date to add, if null the timestamp is added
151      * @return Tinebase_DateTime
152      */
153     public function add($_interval, $_part = self::MODIFIER_SECOND)
154     {
155         if ($_interval instanceof DateInterval) {
156             return parent::add($_interval);
157 //        } else if ($_interval instanceof DateIntervalLight) {
158 //            
159         } else if ($_interval instanceof DateTime) {
160             $_interval = $_interval->get('U');
161             $_part = self::MODIFIER_SECOND;
162         }
163         
164         $sign = $_interval < 0 ? '-' : '+';
165         return $this->modify($sign . abs($_interval) . $_part);
166     }
167     
168     /**
169      * Adds days. The parameter is always a number.
170      * Returns the modified DateTime object or FALSE on failure.
171      *
172      * @param  integer              $number days to add
173      * @return Tinebase_DateTime
174      */
175     public function addDay($number)
176     {
177         return $this->add($number*86400, self::MODIFIER_SECOND);
178     }
179     
180     /**
181      * Adds hours. The parameter is always a number.
182      * Returns the modified DateTime object or FALSE on failure.
183      *
184      * @param  integer              $number hours to add
185      * @return Tinebase_DateTime
186      */
187     public function addHour($number)
188     {
189         return $this->add($number*3600, self::MODIFIER_SECOND);
190     }
191     
192     /**
193      * Adds minutes. The parameter is always a number.
194      * Returns the modified DateTime object or FALSE on failure.
195      *
196      * @param  integer              $number minutes to add
197      * @return Tinebase_DateTime
198      */
199     public function addMinute($number)
200     {
201         return $this->add($number, self::MODIFIER_MINUTE);
202     }
203     
204     /**
205      * Adds months. The parameter is always a number.
206      * Returns the modified DateTime object or FALSE on failure.
207      *
208      * @param  integer              $number months to add
209      * @return Tinebase_DateTime
210      */
211     public function addMonth($number)
212     {
213         return $this->add($number, self::MODIFIER_MONTH);
214     }
215     
216     /**
217      * Adds seconds. The parameter is always a number.
218      * Returns the modified DateTime object or FALSE on failure.
219      *
220      * @param  integer              $number seconds to add
221      * @return Tinebase_DateTime
222      */
223     public function addSecond($number)
224     {
225         return $this->add($number, self::MODIFIER_SECOND);
226     }
227     
228     /**
229      * Adds weeks. The parameter is always a number.
230      * Returns the modified DateTime object or FALSE on failure.
231      *
232      * @param  integer              $number weeks to add
233      * @return Tinebase_DateTime
234      */
235     public function addWeek($number)
236     {
237         return $this->add($number*7*86400, self::MODIFIER_SECOND);
238     }
239     
240     /**
241      * Adds years. The parameter is always a number.
242      * Returns the modified DateTime object or FALSE on failure.
243      *
244      * @param  integer              $number years to add
245      * @return Tinebase_DateTime
246      */
247     public function addYear($number)
248     {
249         return $this->add($number, self::MODIFIER_YEAR);
250     }
251     
252     /**
253      * Compares date or datepart of this with an other.
254      * Returns -1 if earlier, 0 if equal and 1 if later.
255      *
256      * @param  DateTime                        $_date    Date to compare with the date object
257      * @param  string                          $_part    OPTIONAL Part of the date to compare, if null the timestamp is subtracted
258      * @return integer  0 = equal, 1 = later, -1 = earlier
259      */
260     public function compare($_date, $_part = 'c')
261     {
262         if (! $_date instanceof DateTime) {
263             throw new Tinebase_Exception_Date(var_export($_date, TRUE) . ' is not an instance of DateTime');
264         }
265         
266         $cmpTZ = $this->getTimezone();
267         
268         if ($cmpTZ != $_date->getTimezone()) {
269             $_date = clone $_date;
270             $_date->setTimezone($cmpTZ);
271         };
272         
273         $thisValue = $this->format($_part);
274         $dateValue = $_date->format($_part);
275         
276         if ($thisValue == $dateValue) {
277             return 0;
278         } elseif ($thisValue > $dateValue) {
279             return 1;
280         } else {
281             return -1;
282         }
283     }
284     
285     /**
286      * Compares this month with the other.
287      * Returns -1 if earlier, 0 if equal and 1 if later.
288      *
289      * @param  DateTime                        $_date    Date to compare with the date object
290      * @return integer  0 = equal, 1 = later, -1 = earlier
291      */
292     public function compareMonth($_date)
293     {
294         return $this->compare($_date, 'n');
295     }
296     
297     /**
298      * Returns a representation of a date or datepart
299      * This could be for example a monthname, the time without date,
300      * the era or only the fractional seconds. For a list of supported part identifiers
301      * look into the php date docu. 
302      * 
303      * NOTE: This method does not use locales. All output is in English.
304      *
305      * @param  string  $part    OPTIONAL Part of the date to return, if null the timestamp is returned
306      * @return string  date or datepart
307      */
308     public function get($_part = null)
309     {
310         return is_null($_part) ? $this->getTimestamp() : $this->format($_part);
311     }
312     
313     /**
314      * Returns the full ISO 8601 date from the this object.
315      * Always the complete ISO 8601 specifiction is used. If an other ISO date is needed
316      * (ISO 8601 defines several formats) use toString() instead.
317      *
318      * @return string
319      */
320     public function getIso()
321     {
322         return $this->format("c");
323     }
324     
325     /**
326      * Returns the UNIX timestamp representation of this datetime.
327      * 
328      * NOTE: PHP < 5.3.0 dosn't have this fn, so we overwrite it
329      *
330      * @return string  UNIX timestamp
331      */
332     public function getTimestamp()
333     {
334         return $this->format('U');
335     }
336     
337     /**
338      * Returns true when both date objects or date parts are equal.
339      *
340      * @param  DateTime                        $date    Date to equal with
341      * @param  string                          $part    OPTIONAL Part of the date to compare, if null the timestamp is used
342      * @return boolean
343      */
344     public function equals($_date, $_part = 'c')
345     {
346         return $this->compare($_date, $_part) == 0;
347     }
348     
349     /**
350      * set/get the hasTime flag 
351      * 
352      * @param bool optional
353      */
354     public function hasTime()
355     {
356         $currValue = $this->_hasTime;
357         if (func_num_args() === 1) {
358             $paramValue = (bool) func_get_arg(0);
359             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Resetting _hasTime to ' . (int) $paramValue);
360             $this->_hasTime = $paramValue;
361             
362             if ($this->_hasTime === FALSE) {
363                 $this->setTime(0,0,0);
364             }
365         }
366         
367         return $currValue;
368     }
369     
370     /**
371      * Returns true if this date or datepart is earlier than the given date
372      * 
373      * @param  DateTime                        $date    Date to compare with
374      * @param  string                          $part    OPTIONAL Part of the date to compare, if null the timestamp is used
375      * @return boolean
376      */
377     public function isEarlier($_date, $_part = 'c')
378     {
379         return $this->compare($_date, $_part) < 0;
380     }
381     
382     /**
383      * Returns true if this date or datepart is later than the given date
384      * 
385      * @param  DateTime                        $date    Date to compare with
386      * @param  string                          $part    OPTIONAL Part of the date to compare, if null the timestamp is used
387      * @return boolean
388      */
389     public function isLater($_date, $_part = 'c')
390     {
391         return $this->compare($_date, $_part) > 0;
392     }
393     
394     /**
395      * Returns true if this date or datepart is earlier or equals than the given date
396      *
397      * @param  DateTime                        $date    Date to compare with
398      * @param  string                          $part    OPTIONAL Part of the date to compare, if null the timestamp is used
399      * @return boolean
400      */
401     public function isEarlierOrEquals($_date, $_part = 'c')
402     {
403         return $this->compare($_date, $_part) < 1;
404     }
405     
406     /**
407      * Returns true if this date or datepart is later or equals than the given date
408      *
409      * @param  DateTime                        $date    Date to compare with
410      * @param  string                          $part    OPTIONAL Part of the date to compare, if null the timestamp is used
411      * @return boolean
412      */
413     public function isLaterOrEquals($_date, $_part = 'c')
414     {
415         return $this->compare($_date, $_part) > -1;
416     }
417     
418     /**
419      * (non-PHPdoc)
420      * @see DateTime::setDate()
421      * @note PHP 5.3.0 changed the return value on success from NULL to DateTime.
422      */
423     public function setDate($year ,$month ,$day)
424     {
425         parent::setDate($year ,$month ,$day);
426         return $this;
427     }
428     
429     /**
430      * (non-PHPdoc)
431      * @see DateTime::setTime()
432      * @note PHP 5.3.0 changed the return value on success from NULL to DateTime.
433      */
434     public function setTime($hour, $minute, $second = 0)
435     {
436         parent::setTime($hour, $minute, $second);
437         return $this;
438     }
439     
440     /**
441      * (non-PHPdoc)
442      * @see DateTime::setISODate()
443      * @note PHP 5.3.0 changed the return value on success from NULL to DateTime.
444      */
445     public function setISODate($year ,$week, $day = 1)
446     {
447         parent::setISODate($year ,$week, $day);
448         return $this;
449     }
450     
451     /**
452      * Substracts a date or datepart to the existing date. For PHP < 5.3 supported parts are:
453      *  sec, min, hour, day, week, month, year. For later versions check DateTime::sub docu.
454      *  Returns the modified DateTime object or FALSE on failure.
455      *  
456      *  For compability with Zend_Date, if $_interval is a DateTime, the timestamp of this datetime 
457      *  will be substracted
458      *
459      * @param  integer|DateTime|DateInterval    $_interval    Date or datepart to substract
460      * @param  string                           $part    OPTIONAL Part of the date to substract, if null the timestamp is substracted
461      * @return Tinebase_DateTime
462      */
463     public function sub($_interval, $_part = self::MODIFIER_SECOND)
464     {
465         if ($_interval instanceof DateInterval) {
466             return parent::sub($_interval);
467         }
468         
469         $_interval = is_numeric($_interval) ? -1 * $_interval : $_interval;
470         return $this->add($_interval, $_part);
471     }
472     
473     /**
474      * Substracts days. The parameter is always a number.
475      * Returns the modified DateTime object or FALSE on failure.
476      *
477      * @param  integer              $number days to substract
478      * @return Tinebase_DateTime
479      */
480     public function subDay($number)
481     {
482         return $this->addDay(-1 * $number);
483     }
484     
485     /**
486      * Substracts hours. The parameter is always a number.
487      * Returns the modified DateTime object or FALSE on failure.
488      *
489      * @param  integer              $number hours to substract
490      * @return Tinebase_DateTime
491      */
492     public function subHour($number)
493     {
494         return $this->addHour(-1 * $number);
495     }
496     
497     /**
498      * Substracts minutes. The parameter is always a number.
499      * Returns the modified DateTime object or FALSE on failure.
500      *
501      * @param  integer              $number minutes to substract
502      * @return Tinebase_DateTime
503      */
504     public function subMinute($number)
505     {
506         return $this->addMinute(-1 * $number);
507     }
508     
509     /**
510      * Substracts months. The parameter is always a number.
511      * Returns the modified DateTime object or FALSE on failure.
512      *
513      * @param  integer              $number months to substract
514      * @return Tinebase_DateTime
515      */
516     public function subMonth($number)
517     {
518         return $this->addMonth(-1 * $number);
519     }
520     
521     /**
522      * Substracts seconds. The parameter is always a number.
523      * Returns the modified DateTime object or FALSE on failure.
524      *
525      * @param  integer              $number seconds to substract
526      * @return Tinebase_DateTime
527      */
528     public function subSecond($number)
529     {
530         return $this->addSecond(-1 * $number);
531     }
532     
533     /**
534      * Substracts minutes. The parameter is always a number.
535      * Returns the modified DateTime object or FALSE on failure.
536      *
537      * @param  integer              $number minutes to substract
538      * @return Tinebase_DateTime
539      */
540     public function subWeek($number)
541     {
542         return $this->addWeek(-1 * $number);
543     }
544     
545     /**
546      * Substracts minutes. The parameter is always a number.
547      * Returns the modified DateTime object or FALSE on failure.
548      *
549      * @param  integer              $number minutes to substract
550      * @return Tinebase_DateTime
551      */
552     public function subYear($number)
553     {
554         return $this->addYear(-1 * $number);
555     }
556     
557     /**
558      * Alters the timestamp
559      * NOTE: PHP 5.3.0 Changelog: Changed the return value on success from NULL to DateTime
560      *
561      * @param  string $modify A date/time string. Valid formats are explained in Date and Time Formats.
562      * @return Tinebase_DateTime $this
563      */
564     public function modify($modify)
565     {
566         parent::modify($modify);
567         return $this;
568     }
569     
570     /**
571      * Returns the difference between two DateTime objects
572      * 
573      * @param  DateTime $datetime2
574      * @param  bool     $absolute
575      * @return DateInterval
576      */
577     public function php52compat_diff(DateTime $datetime2, $absolute = false)
578     {
579         // use Zend_Date for 32 bit compat
580         $thisZD = new Zend_Date($this->format('U'));
581         $datetime2ZD = new Zend_Date($datetime2->format('U'));
582         
583         return $datetime2ZD->sub($thisZD, Zend_Date::TIMESTAMP);
584     }
585     
586     /**
587      * Returns a string representation of the object
588      * For Supported format tokens see: php.net/date
589      * 
590      * @return String
591      */
592     public function __toString()
593     {
594         return $this->format("Y-m-d H:i:s");
595     }
596     
597     /**
598      * Returns a string representation of the object
599      * For Supported format tokens see: php.net/date
600      * 
601      * @param  String $_format  OPTIONAL Rule for formatting output. If null the default date format is used
602      * @return String
603      */
604     public function toString($_format = "Y-m-d H:i:s")
605     {
606         return $this->format($_format);
607     }
608     
609     /**
610      * Sets a new hour
611      * The hour is always a number.
612      * Returned is the new date object
613      * Example: 04.May.1993 13:07:25 -> setHour(7); -> 04.May.1993 07:07:25
614      *
615      * @param  string|integer $modify    Hour to set
616      * @return Tinebase_DateTime  $this
617      */
618     public function setHour($modify)
619     {
620         list ($i, $s) = explode(' ', $this->format('i s'));
621         
622         $this->setTime($modify, $i, $s);
623         return $this;
624     }
625     
626     /**
627      * Sets a new minute
628      * The minute is always a number.
629      * Returned is the new date object
630      * Example: 04.May.1993 13:23:25 -> setMinute(7); -> 04.May.1993 13:07:25
631      *
632      * @param  string|integer $modify    Minute to set
633      * @return Tinebase_DateTime  $this
634      */
635     public function setMinute($modify)
636     {
637         list ($h, $s) = explode(' ', $this->format('H s'));
638         
639         $this->setTime($h, $modify, $s);
640         return $this;
641     }
642     
643     /**
644      * Sets a new second
645      * The second is always a number.
646      * Returned is the new date object
647      * Example: 04.May.1993 13:07:25 -> setSecond(7); -> 04.May.1993 13:07:07
648      *
649      * @param  string|integer $modify    Second to set
650      * @return Tinebase_DateTime  $this
651      */
652     public function setSecond($modify)
653     {
654         list ($h, $i) = explode(' ', $this->format('H i'));
655         
656         $this->setTime($h, $i, $modify);
657         return $this;
658     }
659     
660     /**
661      * Sets a new timezone.
662      * For a list of supported timezones look here: http://php.net/timezones
663      *
664      * @param  string|DateTimeZone  $_timezone  timezone for date calculation
665      * @return Tinebase_DateTime    $this
666      */
667     public function setTimezone($_timezone)
668     {
669         if ($this->_hasTime === FALSE) $date = $this->format('Y-m-d');
670         
671         $timezone = $_timezone instanceof DateTimeZone ? $_timezone : new DateTimeZone($_timezone);
672         parent::setTimezone($timezone);
673         
674         // if we contain no time info, we are timezone invariant
675         if ($this->_hasTime === FALSE) {
676             call_user_func_array(array($this, 'setDate'), explode('-', $date));
677             $this->setTime(0,0,0);
678         }
679         
680         return $this;
681     }
682     
683     /**
684      * Sets a new week. The week is always a number. The day of week is not changed.
685      * Returned is the new date object
686      * Example: 09.Jan.2007 13:07:25 -> setWeek(1); -> 02.Jan.2007 13:07:25
687      *
688      * @param  string|integer     $week    Week to set
689      * @return Tinebase_DateTime  $this
690      */
691     public function setWeek($week)
692     {
693         $currentWeek = (int)$this->format("W");
694         $operator = ($currentWeek > $week) ? "-" : "+";
695         return $this->modify($operator . abs($currentWeek - $week) . "week");
696     }
697     
698     /**
699      * Sets a new weekday
700      * The weekday can be a number or a string.
701      * Returned is the new date object.
702      * Example: setWeekday(3); will set the wednesday of this week as day.
703      *
704      * @param  string|integer                  $weekDay   Weekday to set
705      * @return Tinebase_DateTime  $this
706      */
707     public function setWeekDay($weekDay)
708     {
709         $currentWeekDay = (int)$this->format("N");
710         $operator = ($currentWeekDay > $weekDay) ? "-" : "+";
711         return $this->modify($operator . abs($currentWeekDay - $weekDay) . "day");
712     }
713     
714     /**
715      * Checks if the given date is a real date or datepart.
716      * Returns false if a expected datepart is missing or a datepart exceeds its possible border.
717      *
718      * @param  string             $_date   Date to parse for correctness
719      * @return boolean            True when all date parts are correct
720      */
721     public static function isDate($_date)
722     {
723         if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
724             $result = date_parse($_date);
725             return empty($result['warning_count']) && empty($result['error_count']);
726         } else {
727             return Zend_Date::isDate($_date, 'yyyy-MM-dd HH:mm:ss');
728         }
729     }
730     
731     /**
732      * Returns an array representation of the object
733      *
734      * @return array
735      */
736     public function toArray()
737     {
738         return array(
739             'day'       => $this->format('j'),
740             'month'     => $this->format('n'),
741             'year'      => $this->format('Y'),
742             'hour'      => $this->format('G'),
743             'minute'    => $this->format('i'),
744             'second'    => $this->format('s'),
745             'timezone'  => $this->format('e'),
746             'timestamp' => $this->format('U'),
747             'weekday'   => $this->format('N'),
748             'dayofyear' => $this->format('z'),
749             'week'      => $this->format('W'),
750             'gmtsecs'   => $this->getOffset()
751         );
752     }
753     
754     /**
755      * Returns the actual date as new date object
756      *
757      * @return Tinebase_DateTime
758      */
759     public static function now()
760     {
761         return new Tinebase_DateTime();
762     }
763     
764 }
765