5 11

[php][PEAR]PEAR::Date_Holidays_Driver_Japanese 0.2.2リリース

Tag: phpuechoco @ 20 : 22 : 53

PEAR::Date_Holidays_Driver_Japanese 0.2.2 - Do You PHP はてな

うえちょこ@ぼろぐ » [php][pear]Date_Holidays_Driver_Japaneseを使ってみた

うえちょこ@ぼろぐ » [php][PEAR]Date_Holidays_Driver::setYear()が変

眠る開発屋blog » Date_Holidays_Driver::setYear()とか

うえちょこ@ぼろぐ » [php][PEAR]Date_Holidays_Driver::setYear()が変の原因調査

4連荘で同じライブラリの記事を書くことも珍しいです。今朝方、Date_Holidays_Driver::setYear()の挙動がおかしいとつぶやいたところ、Date_Holidays_Driver_Japaneseの開発者のshimookaさんに伝わってしまったようで、対応してくださいました。ありがとうございます。

どうやらトリッキーな方法でうまく解決したようです。これによって、Date_Holidays_Driver::setYear()の挙動がおかしいために、毎回インスタンスを作り直して翻訳ファイルを再設定していたところを、setYear()だけで済むようになりました。ただし、内部で同等の処理をするようにしただけなので、コストはほぼ変わっていません。

ところで、

眠る開発屋blog » Date_Holidays_Driver_Japaneseを使ってみるとか2

にもありますが、Date_Holidays_Driver::isHoliday()は非常にコストが高いので、使わないほうがいいですよ。私の代替案としてはDate_Holidays_Driver::getHolidayForDate()がnullかどうかを調べます。こちらのほうが断然早いです。


5 11

[php][PEAR]Date_Holidays_Driver::setYear()が変の原因調査

Tag: phpuechoco @ 18 : 36 : 59

今朝のうえちょこ@ぼろぐ » [php][PEAR]Date_Holidays_Driver::setYear()が変がやっぱり気になったので、少しだけ調査。

※PEAR::Date_Holidays_Driver_Japanese 0.2.2がリリースされました。下記不具合を包括的に解決しています。0.2.0以前を使用してる方は、アップグレードすることを強くお勧めします。

PEAR::Date_Holidays_Driver_Japanese 0.2.2 - Do You PHP はてな

1.$this->_internalNamesが増殖する件 ===================

Date_Holidays_Driver::serYear()メソッドを呼び出すと、内部の仕様でDate_Holidays_Driver::_buildHolidays()メソッドが呼ばれます。_buildHolidays()メソッドは、各国別のドライバがその国々の祝日を登録するメソッドです。

_buildHolidays()メソッドの内部では、祝日の数だけDate_Holidays_Driver::_addHoliday()メソッドを呼び出して、祝日を登録します。_addHoliday()メソッドだけ抜き出してみます。

php:
  1. /**
  2. * Adds a holiday to the driver's holidays
  3. *
  4. * @param string $internalName internal name - must not contain characters
  5. *                              that aren't allowed as variable-names
  6. * @param mixed  $date         date (timestamp | string | PEAR::Date object)
  7. * @param string $title        holiday title
  8. *
  9. * @access   protected
  10. * @return   void
  11. */
  12. function _addHoliday($internalName, $date, $title)
  13. {
  14. if (! is_a($date, 'Date')) {
  15. $date = new Date($date);
  16. }
  17.  
  18. $this->_dates[$internalName]       = $date;
  19. $this->_titles['C'][$internalName] = $title;
  20. $isodate                           = mktime(0, 0, 0,
  21. $date->getMonth(),
  22. $date->getDay(),
  23. $date->getYear());
  24. if (!isset($this->_holidays[$isodate])) {
  25. $this->_holidays[$isodate] = array();
  26. }
  27. array_push($this->_holidays[$isodate], $internalName);
  28. array_push($this->_internalNames, $internalName);
  29. }

$this->_internalNamesが増殖する原因は、この_addHoliday()メソッドの最後のarray_push() x 2です。2回目以降のビルドのときに呼び出されると、$this->_datesや$this->_titlesは与えられた$internalNamesの祝日情報を上書きするのですが、$this->_holidays[$isodate]と$this->_internalNamesだけは上書きせず、後ろに追加します。これが増殖の原因。ただし、増殖することがバグであるかどうかは知りません(そういう仕様が正しいかもしれない)。しかし、素人目ではバグの気がしてなりません。

なお、ライブラリの修正方法としては、最後のarray_push()の前に、しっかりとif (! in_array())の条件分岐をしてあげることです。

2.振替休日と国民の祝日が表示されない件 ===================

2回目以降の再構築のとき、振替休日と国民の祝日が表示されなくなります。実は

$obj->getHolidayForDate($d, 'ja_JP');

つまり、日本語の翻訳ファイルを使用したときには表示されないのですが、

$obj->getHolidayForDate($d, 'C');

('C'というのは、内部仕様で、内部英名を現すロケール)を用いて英名にすると振替休日と国民の祝日が表示されます。

ここからわかることは、翻訳ファイルがらみで何かがおかしいということです。

さてさて、中身を見ていきます。

Date_Holidays_Driver_Japaneseでは、2回目以降の再構築の際、振替休日と国民の祝日の祝日情報を消去します。消去にはDate_Holidays_Driver::_removeHoliday()メソッドを用いています。そして、消去し終わると、すべての祝日を_addHolidays()メソッドで登録し、その上で振替休日と国民の祝日を計算します。

_removeHoliday()メソッドでは、指定した祝日に関する(翻訳情報を含む)すべての祝日情報を消去します。
_addHolidays()メソッドでは、内部英名の祝日情報を登録します。

よろしくないのは、_removeHoliday()は、翻訳情報を消してしまうのです。

安易な考えですが、_removeHoliday()メソッドでの翻訳情報の削除部分を取り除いてしまえばいいのでは!?と思いついてやってみたのですが、うまくいきませんでした。翻訳ファイルを読み込むDate_Holidays_Driver::addTranslationFile()メソッドは、どうやら、現在登録されている祝日だけの翻訳情報を読み込むようです。余分なデータを読み込まないのはいいことですが、年が変わって再構築でひょっこり新しい祝日が出てきてしまうと翻訳情報がないので、そのロケールでは表示できないのです。これが原因です。

エンドユーザー側の解決策としては、setYear()したら、addTranslationFile()で翻訳ファイルを読み直すのが手っ取り早いです。

ライブラリ自体の解決策としては、「this->_titles」とは別の系統に翻訳情報のマッピング変数を用意して、そこに「現在登録されている祝日のものだけ」読み込むのではなく「すべて」読み込んで、内部ロケール以外のロケールで表示するような要求があった場合、マッピング変数を用いて表示するとか、そういう感じですかね。とっても面倒な変更です。他にスマートな方法があればいいのですが。。。

そんなわけで、調査終了。原因がわかったのでスッキリ。ライブラリ自体にはモヤっとだけど。


5 11

[php][PEAR]Date_Holidays_Driver::setYear()が変

Tag: phpuechoco @ 03 : 39 : 33

正確に言うと、Date_Holidays_Driver_Japanese::setYear()の挙動が変。

※PEAR::Date_Holidays_Driver_Japanese 0.2.2がリリースされました。下記不具合を包括的に解決しています。0.2.0以前を使用してる方は、アップグレードすることを強くお勧めします。

PEAR::Date_Holidays_Driver_Japanese 0.2.2 - Do You PHP はてな

Date_Holidaysクラスのfactory()メソッドはDriver名を指定するのと同時に、西暦を指定して祝日リストを生成します。西暦を指定していない場合は、内部で自動的に現在の西暦が採用されます。もし西暦が違う祝日を一緒に扱う場合(たとえば2008年と2009年)、ドライバをもう1度生成し、翻訳ファイルを登録しなければなりません。

それは面倒だと思って、APIドキュメントをあさっていると、Date_Holidays_Driver::setYear()というメソッドを見つけました。どうやら、西暦をセットしなおした上で祝日の再構築を行ってくれるようです。エンドユーザー・マニュアルがないので100%とは言い切れませんが、ソースコードからはそう判断できます。

で、早速使ってみたソースコードがこちら。

php:
  1. /**
  2. * Date_Holidays_Driver_Japanseを用いて
  3. * 祝日名を取得する(挙動がおかしい)
  4. *
  5. * 2008/05/11
  6. *
  7. * Author:   uechoco
  8. * 動作確認: PHP 5.2.5
  9. * require:  Date_Holidays (推奨バージョン:0.18.0)
  10. * require:  Date_Holidays_Driver_Japanese 0.2.0
  11. *
  12. */
  13.  
  14. // Date_Holidaysクラスの読み込み
  15. require_once "Date/Holidays.php";
  16.  
  17. // 言語ファイルの読み込み(PEARのdataディレクトリ内)
  18. define('LANG_FILE', 'C:/php5/pear/data/Date_Holidays_Driver_Japanese/lang');
  19.  
  20. // Date_Holidaysオブジェクトの生成(西暦を省略すると現在の西暦になる)
  21. $obj =& Date_Holidays::factory('Japanese');
  22. if (Date_Holidays::isError($obj)) {
  23. die('Factory was unable to produce driver-object');
  24. }
  25.  
  26. // 翻訳ファイルをロケール=ja_JPとして登録
  27. $obj->addTranslationFile(LANG_FILE . '/Japanese/ja_JP.xml', 'ja_JP');
  28. if (Date_Holidays::isError($obj)) {
  29. die($obj->getMessage());
  30. }
  31.  
  32. $lastYear = 0;
  33. $beginTime = mktime(0, 0, 0, 1, 1, 2008);
  34. for ($i = 0; $i <731; $i++) {
  35. $t = $beginTime + $i * 86400;
  36. $d = strftime('%Y-%m-%d', $t);
  37. // 西暦が更新されたら祝日の再構成
  38. $y = strftime('%Y', $t);
  39. if ($lastYear != $y) {
  40. $obj->setYear(intval($y));
  41. $lastYear = $y;
  42. //デバッグ用にオブジェクトを直接吐く
  43. //echo "
  44.  
  45. <hr /><span style="color: red;">\n\n";
  46. //print_r($obj-&gt;_internalNames);
  47. //echo "</span>
  48.  
  49. <hr />\n\n";
  50.  
  51. }
  52.  
  53. $holiday = $obj-&gt;getHolidayForDate($d, 'ja_JP');
  54. if (!is_null($holiday))
  55. printHolidayInfo($holiday, $d);
  56. }
  57.  
  58. function printHolidayInfo($holiday, $date = '')
  59. {
  60. echo $date . " =========
  61. \n";
  62. printf("名前:%s
  63. \n", $holiday-&gt;getTitle());
  64. //printf("内部名:%s
  65. \n", $holiday-&gt;getInternalName());
  66. //printf("プロパティ:%s
  67. \n", print_r($holiday-&gt;getProperties(), true));
  68. //printf("Dateクラス:%s
  69. \n", print_r($holiday-&gt;getDate(), true));
  70. }
  71. exit;
  72. ?&gt;

で、出力結果がこちら。

CODE:
  1. 2008-01-01 =========
  2. 名前:元日
  3. 2008-01-14 =========
  4. 名前:成人の日
  5. 2008-02-11 =========
  6. 名前:建国記念の日
  7. 2008-03-20 =========
  8. 名前:春分の日
  9. 2008-04-29 =========
  10. 名前:昭和の日
  11. 2008-05-03 =========
  12. 名前:憲法記念日
  13. 2008-05-04 =========
  14. 名前:みどりの日
  15. 2008-05-05 =========
  16. 名前:こどもの日
  17. 2008-07-21 =========
  18. 名前:海の日
  19. 2008-09-15 =========
  20. 名前:敬老の日
  21. 2008-09-23 =========
  22. 名前:秋分の日
  23. 2008-10-13 =========
  24. 名前:体育の日
  25. 2008-11-03 =========
  26. 名前:文化の日
  27. 2008-11-23 =========
  28. 名前:勤労感謝の日
  29. 2008-12-23 =========
  30. 名前:天皇誕生日
  31. 2009-01-01 =========
  32. 名前:元日
  33. 2009-01-12 =========
  34. 名前:成人の日
  35. 2009-02-11 =========
  36. 名前:建国記念の日
  37. 2009-03-20 =========
  38. 名前:春分の日
  39. 2009-04-29 =========
  40. 名前:昭和の日
  41. 2009-05-03 =========
  42. 名前:憲法記念日
  43. 2009-05-04 =========
  44. 名前:みどりの日
  45. 2009-05-05 =========
  46. 名前:こどもの日
  47. 2009-07-20 =========
  48. 名前:海の日
  49. 2009-09-21 =========
  50. 名前:敬老の日
  51. 2009-09-23 =========
  52. 名前:秋分の日
  53. 2009-10-12 =========
  54. 名前:体育の日
  55. 2009-11-03 =========
  56. 名前:文化の日
  57. 2009-11-23 =========
  58. 名前:勤労感謝の日
  59. 2009-12-23 =========
  60. 名前:天皇誕生日

パッと見でよさそうに見えますが、よく見てみると、振替休日と国民の休日が表示されていないことに気がつきます。動作確認環境はWindows/XAMPP PHP5.2.5です。

少しだけ動作確認してみました。

★まず、オブジェクトを西暦が変わるたびに生成しなおすと、正常な結果になります。

php:
  1. $lastYear = 0;
  2. $beginTime = mktime(0, 0, 0, 1, 1, 2008);
  3. for ($i = 0; $i &lt;731; $i++) {
  4. $t = $beginTime + $i * 86400;
  5. $d = strftime('%Y-%m-%d', $t);
  6. // 西暦が更新されたら祝日の再構成
  7. $y = strftime('%Y', $t);
  8. if ($lastYear != $y) {
  9. // 西暦を指定してDate_Holidaysオブジェクトの生成
  10. $obj =&amp; Date_Holidays::factory('Japanese', $y);
  11. if (Date_Holidays::isError($obj)) {
  12. die('Factory was unable to produce driver-object');
  13. }
  14. // 翻訳ファイルをロケール=ja_JPとして登録
  15. $obj-&gt;addTranslationFile(LANG_FILE . '/Japanese/ja_JP.xml', 'ja_JP');
  16. if (Date_Holidays::isError($obj)) {
  17. die($obj-&gt;getMessage());
  18. }
  19. $lastYear = $y;
  20. }
  21. $holiday = $obj-&gt;getHolidayForDate($d, 'ja_JP');
  22. if (!is_null($holiday))
  23. printHolidayInfo($holiday, $d);
  24. }

★setYear()の直後に、$obj->_internalNamesを吐き出してみると、serYear()する度に$_internalNames配列が初期化されておらず、末尾に追加されていることがわかります。おそらく、$objに含まれる他のプロパティにも似たような現象が見られるかもしれません。

CODE:
  1. Array
  2. (
  3. [0] =&gt; newYearsDay
  4. [1] =&gt; comingOfAgeDay
  5. [2] =&gt; nationalFoundationDay
  6. [3] =&gt; Vernal Equinox Day
  7. [4] =&gt; showaDay
  8. [5] =&gt; constitutionMemorialDay
  9. [6] =&gt; greeneryDay
  10. [7] =&gt; childrensDay
  11. [8] =&gt; marineDay
  12. [9] =&gt; respectfortheAgedDay
  13. [10] =&gt; autumnalEquinoxDay
  14. [11] =&gt; healthandSportsDay
  15. [12] =&gt; nationalCultureDay
  16. [13] =&gt; laborThanksgivingDay
  17. [14] =&gt; emperorsBirthday
  18. [17] =&gt; newYearsDay
  19. [18] =&gt; comingOfAgeDay
  20. [19] =&gt; nationalFoundationDay
  21. [20] =&gt; Vernal Equinox Day
  22. [21] =&gt; showaDay
  23. [22] =&gt; constitutionMemorialDay
  24. [23] =&gt; greeneryDay
  25. [24] =&gt; childrensDay
  26. [25] =&gt; marineDay
  27. [26] =&gt; respectfortheAgedDay
  28. [27] =&gt; autumnalEquinoxDay
  29. [28] =&gt; healthandSportsDay
  30. [29] =&gt; nationalCultureDay
  31. [30] =&gt; laborThanksgivingDay
  32. [31] =&gt; emperorsBirthday
  33. [32] =&gt; substituteHolidayForgreeneryDay
  34. [33] =&gt; substituteHolidayForlaborThanksgivingDay
  35. )
  36. Array
  37. (
  38. [0] =&gt; newYearsDay
  39. [1] =&gt; comingOfAgeDay
  40. [2] =&gt; nationalFoundationDay
  41. [3] =&gt; Vernal Equinox Day
  42. [4] =&gt; showaDay
  43. [5] =&gt; constitutionMemorialDay
  44. [6] =&gt; greeneryDay
  45. [7] =&gt; childrensDay
  46. [8] =&gt; marineDay
  47. [9] =&gt; respectfortheAgedDay
  48. [10] =&gt; autumnalEquinoxDay
  49. [11] =&gt; healthandSportsDay
  50. [12] =&gt; nationalCultureDay
  51. [13] =&gt; laborThanksgivingDay
  52. [14] =&gt; emperorsBirthday
  53. [17] =&gt; newYearsDay
  54. [18] =&gt; comingOfAgeDay
  55. [19] =&gt; nationalFoundationDay
  56. [20] =&gt; Vernal Equinox Day
  57. [21] =&gt; showaDay
  58. [22] =&gt; constitutionMemorialDay
  59. [23] =&gt; greeneryDay
  60. [24] =&gt; childrensDay
  61. [25] =&gt; marineDay
  62. [26] =&gt; respectfortheAgedDay
  63. [27] =&gt; autumnalEquinoxDay
  64. [28] =&gt; healthandSportsDay
  65. [29] =&gt; nationalCultureDay
  66. [30] =&gt; laborThanksgivingDay
  67. [31] =&gt; emperorsBirthday
  68. [34] =&gt; newYearsDay
  69. [35] =&gt; comingOfAgeDay
  70. [36] =&gt; nationalFoundationDay
  71. [37] =&gt; Vernal Equinox Day
  72. [38] =&gt; showaDay
  73. [39] =&gt; constitutionMemorialDay
  74. [40] =&gt; greeneryDay
  75. [41] =&gt; childrensDay
  76. [42] =&gt; marineDay
  77. [43] =&gt; respectfortheAgedDay
  78. [44] =&gt; autumnalEquinoxDay
  79. [45] =&gt; nationalHolidayBeforeAutumnalEquinoxDay
  80. [46] =&gt; healthandSportsDay
  81. [47] =&gt; nationalCultureDay
  82. [48] =&gt; laborThanksgivingDay
  83. [49] =&gt; emperorsBirthday
  84. [50] =&gt; substituteHolidayForconstitutionMemorialDay
  85. )

setYear()を使うと振替休日と国民の休日だけ表示されないので、Date_Holidays_Driver_Japaneseがおかしいのかな?って思ったりもしたんですが、内部名が初期化されていないのはDate_Holidays_Driverもおかしい気がしないでもないのですが、今眠くて深追いする気が起きません(ぉぃ。

解決策としては、西暦が変わったら、オブジェクトを新規に生成しなおすのが早そうです。


5 11

[php][pear]Date_Holidays_Driver_Japaneseを使ってみた

Tag: phpuechoco @ 02 : 45 : 05

前から気になっていたので、使ってみました。Date_Holidays_Driver_Japanese

PEAR::Date_Holidays_Driver_Japanese 0.2.0 - Do You PHP はてな

特徴としては、

  • 振替休日 OK
  • 国民の休日 OK
  • 春分・秋分の日 OK(天文学的な計算が必要)
  • ハッピーマンデー OK
  • 古い休日 OK(2006年04月29日="みどりの日")

とまぁ、至れり尽くせりで感動です。

そんなわけで、使ってみました。とても汚いソースコードですね。

php:
  1. <?php
  2.  
  3. /**
  4.  * Date_Holidays_Driver_Japanseを用いて
  5.  * 祝日名を取得する
  6.  *
  7.  * 2008/05/11
  8.  *
  9.  * Author:   uechoco
  10.  * 動作確認: PHP 5.2.5
  11.  * require:  Date_Holidays (推奨バージョン:0.18.0)
  12.  * require:  Date_Holidays_Driver_Japanese 0.2.0
  13.  *
  14.  */
  15.  
  16. // Date_Holidaysクラスの読み込み
  17. require_once "Date/Holidays.php";
  18.  
  19. // 言語ファイルの読み込み(PEARのdataディレクトリ内)
  20. define('LANG_FILE', 'C:/php5/pear/data/Date_Holidays_Driver_Japanese/lang');
  21.  
  22. // 祝日かどうかを判断する
  23. $pattern = array(
  24.     '2008' => array(
  25.         '2008-01-01', // 元旦
  26.         '2008-01-14', // 成人の日
  27.         '2008-02-11', // 建国記念の日
  28.         '2008-03-20', // 春分の日
  29.         '2008-04-29', // 昭和の日
  30.         '2008-05-03', // 憲法記念日
  31.         '2008-05-04', // みどりの日
  32.         '2008-05-05', // こどもの日
  33.         '2008-05-06', // (振替休日)
  34.         '2008-07-21', // 春分の日
  35.         '2008-09-15', // 敬老の日
  36.         '2008-09-23', // 秋分の日
  37.         '2008-10-13', // 体育の日
  38.         '2008-11-03', // 文化の日
  39.         '2008-11-23', // 勤労感謝の日
  40.         '2008-11-24', // (振替休日)
  41.         '2008-12-23', // 天皇誕生日
  42.     ),
  43.     '2009' => array(
  44.         '2009-01-01', // 元日
  45.         '2009-01-12', // 成人の日
  46.         '2009-02-11', // 建国記念の日
  47.         '2009-03-20', // 春分の日
  48.         '2009-04-29', // 昭和の日
  49.         '2009-05-03', // 憲法記念日
  50.         '2009-05-04', // みどりの日
  51.         '2009-05-05', // こどもの日
  52.         '2009-05-06', // (振替休日)
  53.         '2009-07-20', // 春分の日
  54.         '2009-09-21', // 敬老の日
  55.         '2009-09-22', // (国民の祝日)
  56.         '2009-09-23', // 秋分の日
  57.         '2009-10-12', // 体育の日
  58.         '2009-11-03', // 文化の日
  59.         '2009-11-23', // 勤労感謝の日
  60.         '2009-12-23', // 天皇誕生日
  61.     )
  62. );
  63. foreach ($pattern as $year => $yearPattern) {
  64.     // Date_Holidaysオブジェクトの生成(西暦を省略すると現在の西暦になる)
  65.     $obj =& Date_Holidays::factory('Japanese', $year);
  66.     if (Date_Holidays::isError($obj)) {
  67.         die('Factory was unable to produce driver-object');
  68.     }
  69.    
  70.     // 翻訳ファイルをロケール=ja_JPとして登録
  71.     $obj->addTranslationFile(LANG_FILE . '/Japanese/ja_JP.xml', 'ja_JP');
  72.     if (Date_Holidays::isError($obj)) {