DateTimeHelper.php 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. <?php
  2. namespace fphammerle\helpers;
  3. class DateTimeHelper
  4. {
  5. const _timezone_iso_pattern = '(?P<tz>Z|[\+-]\d{2}.\d{2})';
  6. /**
  7. * @param integer|null $timestamp unix timestamp
  8. * @return \DateTime|null
  9. */
  10. public static function timestampToDateTime($timestamp)
  11. {
  12. if($timestamp === null) {
  13. return null;
  14. } elseif(is_int($timestamp)) {
  15. $dt = new \DateTime();
  16. $dt->setTimestamp($timestamp);
  17. return $dt;
  18. } else {
  19. throw new \InvalidArgumentException('expected integer or null');
  20. }
  21. }
  22. /**
  23. * @param string|null $text
  24. * @return \DatePeriod|null
  25. */
  26. public static function parse($text)
  27. {
  28. if($text === null) {
  29. return null;
  30. } else {
  31. if(preg_match(
  32. '/^(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})'
  33. .'([ T](?P<h>\d{2}):(?P<i>\d{2})(:(?P<s>\d{2}))?)?'
  34. . '(' . self::_timezone_iso_pattern . ')?$/',
  35. $text,
  36. $attr
  37. )) {
  38. $start = new \DateTime($text);
  39. if(!empty($attr['s'])) {
  40. $interval = new \DateInterval('PT1S');
  41. } elseif(!empty($attr['i'])) {
  42. $interval = new \DateInterval('PT1M');
  43. } else {
  44. $interval = new \DateInterval('P1D');
  45. }
  46. return new \DatePeriod($start, $interval, 0);
  47. } elseif(preg_match('/^\d{4}-(?P<m>\d{2})(( (?=-)| ?(?!-))' . self::_timezone_iso_pattern . ')?$/', $text, $attr)) {
  48. return new \DatePeriod(
  49. new \DateTime($text),
  50. new \DateInterval('P1M'),
  51. 0
  52. );
  53. } elseif(preg_match('/^(?P<y>\d{4})( ?' . self::_timezone_iso_pattern . ')?$/', $text, $attr)) {
  54. return new \DatePeriod(
  55. new \DateTime(sprintf(
  56. '%s-01-01 %s',
  57. $attr['y'],
  58. isset($attr['tz']) ? $attr['tz'] : ''
  59. )),
  60. new \DateInterval('P1Y'),
  61. 0
  62. );
  63. } else {
  64. throw new \InvalidArgumentException(
  65. sprintf("could not parse string '%s'", $text)
  66. );
  67. }
  68. }
  69. }
  70. /**
  71. * @param string|null $text
  72. * @return \DateTime|null
  73. */
  74. public static function parseGetStart($text)
  75. {
  76. $period = self::parse($text);
  77. if($period) {
  78. return $period->start;
  79. } else {
  80. return null;
  81. }
  82. }
  83. public static function deinvertInterval(\DateInterval $source = null)
  84. {
  85. // \DateInterval does not implement clone.
  86. // @see https://bugs.php.net/bug.php?id=50559
  87. $result = unserialize(serialize($source));
  88. if($result->invert) {
  89. $result->y *= -1;
  90. $result->m *= -1;
  91. $result->d *= -1;
  92. $result->h *= -1;
  93. $result->i *= -1;
  94. $result->s *= -1;
  95. $result->invert = 0;
  96. }
  97. return $result;
  98. }
  99. /**
  100. * @param \DateInterval|\DatePeriod|null $i
  101. * @return string|null
  102. */
  103. public static function iso($i)
  104. {
  105. if(is_null($i)) {
  106. return null;
  107. } elseif(sizeof(get_object_vars($i)) == 0) {
  108. throw new \InvalidArgumentException(
  109. sprintf("given instance is invalid\n%s", print_r($i, true))
  110. );
  111. } elseif($i instanceof \DateTime) {
  112. return $i->format(\DateTime::ATOM);
  113. } elseif($i instanceof \DateInterval) {
  114. $i = self::deinvertInterval($i);
  115. if($i->y < 0 || $i->m < 0 || $i->d < 0 || $i->h < 0 || $i->i < 0 || $i->s < 0) {
  116. throw new \InvalidArgumentException(
  117. sprintf("negative intervals are not supported\n%s", print_r($i, true))
  118. );
  119. } else {
  120. return $i->format('P%yY%mM%dDT%hH%iM%sS');
  121. }
  122. } elseif($i instanceof \DatePeriod) {
  123. // Cave:
  124. // (new \DatePeriod(
  125. // new \DateTime('2016-08-05T14:50:14Z'),
  126. // new \DateInterval('P1D'),
  127. // -1
  128. // )->recurrences == 1
  129. if($i->recurrences <= 0) {
  130. throw new \Exception(
  131. 'conversion of periods with number of occurances'
  132. . ' being negative is not supported'
  133. );
  134. }
  135. $repetitions = -1;
  136. foreach($i as $dt) {
  137. $repetitions++;
  138. // printf("%d. %s\n", $repetitions, $dt->format(\DateTime::ATOM));
  139. }
  140. // \DatePeriod::getStartDate() is available from php 5.6.5.
  141. $start_iso = self::iso($i->start);
  142. // \DatePeriod::getDateInterval() is available from php 5.6.5.
  143. // \DatePeriod::$interval returned an invalid \DatePeriod instance
  144. // in php 7.0.8
  145. $interval_iso = self::iso(get_object_vars($i)['interval']);
  146. switch($repetitions) {
  147. case -1:
  148. // no valid date within period
  149. // e.g. new \DatePeriod(
  150. // new \DateTime('2016-08-05T14:50:14+08:00'),
  151. // new \DateInterval('PT1S'),
  152. // new \DateTime('2016-08-05T14:50:14+08:00')
  153. // )
  154. throw new \InvalidArgumentException(
  155. 'given period does not contain any valid date'
  156. );
  157. case 0:
  158. return sprintf('%s/%s', $start_iso, $interval_iso);
  159. default:
  160. return sprintf('R%d/%s/%s', $repetitions, $start_iso, $interval_iso);
  161. }
  162. } else {
  163. throw new \InvalidArgumentException(sprintf(
  164. "expected \\DateTime, \\DateInterval or \\DatePeriod\n%s",
  165. print_r($i, true)
  166. ));
  167. }
  168. }
  169. }