Zend_Dateクラスを効率よく使うチャレンジ
序
Zend Frameworkに含まれているZend_Dateクラスは、インスタンス生成コストがとにかく高い。
どれくらい高いかというと、以下の2つのプログラムで比較してみるとなんとなく分かる。
<?php $n = 10000; if ($argc >= 2) { $n = (int)$argv[1]; } set_include_path(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'library'); require_once('Zend/Date.php'); date_default_timezone_set('Asia/Tokyo'); $start = microtime(true); for ($i = 0; $i < $n; ++$i) { $zd = new Zend_Date('2015-08-01 03:04:05'); } $end = microtime(true); printf("%8.5lf\n", $end - $start);
<?php $n = 10000; if ($argc >= 2) { $n = (int)$argv[1]; } set_include_path(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'library'); require_once('Zend/Date.php'); date_default_timezone_set('Asia/Tokyo'); $start = microtime(true); for ($i = 0; $i < $n; ++$i) { $zd = new DateTime('2015-08-01 03:04:05'); } $end = microtime(true); printf("%8.5lf\n", $end - $start);
- test0-3.php: Zend_Dateオブジェクトをキャッシュしておいて、strtotimeとsetTimestampで対応
<?php $n = 10000; if ($argc >= 2) { $n = (int)$argv[1]; } set_include_path(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'library'); require_once('Zend/Date.php'); date_default_timezone_set('Asia/Tokyo'); $zd = new Zend_Date(); $start = microtime(true); for ($i = 0; $i < $n; ++$i) { $time = strtotime('2015-08-01 03:04:05'); $zd->setTimestamp($time); } $end = microtime(true); printf("%8.5lf\n", $end - $start);
これらを実行する。
$ for i in {1,2,5,8}{,00,000} ; do > printf "%d" $i > for t in test0-1.php test0-2.php test0-3.php ; do > php ${t} ${i} | awk '{printf(",%s", $0);}' > done > printf "\n" > done 1, 0.03093, 0.00011, 0.00004 100, 0.23281, 0.00048, 0.00074 1000, 2.32891, 0.00825, 0.01088 2, 0.01160, 0.00012, 0.00005 200, 0.48956, 0.00336, 0.00224 2000, 5.51129, 0.01458, 0.02203 5, 0.02411, 0.00018, 0.00012 500, 1.08754, 0.00409, 0.00425 5000,11.39618, 0.03983, 0.06424 8, 0.03674, 0.00031, 0.00000 800, 2.20668, 0.00417, 0.00931 8000,19.13784, 0.07557, 0.11488 $
並べ替えてグラフにしてみるとこんな感じ。
本
とりあえず、欲しいものは「日時計算をして、その結果を日時文字列にしたもの」ということで話を進める。
Zend_Dateオブジェクトをキャッシュして使いまわすとよさそうということで、上記「test0-3.php」をベースに、以下のような計測をしてみる。
<?php $n = 10000; if ($argc >= 2) { $n = (int)$argv[1]; } set_include_path(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'library'); require_once('Zend/Date.php'); date_default_timezone_set('Asia/Tokyo'); function _test(Zend_Date $zd) { return $zd->toString('YYYY-MM-dd'); } $zd = new Zend_Date('2015-08-01 03:04:05'); //var_dump(_test($zd)); $start = microtime(true); for ($i = 0; $i < $n; ++$i) { _test($zd); } $end = microtime(true); printf("%8.5lf\n", $end - $start);
<?php $n = 10000; if ($argc >= 2) { $n = (int)$argv[1]; } set_include_path(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'library'); require_once('Zend/Date.php'); date_default_timezone_set('Asia/Tokyo'); function _test(Zend_Date $zd) { $ts = $zd->getTimestamp(); return date('Y-m-d', $ts); } $zd = new Zend_Date('2015-08-01 03:04:05'); //var_dump(_test($zd)); $start = microtime(true); for ($i = 0; $i < $n; ++$i) { _test($zd); } $end = microtime(true); printf("%8.5lf\n", $end - $start);
<?php $n = 10000; if ($argc >= 2) { $n = (int)$argv[1]; } set_include_path(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'library'); require_once('Zend/Date.php'); date_default_timezone_set('Asia/Tokyo'); function _test(Zend_Date $zd) { $year = $zd->get(Zend_Date::YEAR); $month = $zd->get(Zend_Date::MONTH); $day = $zd->get(Zend_Date::DAY); return sprintf("%04d-%02d-%02d", $year, $month, $day); } $zd = new Zend_Date('2015-08-01 03:04:05'); $start = microtime(true); for ($i = 0; $i < $n; ++$i) { _test($zd); } $end = microtime(true); printf("%8.5lf\n", $end - $start);
さて、実行。
$ for i in {1,2,5,8}{,00,000,0000} ; do > printf "%d" $i > for t in test1-1.php test1-2.php test1-3.php ; do > php ${t} ${i} | awk '{printf(",%s", $0);}' > done > printf "\n" > done
結果。
1, 0.00020, 0.00002, 0.00009 100, 0.01489, 0.00010, 0.01203 1000, 0.15603, 0.00634, 0.17088 10000, 1.28451, 0.04945, 1.37297 2, 0.00009, 0.00002, 0.00063 200, 0.02691, 0.00093, 0.04130 2000, 0.30510, 0.01436, 0.27488 20000, 2.93000, 0.12693, 3.37226 5, 0.00057, 0.00008, 0.00116 500, 0.08533, 0.00256, 0.07560 5000, 0.85879, 0.03124, 0.98879 50000,11.33206, 0.39485, 7.20063 8, 0.00063, 0.00009, 0.00074 800, 0.10203, 0.00917, 0.09432 8000, 0.86644, 0.03868, 0.81726 80000,12.23691, 0.57610,14.96780
さすがに各フィールドの値を取るのはコストが高いが、「test1-2.php」のケースが使えそうなレベル。
また、コードは省略するが、「test2-1.php」「test2-2.php」「test2-3.php」は時分秒まで含めるケース(「HH:mm:ss」「H:i:s」等の追加による)。
「test1-2.php/test2-2.php」はフォーマットに関係なくいいパフォーマンスを出しているが、Zend_Dateは時分秒まで含めるとそれだけコストが高くなる。
結
Zend_Dateクラスとは適度に付き合うのがよさそう(謎)。
HTML5のinputタグにおけるtype属性のサポート状況
ブラウザ標準の機能でカレンダーコンポーネントを使ったコードを書きたくて、ついでに単純なHTMLを書いて検証してみた。
試したのは以下のブラウザ。
- Windows 7 Professional SP1
- Firefox 39.0
- Google Chrome 44.0.2403.107 m (64-bit)
- Opera 30.0.1835.125
- Safari 5.1.7
- Internet Explorer 11.0.9600.17914
- Android 4.4.2
- 標準ブラウザ
書いたのは以下のHTMLコード。
<!DOCTYPE html> <html> <head></head> <body> <form action="#" method="post"> <table> <tbody> <tr> <th>text</th> <td><input type="text" name="f_text" /></td> </tr> <tr> <th>search</th> <td><input type="search" name="f_search" /></td> </tr> <tr> <th>tel</th> <td><input type="tel" name="f_tel" /></td> </tr> <tr> <th>url</th> <td><input type="url" name="f_url" /></td> </tr> <tr> <th>email</th> <td><input type="email" name="f_email" /></td> </tr> <tr> <th>datetime</th> <td><input type="datetime" name="f_datetime" /></td> </tr> <tr> <th>date</th> <td><input type="date" name="f_date" /></td> </tr> <tr> <th>month</th> <td><input type="month" name="f_month" /></td> </tr> <tr> <th>week</th> <td><input type="week" name="f_week" /></td> </tr> <tr> <th>time</th> <td><input type="time" name="f_time" /></td> </tr> <tr> <th>datetime-local</th> <td><input type="datetime-local" name="f_datetime-local" /></td> </tr> <tr> <th>number</th> <td><input type="number" name="f_number" /></td> </tr> <tr> <th>range</th> <td><input type="range" name="f_range" /></td> </tr> <tr> <th>color</th> <td><input type="color" name="f_color" /></td> </tr> </tbody> </table> <input type="submit" /> </form> </body> </html>
これを、ちゃんとWebサーバーを通して各ブラウザで表示させ、状況を見てみた。
サポートされているかどうかの判定は、type="text"
との挙動の違いがあるかどうか。なので、最初は適当にaaa
とか入力して、その場で怒られるものもあれば、submitボタンを押したときにバリデーションエラーを出すものもある。
サポート状況は大体こんな感じ。
Firefox | Chrome | Opera | Safari | IE 11 | Android | |
---|---|---|---|---|---|---|
text | ○ | ○ | ○ | ○ | ○ | ○ |
search | ○ | ○ | ○ | |||
tel | ○ | |||||
url | ○ | ○ | ○ | ○ | ○ | |
○ | ○ | ○ | ○ | ○ | ||
datetime | ○ | ○ | ||||
date | ○ | ○ | ○ | ○ | ||
month | ○ | ○ | ○ | ○ | ||
week | ○ | ○ | ○ | ○(*1) | ||
time | ○ | ○ | ○ | ○ | ||
datetime-local | ○ | ○ | ○ | ○ | ||
number | ○ | ○ | ○ | ○ | ○ | |
range | ○ | ○ | ○ | ○ | ○ | ○ |
color | ○ | ○ | ○ | ○ |
- (*1):それっぽく表示されるけど動作しなかった‥
‥酷いな‥画面キャプチャを貼り付けるのも面倒になるくらい酷い‥
もう、普通のテキストボックスで年月日を分けますよ、えぇ(謎)‥
PHPのrequire_onceが遅い話
もはや専門家の間では有名な話なのだろうが、今頃意識し始めて、ちょっと計ってみるかという気になったので計ってみる。
なんせ、Zend Frameworkのページでもパフォーマンスガイドとして書いてあるくらいだし。
計るにあたっては、「ノートPC上のVMでやりました」では話にならないだろうということで(それでも比率を見れば傾向はつかめると思うが)、とあるVPS上で計測をした。
ちなみに、「『Zend Frameworkと同じ命名規則でクラス名をつける』というルールの元で作ったものに対する計測」ってことでそのあたりはご注意を。
実験環境
環境はこんな感じ。
$ cat /etc/redhat-release CentOS release 6.6 (Final) $ uname -r 2.6.32-504.16.2.el6.x86_64 $ free total used free shared buffers cached Mem: 1922192 1480608 441584 536 202304 748092 -/+ buffers/cache: 530212 1391980 Swap: 2097148 6472 2090676 $ php -v PHP 5.3.3 (cli) (built: Jul 9 2015 17:39:00) Copyright (c) 1997-2010 The PHP Group Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies $ php -r 'var_dump(get_include_path());' string(32) ".:/usr/share/pear:/usr/share/php" $
ちなみにCPUは「Intel Xeon E312xx (Sandy Bridge)」の3コアらしい。
使用するプログラム
- App/Hoge0.php
<?php class App_Hoge0 { public static function test() {} }
- test0.php
- 1回だけrequire_onceする。
<?php printf("%s\n", __FILE__); $n = 1000000; if ($argc >= 2) { $n = (int)$argv[1]; } $startTime = microtime(true); require_once('App/Hoge0.php'); for ($i = 0; $i < $n; ++$i) { App_Hoge0::test(); } $endTime = microtime(true); printf("%8.5lf\n", $endTime - $startTime);
- test1.php
- ループのたびにrequire_onceする。
<?php printf("%s\n", __FILE__); $n = 1000000; if ($argc >= 2) { $n = (int)$argv[1]; } $startTime = microtime(true); for ($i = 0; $i < $n; ++$i) { require_once('App/Hoge0.php'); App_Hoge0::test(); } $endTime = microtime(true); printf("%8.5lf\n", $endTime - $startTime);
<?php printf("%s\n", __FILE__); $n = 1000000; if ($argc >= 2) { $n = (int)$argv[1]; } $startTime = microtime(true); function _myLoader($className) { return @include(str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'); } spl_autoload_register('_myLoader'); for ($i = 0; $i < $n; ++$i) { App_Hoge0::test(); } $endTime = microtime(true); printf("%8.5lf\n", $endTime - $startTime);
実行
面倒なので、以下のようなスクリプトを書いて実行。
for i in {1,2,5}{,00,000,0000,00000,000000} ; do printf "%d" $i for t in test0.php test1.php test2.php ; do php ${t} ${i} | grep -v /home/ | awk '{printf(",%s", $0);}' done printf "\n" done
結果(生データ)
1, 0.00011, 0.00011, 0.00014 100, 0.00013, 0.00030, 0.00016 1000, 0.00037, 0.00217, 0.00040 10000, 0.00264, 0.01997, 0.00298 100000, 0.02665, 0.20718, 0.02599 1000000, 0.24950, 1.90784, 0.25750 2, 0.00012, 0.00011, 0.00020 200, 0.00019, 0.00051, 0.00019 2000, 0.00062, 0.00419, 0.00065 20000, 0.00557, 0.04184, 0.00566 200000, 0.05527, 0.41456, 0.05114 2000000, 0.51434, 3.97734, 0.54890 5, 0.00014, 0.00013, 0.00015 500, 0.00025, 0.00227, 0.00036 5000, 0.00145, 0.00972, 0.00154 50000, 0.01421, 0.11185, 0.01292 500000, 0.13399, 0.98989, 0.13024 5000000, 1.27753,10.18646, 1.28340
結果(グラフ)
生データでもなんとなく分かると思うが、一応並べ替えてグラフ化してみる。
まぁ、火を見るより明らかというか‥。自前クラスローダ版(test2)が意外と健闘している。
そんなわけで(謎)、これからZend Framework本体のrequire_onceを消しまくる作業をしようと思う(と言っても、シェルコマンドのワンライナーで一括だが(謎))‥
2015/07/23追記
上記の検証だと、「require_onceが遅い」という証明にはならない気がしてきた。実行される命令数が違うのだから遅いのは当然。
ということで、test0.phpを以下のようにしてみた。
- test0.php
- 「何かの適当な処理」をする
<?php printf("%s\n", __FILE__); $n = 1000000; if ($argc >= 2) { $n = (int)$argv[1]; } $startTime = microtime(true); require_once('App/Hoge0.php'); for ($i = 0; $i < $n; ++$i) { 何かの適当な処理 App_Hoge0::test(); } $endTime = microtime(true); printf("%8.5lf\n", $endTime - $startTime);
「何かの適当な処理」には以下の2通りのコードを入れて試してみた。
require('./dummy.php');
(「dummy.php」は空のファイル)defined("___{$i}___");
そうしたら、こんな感じになった。
require('./dummy.php');
defined("___{$i}___");
Zend Frameworkのソースを見ると、確かにメソッドの中で、例えば例外を投げる直前にrequire_once('Zend/Exception');
していたりするので(そういうのが何箇所もある)、それは無駄だろうが、諸悪の根源とまではいかないようだ‥
配列の統合時の先勝ち後勝ちの話
起
2つの配列$a
と$b
をマージした結果として$expected
のようなものが欲しくて‥
<?php $a = array( '11', 'k2' => '22', 'k4' => '44', 'k6' => '66', 'k8' => '88', ); $b = array( '111', 'k2' => '222', 'k3' => '333', 'k5' => '555', 'k7' => '777', ); $expected = array ( '111', 'k2' => '222', 'k4' => '44', 'k6' => '66', 'k8' => '88', 'k3' => '333', 'k5' => '555', 'k7' => '777', );
+
演算子やarray_merge
関数を使ってみるが‥
<?php $a = array( '11', 'k2' => '22', 'k4' => '44', 'k6' => '66', 'k8' => '88', ); $b = array( '111', 'k2' => '222', 'k3' => '333', 'k5' => '555', 'k7' => '777', ); $expected = array ( '111', 'k2' => '222', 'k4' => '44', 'k6' => '66', 'k8' => '88', 'k3' => '333', 'k5' => '555', 'k7' => '777', ); printf("===%s===\n", 'expected'); var_dump($expected); printf("===%s===\n", '$a + $b'); var_dump($a + $b); printf("===%s===\n", '$b + $a'); var_dump($b + $a); printf("===%s===\n", 'array_merge($a, $b)'); var_dump(array_merge($a, $b)); printf("===%s===\n", 'array_merge($b, $a)'); var_dump(array_merge($b, $a));
何かが違う‥
===expected=== array(8) { [0]=> string(3) "111" ["k2"]=> string(3) "222" ["k4"]=> string(2) "44" ["k6"]=> string(2) "66" ["k8"]=> string(2) "88" ["k3"]=> string(3) "333" ["k5"]=> string(3) "555" ["k7"]=> string(3) "777" } ===$a + $b=== array(8) { [0]=> string(2) "11" ["k2"]=> string(2) "22" ["k4"]=> string(2) "44" ["k6"]=> string(2) "66" ["k8"]=> string(2) "88" ["k3"]=> string(3) "333" ["k5"]=> string(3) "555" ["k7"]=> string(3) "777" } ===$b + $a=== array(8) { [0]=> string(3) "111" ["k2"]=> string(3) "222" ["k3"]=> string(3) "333" ["k5"]=> string(3) "555" ["k7"]=> string(3) "777" ["k4"]=> string(2) "44" ["k6"]=> string(2) "66" ["k8"]=> string(2) "88" } ===array_merge($a, $b)=== array(9) { [0]=> string(2) "11" ["k2"]=> string(3) "222" ["k4"]=> string(2) "44" ["k6"]=> string(2) "66" ["k8"]=> string(2) "88" [1]=> string(3) "111" ["k3"]=> string(3) "333" ["k5"]=> string(3) "555" ["k7"]=> string(3) "777" } ===array_merge($b, $a)=== array(9) { [0]=> string(3) "111" ["k2"]=> string(2) "22" ["k3"]=> string(3) "333" ["k5"]=> string(3) "555" ["k7"]=> string(3) "777" [1]=> string(2) "11" ["k4"]=> string(2) "44" ["k6"]=> string(2) "66" ["k8"]=> string(2) "88" }
まぁ$b + $a
は、内容的には合っているんだけど、ベースは$a
で後勝ちしたいのでちょっと違和感‥
array_merge
は惜しいが違うし‥
承
見た目の順番が同じものが得られないかな‥と思い、意味的にはこんな感じで、
<?php $a = array( '11', 'k2' => '22', 'k4' => '44', 'k6' => '66', 'k8' => '88', ); $b = array( '111', 'k2' => '222', 'k3' => '333', 'k5' => '555', 'k7' => '777', ); printf("===%s===\n", 'foreach'); $r = $a; foreach ($b as $k => $v) { $r[$k] = $v; } var_dump($r);
こんな感じの結果が得られるやつ。
===foreach=== array(8) { [0]=> string(3) "111" ["k2"]=> string(3) "222" ["k4"]=> string(2) "44" ["k6"]=> string(2) "66" ["k8"]=> string(2) "88" ["k3"]=> string(3) "333" ["k5"]=> string(3) "555" ["k7"]=> string(3) "777" }
転
で、いろいろ試行錯誤して、
<?php $a = array( '11', 'k2' => '22', 'k4' => '44', 'k6' => '66', 'k8' => '88', ); $b = array( '111', 'k2' => '222', 'k3' => '333', 'k5' => '555', 'k7' => '777', ); printf("===%s===\n", 'array_merge(array_diff($a, array_intersect_key($a, $b)), $b)'); $r = array_merge(array_diff($a, array_intersect_key($a, $b)), $b); var_dump($r);
で、見た目の順番は違うけど同じ結果が得られた。
array(8) { ["k4"]=> string(2) "44" ["k6"]=> string(2) "66" ["k8"]=> string(2) "88" [0]=> string(3) "111" ["k2"]=> string(3) "222" ["k3"]=> string(3) "333" ["k5"]=> string(3) "555" ["k7"]=> string(3) "777" }
結
‥と喜んでいたら、array_replace
なるものがあった件。
<?php $a = array( '11', 'k2' => '22', 'k4' => '44', 'k6' => '66', 'k8' => '88', ); $b = array( '111', 'k2' => '222', 'k3' => '333', 'k5' => '555', 'k7' => '777', ); printf("===%s===\n", 'array_replace($a, $b)'); $r = array_replace($a, $b); var_dump($r);
見た目の順番まで期待通り。
===array_replace($a, $b)=== array(8) { [0]=> string(3) "111" ["k2"]=> string(3) "222" ["k4"]=> string(2) "44" ["k6"]=> string(2) "66" ["k8"]=> string(2) "88" ["k3"]=> string(3) "333" ["k5"]=> string(3) "555" ["k7"]=> string(3) "777" }
まとめ
私が知る限りの配列の「足し算」をする演算/関数の違いの簡単なまとめ。
演算/関数 | 数値インデックス | 文字列インデックス |
---|---|---|
$a + $b |
先勝ち | 先勝ち |
array_merge($a, $b) |
続きから追記 | 後勝ち |
array_replace($a, $b) |
後勝ち | 後勝ち |
知らない事がまだまだ多いな‥
DateTimeクラスの罠
PHPにDateTimeクラスとDateIntervalクラスなる便利なものがあると知って、喜び勇んで‥
こんな感じで、日時の加算や減算のテストプログラムを作ってみた。
- DateTimeTest.php
<?php class DateTimeTest { public static function test($date1, $date2, $target = null) { $intervals = array( '年' => new DateInterval('P1Y'), '月' => new DateInterval('P1M'), '週' => new DateInterval('P1W'), '日' => new DateInterval('P1D'), '時' => new DateInterval('PT1H'), '分' => new DateInterval('PT1M'), '秒' => new DateInterval('PT1S'), ); if (!$target) { $target = array_keys($intervals); } printf(" <add> <sub>\n"); foreach ($target as $k) { if (isset($intervals[$k])) { $interval = $intervals[$k]; printf("%s===%s===%s\n", $k, $interval->format('%Y-%M-%D %H:%I:%S'), $k); printf("%5s%20s|%5s%20s\n", "", $date1->format('Y-m-d H:i:s(D)'), "", $date2->format('Y-m-d H:i:s(D)')); $date1->add($interval); $date2->sub($interval); printf(" => %20s| => %20s\n" , $date1->format('Y-m-d H:i:s(D)') , $date2->format('Y-m-d H:i:s(D)')); } } } }
- test1.php
<?php date_default_timezone_set('Asia/Tokyo'); require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'DateTimeTest.php'); DateTimeTest::test(new DateTime(), new DateTime());
要は、指定したフィールドの加算・減算を指定した分だけ行うというもの。
いざ実行。
$ php test1.php <add> <sub> 年===01-00-00 00:00:00===年 2015-07-09 06:05:35(Thu)| 2015-07-09 06:05:35(Thu) => 2016-07-09 06:05:35(Sat)| => 2014-07-09 06:05:35(Wed) 月===00-01-00 00:00:00===月 2016-07-09 06:05:35(Sat)| 2014-07-09 06:05:35(Wed) => 2016-08-09 06:05:35(Tue)| => 2014-06-09 06:05:35(Mon) 週===00-00-07 00:00:00===週 2016-08-09 06:05:35(Tue)| 2014-06-09 06:05:35(Mon) => 2016-08-16 06:05:35(Tue)| => 2014-06-02 06:05:35(Mon) 日===00-00-01 00:00:00===日 2016-08-16 06:05:35(Tue)| 2014-06-02 06:05:35(Mon) => 2016-08-17 06:05:35(Wed)| => 2014-06-01 06:05:35(Sun) 時===00-00-00 01:00:00===時 2016-08-17 06:05:35(Wed)| 2014-06-01 06:05:35(Sun) => 2016-08-17 07:05:35(Wed)| => 2014-06-01 05:05:35(Sun) 分===00-00-00 00:01:00===分 2016-08-17 07:05:35(Wed)| 2014-06-01 05:05:35(Sun) => 2016-08-17 07:06:35(Wed)| => 2014-06-01 05:04:35(Sun) 秒===00-00-00 00:00:01===秒 2016-08-17 07:06:35(Wed)| 2014-06-01 05:04:35(Sun) => 2016-08-17 07:06:36(Wed)| => 2014-06-01 05:04:34(Sun) $
おぉ、これは便利。
‥と思って使っていたのだが、思わぬ罠が待っていた‥
- test2.php
<?php date_default_timezone_set('Asia/Tokyo'); require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'DateTimeTest.php'); DateTimeTest::test(new DateTime('2015-01-31'), new DateTime('2015-01-31'), array('月', '月', '月', '月', '月', '月', '月', '月', '月', '月', '月', '月'));
要は、「1月末」に1ヶ月ずつ加算・減算をしていくわけだが‥
$ php test2.php <add> <sub> 月===00-01-00 00:00:00===月 2015-01-31 00:00:00(Sat)| 2015-01-31 00:00:00(Sat) => 2015-03-03 00:00:00(Tue)| => 2014-12-31 00:00:00(Wed) 月===00-01-00 00:00:00===月 2015-03-03 00:00:00(Tue)| 2014-12-31 00:00:00(Wed) => 2015-04-03 00:00:00(Fri)| => 2014-12-01 00:00:00(Mon) 月===00-01-00 00:00:00===月 2015-04-03 00:00:00(Fri)| 2014-12-01 00:00:00(Mon) => 2015-05-03 00:00:00(Sun)| => 2014-11-01 00:00:00(Sat) 月===00-01-00 00:00:00===月 2015-05-03 00:00:00(Sun)| 2014-11-01 00:00:00(Sat) => 2015-06-03 00:00:00(Wed)| => 2014-10-01 00:00:00(Wed) 月===00-01-00 00:00:00===月 2015-06-03 00:00:00(Wed)| 2014-10-01 00:00:00(Wed) => 2015-07-03 00:00:00(Fri)| => 2014-09-01 00:00:00(Mon) 月===00-01-00 00:00:00===月 2015-07-03 00:00:00(Fri)| 2014-09-01 00:00:00(Mon) => 2015-08-03 00:00:00(Mon)| => 2014-08-01 00:00:00(Fri) 月===00-01-00 00:00:00===月 2015-08-03 00:00:00(Mon)| 2014-08-01 00:00:00(Fri) => 2015-09-03 00:00:00(Thu)| => 2014-07-01 00:00:00(Tue) 月===00-01-00 00:00:00===月 2015-09-03 00:00:00(Thu)| 2014-07-01 00:00:00(Tue) => 2015-10-03 00:00:00(Sat)| => 2014-06-01 00:00:00(Sun) 月===00-01-00 00:00:00===月 2015-10-03 00:00:00(Sat)| 2014-06-01 00:00:00(Sun) => 2015-11-03 00:00:00(Tue)| => 2014-05-01 00:00:00(Thu) 月===00-01-00 00:00:00===月 2015-11-03 00:00:00(Tue)| 2014-05-01 00:00:00(Thu) => 2015-12-03 00:00:00(Thu)| => 2014-04-01 00:00:00(Tue) 月===00-01-00 00:00:00===月 2015-12-03 00:00:00(Thu)| 2014-04-01 00:00:00(Tue) => 2016-01-03 00:00:00(Sun)| => 2014-03-01 00:00:00(Sat) 月===00-01-00 00:00:00===月 2016-01-03 00:00:00(Sun)| 2014-03-01 00:00:00(Sat) => 2016-02-03 00:00:00(Wed)| => 2014-02-01 00:00:00(Sat) $
「2015-01-31」に「1ヶ月」を足したところで既に残念な結果「2015-03-03」になっている。
「2014-12-31」から「1ヶ月」を引いたところも残念な結果「2014-12-01」になっている。
まぁ、単純に処理したら「2015-02-31⇒2015-03-03」で分からんこともないんだが、各月の日数が違うはずなのにその後の処理が問題なく進行しているところを見ると、単純な文字列処理しかしていないんだという事が分かる。
結局、現場(何処)では、「月」が変わるまでループで「日」を加算・減算をするなどというアホなことを一部やっているが、どうにかしたいものだ‥ (ループの回数は、「月」が変わるまでループで「週」を加算・減算して、その後に「月」が戻る直前まで「日」を引いたほうが平均的には少なくなる‥のか(そういう問題じゃない))
日付文字列のフォーマットのメモ
RFC 2822に従った日付文字列の生成
一発で出力できる指定子があることに気付かず、試行錯誤したのだが、せっかくなのでメモしておく。
RFC 2822に従った日付文字列は以下のような形式。
Thu, 18 Jun 2015 16:43:47 +0900
メールのヘッダなんかに使われることになっているもの。 ものによっては以下の形式も見かける。
Thu, 18 Jun 2015 16:43:47 +0900 (JST)
これをPHPで出力しようと思ったら、以下のように書けばいい。なお、PHPの初期設定をちゃんとやっていない環境なので余計な処理が冒頭に入っているが気にしない(謎)。
<?php date_default_timezone_set('Asia/Tokyo'); mb_internal_encoding('UTF-8'); $time = strtotime('2015-06-09 17:02:03'); printf("===date===\n"); printf(" %s\n", 'RFC 2822'); printf(" %s\n", date('r', $time)); printf(" %s\n", date('D, d M Y H:i:s O', $time)); printf(" %s\n", 'RFC 2822 with Timezone abbreviation'); printf(" %s\n", date('r (T)', $time)); printf(" %s\n", date('D, d M Y H:i:s O (T)', $time));
出力は以下のような感じ。
===date=== RFC 2822 Tue, 09 Jun 2015 17:02:03 +0900 Tue, 09 Jun 2015 17:02:03 +0900 RFC 2822 with Timezone abbreviation Tue, 09 Jun 2015 17:02:03 +0900 (JST) Tue, 09 Jun 2015 17:02:03 +0900 (JST)
ISO 8601拡張形式
ついでなのでやってみた(謎)。
ISO 8601拡張形式に従った日付文字列は以下のような形式。
2015-06-18T16:43:47+0900
これも、一発で出力できる指定子がある。
<?php date_default_timezone_set('Asia/Tokyo'); mb_internal_encoding('UTF-8'); $time = strtotime('2015-06-09 17:02:03'); printf("===date===\n"); printf(" %s\n", 'ISO 8601 date'); printf(" %s\n", date('c', $time)); printf(" %s\n", date('Y-m-d\TH:i:sP', $time));
出力。
===date=== ISO 8601 date 2015-06-09T17:02:03+09:00 2015-06-09T17:02:03+09:00
(ダメ)strftimeでRFC 2822形式
さらについでなので、strftime関数で色々やってみた。setLocale呼び出しに影響を受ける関数なので、当然ながら汎用性はないのだが‥
<?php date_default_timezone_set('Asia/Tokyo'); mb_internal_encoding('UTF-8'); $time = strtotime('2015-06-09 17:02:03'); printf("===strftime===\n"); $locales = array( '英語(US) ' => 'en_US', '英語(UK) ' => 'en_GB', '日本語 ' => 'ja_JP', 'ドイツ語 ' => 'de_DE', 'オランダ語 ' => 'nl_NL', 'フランス語(フランス)' => 'fr_FR', 'フランス語(カナダ) ' => 'fr_CA', 'スペイン語 ' => 'es_ES', 'ポルトガル語 ' => 'pt_PT', '中国語(繁体字) ' => 'zh_TW', '中国語(簡体字) ' => 'zh_CN', '韓国語 ' => 'ko_KR', ); $formats = array( 'RFC 2822' => '%a, %d %b %Y %H:%M:%S %z', 'RFC 2822 with Timezone abbreviation' => '%a, %d %b %Y %H:%M:%S %z (%Z)', ); foreach ($formats as $name => $format) { printf("%s\n", $name); foreach ($locales as $country => $locale) { printf(" %s(%s): ", setLocale(LC_ALL, $locale . '.UTF-8'), $country); printf(" %s\n", strftime($format, $time)); } }
出力。
===strftime=== RFC 2822 en_US.UTF-8(英語(US) ): Tue, 09 Jun 2015 17:02:03 +0900 en_GB.UTF-8(英語(UK) ): Tue, 09 Jun 2015 17:02:03 +0900 ja_JP.UTF-8(日本語 ): 火, 09 6月 2015 17:02:03 +0900 de_DE.UTF-8(ドイツ語 ): Di, 09 Jun 2015 17:02:03 +0900 nl_NL.UTF-8(オランダ語 ): di, 09 jun 2015 17:02:03 +0900 fr_FR.UTF-8(フランス語(フランス)): mar., 09 juin 2015 17:02:03 +0900 fr_CA.UTF-8(フランス語(カナダ) ): mar, 09 jun 2015 17:02:03 +0900 es_ES.UTF-8(スペイン語 ): mar, 09 jun 2015 17:02:03 +0900 pt_PT.UTF-8(ポルトガル語 ): Ter, 09 Jun 2015 17:02:03 +0900 zh_TW.UTF-8(中国語(繁体字) ): 二, 09 6月 2015 17:02:03 +0900 zh_CN.UTF-8(中国語(簡体字) ): 二, 09 6月 2015 17:02:03 +0900 ko_KR.UTF-8(韓国語 ): 화, 09 6월 2015 17:02:03 +0900 RFC 2822 with Timezone abbreviation en_US.UTF-8(英語(US) ): Tue, 09 Jun 2015 17:02:03 +0900 (JST) en_GB.UTF-8(英語(UK) ): Tue, 09 Jun 2015 17:02:03 +0900 (JST) ja_JP.UTF-8(日本語 ): 火, 09 6月 2015 17:02:03 +0900 (JST) de_DE.UTF-8(ドイツ語 ): Di, 09 Jun 2015 17:02:03 +0900 (JST) nl_NL.UTF-8(オランダ語 ): di, 09 jun 2015 17:02:03 +0900 (JST) fr_FR.UTF-8(フランス語(フランス)): mar., 09 juin 2015 17:02:03 +0900 (JST) fr_CA.UTF-8(フランス語(カナダ) ): mar, 09 jun 2015 17:02:03 +0900 (JST) es_ES.UTF-8(スペイン語 ): mar, 09 jun 2015 17:02:03 +0900 (JST) pt_PT.UTF-8(ポルトガル語 ): Ter, 09 Jun 2015 17:02:03 +0900 (JST) zh_TW.UTF-8(中国語(繁体字) ): 二, 09 6月 2015 17:02:03 +0900 (JST) zh_CN.UTF-8(中国語(簡体字) ): 二, 09 6月 2015 17:02:03 +0900 (JST) ko_KR.UTF-8(韓国語 ): 화, 09 6월 2015 17:02:03 +0900 (JST)
試した環境では「ja_JP.UTF-8」のようにしないと、マルチバイト文字がうまく見えなかったが、きっと環境のせい。
英語なんかだと、当然ながら期待する出力になっている。 ただし、期待する出力になっているlocaleでも、Windows環境など、環境によっては全然違う文字列が生成されることもあるので注意するようにということがマニュアルには書いてある。
(ダメ)strftimeでISO 8601拡張形式
さらにさらについでなので、strftime関数で色々やってみた。当然ながら(ry
<?php /* * http://php.net/manual/ja/function.strftime.php * http://php.net/manual/ja/function.date.php */ date_default_timezone_set('Asia/Tokyo'); mb_internal_encoding('UTF-8'); $time = strtotime('2015-06-09 17:02:03'); printf("===strftime===\n"); $locales = array( '英語(US) ' => 'en_US', '英語(UK) ' => 'en_GB', '日本語 ' => 'ja_JP', 'ドイツ語 ' => 'de_DE', 'オランダ語 ' => 'nl_NL', 'フランス語(フランス)' => 'fr_FR', 'フランス語(カナダ) ' => 'fr_CA', 'スペイン語 ' => 'es_ES', 'ポルトガル語 ' => 'pt_PT', '中国語(繁体字) ' => 'zh_TW', '中国語(簡体字) ' => 'zh_CN', '韓国語 ' => 'ko_KR', ); $formats = array( 'ISO 8601 date' => '%Y-%m-%dT%H:%M:%S%z', ); foreach ($formats as $name => $format) { printf("%s\n", $name); foreach ($locales as $country => $locale) { printf(" %s(%s): ", setLocale(LC_ALL, $locale . '.UTF-8'), $country); printf(" %s\n", strftime($format, $time)); } }
出力。
===strftime=== ISO 8601 date en_US.UTF-8(英語(US) ): 2015-06-09T17:02:03+0900 en_GB.UTF-8(英語(UK) ): 2015-06-09T17:02:03+0900 ja_JP.UTF-8(日本語 ): 2015-06-09T17:02:03+0900 de_DE.UTF-8(ドイツ語 ): 2015-06-09T17:02:03+0900 nl_NL.UTF-8(オランダ語 ): 2015-06-09T17:02:03+0900 fr_FR.UTF-8(フランス語(フランス)): 2015-06-09T17:02:03+0900 fr_CA.UTF-8(フランス語(カナダ) ): 2015-06-09T17:02:03+0900 es_ES.UTF-8(スペイン語 ): 2015-06-09T17:02:03+0900 pt_PT.UTF-8(ポルトガル語 ): 2015-06-09T17:02:03+0900 zh_TW.UTF-8(中国語(繁体字) ): 2015-06-09T17:02:03+0900 zh_CN.UTF-8(中国語(簡体字) ): 2015-06-09T17:02:03+0900 ko_KR.UTF-8(韓国語 ): 2015-06-09T17:02:03+0900
こっちは、localeに依存しない指定子しかつかっていないからか、localeに関係なく、試した範囲では同じ出力だった。 まぁ、変な心配をするより、素直にdate関数を使えという話だが‥
svn:ignoreで無視されるもの
なんか混乱したので、忘れないようにメモ。
当たり前と言えば当たり前すぎるのだが‥
事前準備
前提として、「/var/lib/svn」の下にリポジトリディレクトリを作成するものとする。
# cd /var/lib/svn # sudo -u apache svnadmin create test
実験
Webサーバー経由でSVNリポジトリにアクセスする準備は整っているものとする。
$ svn --version --quiet 1.6.11 $ svn checkout http://localhost/svn/test リビジョン 0 をチェックアウトしました。 $ cd test $ mkdir files $ svn add files $ cd files $ for f in committed committed.edited ignored.committed ignored.committed.edited > do > echo a > ${f} > done $ svn add committed committed.edited ignored.committed ignored.committed.edited $ svn commit -m '' . committed committed.edited ignored.committed ignored.committed.edited 追加しています files 追加しています files/committed 追加しています files/committed.edited 追加しています files/ignored.committed 追加しています files/ignored.committed.edited ファイルのデータを送信しています .... リビジョン 1 をコミットしました。 $ svn propset svn:ignore 'ignored.*' . 属性 'svn:ignore' を '.' に設定しました $ svn commit -m '' . 送信しています files リビジョン 2 をコミットしました。 $ for f in added ignored.added > do > echo a > ${f} > done $ svn add added ignored.added $ for f in notmanaged ignored.notmanaged > do > echo a > ${f} > done $ echo b >> committed.edited $ echo b >> ignored.committed.edited $ cd .. $ pwd /home/hhelibex/tmp/test $ ls -1 files added committed committed.edited ignored.added ignored.committed ignored.committed.edited ignored.notmanaged notmanaged $ svn status ? files/notmanaged A files/added M files/committed.edited A files/ignored.added M files/ignored.committed.edited $ svn status --no-ignore I files/ignored.notmanaged ? files/notmanaged A files/added M files/committed.edited A files/ignored.added M files/ignored.committed.edited $
結論
「svn:ignore」で無視されるのは、バージョン管理下に置かれていない(commitはもちろん、addすらされていない)ファイルに限定される。
‥当たり前だ‥
‥いや、無視するパターンをファイルに書いて、そのファイル自身もignore対象に入れて、パターン一覧を書いたファイルをバージョン管理するっていう矛盾したサンプル(何)を見つけて混乱したので‥