PageRenderTime 4ms CodeModel.GetById 4ms app.highlight 28ms RepoModel.GetById 0ms app.codeStats 1ms

/php-pear-Date-Holidays-0.21.6/Date_Holidays-0.21.6/Date/Holidays/Driver.php

#
PHP | 1417 lines | 678 code | 110 blank | 629 comment | 120 complexity | da0e7c55200556b0f273ca4d4d97f52c MD5 | raw file
   1<?php
   2/* vim: set expandtab tabstop=4 shiftwidth=4: */
   3/**
   4 * Driver.php
   5 *
   6 * PHP Version 4
   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: Driver.php 321620 2011-12-31 20:59:14Z danielc $
  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: Driver.php 321620 2011-12-31 20:59:14Z danielc $
  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: Driver.php 321620 2011-12-31 20:59:14Z danielc $
  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: Driver.php 321620 2011-12-31 20:59:14Z danielc $
 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        include_once 'XML/Unserializer.php';
1021        $options = array('parseAttributes' => false,
1022                         'attributesArray' => false,
1023                         'keyAttribute'    => array('property' => 'id'),
1024                         'forceEnum'       => array('holiday'));
1025
1026        // unserialize the document
1027        $unserializer = new XML_Unserializer($options);
1028        $status       = $unserializer->unserialize($file, true);
1029
1030        if (PEAR::isError($status)) {
1031            return Date_Holidays::raiseError($status->getCode(),
1032                                             $status->getMessage());
1033        }
1034
1035        $content = $unserializer->getUnserializedData();
1036        if (PEAR::isError($content)) {
1037            return Date_Holidays::raiseError($content->getCode(),
1038                                             $content->getMessage());
1039        }
1040        return $this->_addTranslationData($content, $locale);
1041    }
1042
1043    /**
1044     * Add a compiled language-file's content
1045     *
1046     * The language-file's content will be unserialized and translations,
1047     * properties, etc. for holidays will be made available with the
1048     * specified locale.
1049     *
1050     * @param string $file   filename of the compiled language file
1051     * @param string $locale locale-code of the translation
1052     *
1053     * @access   public
1054     * @return   boolean true on success, otherwise a PEAR_ErrorStack object
1055     * @throws   object PEAR_Errorstack
1056     */
1057    function addCompiledTranslationFile($file, $locale)
1058    {
1059        if (! file_exists($file)) {
1060            Date_Holidays::raiseError(DATE_HOLIDAYS_LANGUAGEFILE_NOT_FOUND,
1061                    'Language-file not found: ' . $file);
1062            return Date_Holidays::getErrorStack();
1063        }
1064
1065        $content = file_get_contents($file);
1066        if ($content === false) {
1067            return false;
1068        }
1069        $data = unserialize($content);
1070        if ($data === false) {
1071            $e   = DATE_HOLIDAYS_UNABLE_TO_READ_TRANSLATIONDATA;
1072            $msg = "Unable to read translation-data - file maybe damaged: $file";
1073            return Date_Holidays::raiseError($e, $msg);
1074        }
1075        return $this->_addTranslationData($data, $locale);
1076    }
1077
1078    /**
1079     * Add a language-file's content. Translations, properties, etc. for
1080     * holidays will be made available with the specified locale.
1081     *
1082     * @param array  $data   translated data
1083     * @param string $locale locale-code of the translation
1084     *
1085     * @access   public
1086     * @return   boolean true on success, otherwise a PEAR_ErrorStack object
1087     * @throws   object PEAR_Errorstack
1088     */
1089    function _addTranslationData($data, $locale)
1090    {
1091        foreach ($data['holidays']['holiday'] as $holiday) {
1092            $this->_addTranslationForHoliday($holiday['internal-name'],
1093                                             $locale,
1094                                             $holiday['translation']);
1095
1096            if (isset($holiday['properties']) && is_array($holiday['properties'])) {
1097                foreach ($holiday['properties'] as $propId => $propVal) {
1098                    $this->_addStringPropertyForHoliday($holiday['internal-name'],
1099                                                        $locale,
1100                                                        $propId,
1101                                                        $propVal);
1102                }
1103            }
1104
1105        }
1106
1107        if (Date_Holidays::errorsOccurred()) {
1108            return Date_Holidays::getErrorStack();
1109        }
1110
1111        return true;
1112    }
1113
1114    /**
1115     * Remove a holiday from internal storage
1116     *
1117     * This method should be used within driver classes to unset holidays that
1118     * were inherited from parent-drivers
1119     *
1120     * @param $string $internalName internal name
1121     *
1122     * @access   protected
1123     * @return   boolean     true on success, otherwise a PEAR_Error object
1124     * @throws   object PEAR_Error   DATE_HOLIDAYS_INVALID_INTERNAL_NAME
1125     */
1126    function _removeHoliday($internalName)
1127    {
1128        if (! in_array($internalName, $this->_internalNames)) {
1129            $msg = "Couldn't remove holiday with this internal name: $internalName";
1130            return Date_Holidays::raiseError(DATE_HOLIDAYS_INVALID_INTERNAL_NAME,
1131                                             $msg);
1132        }
1133
1134        if (isset($this->_dates[$internalName])) {
1135            unset($this->_dates[$internalName]);
1136        }
1137        $locales = array_keys($this->_titles);
1138        foreach ($locales as $locale) {
1139            if (isset($this->_titles[$locale][$internalName])) {
1140                unset($this->_titles[$locale][$internalName]);
1141            }
1142        }
1143        $index = array_search($internalName, $this->_internalNames);
1144        if (! is_null($index)) {
1145            unset($this->_internalNames[$index]);
1146        }
1147        return true;
1148    }
1149
1150    /**
1151     * Finds the best internally available locale for the specified one
1152     *
1153     * @param string $locale locale
1154     *
1155     * @access   protected
1156     * @return   string  best locale available
1157     */
1158    function _findBestLocale($locale)
1159    {
1160        /* exact locale is available */
1161        if (in_array($locale, $this->_availableLocales)) {
1162            return $locale;
1163        }
1164
1165        /* first two letter are equal */
1166        foreach ($this->_availableLocales as $aLocale) {
1167            if (strncasecmp($aLocale, $locale, 2) == 0) {
1168                return $aLocale;
1169            }
1170        }
1171
1172        /* no appropriate locale available, will use driver's internal locale */
1173        return 'C';
1174    }
1175
1176    /**
1177     * Returns date of a holiday
1178     *
1179     * @param string $internalName internal name for holiday
1180     *
1181     * @access   public
1182     * @return   object Date             date of holiday as PEAR::Date object
1183     *                                   on success, otherwise a PEAR_Error object
1184     * @throws   object PEAR_Error       DATE_HOLIDAYS_DATE_UNAVAILABLE
1185     * @throws   object PEAR_Error       DATE_HOLIDAYS_INVALID_INTERNAL_NAME
1186     */
1187    function getHolidayDate($internalName)
1188    {
1189        if (! in_array($internalName, $this->_internalNames)) {
1190            $msg = 'Invalid internal name: ' . $internalName;
1191            return Date_Holidays::raiseError(DATE_HOLIDAYS_INVALID_INTERNAL_NAME,
1192                                             $msg);
1193        }
1194
1195        if (! isset($this->_dates[$internalName])) {
1196            $msg = 'Date for holiday with internal name ' .
1197                   $internalName . ' is not available';
1198            return Date_Holidays::raiseError(DATE_HOLIDAYS_DATE_UNAVAILABLE, $msg);
1199        }
1200
1201        return $this->_dates[$internalName];
1202    }
1203
1204    /**
1205     * Returns dates of all holidays or those accepted by the applied filter.
1206     *
1207     * Structure of the returned array:
1208     * <pre>
1209     * array(
1210     *   'internalNameFoo' => object of type date,
1211     *   'internalNameBar' => object of type date
1212     * )
1213     * </pre>
1214     *
1215     * @param Date_Holidays_Filter $filter filter-object (or an array !DEPRECATED!)
1216     *
1217     * @access   public
1218     * @return   array with holidays' dates on success, otherwise a PEAR_Error object
1219     * @throws   object PEAR_Error   DATE_HOLIDAYS_INVALID_INTERNAL_NAME
1220     * @uses     getHolidayDate()
1221     */
1222    function getHolidayDates($filter = null)
1223    {
1224        if (is_null($filter)) {
1225            $filter = new Date_Holidays_Filter_Blacklist(array());
1226        } elseif (is_array($filter)) {
1227            $filter = new Date_Holidays_Filter_Whitelist($filter);
1228        }
1229
1230        $dates = array();
1231
1232        foreach ($this->_internalNames as $internalName) {
1233            if ($filter->accept($internalName)) {
1234                $date = $this->getHolidayDate($internalName);
1235                if (Date_Holidays::isError($date)) {
1236                    return $date;
1237                }
1238                $dates[$internalName] = $this->getHolidayDate($internalName);
1239            }
1240        }
1241        return $dates;
1242    }
1243
1244    /**
1245     * Sets the driver's locale
1246     *
1247     * @param string $locale locale
1248     *
1249     * @access   public
1250     * @return   void
1251     */
1252    function setLocale($locale)
1253    {
1254        $this->_locale = $locale;
1255        //if possible, load the translation files for this locale
1256        $this->addTranslation($locale);
1257    }
1258
1259    /**
1260     * Sloppily compares two date objects (only year, month and day are compared).
1261     * Does not take the date's timezone into account.
1262     *
1263     * @param Date $d1 a date object
1264     * @param Date $d2 another date object
1265     *
1266     * @static
1267     * @access private
1268     * @return int 0 if the dates are equal,
1269     *             -1 if d1 is before d2,
1270     *             1 if d1 is after d2
1271     */
1272    function dateSloppyCompare($d1, $d2)
1273    {
1274        $d1->setTZ(new Date_TimeZone('UTC'));
1275        $d2->setTZ(new Date_TimeZone('UTC'));
1276        $days1 = Date_Calc::dateToDays($d1->day, $d1->month, $d1->year);
1277        $days2 = Date_Calc::dateToDays($d2->day, $d2->month, $d2->year);
1278        if ($days1 < $days2) return -1;
1279        if ($days1 > $days2) return 1;
1280        return 0;
1281    }
1282    /**
1283     * Find the date of the first monday in the specified year of the current year.
1284     *
1285     * @param integer $month month
1286     *
1287     * @access   private
1288     * @return   object Date date of first monday in specified month.
1289     */
1290    function _calcFirstMonday($month)
1291    {
1292        $month = sprintf("%02d", $month);
1293        $date = new Date($this->_year . "-$month-01");
1294        while ($date->getDayOfWeek() != 1) {
1295            $date = $date->getNextDay();
1296        }
1297        return ($date);
1298    }
1299    /**
1300     * Find the date of the last monday in the specified year of the current year.
1301     *
1302     * @param integer $month month
1303     *
1304     * @access   private
1305     * @return   object Date date of last monday in specified month.
1306     */
1307    function _calcLastMonday($month)
1308    {
1309        //work backwards from the first day of the next month.
1310        $month = sprintf("%02d", $month);
1311        $nm = ((int) $month ) + 1;
1312        if ($nm > 12) {
1313            $nm = 1;
1314        }
1315        $nm = sprintf("%02d", $nm);
1316
1317        $date = new Date($this->_year . "-$nm-01");
1318        $date = $date->getPrevDay();
1319        while ($date->getDayOfWeek() != 1) {
1320            $date = $date->getPrevDay();
1321        }
1322        return ($date);
1323    }
1324    /**
1325     * Calculate Nth monday in a month
1326     *
1327     * @param int $month    month
1328     * @param int $position position
1329     *
1330     * @access   private
1331     * @return   object Date date
1332     */
1333    function _calcNthMondayInMonth($month, $position)
1334    {
1335        if ($position  == 1) {
1336            $startday = '01';
1337        } elseif ($position == 2) {
1338            $startday = '08';
1339        } elseif ($position == 3) {
1340            $startday = '15';
1341        } elseif ($position == 4) {
1342            $startday = '22';
1343        } elseif ($position == 5) {
1344            $startday = '29';
1345        }
1346        $month = sprintf("%02d", $month);
1347
1348        $date = new Date($this->_year . '-' . $month . '-' . $startday);
1349        while ($date->getDayOfWeek() != 1) {
1350            $date = $date->getNextDay();
1351        }
1352        return $date;
1353    }
1354
1355    /**
1356     * Calculate Nth day of the week in a month
1357     *
1358     * @param int $position position
1359     * @param int $weekday  day of the week starting from 1 == sunday
1360     * @param int $month    month
1361     *
1362     * @access   private
1363     * @return   object Date date
1364     */
1365    function _calcNthWeekDayInMonth($position, $weekday, $month)
1366    {
1367        if ($position  == 1) {
1368            $startday = '01';
1369        } elseif ($position == 2) {
1370            $startday = '08';
1371        } elseif ($position == 3) {
1372            $startday = '15';
1373        } elseif ($position == 4) {
1374            $startday = '22';
1375        } elseif ($position == 5) {
1376            $startday = '29';
1377        }
1378        $month = sprintf("%02d", $month);
1379
1380        $date = new Date($this->_year . '-' . $month . '-' . $startday);
1381        while ($date->getDayOfWeek() != $weekday) {
1382            $date = $date->getNextDay();
1383        }
1384        return $date;
1385    }
1386
1387    /**
1388     * Converts the date to the specified no of days from the given date
1389     *
1390     * To subtract days use a negative value for the '$pn_days' parameter
1391     *
1392     * @param Date $date Date object
1393     * @param int $pn_days days to add
1394     *
1395     * @return   Date
1396     * @access   protected
1397     */
1398    function _addDays($date, $pn_days)
1399    {
1400        $new_date = new Date($date);
1401        list($new_date->year, $new_date->month, $new_date->day) =
1402            explode(' ',
1403                    Date_Calc::daysToDate(Date_Calc::dateToDays($date->day,
1404                                                                $date->month,
1405                                                                $date->year) +
1406                                          $pn_days,
1407                                          '%Y %m %d'));
1408        if (isset($new_date->on_standardyear)) {
1409            $new_date->on_standardyear = $new_date->year;
1410            $new_date->on_standardmonth = $new_date->month;
1411            $new_date->on_standardday = $new_date->day;
1412        }
1413        return $new_date;
1414    }
1415
1416}
1417?>