1<?php 2/* vim: set expandtab tabstop=4 shiftwidth=4: */ 3/** 4 * Driver.php 5 * 6 * PHP Version 5 7 * 8 * Copyright (c) 1997-2008 The PHP Group 9 * 10 * This source file is subject to version 2.0 of the PHP license, 11 * that is bundled with this package in the file LICENSE, and is 12 * available at through the world-wide-web at 13 * http://www.php.net/license/2_02.txt. 14 * If you did not receive a copy of the PHP license and are unable to 15 * obtain it through the world-wide-web, please send a note to 16 * license@php.net so we can mail you a copy immediately. 17 * 18 * Authors: Carsten Lucke <luckec@tool-garage.de> 19 * 20 * CVS file id: $Id$ 21 * 22 * @category Date 23 * @package Date_Holidays 24 * @author Carsten Lucke <luckec@tool-garage.de> 25 * @license http://www.php.net/license/3_01.txt PHP License 3.0.1 26 * @version CVS: $Id$ 27 * @link http://pear.php.net/package/Date_Holidays 28 */ 29 30/** 31 * DriverClass and associated defines. 32 * 33 * @abstract 34 * @category Date 35 * @package Date_Holidays 36 * @author Carsten Lucke <luckec@tool-garage.de> 37 * @license http://www.php.net/license/3_01.txt PHP License 3.0.1 38 * @version CVS: $Id$ 39 * @link http://pear.php.net/package/Date_Holidays 40 */ 41 42/** 43 * uses PEAR_Errorstack 44 */ 45require_once 'PEAR/ErrorStack.php'; 46require_once 'Date/Holidays/Filter.php'; 47require_once 'Date/Holidays/Filter/Whitelist.php'; 48require_once 'Date/Holidays/Filter/Blacklist.php'; 49 50/** 51 * invalid internal name 52 * 53 * @access public 54 */ 55define('DATE_HOLIDAYS_INVALID_INTERNAL_NAME', 51); 56 57/** 58 * title for a holiday is not available 59 * 60 * @access public 61 */ 62define('DATE_HOLIDAYS_TITLE_UNAVAILABLE', 52); 63 64/** 65 * date could not be converted into a PEAR::Date object 66 * 67 * date was neither a timestamp nor a string 68 * 69 * @access public 70 * @deprecated will certainly be removed 71 */ 72define('DATE_HOLIDAYS_INVALID_DATE', 53); 73 74/** 75 * string that represents a date has wrong format 76 * 77 * format must be YYYY-MM-DD 78 * 79 * @access public 80 * @deprecated will certainly be removed 81 */ 82define('DATE_HOLIDAYS_INVALID_DATE_FORMAT', 54); 83 84/** 85 * date for a holiday is not available 86 * 87 * @access public 88 */ 89define('DATE_HOLIDAYS_DATE_UNAVAILABLE', 55); 90 91/** 92 * language-file doesn't exist 93 * 94 * @access public 95 */ 96define('DATE_HOLIDAYS_LANGUAGEFILE_NOT_FOUND', 56); 97 98/** 99 * unable to read language-file 100 * 101 * @access public 102 */ 103define('DATE_HOLIDAYS_UNABLE_TO_READ_TRANSLATIONDATA', 57); 104 105/** 106 * Name of the static {@link Date_Holidays_Driver} method returning 107 * a array of possible ISO3166 codes that identify itself. 108 * 109 * @access public 110 */ 111define('DATE_HOLIDAYS_DRIVER_IDENTIFY_ISO3166_METHOD', 'getISO3166Codes'); 112 113/** 114 * class that helps you to locate holidays for a year 115 * 116 * @abstract 117 * @category Date 118 * @package Date_Holidays 119 * @subpackage Driver 120 * @author Carsten Lucke <luckec@tool-garage.de> 121 * @license http://www.php.net/license/3_01.txt PHP License 3.0.1 122 * @version CVS: $Id$ 123 * @link http://pear.php.net/package/Date_Holidays 124 */ 125class Date_Holidays_Driver 126{ 127 /** 128 * this driver's name 129 * 130 * @access protected 131 * @var string 132 */ 133 var $_driverName; 134 135 /** 136 * locale setting for output 137 * 138 * @access protected 139 * @var string 140 */ 141 var $_locale; 142 143 /** 144 * locales for which translations of holiday titles are available 145 * 146 * @access private 147 * @var array 148 */ 149 var $_availableLocales = array('C'); 150 151 /** 152 * object's current year 153 * 154 * @access protected 155 * @var int 156 */ 157 var $_year; 158 159 /** 160 * internal names for the available holidays 161 * 162 * @access protected 163 * @var array 164 */ 165 var $_internalNames = array(); 166 167 /** 168 * dates of the available holidays 169 * 170 * @access protected 171 * @var array 172 */ 173 var $_dates = array(); 174 175 /** 176 * array of the available holidays indexed by date 177 * 178 * @access protected 179 * @var array 180 */ 181 var $_holidays = array(); 182 183 /** 184 * localized names of the available holidays 185 * 186 * @access protected 187 * @var array 188 */ 189 var $_titles = array(); 190 191 /** 192 * Array of holiday-properties indexed by internal-names and 193 * furthermore by locales. 194 * 195 * <code> 196 * $_holidayProperties = array( 197 * 'internalName1' => array( 198 * 'de_DE' => array(), 199 * 'en_US' => array(), 200 * 'fr_FR' => array() 201 * ) 202 * 'internalName2' => array( 203 * 'de_DE' => array(), 204 * 'en_US' => array(), 205 * 'fr_FR' => array() 206 * ) 207 * ); 208 * </code> 209 */ 210 var $_holidayProperties = array(); 211 212 /** 213 * Constructor 214 * 215 * Use the Date_Holidays::factory() method to construct an object of a 216 * certain driver 217 * 218 * @access protected 219 */ 220 function Date_Holidays_Driver() 221 { 222 } 223 224 /** 225 * Method that returns an array containing the ISO3166 codes that may possibly 226 * identify a driver. 227 * 228 * @static 229 * @access public 230 * @return array possible ISO3166 codes 231 */ 232 function getISO3166Codes() 233 { 234 return array(); 235 } 236 237 /** 238 * Sets the driver's current year 239 * 240 * Calling this method forces the object to rebuild the holidays 241 * 242 * @param int $year year 243 * 244 * @access public 245 * @return boolean true on success, otherwise a PEAR_ErrorStack object 246 * @throws object PEAR_ErrorStack 247 * @uses _buildHolidays() 248 */ 249 function setYear($year) 250 { 251 $this->_year = $year; 252 return $this->_buildHolidays(); 253 } 254 255 /** 256 * Returns the driver's current year 257 * 258 * @access public 259 * @return int current year 260 */ 261 function getYear() 262 { 263 return $this->_year; 264 } 265 266 /** 267 * Build the internal arrays that contain data about the calculated holidays 268 * 269 * @abstract 270 * @access protected 271 * @return boolean true on success, otherwise a PEAR_ErrorStack object 272 * @throws object PEAR_ErrorStack 273 */ 274 function _buildHolidays() 275 { 276 } 277 278 /** 279 * Add a driver component 280 * 281 * @param object $driver Date_Holidays_Driver object 282 * 283 * @abstract 284 * @access public 285 * @return void 286 */ 287 function addDriver($driver) 288 { 289 } 290 291 /** 292 * addTranslation 293 * 294 * Search for installed language files appropriate for the specified 295 * locale and add them to the driver 296 * 297 * @param string $locale locale setting to be used 298 * 299 * @access public 300 * @return boolean true on success, otherwise false 301 */ 302 function addTranslation($locale) 303 { 304 $data_dir = "@DATA-DIR@"; 305 $bestLocale = $this->_findBestLocale($locale); 306 $matches = array(); 307 $loaded = false; 308 309 if ($data_dir == '@'.'DATA-DIR'.'@') { 310 $data_dir = dirname(dirname(dirname(__FILE__))); 311 $stubdirs = array( 312 "$data_dir/lang/{$this->_driverName}/", 313 "$data_dir/lang/Christian/"); 314 } else { 315 //Christian driver is exceptional... 316 if ($this->_driverName == 'Christian') { 317 $stubdir = "$data_dir/Date_Holidays/lang/Christian/"; 318 } else { 319 $stubdir = "$data_dir/Date_Holidays_{$this->_driverName}/lang/{$this->_driverName}/"; 320 if (! is_dir($stubdir)) { 321 $stubdir = $data_dir . "/Date_Holidays/lang/"; 322 } 323 } 324 $stubdirs = array( 325 $stubdir, 326 "$data_dir/Date_Holidays_{$this->_driverName}/lang/Christian/"); 327 } 328 329 foreach ($stubdirs as $stubdir) { 330 if (is_dir($stubdir)) { 331 if ($dh = opendir($stubdir)) { 332 while (($file = readdir($dh)) !== false) { 333 if (strlen($locale) == 5) { 334 if (((strncasecmp($file, $bestLocale, 5) == 0)) 335 || (strncasecmp($file, $locale, 5) == 0) 336 ) { 337 array_push($matches, $file); 338 } 339 } 340 if (strlen($locale) == 2) { 341 if (((strncasecmp($file, $bestLocale, 2) == 0)) 342 || (strncasecmp($file, $locale, 2) == 0) 343 ) { 344 array_push($matches, $file); 345 } 346 } 347 } 348 closedir($dh); 349 $forget = array(); 350 sort($matches); 351 foreach ($matches as $am) { 352 if (strpos($am, ".ser") !== false) { 353 $this->addCompiledTranslationFile($stubdir.$am, $locale); 354 $loaded = true; 355 array_push($forget, basename($am, ".ser") . ".xml"); 356 } else { 357 if (!in_array($am, $forget)) { 358 $this->addTranslationFile( 359 $stubdir . $am, 360 str_replace(".xml", "", $am) 361 ); 362 $loaded = true; 363 } 364 } 365 } 366 } 367 } 368 } 369 return $loaded; 370 } 371 372 /** 373 * Remove a driver component 374 * 375 * @param object $driver Date_Holidays_Driver driver-object 376 * 377 * @abstract 378 * @access public 379 * @return boolean true on success, otherwise a PEAR_Error object 380 * @throws object PEAR_Error DATE_HOLIDAYS_DRIVER_NOT_FOUND 381 */ 382 function removeDriver($driver) 383 { 384 } 385 386 /** 387 * Returns the internal names of holidays that were calculated 388 * 389 * @access public 390 * @return array 391 */ 392 function getInternalHolidayNames() 393 { 394 return $this->_internalNames; 395 } 396 397 /** 398 * Returns localized titles of all holidays or those accepted by the filter 399 * 400 * @param Date_Holidays_Filter $filter filter-object (or an array !DEPRECATED!) 401 * @param string $locale locale setting that shall be used 402 * by this method 403 * 404 * @access public 405 * @return array $filter array with localized holiday titles on success, 406 * otherwise a PEAR_Error object 407 * @throws object PEAR_Error DATE_HOLIDAYS_INVALID_INTERNAL_NAME 408 * @uses getHolidayTitle() 409 */ 410 function getHolidayTitles($filter = null, $locale = null) 411 { 412 if (is_null($filter)) { 413 $filter = new Date_Holidays_Filter_Blacklist(array()); 414 } elseif (is_array($filter)) { 415 $filter = new Date_Holidays_Filter_Whitelist($filter); 416 } 417 418 $titles = array(); 419 420 foreach ($this->_internalNames as $internalName) { 421 if ($filter->accept($internalName)) { 422 $title = $this->getHolidayTitle($internalName, $locale); 423 if (Date_Holidays::isError($title)) { 424 return $title; 425 } 426 $titles[$internalName] = $title; 427 } 428 } 429 430 return $titles; 431 } 432 433 /** 434 * Returns localized title for a holiday 435 * 436 * @param string $internalName internal name for holiday 437 * @param string $locale locale setting to be used by this method 438 * 439 * @access public 440 * @return string title on success, otherwise a PEAR_Error object 441 * @throws object PEAR_Error DATE_HOLIDAYS_INVALID_INTERNAL_NAME 442 * @throws object PEAR_Error DATE_HOLIDAYS_TITLE_UNAVAILABLE 443 */ 444 function getHolidayTitle($internalName, $locale = null) 445 { 446 if (! in_array($internalName, $this->_internalNames)) { 447 $msg = 'Invalid internal name: ' . $internalName; 448 return Date_Holidays::raiseError(DATE_HOLIDAYS_INVALID_INTERNAL_NAME, 449 $msg); 450 451 } 452 453 if (is_null($locale)) { 454 $locale = $this->_findBestLocale($this->_locale); 455 } else { 456 $locale = $this->_findBestLocale($locale); 457 } 458 459 if (! isset($this->_titles[$locale][$internalName])) { 460 if (Date_Holidays::staticGetProperty('DIE_ON_MISSING_LOCALE')) { 461 $err = DATE_HOLIDAYS_TITLE_UNAVAILABLE; 462 $msg = 'The internal name (' . $internalName . ') ' . 463 'for the holiday was correct but no ' . 464 'localized title could be found'; 465 return Date_Holidays::raiseError($err, $msg); 466 } 467 } 468 469 if (isset($this->_titles[$locale][$internalName])) { 470 return $this->_titles[$locale][$internalName]; 471 } else { 472 return $this->_titles['C'][$internalName]; 473 } 474 } 475 476 477 /** 478 * Returns the localized properties of a holiday. If no properties have 479 * been stored an empty array will be returned. 480 * 481 * @param string $internalName internal name for holiday 482 * @param string $locale locale setting that shall be used by this method 483 * 484 * @access public 485 * @return array array of properties on success, otherwise 486 * a PEAR_Error object 487 * @throws object PEAR_Error DATE_HOLIDAYS_INVALID_INTERNAL_NAME 488 */ 489 function getHolidayProperties($internalName, $locale = null) 490 { 491 if (! in_array($internalName, $this->_internalNames)) { 492 $msg = 'Invalid internal name: ' . $internalName; 493 return Date_Holidays::raiseError(DATE_HOLIDAYS_INVALID_INTERNAL_NAME, 494 $msg); 495 } 496 497 if (is_null($locale)) { 498 $locale = $this->_findBestLocale($this->_locale); 499 } else { 500 $locale = $this->_findBestLocale($locale); 501 } 502 503 504 $properties = array(); 505 if (isset($this->_holidayProperties[$internalName][$locale])) { 506 $properties = $this->_holidayProperties[$internalName][$locale]; 507 } 508 return $properties; 509 } 510 511 512 /** 513 * Returns all holidays that the driver knows. 514 * 515 * You can limit the holidays by passing a filter, then only those 516 * holidays accepted by the filter will be returned. 517 * 518 * Return format: 519 * <pre> 520 * array( 521 * 'easter' => object of type Date_Holidays_Holiday, 522 * 'eastermonday' => object of type Date_Holidays_Holiday, 523 * ... 524 * ) 525 * </pre> 526 * 527 * @param Date_Holidays_Filter $filter filter-object 528 * (or an array !DEPRECATED!) 529 * @param string $locale locale setting that shall be used 530 * by this method 531 * 532 * @access public 533 * @return array numeric array containing objects of 534 * Date_Holidays_Holiday on success, otherwise a 535 * PEAR_Error object 536 * @throws object PEAR_Error DATE_HOLIDAYS_INVALID_INTERNAL_NAME 537 * @see getHoliday() 538 */ 539 function getHolidays($filter = null, $locale = null) 540 { 541 if (is_null($filter)) { 542 $filter = new Date_Holidays_Filter_Blacklist(array()); 543 } elseif (is_array($filter)) { 544 $filter = new Date_Holidays_Filter_Whitelist($filter); 545 } 546 547 if (is_null($locale)) { 548 $locale = $this->_locale; 549 } 550 551 $holidays = array(); 552 553 foreach ($this->_internalNames as $internalName) { 554 if ($filter->accept($internalName)) { 555 // no need to check for valid internal-name, will be 556 // done by #getHoliday() 557 $holidays[$internalName] = $this->getHoliday($internalName, 558 $locale); 559 } 560 } 561 562 return $holidays; 563 } 564 565 /** 566 * Returns the specified holiday 567 * 568 * Return format: 569 * <pre> 570 * array( 571 * 'title' => 'Easter Sunday' 572 * 'date' => '2004-04-11' 573 * ) 574 * </pre> 575 * 576 * @param string $internalName internal name of the holiday 577 * @param string $locale locale setting that shall be used 578 * by this method 579 * 580 * @access public 581 * @return object Date_Holidays_Holiday holiday's information on 582 * success, otherwise a PEAR_Error 583 * object 584 * @throws object PEAR_Error DATE_HOLIDAYS_INVALID_INTERNAL_NAME 585 * @uses getHolidayTitle() 586 * @uses getHolidayDate() 587 */ 588 function getHoliday($internalName, $locale = null) 589 { 590 if (! in_array($internalName, $this->_internalNames)) { 591 return Date_Holidays::raiseError(DATE_HOLIDAYS_INVALID_INTERNAL_NAME, 592 'Invalid internal name: ' . $internalName); 593 } 594 if (is_null($locale)) { 595 $locale = $this->_locale; 596 } 597 598 $title = $this->getHolidayTitle($internalName, $locale); 599 if (Date_Holidays::isError($title)) { 600 return $title; 601 } 602 $date = $this->getHolidayDate($internalName); 603 if (Date_Holidays::isError($date)) { 604 return $date; 605 } 606 $properties = $this->getHolidayProperties($internalName, $locale); 607 if (Date_Holidays::isError($properties)) { 608 return $properties; 609 } 610 611 $holiday = new Date_Holidays_Holiday($internalName, 612 $title, 613 $date, 614 $properties); 615 return $holiday; 616 } 617 618 /** 619 * Determines whether a date represents a holiday or not 620 * 621 * @param mixed $date a timestamp, string or PEAR::Date object 622 * @param Date_Holidays_Filter $filter filter-object (or an array !DEPRECATED!) 623 * 624 * @access public 625 * @return boolean true if date represents a holiday, otherwise false 626 * @throws object PEAR_Error DATE_HOLIDAYS_INVALID_DATE_FORMAT 627 * @throws object PEAR_Error DATE_HOLIDAYS_INVALID_DATE 628 */ 629 function isHoliday($date, $filter = null) 630 { 631 if (! is_a($date, 'Date')) { 632 $date = $this->_convertDate($date); 633 if (Date_Holidays::isError($date)) { 634 return $date; 635 } 636 } 637 638 //rebuild internal array of holidays if required. 639 $compare_year = $date->getYear(); 640 $this_year = $this->getYear(); 641 if ($this_year !== $compare_year) { 642 $this->setYear($compare_year); 643 } 644 645 if (is_null($filter)) { 646 $filter = new Date_Holidays_Filter_Blacklist(array()); 647 } elseif (is_array($filter)) { 648 $filter = new Date_Holidays_Filter_Whitelist($filter); 649 } 650 651 foreach (array_keys($this->_dates) as $internalName) { 652 if ($filter->accept($internalName)) { 653 if (Date_Holidays_Driver::dateSloppyCompare($date, 654 $this->_dates[$internalName]) != 0) { 655 continue; 656 } 657 $this->setYear($this_year); 658 return true; 659 } 660 } 661 $this->setYear($this_year); 662 return false; 663 } 664 665 /** 666 * Returns a <code>Date_Holidays_Holiday</code> object, if any was found, 667 * matching the specified date. 668 * 669 * Normally the method will return the object of the first holiday matching 670 * the date. If you want the method to continue searching holidays for the 671 * specified date, set the 4th param to true. 672 * 673 * If multiple holidays match your date, the return value will be an array 674 * containing a number of <code>Date_Holidays_Holiday</code> items. 675 * 676 * @param mixed $date date (timestamp | string | PEAR::Date object) 677 * @param string $locale locale setting that shall be used by this method 678 * @param boolean $multiple if true, continue searching holidays for 679 * specified date 680 * 681 * @access public 682 * @return object object of type Date_Holidays_Holiday on success 683 * (numeric array of those on multiple search), 684 * if no holiday was found, matching this date, 685 * null is returned 686 * @throws object PEAR_Error DATE_HOLIDAYS_INVALID_DATE_FORMAT 687 * @throws object PEAR_Error DATE_HOLIDAYS_INVALID_DATE 688 * @uses getHoliday() 689 * @uses getHolidayTitle() 690 * @see getHoliday() 691 **/ 692 function getHolidayForDate($date, $locale = null, $multiple = false) 693 { 694 if (!is_a($date, 'Date')) { 695 $date = $this->_convertDate($date); 696 if (Date_Holidays::isError($date)) { 697 return $date; 698 } 699 } 700 701 if ($date->getYear() != $this->_year) { 702 return null; 703 } 704 705 $isodate = mktime(0, 706 0, 707 0, 708 $date->getMonth(), 709 $date->getDay(), 710 $date->getYear()); 711 unset($date); 712 if (is_null($locale)) { 713 $locale = $this->_locale; 714 } 715 if (array_key_exists($isodate, $this->_holidays)) { 716 if (!$multiple) { 717 //get only the first feast for this day 718 $internalName = $this->_holidays[$isodate][0]; 719 $result = $this->getHoliday($internalName, $locale); 720 return Date_Holidays::isError($result) ? null : $result; 721 } 722 // array that collects data, if multiple searching is done 723 $data = array(); 724 foreach ($this->_holidays[$isodate] as $internalName) { 725 $result = $this->getHoliday($internalName, $locale); 726 if (Date_Holidays::isError($result)) { 727 continue; 728 } 729 $data[] = $result; 730 } 731 return $data; 732 } 733 return null; 734 } 735 736 /** 737 * Returns an array containing a number of 738 * <code>Date_Holidays_Holiday</code> items. 739 * 740 * If no items have been found the returned array will be empty. 741 * 742 * @param mixed $start date: timestamp, string or PEAR::Date 743 * @param mixed $end date: timestamp, string or PEAR::Date 744 * @param Date_Holidays_Filter $filter filter-object (or 745 * an array !DEPRECATED!) 746 * @param string $locale locale setting that shall be used 747 * by this method 748 * 749 * @access public 750 * @throws object PEAR_Error DATE_HOLIDAYS_INVALID_DATE_FORMAT 751 * @throws object PEAR_Error DATE_HOLIDAYS_INVALID_DATE 752 * @return array an array containing a number 753 * of <code>Date_Holidays_Holiday</code> items 754 */ 755 function getHolidaysForDatespan($start, $end, $filter = null, $locale = null) 756 { 757 if (is_null($filter)) { 758 $filter = new Date_Holidays_Filter_Blacklist(array()); 759 } elseif (is_array($filter)) { 760 $filter = new Date_Holidays_Filter_Whitelist($filter); 761 } 762 763 if (!is_a($start, 'Date')) { 764 $start = $this->_convertDate($start); 765 if (Date_Holidays::isError($start)) { 766 return $start; 767 } 768 } 769 if (!is_a($end, 'Date')) { 770 $end = $this->_convertDate($end); 771 if (Date_Holidays::isError($end)) { 772 return $end; 773 } 774 } 775 776 $isodateStart = mktime(0, 777 0, 778 0, 779 $start->getMonth(), 780 $start->getDay(), 781 $start->getYear()); 782 unset($start); 783 $isodateEnd = mktime(0, 784 0, 785 0, 786 $end->getMonth(), 787 $end->getDay(), 788 $end->getYear()); 789 unset($end); 790 if (is_null($locale)) { 791 $locale = $this->_locale; 792 } 793 794 $internalNames = array(); 795 796 foreach ($this->_holidays as $isoDateTS => $arHolidays) { 797 if ($isoDateTS >= $isodateStart && $isoDateTS <= $isodateEnd) { 798 $internalNames = array_merge($internalNames, $arHolidays); 799 } 800 } 801 802 $retval = array(); 803 foreach ($internalNames as $internalName) { 804 if ($filter->accept($internalName)) { 805 $retval[] = $this->getHoliday($internalName, $locale); 806 } 807 } 808 return $retval; 809 810 } 811 812 /** 813 * Converts timestamp or date-string into da PEAR::Date object 814 * 815 * @param mixed $date date 816 * 817 * @static 818 * @access private 819 * @return object PEAR_Date 820 * @throws object PEAR_Error DATE_HOLIDAYS_INVALID_DATE_FORMAT 821 * @throws object PEAR_Error DATE_HOLIDAYS_INVALID_DATE 822 */ 823 function _convertDate($date) 824 { 825 if (is_string($date)) { 826 if (! preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}/', $date)) { 827 return Date_Holidays::raiseError(DATE_HOLIDAYS_INVALID_DATE_FORMAT, 828 'Date-string has wrong format (must be YYYY-MM-DD)'); 829 } 830 $date = new Date($date); 831 return $date; 832 } 833 834 if (is_int($date)) { 835 $date = new Date(date('Y-m-d', $date)); 836 return $date; 837 } 838 839 return Date_Holidays::raiseError(DATE_HOLIDAYS_INVALID_DATE, 840 'The date you specified is invalid'); 841 } 842 843 /** 844 * Adds all holidays in the array to the driver's internal list of holidays. 845 * 846 * Format of the array: 847 * <pre> 848 * array( 849 * 'newYearsDay' => array( 850 * 'date' => '01-01', 851 * 'title' => 'New Year\'s Day', 852 * 'translations' => array( 853 * 'de_DE' => 'Neujahr', 854 * 'en_EN' => 'New Year\'s Day' 855 * ) 856 * ), 857 * 'valentinesDay' => array( 858 * ... 859 * ) 860 * ); 861 * </pre> 862 * 863 * @param array $holidays static holidays' data 864 * 865 * @access protected 866 * @uses _addHoliday() 867 * @return void 868 */ 869 function _addStaticHolidays($holidays) 870 { 871 foreach ($holidays as $internalName => $holiday) { 872 // add the holiday's basic data 873 $this->_addHoliday($internalName, 874 $this->_year . '-' . $holiday['date'], 875 $holiday['title']); 876 } 877 } 878 879 /** 880 * Adds a holiday to the driver's holidays 881 * 882 * @param string $internalName internal name - must not contain characters 883 * that aren't allowed as variable-names 884 * @param mixed $date date (timestamp | string | PEAR::Date object) 885 * @param string $title holiday title 886 * 887 * @access protected 888 * @return void 889 */ 890 function _addHoliday($internalName, $date, $title) 891 { 892 if (! is_a($date, 'Date')) { 893 $date = new Date($date); 894 } 895 896 $this->_dates[$internalName] = $date; 897 $this->_titles['C'][$internalName] = $title; 898 $isodate = mktime(0, 0, 0, 899 $date->getMonth(), 900 $date->getDay(), 901 $date->getYear()); 902 if (!isset($this->_holidays[$isodate])) { 903 $this->_holidays[$isodate] = array(); 904 } 905 array_push($this->_holidays[$isodate], $internalName); 906 array_push($this->_internalNames, $internalName); 907 } 908 909 /** 910 * Add a localized translation for a holiday's title. Overwrites existing data. 911 * 912 * @param string $internalName internal name of an existing holiday 913 * @param string $locale locale setting that shall be used by this method 914 * @param string $title title 915 * 916 * @access protected 917 * @return true on success, otherwise a PEAR_Error object 918 * @throws object PEAR_Error DATE_HOLIDAYS_INVALID_INTERNAL_NAME 919 */ 920 function _addTranslationForHoliday($internalName, $locale, $title) 921 { 922 if (! in_array($internalName, $this->_internalNames)) { 923 $msg = 'Couldn\'t add translation (' . $locale . ') ' . 924 'for holiday with this internal name: ' . $internalName; 925 return Date_Holidays::raiseError(DATE_HOLIDAYS_INVALID_INTERNAL_NAME, 926 $msg); 927 } 928 929 if (! in_array($locale, $this->_availableLocales)) { 930 array_push($this->_availableLocales, $locale); 931 } 932 $this->_titles[$locale][$internalName] = $title; 933 return true; 934 } 935 936 /** 937 * Adds a localized (regrading translation etc.) string-property for a holiday. 938 * Overwrites existing data. 939 * 940 * @param string $internalName internal-name 941 * @param string $locale locale-setting 942 * @param string $propId property-identifier 943 * @param mixed $propVal property-value 944 * 945 * @access public 946 * @return boolean true on success, false otherwise 947 * @throws PEAR_ErrorStack if internal-name does not exist 948 */ 949 function _addStringPropertyForHoliday($internalName, $locale, $propId, $propVal) 950 { 951 if (! in_array($internalName, $this->_internalNames)) { 952 $msg = 'Couldn\'t add property (locale: ' . $locale . ') '. 953 'for holiday with this internal name: ' . $internalName; 954 return Date_Holidays::raiseError(DATE_HOLIDAYS_INVALID_INTERNAL_NAME, 955 $msg); 956 } 957 958 if (!isset($this->_holidayProperties[$internalName]) || 959 !is_array($this->_holidayProperties[$internalName])) { 960 961 $this->_holidayProperties[$internalName] = array(); 962 } 963 964 if (! isset($this->_holidayProperties[$internalName][$locale]) || 965 !is_array($this->_holidayProperties[$internalName][$locale])) { 966 967 $this->_holidayProperties[$internalName][$locale] = array(); 968 } 969 970 $this->_holidayProperties[$internalName][$locale][$propId] = $propVal; 971 return true; 972 } 973 974 /** 975 * Adds a arbitrary number of localized string-properties for the 976 * specified holiday. 977 * 978 * @param string $internalName internal-name 979 * @param string $locale locale-setting 980 * @param array $properties associative array: array(propId1 => val1,...) 981 * 982 * @access public 983 * @return boolean true on success, false otherwise 984 * @throws PEAR_ErrorStack if internal-name does not exist 985 */ 986 function _addStringPropertiesForHoliday($internalName, $locale, $properties) 987 { 988 foreach ($properties as $propId => $propValue) { 989 return $this->_addStringPropertyForHoliday($internalName, 990 $locale, 991 $propId, 992 $propValue); 993 } 994 995 return true; 996 } 997 998 /** 999 * Add a language-file's content 1000 * 1001 * The language-file's content will be parsed and translations, 1002 * properties, etc. for holidays will be made available with the specified 1003 * locale. 1004 * 1005 * @param string $file filename of the language file 1006 * @param string $locale locale-code of the translation 1007 * 1008 * @access public 1009 * @return boolean true on success, otherwise a PEAR_ErrorStack object 1010 * @throws object PEAR_Errorstack 1011 */ 1012 function addTranslationFile($file, $locale) 1013 { 1014 if (! file_exists($file)) { 1015 Date_Holidays::raiseError(DATE_HOLIDAYS_LANGUAGEFILE_NOT_FOUND, 1016 'Language-file not found: ' . $file); 1017 return Date_Holidays::getErrorStack(); 1018 } 1019 1020 // unserialize the document 1021 $document = simplexml_load_file($file); 1022 1023 $content = array(); 1024 $content['holidays'] = array(); 1025 $content['holidays']['holiday'] = array(); 1026 1027 $nodes = $document->xpath('//holiday'); 1028 foreach ($nodes as $node) { 1029 $content['holidays']['holiday'][] = (array)$node; 1030 } 1031 1032 return $this->_addTranslationData($content, $locale); 1033 } 1034 1035 /** 1036 * Add a compiled language-file's content 1037 * 1038 * The language-file's content will be unserialized and translations, 1039 * properties, etc. for holidays will be made available with the 1040 * specified locale. 1041 * 1042 * @param string $file filename of the compiled language file 1043 * @param string $locale locale-code of the translation 1044 * 1045 * @access public 1046 * @return boolean true on success, otherwise a PEAR_ErrorStack object 1047 * @throws object PEAR_Errorstack 1048 */ 1049 function addCompiledTranslationFile($file, $locale) 1050 { 1051 if (! file_exists($file)) { 1052 Date_Holidays::raiseError(DATE_HOLIDAYS_LANGUAGEFILE_NOT_FOUND, 1053 'Language-file not found: ' . $file); 1054 return Date_Holidays::getErrorStack(); 1055 } 1056 1057 $content = file_get_contents($file); 1058 if ($content === false) { 1059 return false; 1060 } 1061 $data = unserialize($content); 1062 if ($data === false) { 1063 $e = DATE_HOLIDAYS_UNABLE_TO_READ_TRANSLATIONDATA; 1064 $msg = "Unable to read translation-data - file maybe damaged: $file"; 1065 return Date_Holidays::raiseError($e, $msg); 1066 } 1067 return $this->_addTranslationData($data, $locale); 1068 } 1069 1070 /** 1071 * Add a language-file's content. Translations, properties, etc. for 1072 * holidays will be made available with the specified locale. 1073 * 1074 * @param array $data translated data 1075 * @param string $locale locale-code of the translation 1076 * 1077 * @access public 1078 * @return boolean true on success, otherwise a PEAR_ErrorStack object 1079 * @throws object PEAR_Errorstack 1080 */ 1081 function _addTranslationData($data, $locale) 1082 { 1083 foreach ($data['holidays']['holiday'] as $holiday) { 1084 $this->_addTranslationForHoliday($holiday['internal-name'], 1085 $locale, 1086 $holiday['translation']); 1087 1088 if (isset($holiday['properties']) && is_array($holiday['properties'])) { 1089 foreach ($holiday['properties'] as $propId => $propVal) { 1090 $this->_addStringPropertyForHoliday($holiday['internal-name'], 1091 $locale, 1092 $propId, 1093 $propVal); 1094 } 1095 } 1096 1097 } 1098 1099 if (Date_Holidays::errorsOccurred()) { 1100 return Date_Holidays::getErrorStack(); 1101 } 1102 1103 return true; 1104 } 1105 1106 /** 1107 * Remove a holiday from internal storage 1108 * 1109 * This method should be used within driver classes to unset holidays that 1110 * were inherited from parent-drivers 1111 * 1112 * @param $string $internalName internal name 1113 * 1114 * @access protected 1115 * @return boolean true on success, otherwise a PEAR_Error object 1116 * @throws object PEAR_Error DATE_HOLIDAYS_INVALID_INTERNAL_NAME 1117 */ 1118 function _removeHoliday($internalName) 1119 { 1120 if (! in_array($internalName, $this->_internalNames)) { 1121 $msg = "Couldn't remove holiday with this internal name: $internalName"; 1122 return Date_Holidays::raiseError(DATE_HOLIDAYS_INVALID_INTERNAL_NAME, 1123 $msg); 1124 } 1125 1126 if (isset($this->_dates[$internalName])) { 1127 unset($this->_dates[$internalName]); 1128 } 1129 $locales = array_keys($this->_titles); 1130 foreach ($locales as $locale) { 1131 if (isset($this->_titles[$locale][$internalName])) { 1132 unset($this->_titles[$locale][$internalName]); 1133 } 1134 } 1135 $index = array_search($internalName, $this->_internalNames); 1136 if (! is_null($index)) { 1137 unset($this->_internalNames[$index]); 1138 } 1139 return true; 1140 } 1141 1142 /** 1143 * Finds the best internally available locale for the specified one 1144 * 1145 * @param string $locale locale 1146 * 1147 * @access protected 1148 * @return string best locale available 1149 */ 1150 function _findBestLocale($locale) 1151 { 1152 /* exact locale is available */ 1153 if (in_array($locale, $this->_availableLocales)) { 1154 return $locale; 1155 } 1156 1157 /* first two letter are equal */ 1158 foreach ($this->_availableLocales as $aLocale) { 1159 if (strncasecmp($aLocale, $locale, 2) == 0) { 1160 return $aLocale; 1161 } 1162 } 1163 1164 /* no appropriate locale available, will use driver's internal locale */ 1165 return 'C'; 1166 } 1167 1168 /** 1169 * Returns date of a holiday 1170 * 1171 * @param string $internalName internal name for holiday 1172 * 1173 * @access public 1174 * @return object Date date of holiday as PEAR::Date object 1175 * on success, otherwise a PEAR_Error object 1176 * @throws object PEAR_Error DATE_HOLIDAYS_DATE_UNAVAILABLE 1177 * @throws object PEAR_Error DATE_HOLIDAYS_INVALID_INTERNAL_NAME 1178 */ 1179 function getHolidayDate($internalName) 1180 { 1181 if (! in_array($internalName, $this->_internalNames)) { 1182 $msg = 'Invalid internal name: ' . $internalName; 1183 return Date_Holidays::raiseError(DATE_HOLIDAYS_INVALID_INTERNAL_NAME, 1184 $msg); 1185 } 1186 1187 if (! isset($this->_dates[$internalName])) { 1188 $msg = 'Date for holiday with internal name ' . 1189 $internalName . ' is not available'; 1190 return Date_Holidays::raiseError(DATE_HOLIDAYS_DATE_UNAVAILABLE, $msg); 1191 } 1192 1193 return $this->_dates[$internalName]; 1194 } 1195 1196 /** 1197 * Returns dates of all holidays or those accepted by the applied filter. 1198 * 1199 * Structure of the returned array: 1200 * <pre> 1201 * array( 1202 * 'internalNameFoo' => object of type date, 1203 * 'internalNameBar' => object of type date 1204 * ) 1205 * </pre> 1206 * 1207 * @param Date_Holidays_Filter $filter filter-object (or an array !DEPRECATED!) 1208 * 1209 * @access public 1210 * @return array with holidays' dates on success, otherwise a PEAR_Error object 1211 * @throws object PEAR_Error DATE_HOLIDAYS_INVALID_INTERNAL_NAME 1212 * @uses getHolidayDate() 1213 */ 1214 function getHolidayDates($filter = null) 1215 { 1216 if (is_null($filter)) { 1217 $filter = new Date_Holidays_Filter_Blacklist(array()); 1218 } elseif (is_array($filter)) { 1219 $filter = new Date_Holidays_Filter_Whitelist($filter); 1220 } 1221 1222 $dates = array(); 1223 1224 foreach ($this->_internalNames as $internalName) { 1225 if ($filter->accept($internalName)) { 1226 $date = $this->getHolidayDate($internalName); 1227 if (Date_Holidays::isError($date)) { 1228 return $date; 1229 } 1230 $dates[$internalName] = $this->getHolidayDate($internalName); 1231 } 1232 } 1233 return $dates; 1234 } 1235 1236 /** 1237 * Sets the driver's locale 1238 * 1239 * @param string $locale locale 1240 * 1241 * @access public 1242 * @return void 1243 */ 1244 function setLocale($locale) 1245 { 1246 $this->_locale = $locale; 1247 //if possible, load the translation files for this locale 1248 $this->addTranslation($locale); 1249 } 1250 1251 /** 1252 * Sloppily compares two date objects (only year, month and day are compared). 1253 * Does not take the date's timezone into account. 1254 * 1255 * @param Date $d1 a date object 1256 * @param Date $d2 another date object 1257 * 1258 * @static 1259 * @access private 1260 * @return int 0 if the dates are equal, 1261 * -1 if d1 is before d2, 1262 * 1 if d1 is after d2 1263 */ 1264 function dateSloppyCompare($d1, $d2) 1265 { 1266 $d1->setTZ(new Date_TimeZone('UTC')); 1267 $d2->setTZ(new Date_TimeZone('UTC')); 1268 $days1 = Date_Calc::dateToDays($d1->day, $d1->month, $d1->year); 1269 $days2 = Date_Calc::dateToDays($d2->day, $d2->month, $d2->year); 1270 if ($days1 < $days2) return -1; 1271 if ($days1 > $days2) return 1; 1272 return 0; 1273 } 1274 /** 1275 * Find the date of the first monday in the specified year of the current year. 1276 * 1277 * @param integer $month month 1278 * 1279 * @access private 1280 * @return object Date date of first monday in specified month. 1281 */ 1282 function _calcFirstMonday($month) 1283 { 1284 $month = sprintf("%02d", $month); 1285 $date = new Date($this->_year . "-$month-01"); 1286 while ($date->getDayOfWeek() != 1) { 1287 $date = $date->getNextDay(); 1288 } 1289 return ($date); 1290 } 1291 /** 1292 * Find the date of the last monday in the specified year of the current year. 1293 * 1294 * @param integer $month month 1295 * 1296 * @access private 1297 * @return object Date date of last monday in specified month. 1298 */ 1299 function _calcLastMonday($month) 1300 { 1301 //work backwards from the first day of the next month. 1302 $month = sprintf("%02d", $month); 1303 $nm = ((int) $month ) + 1; 1304 if ($nm > 12) { 1305 $nm = 1; 1306 } 1307 $nm = sprintf("%02d", $nm); 1308 1309 $date = new Date($this->_year . "-$nm-01"); 1310 $date = $date->getPrevDay(); 1311 while ($date->getDayOfWeek() != 1) { 1312 $date = $date->getPrevDay(); 1313 } 1314 return ($date); 1315 } 1316 /** 1317 * Calculate Nth monday in a month 1318 * 1319 * @param int $month month 1320 * @param int $position position 1321 * 1322 * @access private 1323 * @return object Date date 1324 */ 1325 function _calcNthMondayInMonth($month, $position) 1326 { 1327 if ($position == 1) { 1328 $startday = '01'; 1329 } elseif ($position == 2) { 1330 $startday = '08'; 1331 } elseif ($position == 3) { 1332 $startday = '15'; 1333 } elseif ($position == 4) { 1334 $startday = '22'; 1335 } elseif ($position == 5) { 1336 $startday = '29'; 1337 } 1338 $month = sprintf("%02d", $month); 1339 1340 $date = new Date($this->_year . '-' . $month . '-' . $startday); 1341 while ($date->getDayOfWeek() != 1) { 1342 $date = $date->getNextDay(); 1343 } 1344 return $date; 1345 } 1346 1347 /** 1348 * Calculate Nth day of the week in a month 1349 * 1350 * @param int $position position 1351 * @param int $weekday day of the week starting from 1 == sunday 1352 * @param int $month month 1353 * 1354 * @access private 1355 * @return object Date date 1356 */ 1357 function _calcNthWeekDayInMonth($position, $weekday, $month) 1358 { 1359 if ($position == 1) { 1360 $startday = '01'; 1361 } elseif ($position == 2) { 1362 $startday = '08'; 1363 } elseif ($position == 3) { 1364 $startday = '15'; 1365 } elseif ($position == 4) { 1366 $startday = '22'; 1367 } elseif ($position == 5) { 1368 $startday = '29'; 1369 } 1370 $month = sprintf("%02d", $month); 1371 1372 $date = new Date($this->_year . '-' . $month . '-' . $startday); 1373 while ($date->getDayOfWeek() != $weekday) { 1374 $date = $date->getNextDay(); 1375 } 1376 return $date; 1377 } 1378 1379 /** 1380 * Converts the date to the specified no of days from the given date 1381 * 1382 * To subtract days use a negative value for the '$pn_days' parameter 1383 * 1384 * @param Date $date Date object 1385 * @param int $pn_days days to add 1386 * 1387 * @return Date 1388 * @access protected 1389 */ 1390 function _addDays($date, $pn_days) 1391 { 1392 $new_date = new Date($date); 1393 list($new_date->year, $new_date->month, $new_date->day) = 1394 explode(' ', 1395 Date_Calc::daysToDate(Date_Calc::dateToDays($date->day, 1396 $date->month, 1397 $date->year) + 1398 $pn_days, 1399 '%Y %m %d')); 1400 if (isset($new_date->on_standardyear)) { 1401 $new_date->on_standardyear = $new_date->year; 1402 $new_date->on_standardmonth = $new_date->month; 1403 $new_date->on_standardday = $new_date->day; 1404 } 1405 return $new_date; 1406 } 1407 1408} 1409?> 1410