PageRenderTime 56ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

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