2011年11月8日火曜日

strftimeで日付がずれる

もしかしたら有名な話なのかもしれませんが、今日 スケジュールの画面のデモをphpで作っていたときにちょっとはまった。 クライアントにイメージをつかんでもらうための簡単なカレンダを phpでループさせて作っていたのだが、デモプログムの一部で、
date_default_timezone_set('Asia/Tokyo');
$base = strtotime('2011-11-01');
$days = explode(',', '日,月,火,水,木,金,土');
for ($i=0; $i<30; ++$i) {
 $date = $base+24*60*60*$i;
 $mmdd = strftime('%m/%d', $date);
 $day = $days[strftime('%w', $date)];
 echo "<br>$i: $mmdd($day)";
}

みたいに、今月の頭からループで24時間づつ増やしながら日付を表示させて、カレンダの日付の部分を表示させようとしたら、想定外の出力に!

0: 11/01(火)
1: 11/02(水)
2: 11/03(木)
3: 11/04(金)
4: 11/05(土)
5: 11/06(日)
6: 11/06(日)

7: 11/07(月)
8: 11/08(火)
9: 11/09(水)
10: 11/10(木)
: 
打ち合わせ開始まで30分しかなかったので、軽いパニックに。
誤差が蓄積されたような動きであったので、とりあえず、その場は
$base = strtotime('2011-11-01 14:00');
変更したところ、想定の出力で動き、残りの部分のコーディングに戻ることができた。

気になったので、今、「夏時間」をwikipediaで調べたら、


アメリカ合衆国(一部除く。前述のとおり2007年から次のように変更され実行されている)、カナダ(一部除く)、メキシコ(一部除く) - 3月第2日曜日午前2時〜11月第1日曜日午前2時(現地時間基準。開始日には2時が3時になり(1時59分59秒の次が3時00分00秒)、終了日は2時が再度1時(1時59分59秒の次が1時00分00秒)になるため、開始日は1日が23時間、終了日は逆に25時間になる)
と、アメリカでは11/6は25時間あることがわかった。ちなみに前のスクリプトに時間まで表示してみると、
0: 11/01 00:00(火)
1: 11/02 00:00(水)
2: 11/03 00:00(木)
3: 11/04 00:00(金)
4: 11/05 00:00(土)
5: 11/06 00:00(日)
6: 11/06 23:00(日)

7: 11/07 23:00(月)
8: 11/08 23:00(火)
9: 11/09 23:00(水)
10: 11/10 23:00(木)
11/6の00:00に24時間を足すと、11/6の23:00になっている。


ああ、すっきり!!、でもさ、夏時間のない'Asia/Tokyo'のロケールのはずじゃん。まいっか。いや、よくないな、よくない。
ここで、デジャヴが、どっかの環境で'Asia/Tokyo'が効かなくて、GMT-9って指定したことあったっけ。

 date_default_timezone_set('Etc/GMT-9');
 $base = strtotime('2011-11-01');
 $days = explode(',', '日,月,火,水,木,金,土');
 for ($i=0; $i<30; ++$i) {
  $date = $base+24*60*60*$i;
  $mmdd = strftime('%m/%d  %H:%M', $date);
  $day = $days[strftime('%w', $date)];
  echo "<br>$i: $mmdd($day)";
 }
 結果は
0: 11/01 00:00(火)
1: 11/02 00:00(水)
2: 11/03 00:00(木)
3: 11/04 00:00(金)
4: 11/05 00:00(土)
5: 11/06 00:00(日)
6: 11/07 00:00(月)

7: 11/08 00:00(火)
8: 11/09 00:00(水)
9: 11/10 00:00(木)
10: 11/11 00:00(金)
と、よしっ!きれいだ。ってなんでAsia/Tokyoと結果が違うの?
あ、そっか。日本って震災の節電の関係で夏時間設定されたんだっけ。
なんか違う。眠いからに違いない。寝よう。

よし、リフレッシュ。

ソースを少し変えてタイムゾーンを変えて流せるようにした。

echo "<pre>\n";
printDays('UCT');
printDays('Etc/GMT-9');
printDays('Asia/Tokyo');
printDays('America/New_York'); 
function printDays($timezone) {
echo "timezone:$timezone\n";
date_default_timezone_set($timezone);
$base = strtotime('2011-11-01');
for ($i=4; $i<8; ++$i) {
$date = $base+24*60*60*$i;
$mmdd = strftime('%m/%d  %H:%M', $date);
echo "$i: $mmdd\n";
}
echo "base:$base\n";
echo "now:".strftime('%Y-%m-%d %H:%M:%S')."\n\n";
}
結果はこんな感じ
timezone:UCT
4: 11/05 00:00
5: 11/06 00:00
6: 11/07 00:00
7: 11/08 00:00
base:1320105600
now:2011-11-08 23:50:43 
timezone:Etc/GMT-9
4: 11/05 00:00
5: 11/06 00:00
6: 11/07 00:00
7: 11/08 00:00
base:1320073200
now:2011-11-09 08:50:43 
timezone:Asia/Tokyo
4: 11/05 00:00
5: 11/06 00:00
6: 11/06 23:00
7: 11/07 23:00
base:1320120000
now:2011-11-08 18:50:43 
timezone:America/New_York
4: 11/05 00:00
5: 11/06 00:00
6: 11/06 23:00
7: 11/07 23:00
base:1320120000 
now:2011-11-08 18:50:43
そうそう、 日本は震災でアメリカに..そんなわけない。
使っているサーバのタイムゾーンのデータが壊れているようだ。

ローカルのphp5.3.3の環境では、timezone:Asia/Tokyoの表示は
timezone:Etc/GMT-9と同じ結果になっている。
不思議な動作をするサーバはphp5.3.1。
業務で使うサーバもほぼ同じ環境だから確認しておかなくては。

2011年9月8日木曜日

mySQLの CASE WHENはSQLが長くなる

mySQLの CASE WHENはSQLが長くなるので、短く書く方法を探ってみる。

mySQLのマニュアル比較演算子の冒頭に
「比較演算は、1(TRUE)、0(FALSE)、または NULL の値を返します。」
と書かれている。

なので、下記4つのSQLはすべて同じ結果になる。(はずw。ただし列Cは数値型だとする)

SELECT CASE WHEN `a`=`b` THEN `c` ELSE 0 END FROM `abc`;
SELECT CASE `a` WHEN `b` THEN `c` ELSE 0 END FROM `abc`;
SELECT IF(`a`=`b`,`c`,0) FROM `abc`;
SELECT (`a`=`b`)*`c` FROM `abc`

お好みはどれでしょう。

2011年5月11日水曜日

mySQLについてのメモ

mySQLのINTのMAX値

  • Unsigned で、4G(40億)

mySQLでの正規表現の利用

  • 正規表現は where `name` REGEXP 'a*' のように使用する

mySQLのvarchar、textの比較について

  • インデックスを張る場合255文字以内のキー長を指定する必要がある

他のDBのテーブルを参照するには

  • 同じmySQLサーバ内なら、select * from db_name.table_nameでOK

他のDBのテーブルとJOINできるか

  • 下記のように普通にJOINできる
    SELECT * FROM `mytable` join `dbname`.`atable` A on `mytable`.`id`=A.`id`

synonymはあるか

  • 無い。
    viewを使えば同様のことができる。オーバーヘッドが問題にならないか注意して使う

日付時刻を丸める関数(TRUNCなど)はあるか

  • 日付丸め専用の関数はない。
  • 文字列にフォーマットして丸める例はよく見かける
  • 計算式で丸める例
     時間:DATE(`date`)+INTERVAL HOUR(`date`) HOUR
     日: DATE(`date`)
     週: DATE(`date`)-INTERVAL DAYOFWEEK(`date`)-1 DAY
     月: DATE(`date`)-INTERVAL DAYOFMONTH(`date`)-1 DAY

PostgreSqlにある「関数(式)インデックス」はあるか

  • 無い。→インデックスを使いたい式を展開した列が必要

タイムゾーンはDBもしくは接続毎に再指定可能か
  • 可能
    mysql> set time_zone = '-7:00';
    Query OK, 0 rows affected (0.00 sec)

    mysql> select now();
    +---------------------+
    | now() |
    +---------------------+
    | 2011-05-05 22:21:37 |
    +---------------------+
    1 row in set (0.00 sec)

    mysql> set time_zone='+9:00' ;
    Query OK, 0 rows affected (0.00 sec)

    mysql> select now();
    +---------------------+
    | now() |
    +---------------------+
    | 2011-05-06 14:22:01 |
    +---------------------+
    1 row in set (0.00 sec)

    2011年4月21日木曜日

    Ext4.0ドキュメントの印刷

    Ext4.0のドキュメントページを印刷するブックマークレットを作ってみた。

    今回は隠したインラインフレームに作成して、そのまま印刷するのではなく、
    印刷可能なWindowを表示するようにした。ExtWikiでも試行錯誤したが、プレビュー代わりになりこれが一番便利だった。
    また、ガイドがマニュアルに統合されたので、こちらも印刷しやすいようにしてみる。

    例によってブックマークレット表示サイトで表示してみる。
    ExtJS4.0 ドキュメント印刷(クラス)

    ExtJS4.0 ドキュメント印刷(ガイド)

    もともとβ3の時に作成したが、ファイナルで少し変わっていたので上記に修正した。β3の時のものはこんな感じ
    ExtJS4.0β3 ドキュメント印刷(クラス)

    2011年4月3日日曜日

    画像をphpスクリプトのみで表示する

    画像のパスが問題で、プラグインやライブラリの設計や導入方法が若干複雑になる事がある。
    こんなとき、画像をプログラムに取り込んでしまうと設定自体不要になるケースがある。

    そのやり方をメモしておく
    1.画像をbase64文字列に変換する
    2.変換したbase64文字列をプログラムに組み込む

    1.画像をbase64文字列に変換する
     次のようなプログラムでbase64文字列を作成できる
    img_encode.php
    
    <?php
      //クォート内はターゲットのファイル名
      die(base64_encode(file_get_contents(''));
    ?>
    

    出力は下記のようになる

    R0lGODlhFQATAPcAAP//////zP//mf//Zv//M///AP/M///MzP/Mmf/MZv/MM//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9mZv9mM/9mAP8z//8zzP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8AM/8AAMz//8z/zMz/mcz/Zsz/M8z/AMzM/8zMzMzMmczMZszMM8zMAMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xmzMxmmcxmZsxmM8xmAMwz/8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswAM8wAAJn//5n/zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZzJmZmZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkzZpkzM5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/M2b/AGbM/2bMzGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZAGZm/2ZmzGZmmWZmZmZmM2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA/2YAzGYAmWYAZmYAM2YAADP//zP/zDP/mTP/ZjP/MzP/ADPM/zPMzDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZMzOZADNm/zNmzDNmmTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMAzDMAmTMAZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDMMwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBmAAAz/wAzzAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAMwAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAFQATAAAI/AABCBzoaRQpUVSopFJFaqDDgXny/JH4B8+fUchUOVP1UOCTKhEjjho5KlUqUqpGPXxSoKVLPwmp+CnZ0s/AJypaOqmCJ48fPFScUBGVqmVKgSRaqkhosSWVmi4LOAPgpkTUlkBdPjXqzBmpEjkLqFAhNKFSoUZNkkoqduxSJy7H1iw5ShXbq1iVbi0gCtVXt0up4PnpByYVskz9kKIz1okKnnkGQy0Aw2wBZCUcF6iy2elMP1H9iPLzCcAczqEnF8ha4I/AEnudthTlEtVWTwOtuqS9enZUPQ9LzASacKloVKj8dOoIoESehGXLCgbOXCAmT53++JmYB/fDgAA7

    2.変換した文字列をプログラムに組み込む
     上記で変換して得られたbase64文字列をbase64_decodeしヘッダーとともに出力する。
     1行が長い場合、適当な長さに切り、使うときに文字列として連結する。
    img_decode.php
    
    <?php
      $img = 'R0lGODlhFQATAPcAAP//////zP//mf//Zv//M///AP/M///MzP/Mmf/MZv/M'
     .'M//MAP+Z//+ZzP+Zmf+ZZv+ZM/+ZAP9m//9mzP9mmf9mZv9mM/9mAP8z//8z'
     .'zP8zmf8zZv8zM/8zAP8A//8AzP8Amf8AZv8AM/8AAMz//8z/zMz/mcz/Zsz/'
     .'M8z/AMzM/8zMzMzMmczMZszMM8zMAMyZ/8yZzMyZmcyZZsyZM8yZAMxm/8xm'
     .'zMxmmcxmZsxmM8xmAMwz/8wzzMwzmcwzZswzM8wzAMwA/8wAzMwAmcwAZswA'
     .'M8wAAJn//5n/zJn/mZn/Zpn/M5n/AJnM/5nMzJnMmZnMZpnMM5nMAJmZ/5mZ'
     .'zJmZmZmZZpmZM5mZAJlm/5lmzJlmmZlmZplmM5lmAJkz/5kzzJkzmZkzZpkz'
     .'M5kzAJkA/5kAzJkAmZkAZpkAM5kAAGb//2b/zGb/mWb/Zmb/M2b/AGbM/2bM'
     .'zGbMmWbMZmbMM2bMAGaZ/2aZzGaZmWaZZmaZM2aZAGZm/2ZmzGZmmWZmZmZm'
     .'M2ZmAGYz/2YzzGYzmWYzZmYzM2YzAGYA/2YAzGYAmWYAZmYAM2YAADP//zP/'
     .'zDP/mTP/ZjP/MzP/ADPM/zPMzDPMmTPMZjPMMzPMADOZ/zOZzDOZmTOZZjOZ'
     .'MzOZADNm/zNmzDNmmTNmZjNmMzNmADMz/zMzzDMzmTMzZjMzMzMzADMA/zMA'
     .'zDMAmTMAZjMAMzMAAAD//wD/zAD/mQD/ZgD/MwD/AADM/wDMzADMmQDMZgDM'
     .'MwDMAACZ/wCZzACZmQCZZgCZMwCZAABm/wBmzABmmQBmZgBmMwBmAAAz/wAz'
     .'zAAzmQAzZgAzMwAzAAAA/wAAzAAAmQAAZgAAMwAAAP///wAAAAAAAAAAAAAA'
     .'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
     .'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
     .'AAAAAAAAAAAAAAAAAAAAACwAAAAAFQATAAAI/AABCBzoaRQpUVSopFJFaqDD'
     .'gXny/JH4B8+fUchUOVP1UOCTKhEjjho5KlUqUqpGPXxSoKVLPwmp+CnZ0s/A'
     .'JypaOqmCJ48fPFScUBGVqmVKgSRaqkhosSWVmi4LOAPgpkTUlkBdPjXqzBmp'
     .'EjkLqFAhNKFSoUZNkkoqduxSJy7H1iw5ShXbq1iVbi0gCtVXt0up4PnpByYV'
     .'skz9kKIz1okKnnkGQy0Aw2wBZCUcF6iy2elMP1H9iPLzCcAczqEnF8ha4I/A'
     .'EnudthTlEtVWTwOtuqS9enZUPQ9LzASacKloVKj8dOoIoESehGXLCgbOXCAm'
     .'T53++JmYB/fDgAA7';
      header('Content-Type: image/gif');
      die(base64_decode($img));
    ?>
    

    apacheのmod_rewriteで%2fを変換

    apacheは通常エンコードされたパス分離文字(%2F)が存在するURLを404(Not found)エラーで拒否する。

    通常は問題にならないのだろうが、パラメータをURLに埋め込み、mod_rewiteでphpに処理させるとき、urlやpathのようなパラメータを扱おうとすると顕在化する。

    apacheにこれを許可させるには、apacheのhttpd.confに「AllowEncodedSlashes On」を記述すればよい。

    VartualHostディレクティブ内でもOKだが、.htaccessに記述するとエラーとなる。

    参考
    apacheマニュアル
    PHPプロ!mod_rewriteでの%2F問題