1: <?php
2:
3: // These Aid with the PHP 5.2 DateTime error handling
4: class QDateTimeNullException extends QCallerException {}
5: function QDateTimeErrorHandler() {}
6:
7: /**
8: * QDateTime
9: * This DateTime class provides a nice wrapper around the PHP DateTime class,
10: * which is included with all versions of PHP >= 5.2.0. It includes many enhancements,
11: * including the ability to specify a null date or time portion to represent a date only or
12: * time only object.
13: * Inherits from the php DateTime object, and the built-in methods are available for you to call
14: * as well. In particular, note that the built-in format, and the qFormat routines here take different
15: * specifiers. Feel free to use either.
16: *
17: * @property null|integer $Month
18: * @property null|integer $Day
19: * @property null|integer $Year
20: * @property null|integer $Hour
21: * @property null|integer $Minute
22: * @property null|integer $Second
23: * @property integer $Timestamp
24: * @property-read string $Age A string representation of the age relative to now.
25: * @property-read QDateTime $LastDayOfTheMonth A new QDateTime representing the last day of this date's month.
26: * @property-read QDateTime $FirstDayOfTheMonth A new QDateTime representing the first day of this date's month.
27: */
28: class QDateTime extends DateTime implements JsonSerializable {
29: /** Used to specify the time right now (used when creating new instances of this class) */
30: const Now = 'now';
31: /** Date and time in ISO format */
32: const FormatIso = 'YYYY-MM-DD hhhh:mm:ss';
33: /** Date and time in ISO compressed format */
34: const FormatIsoCompressed = 'YYYYMMDDhhhhmmss';
35: /** Format used for displaying short date */
36: const FormatDisplayDate = 'MMM DD YYYY';
37: /** Format used for displaying the full date */
38: const FormatDisplayDateFull = 'DDD, MMMM D, YYYY';
39: /** Format used for displaying the short date and time */
40: const FormatDisplayDateTime = 'MMM DD YYYY hh:mm zz';
41: /** Format used for displaying the full date and time */
42: const FormatDisplayDateTimeFull = 'DDDD, MMMM D, YYYY, h:mm:ss zz';
43: /** Format to display only the time */
44: const FormatDisplayTime = 'hh:mm:ss zz';
45: /** Date and time format according to RFC 822 */
46: const FormatRfc822 = 'DDD, DD MMM YYYY hhhh:mm:ss ttt';
47: /** Date and time format according to RFC 5322 */
48: const FormatRfc5322 = 'DDD, DD MMM YYYY hhhh:mm:ss ttttt';
49:
50: /** Format used to represent date for SOAP */
51: const FormatSoap = 'YYYY-MM-DDThhhh:mm:ss';
52:
53: /* Type in which the date and time has to be interpreted */
54: /** Unknown type interpretation */
55: const UnknownType = 0;
56: /** Interpret as only date (used in QDateTimePicket and MySqli database type) */
57: const DateOnlyType = 1;
58: /** Interpret as only time (used in QDateTimePicket and MySqli database type) */
59: const TimeOnlyType = 2;
60: /** Interpret as both date and type (used in QDateTimePicket and MySqli database type) */
61: const DateAndTimeType = 3;
62:
63: /** @var bool true if date is null */
64: protected $blnDateNull = true;
65: /** @var bool true if time is null, rather than just zero (beginning of day) */
66: protected $blnTimeNull = true;
67:
68:
69: /**
70: * The "Default" Display Format
71: * @var string $DefaultFormat
72: */
73: public static $DefaultFormat = QDateTime::FormatDisplayDateTime;
74:
75: /**
76: * The "Default" Display Format for Times
77: * @var string $DefaultTimeFormat
78: */
79: public static $DefaultTimeFormat = QDateTime::FormatDisplayTime;
80:
81: /**
82: * The "Default" Display Format for Dates with null times
83: * @var string $DefaultDateOnlyFormat
84: */
85: public static $DefaultDateOnlyFormat = QDateTime::FormatDisplayDate;
86:
87:
88: /**
89: * Returns a new QDateTime object that's set to "Now"
90: * Set blnTimeValue to true (default) for a DateTime, and set blnTimeValue to false for just a Date
91: *
92: * @param boolean $blnTimeValue whether or not to include the time value
93: * @return QDateTime the current date and/or time
94: */
95: public static function Now($blnTimeValue = true) {
96: $dttToReturn = new QDateTime(QDateTime::Now);
97: if (!$blnTimeValue) {
98: $dttToReturn->blnTimeNull = true;
99: $dttToReturn->ReinforceNullProperties();
100: }
101: return $dttToReturn;
102: }
103:
104: /**
105: * Return Now as a string. Uses the default datetime format if none speicifed.
106: * @param string|null $strFormat
107: * @return string
108: */
109: public static function NowToString($strFormat = null) {
110: $dttNow = new QDateTime(QDateTime::Now);
111: return $dttNow->qFormat($strFormat);
112: }
113:
114: /**
115: * @return bool
116: */
117: public function IsDateNull() {
118: return $this->blnDateNull;
119: }
120:
121: /**
122: * @return bool
123: */
124: public function IsNull() {
125: return ($this->blnDateNull && $this->blnTimeNull);
126: }
127:
128: /**
129: * @return bool
130: */
131: public function IsTimeNull() {
132: return $this->blnTimeNull;
133: }
134:
135: /**
136: * @param $strFormat
137: * @return string
138: */
139: public function PhpDate($strFormat) {
140: // This just makes a call to format
141: return parent::format($strFormat);
142: }
143:
144: /**
145: * @param QDateTime[] $dttArray
146: * @return array
147: */
148: public function GetSoapDateTimeArray($dttArray) {
149: if (!$dttArray)
150: return null;
151:
152: $strArrayToReturn = array();
153: foreach ($dttArray as $dttItem)
154: array_push($strArrayToReturn, $dttItem->qFormat(QDateTime::FormatSoap));
155: return $strArrayToReturn;
156: }
157:
158: /**
159: * Create from a unix timestamp. Improves over php by taking into consideration the
160: * timezone, so that the internal format is automatically converted to the internal timezone,
161: * or the default timezone.
162: *
163: * @param integer $intTimestamp
164: * @param DateTimeZone $objTimeZone
165: * @return QDateTime
166: */
167: public static function FromTimestamp($intTimestamp, DateTimeZone $objTimeZone = null) {
168: return new QDateTime(date('Y-m-d H:i:s', $intTimestamp), $objTimeZone);
169: }
170:
171: /**
172: * Construct a QDateTime. Does a few things differently than the php version:
173: * - Always stores timestamps in local or given timezone, so time extraction is easy
174: * - Has settings to determine if you want a date only or time only type
175: * - Will NOT throw exceptions. Errors simply result in a null datetime.
176: *
177: * @param null|integer|string|QDateTime|DateTime $mixValue
178: * @param DateTimeZone $objTimeZone
179: * @param int $intType
180: *
181: * @throws QCallerException
182: */
183: public function __construct($mixValue = null, DateTimeZone $objTimeZone = null, $intType = QDateTime::UnknownType) {
184: if ($mixValue instanceof QDateTime) {
185: // Cloning from another QDateTime object
186: if ($objTimeZone)
187: throw new QCallerException('QDateTime cloning cannot take in a DateTimeZone parameter');
188: parent::__construct($mixValue->format('Y-m-d H:i:s'), $mixValue->GetTimeZone());
189: $this->blnDateNull = $mixValue->IsDateNull();
190: $this->blnTimeNull = $mixValue->IsTimeNull();
191: $this->ReinforceNullProperties();
192:
193: } else if ($mixValue instanceof DateTime) {
194: // Subclassing from a PHP DateTime object
195: if ($objTimeZone)
196: throw new QCallerException('QDateTime subclassing of a DateTime object cannot take in a DateTimeZone parameter');
197: parent::__construct($mixValue->format('Y-m-d H:i:s'), $mixValue->getTimezone());
198:
199: // By definition, a DateTime object doesn't have anything nulled
200: $this->blnDateNull = false;
201: $this->blnTimeNull = false;
202: } else if (!$mixValue) {
203: // Set to "null date"
204: // And Do Nothing Else -- Default Values are already set to Nulled out
205: parent::__construct('2000-01-01 00:00:00', $objTimeZone);
206: } else if (strtolower($mixValue) == QDateTime::Now) {
207: // very common, so quickly deal with now string
208: parent::__construct('now', $objTimeZone);
209: $this->blnDateNull = false;
210: $this->blnTimeNull = false;
211: } else if (substr($mixValue, 0, 1) == '@') {
212: // unix timestamp. PHP superclass will always store ts in UTC. Our class will store in given timezone, or local tz
213: parent::__construct(date('Y-m-d H:i:s', substr($mixValue, 1)), $objTimeZone);
214: $this->blnDateNull = false;
215: $this->blnTimeNull = false;
216: }
217: else {
218: // string relative date or time
219: if ($intTime = strtotime($mixValue)) {
220: parent::__construct(date('Y-m-d H:i:s', $intTime), $objTimeZone);
221: $this->blnDateNull = false;
222: $this->blnTimeNull = false;
223: } else { // error
224: parent::__construct();
225: $this->blnDateNull = true;
226: $this->blnTimeNull = true;
227: }
228: }
229:
230: // User is requesting to force a particular type.
231: switch ($intType) {
232: case QDateTime::DateOnlyType:
233: $this->blnTimeNull = true;
234: $this->ReinforceNullProperties();
235: return;
236: case QDateTime::TimeOnlyType:
237: $this->blnDateNull = true;
238: $this->ReinforceNullProperties();
239: return;
240: case QDateTime::DateAndTimeType: // forcing both a date and time type to not be null
241: $this->blnDateNull = false;
242: $this->blnTimeNull = false;
243: break;
244: default:
245: break;
246: }
247: }
248:
249: /**
250: * Returns a new QDateTime object set to the last day of the specified month.
251: *
252: * @param int $intMonth
253: * @param int $intYear
254: * @return QDateTime the last day to a month in a year
255: */
256: public static function LastDayOfTheMonth($intMonth, $intYear) {
257: $temp = date('Y-m-t',mktime(0,0,0,$intMonth,1,$intYear));
258: return new QDateTime($temp);
259: }
260:
261: /**
262: * Returns a new QDateTime object set to the first day of the specified month.
263: *
264: * @param int $intMonth
265: * @param int $intYear
266: * @return QDateTime the first day of the month
267: */
268: public static function FirstDayOfTheMonth($intMonth, $intYear) {
269: $temp = date('Y-m-d',mktime(0,0,0,$intMonth,1,$intYear));
270: return new QDateTime($temp);
271: }
272:
273: /**
274: * Formats a date as a string using the default format type.
275: * @return string
276: */
277: public function __toString() {
278: return $this->qFormat();
279: }
280:
281: /**
282: * The following code is a workaround for a PHP bug in 5.2 and greater (at least to 5.4).
283: */
284: protected $strSerializedData;
285: protected $strSerializedTZ;
286: public function __sleep() {
287: $tz = $this->getTimezone();
288: if ($tz && in_array ($tz->getName(), timezone_identifiers_list())) {
289: // valid relative timezone name found
290: $this->strSerializedData = parent::format('Y-m-d H:i:s');
291: $this->strSerializedTZ = $tz->getName();
292: return array('blnDateNull', 'blnTimeNull', 'strSerializedData', 'strSerializedTZ');
293: } else {
294: // absolute timezone, which can't be sent into the constructor of DateTimeZone
295: $this->strSerializedData = parent::format (DateTime::ISO8601);
296: return array('blnDateNull', 'blnTimeNull', 'strSerializedData');
297: }
298: }
299: public function __wakeup() {
300: $tz = null;
301: if ($this->strSerializedTZ) {
302: $tz = new DateTimeZone($this->strSerializedTZ);
303: }
304: parent::__construct($this->strSerializedData, $tz);
305: $this->strSerializedData = null;
306: $this->strSerializedTZ = null;
307: }
308:
309:
310: /**
311: * Outputs the date as a string given the format strFormat. Will use
312: * the static defaults if none given.
313: *
314: * Properties of strFormat are (using Sunday, March 2, 1977 at 1:15:35 pm
315: * in the following examples):
316: *
317: * M - Month as an integer (e.g., 3)
318: * MM - Month as an integer with leading zero (e.g., 03)
319: * MMM - Month as three-letters (e.g., Mar)
320: * MMMM - Month as full name (e.g., March)
321: *
322: * D - Day as an integer (e.g., 2)
323: * DD - Day as an integer with leading zero (e.g., 02)
324: * DDD - Day of week as three-letters (e.g., Wed)
325: * DDDD - Day of week as full name (e.g., Wednesday)
326: *
327: * YY - Year as a two-digit integer (e.g., 77)
328: * YYYY - Year as a four-digit integer (e.g., 1977)
329: *
330: * h - Hour as an integer in 12-hour format (e.g., 1)
331: * hh - Hour as an integer in 12-hour format with leading zero (e.g., 01)
332: * hhh - Hour as an integer in 24-hour format (e.g., 13)
333: * hhhh - Hour as an integer in 24-hour format with leading zero (e.g., 13)
334: *
335: * mm - Minute as a two-digit integer
336: *
337: * ss - Second as a two-digit integer
338: *
339: * z - "pm" or "am"
340: * zz - "PM" or "AM"
341: * zzz - "p.m." or "a.m."
342: * zzzz - "P.M." or "A.M."
343: *
344: * ttt - Timezone Abbreviation as a three-letter code (e.g. PDT, GMT)
345: * tttt - Timezone Identifier (e.g. America/Los_Angeles)
346: *
347: * @param string $strFormat the format of the date
348: * @return string the formatted date as a string
349: */
350: public function qFormat($strFormat = null) {
351: if ($this->blnDateNull && $this->blnTimeNull) {
352: return '';
353: }
354:
355: if (is_null($strFormat)) {
356: if ($this->blnDateNull && !$this->blnTimeNull) {
357: $strFormat = QDateTime::$DefaultTimeFormat;
358: } elseif (!$this->blnDateNull && $this->blnTimeNull) {
359: $strFormat = QDateTime::$DefaultDateOnlyFormat;
360: } else {
361: $strFormat = QDateTime::$DefaultFormat;
362: }
363: }
364:
365: /*
366: (?(?=D)([D]+)|
367: (?(?=M)([M]+)|
368: (?(?=Y)([Y]+)|
369: (?(?=h)([h]+)|
370: (?(?=m)([m]+)|
371: (?(?=s)([s]+)|
372: (?(?=z)([z]+)|
373: (?(?=t)([t]+)|
374: ))))))))
375: */
376:
377: // $strArray = preg_split('/([^D^M^Y^h^m^s^z^t])+/', $strFormat);
378: preg_match_all('/(?(?=D)([D]+)|(?(?=M)([M]+)|(?(?=Y)([Y]+)|(?(?=h)([h]+)|(?(?=m)([m]+)|(?(?=s)([s]+)|(?(?=z)([z]+)|(?(?=t)([t]+)|))))))))/', $strFormat, $strArray);
379: $strArray = $strArray[0];
380: $strToReturn = '';
381:
382: $intStartPosition = 0;
383: for ($intIndex = 0; $intIndex < count($strArray); $intIndex++) {
384: $strToken = trim($strArray[$intIndex]);
385: if ($strToken) {
386: $intEndPosition = strpos($strFormat, $strArray[$intIndex], $intStartPosition);
387: $strToReturn .= substr($strFormat, $intStartPosition, $intEndPosition - $intStartPosition);
388: $intStartPosition = $intEndPosition + strlen($strArray[$intIndex]);
389:
390: switch ($strArray[$intIndex]) {
391: case 'M':
392: $strToReturn .= parent::format('n');
393: break;
394: case 'MM':
395: $strToReturn .= parent::format('m');
396: break;
397: case 'MMM':
398: $strToReturn .= parent::format('M');
399: break;
400: case 'MMMM':
401: $strToReturn .= parent::format('F');
402: break;
403:
404: case 'D':
405: $strToReturn .= parent::format('j');
406: break;
407: case 'DD':
408: $strToReturn .= parent::format('d');
409: break;
410: case 'DDD':
411: $strToReturn .= parent::format('D');
412: break;
413: case 'DDDD':
414: $strToReturn .= parent::format('l');
415: break;
416:
417: case 'YY':
418: $strToReturn .= parent::format('y');
419: break;
420: case 'YYYY':
421: $strToReturn .= parent::format('Y');
422: break;
423:
424: case 'h':
425: $strToReturn .= parent::format('g');
426: break;
427: case 'hh':
428: $strToReturn .= parent::format('h');
429: break;
430: case 'hhh':
431: $strToReturn .= parent::format('G');
432: break;
433: case 'hhhh':
434: $strToReturn .= parent::format('H');
435: break;
436:
437: case 'mm':
438: $strToReturn .= parent::format('i');
439: break;
440:
441: case 'ss':
442: $strToReturn .= parent::format('s');
443: break;
444:
445: case 'z':
446: $strToReturn .= parent::format('a');
447: break;
448: case 'zz':
449: $strToReturn .= parent::format('A');
450: break;
451: case 'zzz':
452: $strToReturn .= sprintf('%s.m.', substr(parent::format('a'), 0, 1));
453: break;
454: case 'zzzz':
455: $strToReturn .= sprintf('%s.M.', substr(parent::format('A'), 0, 1));
456: break;
457:
458: case 'ttt':
459: $strToReturn .= parent::format('T');
460: break;
461: case 'tttt':
462: $strToReturn .= parent::format('e');
463: break;
464: case 'ttttt':
465: $strToReturn .= parent::format('O');
466: break;
467:
468: default:
469: $strToReturn .= $strArray[$intIndex];
470: }
471: }
472: }
473:
474: if ($intStartPosition < strlen($strFormat))
475: $strToReturn .= substr($strFormat, $intStartPosition);
476:
477: return $strToReturn;
478: }
479:
480: /**
481: * Sets the time portion to the given time. If a QDateTime is given, will use the time portion of that object.
482: * Works around a problem in php that if you set the time across a daylight savings time boundary, the timezone
483: * does not advance. This version will detect that and advance the timezone.
484: *
485: * @param int|QDateTime $mixValue
486: * @param int|null $intMinute
487: * @param int|null $intSecond
488: * @return QDateTime
489: */
490: public function setTime($mixValue, $intMinute = null, $intSecond = null) {
491: if ($mixValue instanceof QDateTime) {
492: if ($mixValue->IsTimeNull()) {
493: $this->blnTimeNull = true;
494: $this->ReinforceNullProperties();
495: return $this;
496: }
497: // normalize the timezones
498: $tz = $this->getTimezone();
499: if ($tz && in_array ($tz->getName(), timezone_identifiers_list())) {
500: // php limits you to ID only timezones here, so make sure we have one of those
501: $mixValue->setTimezone ($tz);
502: }
503: $intHour = $mixValue->Hour;
504: $intMinute = $mixValue->Minute;
505: $intSecond = $mixValue->Second;
506: } else {
507: $intHour = $mixValue;
508: }
509: // If HOUR or MINUTE is NULL...
510: if (is_null($intHour) || is_null($intMinute)) {
511: parent::setTime($intHour, $intMinute, $intSecond);
512: $this->blnTimeNull = true;
513: $this->ReinforceNullProperties();
514: return $this;
515: }
516:
517: $intHour = QType::Cast($intHour, QType::Integer);
518: $intMinute = QType::Cast($intMinute, QType::Integer);
519: $intSecond = QType::Cast($intSecond, QType::Integer);
520: $this->blnTimeNull = false;
521:
522: /*
523: // Possible fix for a PHP problem. Can't reproduce, so leaving code here just in case it comes back.
524: // The problem is with setting times across dst barriers
525: if ($this->Hour == 0 && preg_match('/[0-9]+/', $this->getTimezone()->getName())) {
526: // fix a php problem with GMT and relative timezones
527: $s = 'PT' . $intHour . 'H' . $intMinute . 'M' . $intSecond . 'S';
528: $this->add (new DateInterval ($s));
529: // will continue and set again to make sure, because boundary crossing will change the time
530: }*/
531:
532: parent::setTime($intHour, $intMinute, $intSecond);
533:
534: return $this;
535: }
536:
537: /**
538: * Set the date.
539: *
540: * @param int $intYear
541: * @param int $intMonth
542: * @param int $intDay
543: * @return $this|DateTime
544: */
545: public function setDate($intYear, $intMonth, $intDay) {
546: $intYear = QType::Cast($intYear, QType::Integer);
547: $intMonth = QType::Cast($intMonth, QType::Integer);
548: $intDay = QType::Cast($intDay, QType::Integer);
549: $this->blnDateNull = false;
550: parent::setDate($intYear, $intMonth, $intDay);
551: return $this;
552: }
553:
554: protected function ReinforceNullProperties() {
555: if ($this->blnDateNull)
556: parent::setDate(2000, 1, 1);
557: if ($this->blnTimeNull) {
558: parent::setTime(0, 0, 0);
559: }
560:
561: }
562:
563: /**
564: * Converts the current QDateTime object to a different TimeZone.
565: *
566: * TimeZone should be passed in as a string-based identifier.
567: *
568: * Note that this is different than the built-in DateTime::SetTimezone() method which expicitly
569: * takes in a DateTimeZone object. QDateTime::ConvertToTimezone allows you to specify any
570: * string-based Timezone identifier. If none is specified and/or if the specified timezone
571: * is not a valid identifier, it will simply remain unchanged as opposed to throwing an exeception
572: * or error.
573: *
574: * @param string $strTimezoneIdentifier a string-based parameter specifying a timezone identifier (e.g. America/Los_Angeles)
575: * @return void
576: */
577: public function ConvertToTimezone($strTimezoneIdentifier) {
578: try {
579: $dtzNewTimezone = new DateTimeZone($strTimezoneIdentifier);
580: $this->SetTimezone($dtzNewTimezone);
581: } catch (Exception $objExc) {}
582: }
583:
584: /**
585: * Returns true if give QDateTime is the same.
586: *
587: * @param QDateTime $dttCompare
588: * @return bool
589: */
590: public function IsEqualTo(QDateTime $dttCompare) {
591: // All comparison operations MUST have operands with matching Date Nullstates
592: if ($this->blnDateNull != $dttCompare->blnDateNull)
593: return false;
594:
595: // If mismatched Time nullstates, then only compare the Date portions
596: if ($this->blnTimeNull != $dttCompare->blnTimeNull) {
597: // Let's "Null Out" the Time
598: $dttThis = new QDateTime($this);
599: $dttThat = new QDateTime($dttCompare);
600: $dttThis->Hour = null;
601: $dttThat->Hour = null;
602:
603: // Return the Result
604: return ($dttThis->Timestamp == $dttThat->Timestamp);
605: } else {
606: // Return the Result for the both Date and Time components
607: return ($this->Timestamp == $dttCompare->Timestamp);
608: }
609: }
610:
611: /**
612: * Returns true if current date time is earlier than the given one.
613: * @param QDateTime $dttCompare
614: * @return bool
615: */
616: public function IsEarlierThan(QDateTime $dttCompare) {
617: // All comparison operations MUST have operands with matching Date Nullstates
618: if ($this->blnDateNull != $dttCompare->blnDateNull)
619: return false;
620:
621: // If mismatched Time nullstates, then only compare the Date portions
622: if ($this->blnTimeNull != $dttCompare->blnTimeNull) {
623: // Let's "Null Out" the Time
624: $dttThis = new QDateTime($this);
625: $dttThat = new QDateTime($dttCompare);
626: $dttThis->Hour = null;
627: $dttThat->Hour = null;
628:
629: // Return the Result
630: return ($dttThis->Timestamp < $dttThat->Timestamp);
631: } else {
632: // Return the Result for the both Date and Time components
633: return ($this->Timestamp < $dttCompare->Timestamp);
634: }
635: }
636:
637: /**
638: * Returns true if current date time is earlier than the given one.
639: * @param QDateTime $dttCompare
640: * @return bool
641: */
642: public function IsEarlierOrEqualTo(QDateTime $dttCompare) {
643: // All comparison operations MUST have operands with matching Date Nullstates
644: if ($this->blnDateNull != $dttCompare->blnDateNull)
645: return false;
646:
647: // If mismatched Time nullstates, then only compare the Date portions
648: if ($this->blnTimeNull != $dttCompare->blnTimeNull) {
649: // Let's "Null Out" the Time
650: $dttThis = new QDateTime($this);
651: $dttThat = new QDateTime($dttCompare);
652: $dttThis->Hour = null;
653: $dttThat->Hour = null;
654:
655: // Return the Result
656: return ($dttThis->Timestamp <= $dttThat->Timestamp);
657: } else {
658: // Return the Result for the both Date and Time components
659: return ($this->Timestamp <= $dttCompare->Timestamp);
660: }
661: }
662:
663: /**
664: * Returns true if current date time is later than the given one.
665: * @param QDateTime $dttCompare
666: * @return bool
667: */
668: public function IsLaterThan(QDateTime $dttCompare) {
669: // All comparison operations MUST have operands with matching Date Nullstates
670: if ($this->blnDateNull != $dttCompare->blnDateNull)
671: return false;
672:
673: // If mismatched Time nullstates, then only compare the Date portions
674: if ($this->blnTimeNull != $dttCompare->blnTimeNull) {
675: // Let's "Null Out" the Time
676: $dttThis = new QDateTime($this);
677: $dttThat = new QDateTime($dttCompare);
678: $dttThis->Hour = null;
679: $dttThat->Hour = null;
680:
681: // Return the Result
682: return ($dttThis->Timestamp > $dttThat->Timestamp);
683: } else {
684: // Return the Result for the both Date and Time components
685: return ($this->Timestamp > $dttCompare->Timestamp);
686: }
687: }
688:
689: /**
690: * Returns true if current date time is later than or equal to the given one.
691: * @param QDateTime $dttCompare
692: * @return bool
693: */
694: public function IsLaterOrEqualTo(QDateTime $dttCompare) {
695: // All comparison operations MUST have operands with matching Date Nullstates
696: if ($this->blnDateNull != $dttCompare->blnDateNull)
697: return false;
698:
699: // If mismatched Time nullstates, then only compare the Date portions
700: if ($this->blnTimeNull != $dttCompare->blnTimeNull) {
701: // Let's "Null Out" the Time
702: $dttThis = new QDateTime($this);
703: $dttThat = new QDateTime($dttCompare);
704: $dttThis->Hour = null;
705: $dttThat->Hour = null;
706:
707: // Return the Result
708: return ($dttThis->Timestamp >= $dttThat->Timestamp);
709: } else {
710: // Return the Result for the both Date and Time components
711: return ($this->Timestamp >= $dttCompare->Timestamp);
712: }
713: }
714:
715: /**
716: * Compare the current date with the given date. Return -1 if current date is less than given date, 0 if equal,
717: * and 1 if greater. Null dates are considered the earliest possible date.
718: *
719: * @param QDateTime $dttCompare
720: * @return int
721: */
722: public function Compare(QDateTime $dttCompare) {
723: // All comparison operations MUST have operands with matching Date Nullstates
724: if ($this->blnDateNull && !$dttCompare->blnDateNull) {
725: return -1;
726: } elseif (!$this->blnDateNull && $dttCompare->blnDateNull) {
727: return 1;
728: }
729:
730: // If mismatched Time nullstates, then only compare the Date portions
731: if ($this->blnTimeNull != $dttCompare->blnTimeNull) {
732: // Let's "Null Out" the Time
733: $dttThis = new QDateTime($this);
734: $dttThat = new QDateTime($dttCompare);
735: $dttThis->Hour = null;
736: $dttThat->Hour = null;
737: } else {
738: $dttThis = $this;
739: $dttThat = $dttCompare;
740: }
741: return ($dttThis->Timestamp < $dttThat->Timestamp ? -1 : ($dttThis->Timestamp == $dttThat->Timestamp ? 0 : 1));
742: }
743:
744: /**
745: * Returns the difference as a QDateSpan, which is easier to work with and more full featured than
746: * the php DateTimeInterval class.
747: *
748: * @param QDateTime $dttDateTime
749: * @return QDateTimeSpan
750: */
751: public function Difference(QDateTime $dttDateTime) {
752: $intDifference = $this->Timestamp - $dttDateTime->Timestamp;
753: return new QDateTimeSpan($intDifference);
754: }
755:
756: /**
757: * Add a datespan or interval to the current date.
758: *
759: * @param DateInterval|QDateTimeSpan $dtsSpan
760: * @return QDateTime
761: * @throws QCallerException
762: */
763: public function Add($dtsSpan){
764: if ($dtsSpan instanceof DateInterval) {
765: parent::add($dtsSpan);
766: return $this;
767: }
768: elseif (!$dtsSpan instanceof QDateTimeSpan) {
769: throw new QCallerException("Can only add DateTimeInterval or QDateTimeSpan objects");
770: }
771:
772: // And add the Span Second count to it
773: $this->Timestamp = $this->Timestamp + $dtsSpan->Seconds;
774: return $this;
775: }
776:
777: /**
778: * Add a number of seconds. Use negative value to go earlier in time.
779: *
780: * @param integer $intSeconds
781: * @return QDateTime
782: */
783: public function AddSeconds($intSeconds){
784: $this->Second += $intSeconds;
785: return $this;
786: }
787:
788: /**
789: * Add minutes to the time.
790: *
791: * @param integer $intMinutes
792: * @return QDateTime
793: */
794: public function AddMinutes($intMinutes){
795: $this->Minute += $intMinutes;
796: return $this;
797: }
798:
799: /**
800: * Add hours to the time.
801: *
802: * @param integer $intHours
803: * @return QDateTime
804: */
805: public function AddHours($intHours){
806: $this->Hour += $intHours;
807: return $this;
808: }
809:
810: /**
811: * Add days to the time.
812: *
813: * @param integer $intDays
814: * @return QDateTIme
815: */
816: public function AddDays($intDays){
817: $this->Day += $intDays;
818: return $this;
819: }
820:
821: /**
822: * Add months to the time. If the day on the new month is greater than the month will allow, the day is adjusted
823: * to be the last day of that month.
824: *
825: * @param integer $intMonths
826: * @return QDateTime
827: */
828: public function AddMonths($intMonths){
829: $prevDay = $this->Day;
830: $this->Month += $intMonths;
831: if ($this->Day != $prevDay) {
832: $this->Day = 1;
833: $this->AddDays (-1);
834: }
835: return $this;
836: }
837:
838: /**
839: * Add years to the time.
840: *
841: * @param integer $intYears
842: * @return QDateTime
843: */
844: public function AddYears($intYears){
845: $this->Year += $intYears;
846: return $this;
847: }
848:
849: /**
850: * Modifies the date or time based on values found int a string.
851: *
852: * @see DateTime::modify()
853: * @param string $mixValue
854: * @return QDateTime
855: */
856: public function Modify($mixValue) {
857: parent::modify($mixValue);
858: return $this;
859: }
860:
861: /**
862: * Convert the object to a javascript object. This is code that if executed in javascript would produce a Date
863: * javascript object. Note that the date will be created in the browser's local timezone, so convert to the
864: * browser's timezone first if that is important for you.
865: *
866: * @return string
867: */
868: public function toJsObject() {
869: if ($this->blnDateNull) {
870: $dt = self::Now(); // time only will use today's date.
871: $dt->setTime($this);
872: } else {
873: $dt = clone $this;
874: }
875:
876: if ($this->blnTimeNull) {
877: return sprintf ('new Date(%s, %s, %s)', $dt->Year, $dt->Month - 1, $dt->Day);
878: } else {
879: return sprintf ('new Date(%s, %s, %s, %s, %s, %s)', $dt->Year, $dt->Month - 1, $dt->Day, $dt->Hour, $dt->Minute, $dt->Second);
880: }
881: }
882:
883: /**
884: * Returns a datetime in a way that it will pass through a json_encode and be decodalbe in qcubed.js.
885: * qcubed.unpackParams looks for this.
886: *
887: * @return array|mixed;
888: */
889: public function jsonSerialize() {
890: if ($this->blnDateNull) {
891: $dt = self::Now(); // time only will use today's date.
892: $dt->setTime($this);
893: } else {
894: $dt = clone $this;
895: }
896:
897: if ($this->blnTimeNull) {
898: return [JavaScriptHelper::ObjectType=>'qDateTime', 'year'=>$dt->Year, 'month'=>$dt->Month - 1,
899: 'day'=>$dt->Day];
900:
901: } else {
902: return [JavaScriptHelper::ObjectType=>'qDateTime', 'year'=>$dt->Year, 'month'=>$dt->Month - 1,
903: 'day'=>$dt->Day, 'hour'=>$dt->Hour, 'minute'=>$dt->Minute, 'second'=>$dt->Second];
904: }
905: }
906:
907: /**
908: * PHP magic method
909: * @param $strName
910: *
911: * @return QDateTime|string
912: * @throws QUndefinedPropertyException
913: */
914: public function __get($strName) {
915: switch ($strName) {
916: case 'Month':
917: if ($this->blnDateNull)
918: return null;
919: else
920: return (int) parent::format('m');
921:
922: case 'Day':
923: if ($this->blnDateNull)
924: return null;
925: else
926: return (int) parent::format('d');
927:
928: case 'Year':
929: if ($this->blnDateNull)
930: return null;
931: else
932: return (int) parent::format('Y');
933:
934: case 'Hour':
935: if ($this->blnTimeNull)
936: return null;
937: else
938: return (int) parent::format('H');
939:
940: case 'Minute':
941: if ($this->blnTimeNull)
942: return null;
943: else
944: return (int) parent::format('i');
945:
946: case 'Second':
947: if ($this->blnTimeNull)
948: return null;
949: else
950: return (int) parent::format('s');
951:
952: case 'Timestamp':
953: return (int) parent::format('U'); // range depends on the platform's max and min integer values
954:
955: case 'Age':
956: // Figure out the Difference from "Now"
957: $dtsFromCurrent = $this->Difference(QDateTime::Now());
958:
959: // It's in the future ('about 2 hours from now')
960: if ($dtsFromCurrent->IsPositive())
961: return $dtsFromCurrent->SimpleDisplay() . QApplication::Translate(' from now');
962:
963: // It's in the past ('about 5 hours ago')
964: else if ($dtsFromCurrent->IsNegative()) {
965: $dtsFromCurrent->Seconds = abs($dtsFromCurrent->Seconds);
966: return $dtsFromCurrent->SimpleDisplay() . QApplication::Translate(' ago');
967:
968: // It's current
969: } else
970: return QApplication::Translate('right now');
971:
972: case 'LastDayOfTheMonth':
973: return self::LastDayOfTheMonth($this->Month, $this->Year);
974: case 'FirstDayOfTheMonth':
975: return self::FirstDayOfTheMonth($this->Month, $this->Year);
976: default:
977: throw new QUndefinedPropertyException('GET', 'QDateTime', $strName);
978: }
979: }
980:
981: /**
982: * PHP magic method
983: *
984: * @param $strName
985: * @param $mixValue
986: *
987: * @return mixed
988: * @throws Exception|QCallerException|QDateTimeNullException|QInvalidCastException|QUndefinedPropertyException
989: */
990: public function __set($strName, $mixValue) {
991: try {
992: switch ($strName) {
993: case 'Month':
994: if ($this->blnDateNull && (!is_null($mixValue)))
995: throw new QDateTimeNullException('Cannot set the Month property on a null date. Use SetDate().');
996: if (is_null($mixValue)) {
997: $this->blnDateNull = true;
998: $this->ReinforceNullProperties();
999: return null;
1000: }
1001: $mixValue = QType::Cast($mixValue, QType::Integer);
1002: parent::setDate(parent::format('Y'), $mixValue, parent::format('d'));
1003: return $mixValue;
1004:
1005: case 'Day':
1006: if ($this->blnDateNull && (!is_null($mixValue)))
1007: throw new QDateTimeNullException('Cannot set the Day property on a null date. Use SetDate().');
1008: if (is_null($mixValue)) {
1009: $this->blnDateNull = true;
1010: $this->ReinforceNullProperties();
1011: return null;
1012: }
1013: $mixValue = QType::Cast($mixValue, QType::Integer);
1014: parent::setDate(parent::format('Y'), parent::format('m'), $mixValue);
1015: return $mixValue;
1016:
1017: case 'Year':
1018: if ($this->blnDateNull && (!is_null($mixValue)))
1019: throw new QDateTimeNullException('Cannot set the Year property on a null date. Use SetDate().');
1020: if (is_null($mixValue)) {
1021: $this->blnDateNull = true;
1022: $this->ReinforceNullProperties();
1023: return null;
1024: }
1025: $mixValue = QType::Cast($mixValue, QType::Integer);
1026: parent::setDate($mixValue, parent::format('m'), parent::format('d'));
1027: return $mixValue;
1028:
1029: case 'Hour':
1030: if ($this->blnTimeNull && (!is_null($mixValue)))
1031: throw new QDateTimeNullException('Cannot set the Hour property on a null time. Use SetTime().');
1032: if (is_null($mixValue)) {
1033: $this->blnTimeNull = true;
1034: $this->ReinforceNullProperties();
1035: return null;
1036: }
1037: $mixValue = QType::Cast($mixValue, QType::Integer);
1038: parent::setTime($mixValue, parent::format('i'), parent::format('s'));
1039: return $mixValue;
1040:
1041: case 'Minute':
1042: if ($this->blnTimeNull && (!is_null($mixValue)))
1043: throw new QDateTimeNullException('Cannot set the Minute property on a null time. Use SetTime().');
1044: if (is_null($mixValue)) {
1045: $this->blnTimeNull = true;
1046: $this->ReinforceNullProperties();
1047: return null;
1048: }
1049: $mixValue = QType::Cast($mixValue, QType::Integer);
1050: parent::setTime(parent::format('H'), $mixValue, parent::format('s'));
1051: return $mixValue;
1052:
1053: case 'Second':
1054: if ($this->blnTimeNull && (!is_null($mixValue)))
1055: throw new QDateTimeNullException('Cannot set the Second property on a null time. Use SetTime().');
1056: if (is_null($mixValue)) {
1057: $this->blnTimeNull = true;
1058: $this->ReinforceNullProperties();
1059: return null;
1060: }
1061: $mixValue = QType::Cast($mixValue, QType::Integer);
1062: parent::setTime(parent::format('H'), parent::format('i'), $mixValue);
1063: return $mixValue;
1064:
1065: case 'Timestamp':
1066: $mixValue = QType::Cast($mixValue, QType::Integer);
1067: $this->setTimestamp($mixValue);
1068: $this->blnDateNull = false;
1069: $this->blnTimeNull = false;
1070: return $mixValue;
1071:
1072: default:
1073: throw new QUndefinedPropertyException('SET', 'QDateTime', $strName);
1074: }
1075: } catch (QInvalidCastException $objExc) {
1076: $objExc->IncrementOffset();
1077: throw $objExc;
1078: }
1079: }
1080: }
1081:
1082: /*
1083: This is a reference to the documentation for hte PHP DateTime classes (as of PHP 5.2)
1084:
1085: DateTime::ATOM
1086: DateTime::COOKIE
1087: DateTime::ISO8601
1088: DateTime::RFC822
1089: DateTime::RFC850
1090: DateTime::RFC1036
1091: DateTime::RFC1123
1092: DateTime::RFC2822
1093: DateTime::RFC3339
1094: DateTime::RSS
1095: DateTime::W3C
1096:
1097: DateTime::__construct([string time[, DateTimeZone object]])
1098: - Returns new DateTime object
1099:
1100: string DateTime::format(string format)
1101: - Returns date formatted according to given format
1102:
1103: long DateTime::getOffset()
1104: - Returns the DST offset
1105:
1106: DateTimeZone DateTime::getTimezone()
1107: - Return new DateTimeZone object relative to give DateTime
1108:
1109: void DateTime::modify(string modify)
1110: - Alters the timestamp
1111:
1112: array DateTime::parse(string date)
1113: - Returns associative array with detailed info about given date
1114:
1115: void DateTime::setDate(long year, long month, long day)
1116: - Sets the date
1117:
1118: void DateTime::setISODate(long year, long week[, long day])
1119: - Sets the ISO date
1120:
1121: void DateTime::setTime(long hour, long minute[, long second])
1122: - Sets the time
1123:
1124: void DateTime::setTimezone(DateTimeZone object)
1125: - Sets the timezone for the DateTime object
1126: */
1127:
1128: /* Some quick and dirty test harnesses
1129: $dtt1 = new QDateTime();
1130: $dtt2 = new QDateTime();
1131: printTable($dtt1, $dtt2);
1132: $dtt2->setDate(2000, 1, 1);
1133: $dtt1->setTime(0,0,3);
1134: $dtt2->setTime(0,0,2);
1135: // $dtt2->Month++;
1136: printTable($dtt1, $dtt2);
1137:
1138: function printTable($dtt1, $dtt2) {
1139: print('<table border="1" cellpadding="2"><tr><td>');
1140: printDate($dtt1);
1141: print('</td><td>');
1142: printDate($dtt2);
1143: print ('</td></tr>');
1144:
1145: print ('<tr><td colspan="2" align="center">IsEqualTo: <b>' . (($dtt1->IsEqualTo($dtt2)) ? 'Yes' : 'No') . '</b></td></tr>');
1146: print ('<tr><td colspan="2" align="center">IsEarlierThan: <b>' . (($dtt1->IsEarlierThan($dtt2)) ? 'Yes' : 'No') . '</b></td></tr>');
1147: print ('<tr><td colspan="2" align="center">IsLaterThan: <b>' . (($dtt1->IsLaterThan($dtt2)) ? 'Yes' : 'No') . '</b></td></tr>');
1148: print ('<tr><td colspan="2" align="center">IsEarlierOrEqualTo: <b>' . (($dtt1->IsEarlierOrEqualTo($dtt2)) ? 'Yes' : 'No') . '</b></td></tr>');
1149: print ('<tr><td colspan="2" align="center">IsLaterOrEqualTo: <b>' . (($dtt1->IsLaterOrEqualTo($dtt2)) ? 'Yes' : 'No') . '</b></td></tr>');
1150: print('</table>');
1151: }
1152:
1153: function printDate($dtt) {
1154: print ('Time Null: ' . (($dtt->IsTimeNull()) ? 'Yes' : 'No'));
1155: print ('<br/>');
1156: print ('Date Null: ' . (($dtt->IsDateNull()) ? 'Yes' : 'No'));
1157: print ('<br/>');
1158: print ('Date: ' . $dtt->qFormat(QDateTime::FormatDisplayDateTimeFull));
1159: print ('<br/>');
1160: print ('Month: ' . $dtt->Month . '<br/>');
1161: print ('Day: ' . $dtt->Day . '<br/>');
1162: print ('Year: ' . $dtt->Year . '<br/>');
1163: print ('Hour: ' . $dtt->Hour . '<br/>');
1164: print ('Minute: ' . $dtt->Minute . '<br/>');
1165: print ('Second: ' . $dtt->Second . '<br/>');
1166: }*/