1 <?php
  2 /**
  3  * @package     Joomla.Platform
  4  * @subpackage  Date
  5  *
  6  * @copyright   Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
  7  * @license     GNU General Public License version 2 or later; see LICENSE
  8  */
  9 
 10 defined('JPATH_PLATFORM') or die;
 11 
 12 /**
 13  * JDate is a class that stores a date and provides logic to manipulate
 14  * and render that date in a variety of formats.
 15  *
 16  * @method  JDate|bool  add(DateInterval $interval)  Adds an amount of days, months, years, hours, minutes and seconds to a JDate object.
 17  * @method  JDate|bool  sub(DateInterval $interval)  Subtracts an amount of days, months, years, hours, minutes and seconds from a JDate object.
 18  * @method  JDate|bool  modify(string $modify)       Alter the timestamp of this object by incre/decre-menting in a format accepted by strtotime().
 19  *
 20  * @property-read  string   $daysinmonth   t - Number of days in the given month.
 21  * @property-read  string   $dayofweek     N - ISO-8601 numeric representation of the day of the week.
 22  * @property-read  string   $dayofyear     z - The day of the year (starting from 0).
 23  * @property-read  boolean  $isleapyear    L - Whether it's a leap year.
 24  * @property-read  string   $day           d - Day of the month, 2 digits with leading zeros.
 25  * @property-read  string   $hour          H - 24-hour format of an hour with leading zeros.
 26  * @property-read  string   $minute        i - Minutes with leading zeros.
 27  * @property-read  string   $second        s - Seconds with leading zeros.
 28  * @property-read  string   $microsecond   u - Microseconds with leading zeros.
 29  * @property-read  string   $month         m - Numeric representation of a month, with leading zeros.
 30  * @property-read  string   $ordinal       S - English ordinal suffix for the day of the month, 2 characters.
 31  * @property-read  string   $week          W - ISO-8601 week number of year, weeks starting on Monday.
 32  * @property-read  string   $year          Y - A full numeric representation of a year, 4 digits.
 33  *
 34  * @since  11.1
 35  */
 36 class JDate extends DateTime
 37 {
 38     const DAY_ABBR = "\x021\x03";
 39     const DAY_NAME = "\x022\x03";
 40     const MONTH_ABBR = "\x023\x03";
 41     const MONTH_NAME = "\x024\x03";
 42 
 43     /**
 44      * The format string to be applied when using the __toString() magic method.
 45      *
 46      * @var    string
 47      * @since  11.1
 48      */
 49     public static $format = 'Y-m-d H:i:s';
 50 
 51     /**
 52      * Placeholder for a DateTimeZone object with GMT as the time zone.
 53      *
 54      * @var    object
 55      * @since  11.1
 56      */
 57     protected static $gmt;
 58 
 59     /**
 60      * Placeholder for a DateTimeZone object with the default server
 61      * time zone as the time zone.
 62      *
 63      * @var    object
 64      * @since  11.1
 65      */
 66     protected static $stz;
 67 
 68     /**
 69      * The DateTimeZone object for usage in rending dates as strings.
 70      *
 71      * @var    DateTimeZone
 72      * @since  12.1
 73      */
 74     protected $tz;
 75 
 76     /**
 77      * Constructor.
 78      *
 79      * @param   string  $date  String in a format accepted by strtotime(), defaults to "now".
 80      * @param   mixed   $tz    Time zone to be used for the date. Might be a string or a DateTimeZone object.
 81      *
 82      * @since   11.1
 83      */
 84     public function __construct($date = 'now', $tz = null)
 85     {
 86         // Create the base GMT and server time zone objects.
 87         if (empty(self::$gmt) || empty(self::$stz))
 88         {
 89             self::$gmt = new DateTimeZone('GMT');
 90             self::$stz = new DateTimeZone(@date_default_timezone_get());
 91         }
 92 
 93         // If the time zone object is not set, attempt to build it.
 94         if (!($tz instanceof DateTimeZone))
 95         {
 96             if ($tz === null)
 97             {
 98                 $tz = self::$gmt;
 99             }
100             elseif (is_string($tz))
101             {
102                 $tz = new DateTimeZone($tz);
103             }
104         }
105 
106         // If the date is numeric assume a unix timestamp and convert it.
107         date_default_timezone_set('UTC');
108         $date = is_numeric($date) ? date('c', $date) : $date;
109 
110         // Call the DateTime constructor.
111         parent::__construct($date, $tz);
112 
113         // Reset the timezone for 3rd party libraries/extension that does not use JDate
114         date_default_timezone_set(self::$stz->getName());
115 
116         // Set the timezone object for access later.
117         $this->tz = $tz;
118     }
119 
120     /**
121      * Magic method to access properties of the date given by class to the format method.
122      *
123      * @param   string  $name  The name of the property.
124      *
125      * @return  mixed   A value if the property name is valid, null otherwise.
126      *
127      * @since   11.1
128      */
129     public function __get($name)
130     {
131         $value = null;
132 
133         switch ($name)
134         {
135             case 'daysinmonth':
136                 $value = $this->format('t', true);
137                 break;
138 
139             case 'dayofweek':
140                 $value = $this->format('N', true);
141                 break;
142 
143             case 'dayofyear':
144                 $value = $this->format('z', true);
145                 break;
146 
147             case 'isleapyear':
148                 $value = (boolean) $this->format('L', true);
149                 break;
150 
151             case 'day':
152                 $value = $this->format('d', true);
153                 break;
154 
155             case 'hour':
156                 $value = $this->format('H', true);
157                 break;
158 
159             case 'minute':
160                 $value = $this->format('i', true);
161                 break;
162 
163             case 'second':
164                 $value = $this->format('s', true);
165                 break;
166 
167             case 'month':
168                 $value = $this->format('m', true);
169                 break;
170 
171             case 'ordinal':
172                 $value = $this->format('S', true);
173                 break;
174 
175             case 'week':
176                 $value = $this->format('W', true);
177                 break;
178 
179             case 'year':
180                 $value = $this->format('Y', true);
181                 break;
182 
183             default:
184                 $trace = debug_backtrace();
185                 trigger_error(
186                     'Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'],
187                     E_USER_NOTICE
188                 );
189         }
190 
191         return $value;
192     }
193 
194     /**
195      * Magic method to render the date object in the format specified in the public
196      * static member JDate::$format.
197      *
198      * @return  string  The date as a formatted string.
199      *
200      * @since   11.1
201      */
202     public function __toString()
203     {
204         return (string) parent::format(self::$format);
205     }
206 
207     /**
208      * Proxy for new JDate().
209      *
210      * @param   string  $date  String in a format accepted by strtotime(), defaults to "now".
211      * @param   mixed   $tz    Time zone to be used for the date.
212      *
213      * @return  JDate
214      *
215      * @since   11.3
216      */
217     public static function getInstance($date = 'now', $tz = null)
218     {
219         return new JDate($date, $tz);
220     }
221 
222     /**
223      * Translates day of week number to a string.
224      *
225      * @param   integer  $day   The numeric day of the week.
226      * @param   boolean  $abbr  Return the abbreviated day string?
227      *
228      * @return  string  The day of the week.
229      *
230      * @since   11.1
231      */
232     public function dayToString($day, $abbr = false)
233     {
234         switch ($day)
235         {
236             case 0:
237                 return $abbr ? JText::_('SUN') : JText::_('SUNDAY');
238             case 1:
239                 return $abbr ? JText::_('MON') : JText::_('MONDAY');
240             case 2:
241                 return $abbr ? JText::_('TUE') : JText::_('TUESDAY');
242             case 3:
243                 return $abbr ? JText::_('WED') : JText::_('WEDNESDAY');
244             case 4:
245                 return $abbr ? JText::_('THU') : JText::_('THURSDAY');
246             case 5:
247                 return $abbr ? JText::_('FRI') : JText::_('FRIDAY');
248             case 6:
249                 return $abbr ? JText::_('SAT') : JText::_('SATURDAY');
250         }
251     }
252 
253     /**
254      * Gets the date as a formatted string in a local calendar.
255      *
256      * @param   string   $format     The date format specification string (see {@link PHP_MANUAL#date})
257      * @param   boolean  $local      True to return the date string in the local time zone, false to return it in GMT.
258      * @param   boolean  $translate  True to translate localised strings
259      *
260      * @return  string   The date string in the specified format format.
261      *
262      * @since   11.1
263      */
264     public function calendar($format, $local = false, $translate = true)
265     {
266         return $this->format($format, $local, $translate);
267     }
268 
269     /**
270      * Gets the date as a formatted string.
271      *
272      * @param   string   $format     The date format specification string (see {@link PHP_MANUAL#date})
273      * @param   boolean  $local      True to return the date string in the local time zone, false to return it in GMT.
274      * @param   boolean  $translate  True to translate localised strings
275      *
276      * @return  string   The date string in the specified format format.
277      *
278      * @since   11.1
279      */
280     public function format($format, $local = false, $translate = true)
281     {
282         if ($translate)
283         {
284             // Do string replacements for date format options that can be translated.
285             $format = preg_replace('/(^|[^\\\])D/', "\\1" . self::DAY_ABBR, $format);
286             $format = preg_replace('/(^|[^\\\])l/', "\\1" . self::DAY_NAME, $format);
287             $format = preg_replace('/(^|[^\\\])M/', "\\1" . self::MONTH_ABBR, $format);
288             $format = preg_replace('/(^|[^\\\])F/', "\\1" . self::MONTH_NAME, $format);
289         }
290 
291         // If the returned time should not be local use GMT.
292         if ($local == false && !empty(self::$gmt))
293         {
294             parent::setTimezone(self::$gmt);
295         }
296 
297         // Format the date.
298         $return = parent::format($format);
299 
300         if ($translate)
301         {
302             // Manually modify the month and day strings in the formatted time.
303             if (strpos($return, self::DAY_ABBR) !== false)
304             {
305                 $return = str_replace(self::DAY_ABBR, $this->dayToString(parent::format('w'), true), $return);
306             }
307 
308             if (strpos($return, self::DAY_NAME) !== false)
309             {
310                 $return = str_replace(self::DAY_NAME, $this->dayToString(parent::format('w')), $return);
311             }
312 
313             if (strpos($return, self::MONTH_ABBR) !== false)
314             {
315                 $return = str_replace(self::MONTH_ABBR, $this->monthToString(parent::format('n'), true), $return);
316             }
317 
318             if (strpos($return, self::MONTH_NAME) !== false)
319             {
320                 $return = str_replace(self::MONTH_NAME, $this->monthToString(parent::format('n')), $return);
321             }
322         }
323 
324         if ($local == false && !empty($this->tz))
325         {
326             parent::setTimezone($this->tz);
327         }
328 
329         return $return;
330     }
331 
332     /**
333      * Get the time offset from GMT in hours or seconds.
334      *
335      * @param   boolean  $hours  True to return the value in hours.
336      *
337      * @return  float  The time offset from GMT either in hours or in seconds.
338      *
339      * @since   11.1
340      */
341     public function getOffsetFromGmt($hours = false)
342     {
343         return (float) $hours ? ($this->tz->getOffset($this) / 3600) : $this->tz->getOffset($this);
344     }
345 
346     /**
347      * Translates month number to a string.
348      *
349      * @param   integer  $month  The numeric month of the year.
350      * @param   boolean  $abbr   If true, return the abbreviated month string
351      *
352      * @return  string  The month of the year.
353      *
354      * @since   11.1
355      */
356     public function monthToString($month, $abbr = false)
357     {
358         switch ($month)
359         {
360             case 1:
361                 return $abbr ? JText::_('JANUARY_SHORT') : JText::_('JANUARY');
362             case 2:
363                 return $abbr ? JText::_('FEBRUARY_SHORT') : JText::_('FEBRUARY');
364             case 3:
365                 return $abbr ? JText::_('MARCH_SHORT') : JText::_('MARCH');
366             case 4:
367                 return $abbr ? JText::_('APRIL_SHORT') : JText::_('APRIL');
368             case 5:
369                 return $abbr ? JText::_('MAY_SHORT') : JText::_('MAY');
370             case 6:
371                 return $abbr ? JText::_('JUNE_SHORT') : JText::_('JUNE');
372             case 7:
373                 return $abbr ? JText::_('JULY_SHORT') : JText::_('JULY');
374             case 8:
375                 return $abbr ? JText::_('AUGUST_SHORT') : JText::_('AUGUST');
376             case 9:
377                 return $abbr ? JText::_('SEPTEMBER_SHORT') : JText::_('SEPTEMBER');
378             case 10:
379                 return $abbr ? JText::_('OCTOBER_SHORT') : JText::_('OCTOBER');
380             case 11:
381                 return $abbr ? JText::_('NOVEMBER_SHORT') : JText::_('NOVEMBER');
382             case 12:
383                 return $abbr ? JText::_('DECEMBER_SHORT') : JText::_('DECEMBER');
384         }
385     }
386 
387     /**
388      * Method to wrap the setTimezone() function and set the internal time zone object.
389      *
390      * @param   DateTimeZone  $tz  The new DateTimeZone object.
391      *
392      * @return  JDate
393      *
394      * @since   11.1
395      * @note    This method can't be type hinted due to a PHP bug: https://bugs.php.net/bug.php?id=61483
396      */
397     public function setTimezone($tz)
398     {
399         $this->tz = $tz;
400 
401         return parent::setTimezone($tz);
402     }
403 
404     /**
405      * Gets the date as an ISO 8601 string.  IETF RFC 3339 defines the ISO 8601 format
406      * and it can be found at the IETF Web site.
407      *
408      * @param   boolean  $local  True to return the date string in the local time zone, false to return it in GMT.
409      *
410      * @return  string  The date string in ISO 8601 format.
411      *
412      * @link    http://www.ietf.org/rfc/rfc3339.txt
413      * @since   11.1
414      */
415     public function toISO8601($local = false)
416     {
417         return $this->format(DateTime::RFC3339, $local, false);
418     }
419 
420     /**
421      * Gets the date as an SQL datetime string.
422      *
423      * @param   boolean          $local  True to return the date string in the local time zone, false to return it in GMT.
424      * @param   JDatabaseDriver  $db     The database driver or null to use JFactory::getDbo()
425      *
426      * @return  string     The date string in SQL datetime format.
427      *
428      * @link    http://dev.mysql.com/doc/refman/5.0/en/datetime.html
429      * @since   11.4
430      */
431     public function toSql($local = false, JDatabaseDriver $db = null)
432     {
433         if ($db === null)
434         {
435             $db = JFactory::getDbo();
436         }
437 
438         return $this->format($db->getDateFormat(), $local, false);
439     }
440 
441     /**
442      * Gets the date as an RFC 822 string.  IETF RFC 2822 supercedes RFC 822 and its definition
443      * can be found at the IETF Web site.
444      *
445      * @param   boolean  $local  True to return the date string in the local time zone, false to return it in GMT.
446      *
447      * @return  string   The date string in RFC 822 format.
448      *
449      * @link    http://www.ietf.org/rfc/rfc2822.txt
450      * @since   11.1
451      */
452     public function toRFC822($local = false)
453     {
454         return $this->format(DateTime::RFC2822, $local, false);
455     }
456 
457     /**
458      * Gets the date as UNIX time stamp.
459      *
460      * @return  integer  The date as a UNIX timestamp.
461      *
462      * @since   11.1
463      */
464     public function toUnix()
465     {
466         return (int) parent::format('U');
467     }
468 }
469