| 1 |
<?php |
| 2 |
/** |
| 3 |
* iCalcreator, the PHP class package managing iCal (rfc2445/rfc5445) calendar information. |
| 4 |
* |
| 5 |
* copyright (c) 2007-2021 Kjell-Inge Gustafsson, kigkonsult, All rights reserved |
| 6 |
* Link https://kigkonsult.se |
| 7 |
* Package iCalcreator |
| 8 |
* Version 2.30 |
| 9 |
* License Subject matter of licence is the software iCalcreator. |
| 10 |
* The above copyright, link, package and version notices, |
| 11 |
* this licence notice and the invariant [rfc5545] PRODID result use |
| 12 |
* as implemented and invoked in iCalcreator shall be included in |
| 13 |
* all copies or substantial portions of the iCalcreator. |
| 14 |
* |
| 15 |
* iCalcreator is free software: you can redistribute it and/or modify |
| 16 |
* it under the terms of the GNU Lesser General Public License as published |
| 17 |
* by the Free Software Foundation, either version 3 of the License, |
| 18 |
* or (at your option) any later version. |
| 19 |
* |
| 20 |
* iCalcreator is distributed in the hope that it will be useful, |
| 21 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 22 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 23 |
* GNU Lesser General Public License for more details. |
| 24 |
* |
| 25 |
* You should have received a copy of the GNU Lesser General Public License |
| 26 |
* along with iCalcreator. If not, see <https://www.gnu.org/licenses/>. |
| 27 |
* |
| 28 |
* This file is a part of iCalcreator. |
| 29 |
*/ |
| 30 |
|
| 31 |
namespace Kigkonsult\Icalcreator\Util; |
| 32 |
|
| 33 |
use DateInterval; |
| 34 |
use DateTime; |
| 35 |
use Exception; |
| 36 |
use InvalidArgumentException; |
| 37 |
|
| 38 |
use function floor; |
| 39 |
use function is_array; |
| 40 |
use function strlen; |
| 41 |
use function substr; |
| 42 |
use function trim; |
| 43 |
|
| 44 |
/** |
| 45 |
* iCalcreator DateInterval utility/support class |
| 46 |
* |
| 47 |
* @see https://en.wikipedia.org/wiki/Iso8601 |
| 48 |
* @author Kjell-Inge Gustafsson, kigkonsult <ical@kigkonsult.se> |
| 49 |
* @since 2.29.20 - 2020-01-31 |
| 50 |
*/ |
| 51 |
class DateIntervalFactory |
| 52 |
{ |
| 53 |
/** |
| 54 |
* Class constant |
| 55 |
*/ |
| 56 |
const INTERVAL_ISO8601 = 'P%yY%mM%dDT%hH%iM%sS'; |
| 57 |
|
| 58 |
/** |
| 59 |
* @var string duration keys etc |
| 60 |
* @access private |
| 61 |
* @static |
| 62 |
*/ |
| 63 |
private static $Y = 'Y'; |
| 64 |
private static $T = 'T'; |
| 65 |
private static $W = 'W'; |
| 66 |
private static $D = 'D'; |
| 67 |
private static $H = 'H'; |
| 68 |
private static $M = 'M'; |
| 69 |
private static $S = 'S'; |
| 70 |
private static $PT0H0M0S = 'PT0H0M0S'; |
| 71 |
private static $s = 's'; |
| 72 |
private static $i = 'i'; |
| 73 |
private static $h = 'h'; |
| 74 |
private static $d = 'd'; |
| 75 |
private static $m = 'm'; |
| 76 |
private static $y = 'y'; |
| 77 |
private static $invert = 'invert'; |
| 78 |
|
| 79 |
/** |
| 80 |
* @var string |
| 81 |
* @static |
| 82 |
*/ |
| 83 |
public static $P = 'P'; |
| 84 |
|
| 85 |
/** |
| 86 |
* Return new DateTimeZone object instance |
| 87 |
* |
| 88 |
* @param string $dateIntervalString |
| 89 |
* @return DateInterval |
| 90 |
* @throws InvalidArgumentException |
| 91 |
* @static |
| 92 |
* @since 2.27.8 - 2019-01-12 |
| 93 |
*/ |
| 94 |
public static function factory( $dateIntervalString ) |
| 95 |
{ |
| 96 |
return self::assertDateIntervalString( $dateIntervalString ); |
| 97 |
} |
| 98 |
|
| 99 |
/** |
| 100 |
* Assert DateIntervalString |
| 101 |
* |
| 102 |
* @param string $dateIntervalString |
| 103 |
* @return DateInterval |
| 104 |
* @throws InvalidArgumentException |
| 105 |
* @static |
| 106 |
* @since 2.27.8 - 2019-01-12 |
| 107 |
*/ |
| 108 |
public static function assertDateIntervalString( $dateIntervalString ) |
| 109 |
{ |
| 110 |
static $ERR = 'Invalid DateInterval \'%s\''; |
| 111 |
try { |
| 112 |
$dateInterval = new DateInterval( $dateIntervalString ); |
| 113 |
} |
| 114 |
catch( Exception $e ) { |
| 115 |
throw new InvalidArgumentException( |
| 116 |
sprintf( $ERR, $dateIntervalString ), |
| 117 |
null, |
| 118 |
$e |
| 119 |
); |
| 120 |
} |
| 121 |
return $dateInterval; |
| 122 |
} |
| 123 |
|
| 124 |
/** |
| 125 |
* Return bool true is string is a duration |
| 126 |
* |
| 127 |
* @param mixed $value |
| 128 |
* @return bool |
| 129 |
* @static |
| 130 |
* @since 2.29.22 - 2020-08-22 |
| 131 |
*/ |
| 132 |
public static function isStringAndDuration( $value ) |
| 133 |
{ |
| 134 |
static $PREFIXARR = [ 'P', '+', '-' ]; |
| 135 |
if( ! is_string( $value )) { |
| 136 |
return false; |
| 137 |
} |
| 138 |
$value = trim( $value ); |
| 139 |
$value = StringFactory::trimTrailNL( $value ); |
| 140 |
return (( 3 <= strlen( $value )) && |
| 141 |
( in_array( substr( $value, 0, 1 ), $PREFIXARR ))); |
| 142 |
} |
| 143 |
|
| 144 |
/** |
| 145 |
* Return bool true if dateInterval array 'invert' is set // fix pre 7.0.5 bug |
| 146 |
* |
| 147 |
* @param mixed $dateInterval |
| 148 |
* @return bool |
| 149 |
* @static |
| 150 |
* @since 2.29.2 - 2019-06-27 |
| 151 |
*/ |
| 152 |
public static function isDateIntervalArrayInvertSet( $dateInterval ) |
| 153 |
{ |
| 154 |
return( is_array( $dateInterval ) && isset( $dateInterval[self::$invert] )); |
| 155 |
} |
| 156 |
|
| 157 |
/** |
| 158 |
* Return value with removed opt. prefix +/- |
| 159 |
* |
| 160 |
* @param string $value |
| 161 |
* @return string |
| 162 |
* @static |
| 163 |
* @since 2.16.7 - 2018-11-26 |
| 164 |
* @todo remove -> $isMinus = ( 0 > $value ); $tz = abs((int) $value ); |
| 165 |
*/ |
| 166 |
public static function removePlusMinusPrefix( $value ) |
| 167 |
{ |
| 168 |
if( self::hasPlusMinusPrefix( $value )) { |
| 169 |
$value = substr( $value, 1 ); |
| 170 |
} |
| 171 |
return $value; |
| 172 |
} |
| 173 |
|
| 174 |
/** |
| 175 |
* Return bool true if string has a leading +/- |
| 176 |
* |
| 177 |
* @param string $value |
| 178 |
* @return bool |
| 179 |
* @static |
| 180 |
* @since 2.16.14 - 2019-02-18 |
| 181 |
*/ |
| 182 |
public static function hasPlusMinusPrefix( $value ) |
| 183 |
{ |
| 184 |
static $PLUSMINUSARR = [ '+', '-' ]; |
| 185 |
return ( in_array( substr( $value, 0, 1 ), $PLUSMINUSARR )); |
| 186 |
} |
| 187 |
|
| 188 |
/** |
| 189 |
* Return DateInterval as string |
| 190 |
* |
| 191 |
* @param DateInterval $dateInterval |
| 192 |
* @param bool $showOptSign |
| 193 |
* @return string |
| 194 |
* @static |
| 195 |
* @since 2.16.14 - 2019-02-15 |
| 196 |
*/ |
| 197 |
public static function dateInterval2String( |
| 198 |
DateInterval $dateInterval, |
| 199 |
$showOptSign=false |
| 200 |
) { |
| 201 |
$dateIntervalArr = (array) $dateInterval; |
| 202 |
$result = self::$P; |
| 203 |
if( empty( $dateIntervalArr[self::$y] ) && |
| 204 |
empty( $dateIntervalArr[self::$m] ) && |
| 205 |
empty( $dateIntervalArr[self::$h] ) && |
| 206 |
empty( $dateIntervalArr[self::$i] ) && |
| 207 |
empty( $dateIntervalArr[self::$s] ) && |
| 208 |
! empty( $dateIntervalArr[self::$d] ) && |
| 209 |
( 0 == ( $dateIntervalArr[self::$d] % 7 ))) { |
| 210 |
$result .= (int) floor( $dateIntervalArr[self::$d] / 7 ) . |
| 211 |
self::$W; |
| 212 |
return ( $showOptSign && ( 0 < $dateIntervalArr[self::$invert] )) |
| 213 |
? Util::$MINUS . $result : $result; |
| 214 |
} |
| 215 |
if( 0 < $dateIntervalArr[self::$y] ) { |
| 216 |
$result .= $dateIntervalArr[self::$y] . self::$Y; |
| 217 |
} |
| 218 |
if( 0 < $dateIntervalArr[self::$m] ) { |
| 219 |
$result .= $dateIntervalArr[self::$m] . self::$M; |
| 220 |
} |
| 221 |
if( 0 < $dateIntervalArr[self::$d] ) { |
| 222 |
$result .= $dateIntervalArr[self::$d] . self::$D; |
| 223 |
} |
| 224 |
$hourIsSet = ! empty( $dateIntervalArr[self::$h] ); |
| 225 |
$minIsSet = ! empty( $dateIntervalArr[self::$i] ); |
| 226 |
$secIsSet = ! empty( $dateIntervalArr[self::$s] ); |
| 227 |
if( ! $hourIsSet && ! $minIsSet && ! $secIsSet ) { |
| 228 |
if( self::$P == $result ) { |
| 229 |
$result = self::$PT0H0M0S; |
| 230 |
} |
| 231 |
return ( $showOptSign && ( 0 < $dateIntervalArr[self::$invert] )) |
| 232 |
? Util::$MINUS . $result : $result; |
| 233 |
} |
| 234 |
$result .= self::$T; |
| 235 |
if( $hourIsSet ) { |
| 236 |
$result .= $dateIntervalArr[self::$h] . self::$H; |
| 237 |
} |
| 238 |
if( $minIsSet ) { |
| 239 |
$result .= $dateIntervalArr[self::$i] . self::$M; |
| 240 |
} |
| 241 |
if( $secIsSet ) { |
| 242 |
$result .= $dateIntervalArr[self::$s] . self::$S; |
| 243 |
} |
| 244 |
return ( $showOptSign && ( 0 < $dateIntervalArr[self::$invert] )) |
| 245 |
? Util::$MINUS . $result : $result; |
| 246 |
} |
| 247 |
|
| 248 |
/** |
| 249 |
* Return conformed DateInterval |
| 250 |
* |
| 251 |
* @param DateInterval $dateInterval |
| 252 |
* @return DateInterval |
| 253 |
* @static |
| 254 |
* @throws Exception on DateInterval create error |
| 255 |
* @since 2.27.14 - 2019-03-09 |
| 256 |
*/ |
| 257 |
public static function conformDateInterval( DateInterval $dateInterval ) |
| 258 |
{ |
| 259 |
$dateIntervalArr = (array) $dateInterval; |
| 260 |
if( 60 <= $dateIntervalArr[self::$s] ) { |
| 261 |
$dateIntervalArr[self::$i] += |
| 262 |
(int) floor( $dateIntervalArr[self::$s] / 60 ); |
| 263 |
$dateIntervalArr[self::$s] = |
| 264 |
$dateIntervalArr[self::$s] % 60; |
| 265 |
} |
| 266 |
if( 60 <= $dateIntervalArr[self::$i] ) { |
| 267 |
$dateIntervalArr[self::$h] += |
| 268 |
(int) floor( $dateIntervalArr[self::$i] / 60 ); |
| 269 |
$dateIntervalArr[self::$i] = |
| 270 |
$dateIntervalArr[self::$i] % 60; |
| 271 |
} |
| 272 |
if( 24 <= $dateIntervalArr[self::$h] ) { |
| 273 |
$dateIntervalArr[self::$d] += |
| 274 |
(int) floor( $dateIntervalArr[self::$h] / 24 ); |
| 275 |
$dateIntervalArr[self::$h] = |
| 276 |
$dateIntervalArr[self::$h] % 24; |
| 277 |
} |
| 278 |
return self::DateIntervalArr2DateInterval( $dateIntervalArr ); |
| 279 |
} |
| 280 |
|
| 281 |
/** |
| 282 |
* Modify DateTime from DateInterval |
| 283 |
* |
| 284 |
* @param DateTime $dateTime |
| 285 |
* @param DateInterval $dateInterval |
| 286 |
* @static |
| 287 |
* @since 2.29.2 - 2019-06-20 |
| 288 |
* @tofo error mgnt |
| 289 |
*/ |
| 290 |
public static function modifyDateTimeFromDateInterval( |
| 291 |
DateTime $dateTime, |
| 292 |
DateInterval $dateInterval ) |
| 293 |
{ |
| 294 |
static $YEAR = 'year'; |
| 295 |
static $MONTH = 'month'; |
| 296 |
static $DAY = 'day'; |
| 297 |
static $HOUR = 'hour'; |
| 298 |
static $MIN = 'minute'; |
| 299 |
static $SEC = 'second'; |
| 300 |
static $KEYS = null; |
| 301 |
if( empty( $KEYS )) { |
| 302 |
$KEYS = [ |
| 303 |
self::$y => $YEAR, |
| 304 |
self::$m => $MONTH, |
| 305 |
self::$d => $DAY, |
| 306 |
self::$h => $HOUR, |
| 307 |
self::$i => $MIN, |
| 308 |
self::$s => $SEC |
| 309 |
]; |
| 310 |
} |
| 311 |
$dateIntervalArr = (array) $dateInterval; |
| 312 |
$operator = ( 0 < $dateIntervalArr[self::$invert] ) |
| 313 |
? Util::$MINUS |
| 314 |
: Util::$PLUS; |
| 315 |
foreach( $KEYS as $diKey => $dtKey ) { |
| 316 |
if( 0 < $dateIntervalArr[$diKey] ) { |
| 317 |
$dateTime->modify( |
| 318 |
self::getModifyString ( $operator, $dateIntervalArr[$diKey], $dtKey ) |
| 319 |
); |
| 320 |
} |
| 321 |
} |
| 322 |
} |
| 323 |
private static function getModifyString ( $operator, $number, $unit ) |
| 324 |
{ |
| 325 |
static $MONTH = 'month'; |
| 326 |
$suffix = ( $MONTH != $unit ) ? self::getOptPluralSuffix( $number ) : null; |
| 327 |
return $operator . $number . Util::$SP1 . $unit . $suffix; |
| 328 |
} |
| 329 |
private static function getOptPluralSuffix ( $number ) |
| 330 |
{ |
| 331 |
static $PLS = 's'; |
| 332 |
return ( 1 < $number ) ? $PLS : Util::$SP0; |
| 333 |
} |
| 334 |
|
| 335 |
/** |
| 336 |
* Get DateInterval from (DateInterval) array |
| 337 |
* |
| 338 |
* @param array $dateIntervalArr |
| 339 |
* @return DateInterval |
| 340 |
* @static |
| 341 |
* @throws Exception on DateInterval create error |
| 342 |
* @since 2.27.2 - 2018-12-21 |
| 343 |
*/ |
| 344 |
public static function DateIntervalArr2DateInterval( $dateIntervalArr ) |
| 345 |
{ |
| 346 |
static $P0D = 'P0D'; |
| 347 |
if( ! is_array( $dateIntervalArr )) { |
| 348 |
$dateIntervalArr = []; |
| 349 |
} |
| 350 |
try { |
| 351 |
$dateInterval = new DateInterval( $P0D ); |
| 352 |
} |
| 353 |
catch( Exception $e ) { |
| 354 |
throw $e; |
| 355 |
} |
| 356 |
foreach( $dateIntervalArr as $key => $value ) { |
| 357 |
$dateInterval->{$key} = $value; |
| 358 |
} |
| 359 |
return $dateInterval; |
| 360 |
} |
| 361 |
} |
| 362 |
|
| 363 |
|