| [ Index ] |
PHP Cross Reference of Drupal 6 (gatewave) |
[Summary view] [Print] [Text view]
1 <?php 2 // $Id: date_api_sql.inc,v 1.9.2.3.2.39 2010/08/12 18:37:07 karens Exp $ 3 4 /** 5 * A helper function to do cross-database concatation of date parts 6 * 7 * @param $array - an array of values to be concatonated in sql 8 * @return - correct sql string for database type 9 */ 10 function date_sql_concat($array) { 11 global $db_type; 12 switch ($db_type) { 13 case ('mysql'): 14 case ('mysqli'): 15 return "CONCAT(". implode(",", $array) .")"; 16 case ('pgsql'): 17 return implode(" || ", $array); 18 } 19 } 20 21 /** 22 * Helper function to do cross-database NULL replacements 23 * 24 * @param an array of values to test for NULL values 25 * @return SQL statement to return the first non-NULL value in the list. 26 */ 27 function date_sql_coalesce($array) { 28 global $db_type; 29 switch ($db_type) { 30 case ('mysql'): 31 case ('mysqli'): 32 case ('pgsql'): 33 return "COALESCE(". implode(',', $array) .")"; 34 } 35 } 36 37 /** 38 * A helper function to do cross-database padding of date parts 39 * 40 * @param $str - a string to apply padding to 41 * @param $size - the size the final string should be 42 * @param $pad - the value to pad the string with 43 * @param $side - the side of the string to pad 44 */ 45 function date_sql_pad($str, $size = 2, $pad = '0', $side = 'l') { 46 switch ($side) { 47 case ('r'): 48 return "RPAD($str, $size, '$pad')"; 49 default: 50 return "LPAD($str, $size, '$pad')"; 51 } 52 } 53 54 /** 55 * A class to manipulate date SQL. 56 */ 57 class date_sql_handler { 58 var $db_type = 'mysql'; 59 var $date_type = DATE_DATETIME; 60 var $db_timezone = 'UTC'; // A string timezone name. 61 var $local_timezone = NULL; // A string timezone name. 62 var $db_timezone_field = NULL; // Use if the db timezone is stored in a field. 63 var $local_timezone_field = NULL; // Use if the local timezone is stored in a field. 64 var $offset_field = NULL; // Use if the offset is stored in a field. 65 66 function construct($date_type = DATE_DATETIME, $local_timezone = NULL) { 67 $this->db_type = $GLOBALS['db_type']; 68 $this->date_type = $date_type; 69 $this->db_timezone = 'UTC'; 70 $this->local_timezone = isset($local_timezone) ? $local_timezone : date_default_timezone_name(); 71 if (isset($this->definition['content_field'])) { 72 $this->date_handler->date_type = $this->definition['content_field']['type']; 73 } 74 date_api_set_db_timezone(); 75 } 76 77 /** 78 * See if the db has timezone name support. 79 */ 80 function db_tz_support($reset = FALSE) { 81 $has_support = variable_get('date_db_tz_support', -1); 82 if ($has_support == -1 || $reset) { 83 date_api_set_db_timezone(); 84 $has_support = FALSE; 85 switch ($this->db_type) { 86 case 'mysql': 87 case 'mysqli': 88 if (version_compare(db_version(), '4.1.3', '>=')) { 89 $test = db_result(db_query("SELECT CONVERT_TZ('2008-02-15 12:00:00', 'UTC', 'US/Central')")); 90 if ($test == '2008-02-15 06:00:00') { 91 $has_support = TRUE; 92 } 93 } 94 break; 95 case 'pgsql': 96 $test = db_result(db_query("SELECT '2008-02-15 12:00:00 UTC' AT TIME ZONE 'US/Central'")); 97 if ($test == '2008-02-15 06:00:00') { 98 $has_support = TRUE; 99 } 100 break; 101 } 102 variable_set('date_db_tz_support', $has_support); 103 } 104 return $has_support; 105 } 106 107 /** 108 * Set the database timzone offset. 109 * 110 * Setting the db timezone to UTC is done to ensure consistency in date 111 * handling whether or not the database can do proper timezone conversion. 112 * 113 * Views filters that not exposed are cached and won't set the timezone 114 * so views date filters should add 'cacheable' => 'no' to their 115 * definitions to ensure that the database timezone gets set properly 116 * when the query is executed. 117 * 118 * @param $offset 119 * An offset value to set the database timezone to. This will only 120 * set a fixed offset, not a timezone, so any value other than 121 * '+00:00' should be used with caution. 122 */ 123 function set_db_timezone($offset = '+00:00') { 124 static $already_set = FALSE; 125 $type = $GLOBALS['db_type']; 126 if (!$already_set) { 127 if (($type == 'mysqli' || $type == 'mysql') && version_compare(db_version(), '4.1.3', '>=')) { 128 db_query("SET @@session.time_zone = '$offset'"); 129 } 130 elseif ($type == 'pgsql') { 131 db_query("SET TIME ZONE INTERVAL '$offset' HOUR TO MINUTE"); 132 } 133 $already_set = true; 134 } 135 } 136 137 /** 138 * Return timezone offset for the date being processed. 139 */ 140 function get_offset() { 141 if (!empty($this->db_timezone) && !empty($this->local_timezone)) { 142 if ($this->db_timezone != $this->local_timezone) { 143 $date = date_now($this->db_timezone); 144 date_timezone_set($date, timezone_open($this->local_timezone)); 145 return date_offset_get($date); 146 } 147 } 148 return 0; 149 } 150 151 /** 152 * Helper function to create cross-database SQL dates. 153 * 154 * @param $field 155 * The real table and field name, like 'tablename.fieldname'. 156 * @param $offset 157 * The name of a field that holds the timezone offset or an 158 * offset value. If NULL, the normal Drupal timezone handling 159 * will be used, if $offset = 0 no adjustment will be made. 160 * @return 161 * An appropriate SQL string for the db type and field type. 162 */ 163 function sql_field($field, $offset = NULL) { 164 if (strtoupper($field) == 'NOW') { 165 // NOW() will be in UTC since that is what we set the db timezone to. 166 $this->local_timezone = 'UTC'; 167 return $this->sql_offset('NOW()', $offset); 168 } 169 switch ($this->db_type) { 170 case 'mysql': 171 case 'mysqli': 172 switch ($this->date_type) { 173 case DATE_UNIX: 174 $field = "FROM_UNIXTIME($field)"; 175 break; 176 case DATE_ISO: 177 if (version_compare(db_version(), '4.1.1', '>=')) { 178 $field = "STR_TO_DATE($field, '%Y-%m-%%dT%T')"; 179 } 180 else { 181 $field = "REPLACE($field, 'T', ' ')"; 182 } 183 break; 184 case DATE_DATETIME: 185 break; 186 } 187 break; 188 case 'pgsql': 189 switch ($this->date_type) { 190 case DATE_UNIX: 191 $field = "$field::ABSTIME"; 192 break; 193 case DATE_ISO: 194 $field = "TO_DATE($field, 'FMYYYY-FMMM-FMDDTFMHH24:FMMI:FMSS')"; 195 break; 196 case DATE_DATETIME: 197 break; 198 } 199 break; 200 } 201 // Adjust the resulting value to the right timezone/offset. 202 return $this->sql_tz($field, $offset); 203 } 204 205 /** 206 * Adjust a field value by an offset in seconds. 207 */ 208 function sql_offset($field, $offset = NULL) { 209 if (!empty($offset)) { 210 switch ($this->db_type) { 211 case 'mysql': 212 case 'mysqli': 213 if (version_compare(db_version(), '4.1.1', '>=')) { 214 return "ADDTIME($field, SEC_TO_TIME($offset))"; 215 } 216 else { 217 return "DATE_ADD($field, INTERVAL $offset SECOND)"; 218 } 219 case 'pgsql': 220 return "($field + INTERVAL '$offset SECONDS')";; 221 } 222 } 223 return $field; 224 } 225 226 /** 227 * Adjust a field value by time interval. 228 * 229 * @param $field 230 * The field to be adjusted. 231 * @param $direction 232 * Either ADD or SUB. 233 * @param $count 234 * The number of values to adjust. 235 * @param $granularity 236 * The granularity of the adjustment, should be singular, 237 * like SECOND, MINUTE, DAY, HOUR. 238 */ 239 function sql_date_math($field, $direction, $count, $granularity) { 240 $granularity = strtoupper($granularity); 241 switch ($this->db_type) { 242 case 'mysql': 243 case 'mysqli': 244 switch ($direction) { 245 case 'ADD': 246 return "DATE_ADD($field, INTERVAL $count $granularity)"; 247 case 'SUB': 248 return "DATE_SUB($field, INTERVAL $count $granularity)"; 249 } 250 251 case 'pgsql': 252 $granularity .= 'S'; 253 switch ($direction) { 254 case 'ADD': 255 return "($field + INTERVAL '$count $granularity')"; 256 case 'SUB': 257 return "($field - INTERVAL '$count $granularity')"; 258 } 259 } 260 return $field; 261 } 262 263 /** 264 * Select a date value from the database, adjusting the value 265 * for the timezone. 266 * 267 * Check whether database timezone conversion is supported in 268 * this system and use it if possible, otherwise use an 269 * offset. 270 * 271 * @param $offset 272 * Set a fixed offset or offset field to use for the date. 273 * If set, no timezone conversion will be done and the 274 * offset will be used. 275 */ 276 function sql_tz($field, $offset = NULL) { 277 // If the timezones are values they need to be quoted, but 278 // if they are field names they do not. 279 $db_zone = $this->db_timezone_field ? $this->db_timezone_field : "'{$this->db_timezone}'"; 280 $localzone = $this->local_timezone_field ? $this->local_timezone_field : "'{$this->local_timezone}'"; 281 282 // If a fixed offset is required, use it. 283 if ($offset !== NULL) { 284 return $this->sql_offset($field, $offset); 285 } 286 // If the db and local timezones are the same, make no adjustment. 287 elseif ($db_zone == $localzone) { 288 return $this->sql_offset($field, 0); 289 } 290 // If the db has no timezone support, adjust by the offset, 291 // could be either a field name or a value. 292 elseif (!$this->db_tz_support()) { 293 if (!empty($this->offset_field)) { 294 return $this->sql_offset($field, $this->offset_field); 295 } 296 else { 297 return $this->sql_offset($field, $this->get_offset()); 298 } 299 } 300 // Otherwise make a database timezone adjustment to the field. 301 else { 302 switch ($this->db_type) { 303 case 'mysql': 304 case 'mysqli': 305 return "CONVERT_TZ($field, $db_zone, $localzone)"; 306 case 'pgsql': 307 // WITH TIME ZONE assumes the date is using the system 308 // timezone, which should have been set to UTC. 309 return "$field::timestamp with time zone AT TIME ZONE $localzone"; 310 } 311 } 312 } 313 314 /** 315 * Helper function to create cross-database SQL date formatting. 316 * 317 * @param $format 318 * A format string for the result, like 'Y-m-d H:i:s'. 319 * @param $field 320 * The real table and field name, like 'tablename.fieldname'. 321 * @return 322 * An appropriate SQL string for the db type and field type. 323 */ 324 function sql_format($format, $field) { 325 switch ($this->db_type) { 326 case 'mysql': 327 case 'mysqli': 328 $replace = array( 329 'Y' => '%Y', 'y' => '%y', 330 'm' => '%m', 'n' => '%c', 331 'd' => '%%d', 'j' => '%e', 332 'H' => '%H', 333 'i' => '%i', 334 's' => '%%s', 335 '\WW' => 'W%U', 336 ); 337 $format = strtr($format, $replace); 338 return "DATE_FORMAT($field, '$format')"; 339 case 'pgsql': 340 $replace = array( 341 'Y' => 'YYYY', 'y' => 'Y', 342 'm' => 'MM', 'n' => 'M', 343 'd' => 'DD', 'j' => 'D', 344 'H' => 'HH24', 345 'i' => 'MI', 346 's' => 'SS', 347 '\T' => '"T"', 348 //'\W' => // TODO, what should this be? 349 ); 350 $format = strtr($format, $replace); 351 return "TO_CHAR($field, '$format')"; 352 } 353 } 354 355 /** 356 * Helper function to create cross-database SQL date extraction. 357 * 358 * @param $extract_type 359 * The type of value to extract from the date, like 'MONTH'. 360 * @param $field 361 * The real table and field name, like 'tablename.fieldname'. 362 * @return 363 * An appropriate SQL string for the db type and field type. 364 */ 365 function sql_extract($extract_type, $field) { 366 // Note there is no space after FROM to avoid db_rewrite problems 367 // see http://drupal.org/node/79904. 368 switch (strtoupper($extract_type)) { 369 case ('DATE'): 370 return $field; 371 case ('YEAR'): 372 return "EXTRACT(YEAR FROM($field))"; 373 case ('MONTH'): 374 return "EXTRACT(MONTH FROM($field))"; 375 case ('DAY'): 376 return "EXTRACT(DAY FROM($field))"; 377 case ('HOUR'): 378 return "EXTRACT(HOUR FROM($field))"; 379 case ('MINUTE'): 380 return "EXTRACT(MINUTE FROM($field))"; 381 case ('SECOND'): 382 return "EXTRACT(SECOND FROM($field))"; 383 case ('WEEK'): // ISO week number for date 384 switch ($this->db_type) { 385 case ('mysql'): 386 case ('mysqli'): 387 // WEEK using arg 3 in mysql should return the same value as postgres EXTRACT 388 return "WEEK($field, 3)"; 389 case ('pgsql'): 390 return "EXTRACT(WEEK FROM($field))"; 391 } 392 case ('DOW'): 393 switch ($this->db_type) { 394 case ('mysql'): 395 case ('mysqli'): 396 // mysql returns 1 for Sunday through 7 for Saturday 397 // php date functions and postgres use 0 for Sunday and 6 for Saturday 398 return "INTEGER(DAYOFWEEK($field) - 1)"; 399 case ('pgsql'): 400 return "EXTRACT(DOW FROM($field))"; 401 } 402 case ('DOY'): 403 switch ($this->db_type) { 404 case ('mysql'): 405 case ('mysqli'): 406 return "DAYOFYEAR($field)"; 407 case ('pgsql'): 408 return "EXTRACT(DOY FROM($field))"; 409 } 410 } 411 } 412 413 /** 414 * Create a where clause to compare a complete date field to a complete date value. 415 * 416 * @param string $type 417 * The type of value we're comparing to, could be another field 418 * or a date value. 419 * @param string $field 420 * The db table and field name, like "$table.$field". 421 * @param string $operator 422 * The db comparison operator to use, like '='. 423 * @param int $value 424 * The value to compare the extracted date part to, could be a 425 * field name or a date string or NOW(). 426 * @return 427 * SQL for the where clause for this operation. 428 */ 429 function sql_where_date($type, $field, $operator, $value, $adjustment = 0) { 430 $type = strtoupper($type); 431 if (strtoupper($value) == 'NOW') { 432 $value = $this->sql_field('NOW', $adjustment); 433 } 434 elseif ($type == 'FIELD') { 435 $value = $this->sql_field($value, $adjustment); 436 } 437 elseif ($type == 'DATE') { 438 $date = date_make_date($value, date_default_timezone_name(), DATE_DATETIME); 439 if (!empty($adjustment)) { 440 date_modify($date, $adjustment .' seconds'); 441 } 442 // When comparing a field to a date we can avoid doing timezone 443 // conversion by altering the comparison date to the db timezone. 444 // This won't work if the timezone is a field instead of a value. 445 if (empty($this->db_timezone_field) && empty($this->local_timezone_field) && $this->db_timezone_field != $this->local_timezone_field) { 446 date_timezone_set($date, timezone_open($this->db_timezone)); 447 $this->local_timezone = $this->db_timezone; 448 } 449 $value = "'". date_format_date($date, 'custom', DATE_FORMAT_DATETIME) ."'"; 450 } 451 if ($this->local_timezone != $this->db_timezone) { 452 $field = $this->sql_field($field); 453 } 454 else { 455 $field = $this->sql_field($field, 0); 456 } 457 return "$field $operator $value"; 458 } 459 460 /** 461 * Create a where clause to compare an extracted part of a field to an integer value. 462 * 463 * @param string $part 464 * The part to extract, YEAR, MONTH, DAY, etc. 465 * @param string $field 466 * The db table and field name, like "$table.$field". 467 * @param string $operator 468 * The db comparison operator to use, like '='. 469 * @param int $value 470 * The integer value to compare the extracted date part to. 471 * @return 472 * SQL for the where clause for this operation. 473 */ 474 function sql_where_extract($part, $field, $operator, $value) { 475 if ($this->local_timezone != $this->db_timezone) { 476 $field = $this->sql_field($field); 477 } 478 else { 479 $field = $this->sql_field($field, 0); 480 } 481 return $this->sql_extract($part, $field) ." $operator $value"; 482 } 483 484 /** 485 * Create a where clause to compare a formated field to a formated value. 486 * 487 * @param string $format 488 * The format to use on the date and the value when comparing them. 489 * @param string $field 490 * The db table and field name, like "$table.$field". 491 * @param string $operator 492 * The db comparison operator to use, like '='. 493 * @param string $value 494 * The value to compare the extracted date part to, could be a 495 * field name or a date string or NOW(). 496 * @return 497 * SQL for the where clause for this operation. 498 */ 499 function sql_where_format($format, $field, $operator, $value) { 500 if ($this->local_timezone != $this->db_timezone) { 501 $field = $this->sql_field($field); 502 } 503 else { 504 $field = $this->sql_field($field, 0); 505 } 506 return $this->sql_format($format, $field) ." $operator '$value'"; 507 } 508 509 /** 510 * An array of all date parts, 511 * optionally limited to an array of allowed parts. 512 */ 513 function date_parts($limit = NULL) { 514 $parts = array( 515 'year' => date_t('Year', 'datetime'), 'month' => date_t('Month', 'datetime'), 'day' => date_t('Day', 'datetime'), 516 'hour' => date_t('Hour', 'datetime'), 'minute' => date_t('Minute', 'datetime'), 'second' => date_t('Second', 'datetime'), 517 ); 518 if (!empty($limit)) { 519 $last = FALSE; 520 foreach ($parts as $key => $part) { 521 if ($last) { 522 unset($parts[$key]); 523 } 524 if ($key == $limit) { 525 $last = TRUE; 526 } 527 } 528 } 529 return $parts; 530 } 531 532 /** 533 * Part information. 534 * 535 * @param $op 536 * 'min', 'max', 'format', 'sep', 'empty_now', 'empty_min', 'empty_max'. 537 * Returns all info if empty. 538 * @param $part 539 * 'year', 'month', 'day', 'hour', 'minute', or 'second. 540 * returns info for all parts if empty. 541 */ 542 function part_info($op = NULL, $part = NULL) { 543 $info = array(); 544 $info['min'] = array( 545 'year' => 100, 'month' => 1, 'day' => 1, 546 'hour' => 0, 'minute' => 0, 'second' => 0); 547 $info['max'] = array( 548 'year' => 4000, 'month' => 12, 'day' => 31, 549 'hour' => 23, 'minute' => 59, 'second' => 59); 550 $info['format'] = array( 551 'year' => 'Y', 'month' => 'm', 'day' => 'd', 552 'hour' => 'H', 'minute' => 'i', 'second' => 's'); 553 $info['sep'] = array( 554 'year' => '', 'month' => '-', 'day' => '-', 555 'hour' => ' ', 'minute' => ':', 'second' => ':'); 556 $info['empty_now'] = array( 557 'year' => date('Y'), 'month' => date('m'), 'day' => min('28', date('d')), 558 'hour' => date('H'), 'minute' => date('i'), 'second' => date('s')); 559 $info['empty_min'] = array( 560 'year' => '1000', 'month' => '01', 'day' => '01', 561 'hour' => '00', 'minute' => '00', 'second' => '00'); 562 $info['empty_max'] = array( 563 'year' => '9999', 'month' => '12', 'day' => '31', 564 'hour' => '23', 'minute' => '59', 'second' => '59'); 565 if (!empty($op)) { 566 if (!empty($part)) { 567 return $info[$op][$part]; 568 } 569 else { 570 return $info[$op]; 571 } 572 } 573 return $info; 574 } 575 576 /** 577 * Create a complete datetime value out of an 578 * incomplete array of selected values. 579 * 580 * For example, array('year' => 2008, 'month' => 05) will fill 581 * in the day, hour, minute and second with the earliest possible 582 * values if type = 'min', the latest possible values if type = 'max', 583 * and the current values if type = 'now'. 584 */ 585 function complete_date($selected, $type = 'now') { 586 if (empty($selected)) { 587 return ''; 588 } 589 // Special case for weeks. 590 if (array_key_exists('week', $selected)) { 591 $dates = date_week_range($selected['week'], $selected['year']); 592 switch ($type) { 593 case 'empty_now': 594 case 'empty_min': 595 case 'min': 596 return date_format($dates[0], 'Y-m-d H:i:s'); 597 case 'empty_max': 598 case 'max': 599 return date_format($dates[1], 'Y-m-d H:i:s'); 600 default: 601 return; 602 } 603 } 604 605 $compare = array_merge($this->part_info('empty_'. $type), $selected); 606 // If this is a max date, make sure the last day of 607 // the month is the right one for this date. 608 if ($type == 'max') { 609 $compare['day'] = date_days_in_month($compare['year'], $compare['month']); 610 } 611 $value = ''; 612 $separators = $this->part_info('sep'); 613 foreach ($this->date_parts() as $key => $name) { 614 $value .= $separators[$key] . (!empty($selected[$key]) ? $selected[$key] : $compare[$key]); 615 } 616 return $value; 617 } 618 /** 619 * Convert a format string into help text, 620 * i.e. 'Y-m-d' becomes 'YYYY-MM-DD'. 621 * 622 * @param unknown_type $format 623 * @return unknown 624 */ 625 function format_help($format) { 626 $replace = array( 627 'Y' => 'YYYY', 'm' => 'MM', 'd' => 'DD', 628 'H' => 'HH', 'i' => 'MM', 's' => 'SS', '\T' => 'T'); 629 return strtr($format, $replace); 630 } 631 632 /** 633 * A function to test the validity of various date parts 634 */ 635 function part_is_valid($value, $type) { 636 if ( !preg_match('/^[0-9]*$/', $value) ) { 637 return false; 638 } 639 $value = intval($value); 640 if ($value <= 0) return false; 641 switch ($type) { 642 case 'year': 643 if ($value < DATE_MIN_YEAR) return false; 644 break; 645 case 'month': 646 if ($value < 0 || $value > 12) return false; 647 break; 648 case 'day': 649 if ($value < 0 || $value > 31) return false; 650 break; 651 case 'week': 652 if ($value < 0 || $value > 53) return false; 653 } 654 return true; 655 } 656 657 function views_formats($granularity, $type = 'sql') { 658 $formats = array('display', 'sql'); 659 // Start with the site long date format and add seconds to it 660 $long = str_replace(':i', ':i:s', variable_get('date_format_long', 'l, F j, Y - H:i')); 661 switch ($granularity) { 662 case ('year'): 663 $formats['display'] = 'Y'; 664 $formats['sql'] = 'Y'; 665 break; 666 case ('month'): 667 $formats['display'] = date_limit_format($long, array('year', 'month')); 668 $formats['sql'] = 'Y-m'; 669 break; 670 case ('day'): 671 $formats['display'] = date_limit_format($long, array('year', 'month', 'day')); 672 $formats['sql'] = 'Y-m-d'; 673 break; 674 case ('hour'): 675 $formats['display'] = date_limit_format($long, array('year', 'month', 'day', 'hour')); 676 $formats['sql'] = 'Y-m-d\TH'; 677 break; 678 case ('minute'): 679 $formats['display'] = date_limit_format($long, array('year', 'month', 'day', 'hour', 'minute')); 680 $formats['sql'] = 'Y-m-d\TH:i'; 681 break; 682 case ('second'): 683 $formats['display'] = date_limit_format($long, array('year', 'month', 'day', 'hour', 'minute', 'second')); 684 $formats['sql'] = 'Y-m-d\TH:i:s'; 685 break; 686 case ('week'): 687 $formats['display'] = 'F j Y (W)'; 688 $formats['sql'] = 'Y-\WW'; 689 break; 690 } 691 return $formats[$type]; 692 } 693 694 function granularity_form($granularity) { 695 $form = array( 696 '#title' => t('Granularity'), 697 '#type' => 'radios', 698 '#default_value' => $granularity, 699 '#options' => $this->date_parts(), 700 ); 701 return $form; 702 } 703 704 /** 705 * Parse date parts from an ISO date argument. 706 * 707 * Based on ISO 8601 date duration and time interval standards. 708 * 709 * See http://en.wikipedia.org/wiki/ISO_8601#Week_dates for definitions of ISO weeks. 710 * See http://en.wikipedia.org/wiki/ISO_8601#Duration for definitions of ISO duration and time interval. 711 * 712 * Parses a value like 2006-01-01--2006-01-15, or 2006-W24, or @P1W. 713 * Separate from and to dates or date and period with a double hyphen (--). 714 * 715 * The 'to' portion of the argument can be eliminated if it is the same as the 'from' portion. 716 * Use @ instead of a date to substitute in the current date and time. 717 * 718 * Use periods (P1H, P1D, P1W, P1M, P1Y) to get next hour/day/week/month/year from now. 719 * Use date before P sign to get next hour/day/week/month/year from that date. 720 * Use period then date to get a period that ends on the date. 721 * 722 */ 723 function arg_parts($argument) { 724 $values = array(); 725 // Keep mal-formed arguments from creating errors. 726 if (empty($argument) || is_array($argument)) { 727 return array('date' => array(), 'period' => array()); 728 } 729 $fromto = explode('--', $argument); 730 foreach ($fromto as $arg) { 731 $parts = array(); 732 if ($arg == '@') { 733 $parts['date'] = date_array(date_now()); 734 } 735 elseif (preg_match('/(\d{4})?-?(W)?(\d{1,2})?-?(\d{1,2})?[T\s]?(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?/', $arg, $matches)) { 736 $date = array(); 737 if (!empty($matches[1])) $date['year'] = $matches[1]; 738 if (!empty($matches[3])) { 739 if (empty($matches[2])) { 740 $date['month'] = $matches[3]; 741 } 742 else { 743 $date['week'] = $matches[3]; 744 } 745 } 746 if (!empty($matches[4])) $date['day'] = $matches[4]; 747 if (!empty($matches[5])) $date['hour'] = $matches[5]; 748 if (!empty($matches[6])) $date['minute'] = $matches[6]; 749 if (!empty($matches[7])) $date['second'] = $matches[7]; 750 $parts['date'] = $date; 751 } 752 if (preg_match('/^P(\d{1,4}[Y])?(\d{1,2}[M])?(\d{1,2}[W])?(\d{1,2}[D])?([T]{0,1})?(\d{1,2}[H])?(\d{1,2}[M])?(\d{1,2}[S])?/', $arg, $matches)) { 753 $period = array(); 754 if (!empty($matches[1])) $period['year'] = str_replace('Y', '', $matches[1]); 755 if (!empty($matches[2])) $period['month'] = str_replace('M', '', $matches[2]); 756 if (!empty($matches[3])) $period['week'] = str_replace('W', '', $matches[3]); 757 if (!empty($matches[4])) $period['day'] = str_replace('D', '', $matches[4]); 758 if (!empty($matches[6])) $period['hour'] = str_replace('H', '', $matches[6]); 759 if (!empty($matches[7])) $period['minute'] = str_replace('M', '', $matches[7]); 760 if (!empty($matches[8])) $period['second'] = str_replace('S', '', $matches[8]); 761 $parts['period'] = $period; 762 } 763 $values[] = $parts; 764 } 765 return $values; 766 } 767 768 /** 769 * Convert strings like '+1 day' to the ISO equivalent, like 'P1D'. 770 */ 771 function arg_replace($arg) { 772 if (!preg_match('/([+|-])\s?([0-9]{1,32})\s?([day(s)?|week(s)?|month(s)?|year(s)?|hour(s)?|minute(s)?|second(s)?]{1,10})/', $arg, $results)) { 773 return str_replace('now', '@', $arg); 774 } 775 $direction = $results[1]; 776 $count = $results[2]; 777 $item = $results[3]; 778 779 $replace = array( 780 'now' => '@', 781 '+' => 'P', 782 '-' => 'P-', 783 'years' => 'Y', 784 'year' => 'Y', 785 'months' => 'M', 786 'month' => 'M', 787 'weeks' => 'W', 788 'week' => 'W', 789 'days' => 'D', 790 'day' => 'D', 791 'hours' => 'H', 792 'hour' => 'H', 793 'minutes' => 'M', 794 'minute' => 'M', 795 'seconds' => 'S', 796 'second' => 'S', 797 ' ' => '', 798 ' ' => '', 799 ); 800 $prefix = in_array($item, array('hours', 'hour', 'minutes', 'minute', 'seconds', 'second')) ? 'T' : ''; 801 return $prefix . strtr($direction, $replace) . $count . strtr($item, $replace); 802 } 803 804 /** 805 * Use the parsed values from the ISO argument to determine the 806 * granularity of this period. 807 */ 808 function arg_granularity($arg) { 809 $granularity = ''; 810 $parts = $this->arg_parts($arg); 811 $date = !empty($parts[0]['date']) ? $parts[0]['date'] : (!empty($parts[1]['date']) ? $parts[1]['date'] : array()); 812 foreach ($date as $key => $part) { 813 $granularity = $key; 814 } 815 return $granularity; 816 } 817 818 /** 819 * Use the parsed values from the ISO argument to determine the 820 * min and max date for this period. 821 */ 822 function arg_range($arg) { 823 // Parse the argument to get its parts 824 $parts = $this->arg_parts($arg); 825 826 // Build a range from a period-only argument (assumes the min date is now.) 827 if (empty($parts[0]['date']) && !empty($parts[0]['period']) && (empty($parts[1]))) { 828 $min_date = date_now(); 829 $max_date = drupal_clone($min_date); 830 foreach ($parts[0]['period'] as $part => $value) { 831 date_modify($max_date, "+$value $part"); 832 } 833 date_modify($max_date, '-1 second'); 834 return array($min_date, $max_date); 835 } 836 // Build a range from a period to period argument 837 if (empty($parts[0]['date']) && !empty($parts[0]['period']) && !empty($parts[1]['period'])) { 838 $min_date = date_now(); 839 $max_date = drupal_clone($min_date); 840 foreach ($parts[0]['period'] as $part => $value) { 841 date_modify($min_date, "+$value $part"); 842 } 843 date_modify($min_date, '-1 second'); 844 foreach ($parts[1]['period'] as $part => $value) { 845 date_modify($max_date, "+$value $part"); 846 } 847 date_modify($max_date, '-1 second'); 848 return array($min_date, $max_date); 849 } 850 if (!empty($parts[0]['date'])) { 851 $value = date_fuzzy_datetime($this->complete_date($parts[0]['date'], 'min')); 852 $min_date = date_make_date($value, date_default_timezone_name(), DATE_ISO); 853 // Build a range from a single date-only argument. 854 if (empty($parts[1]) || (empty($parts[1]['date']) && empty($parts[1]['period']))) { 855 $value = date_fuzzy_datetime($this->complete_date($parts[0]['date'], 'max')); 856 $max_date = date_make_date($value, date_default_timezone_name(), DATE_ISO); 857 return array($min_date, $max_date); 858 } 859 // Build a range from start date + period. 860 elseif (!empty($parts[1]['period'])) { 861 foreach ($parts[1]['period'] as $part => $value) { 862 $max_date = drupal_clone($min_date); 863 date_modify($max_date, "+$value $part"); 864 } 865 date_modify($max_date, '-1 second'); 866 return array($min_date, $max_date); 867 } 868 } 869 // Build a range from start date and end date. 870 if (!empty($parts[1]['date'])) { 871 $value = date_fuzzy_datetime($this->complete_date($parts[1]['date'], 'max')); 872 $max_date = date_make_date($value, date_default_timezone_name(), DATE_ISO); 873 if (isset($min_date)) { 874 return array($min_date, $max_date); 875 } 876 } 877 // Build a range from period + end date. 878 if (!empty($parts[0]['period'])) { 879 $min_date = date_now(); 880 foreach ($parts[0]['period'] as $part => $value) { 881 date_modify($min_date, "$value $part"); 882 } 883 return array($min_date, $max_date); 884 } 885 // Intercept invalid info and fall back to the current date. 886 $now = date_now(); 887 return array($now, $now); 888 } 889 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Thu Mar 24 11:18:33 2011 | Cross-referenced by PHPXref 0.7 |