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