‘Server’ カテゴリーのアーカイブ

Mac からのリモートデスクトップ接続で日本語キーボード使う

muraveです。仕事では日本語キーボードな Mac を使ってます。

Windows Server でがんばる系のお仕事がきそうなのでクラウド上に実験環境を作ろうと思いました。

(さらに…)

Facebooktwittergoogle_pluslinkedintumblrmail

DockerのJenkinsコンテナでアクセスログについて悩んでいた

muraveです。

今年の4月にリリースされたJenkins 2系をDocker公式イメージ

OFFICIAL REPOSITORY jenkins

で試していたのですが、Webフックからキックしてのビルドを試していて大変ハマリました。Webフックからのアクセス状況とかどうなってんだ?などなどtcpdumpとか使って調べてたんですけど、アクセスログもあったほうがいいよね、と思ったのでした。

公式イメージのJenkinsさんってそのまま立ち上げたらアクセスログ出してくれないんですよね。

調べまして、JENKINS_OPTSに

--accessLoggerClassName=winstone.accesslog.SimpleAccessLogger --simpleAccessLogger.format=combined --simpleAccessLogger.file=ログファイルのパス

てな感じで設定すれば良いことはわかりました。分かりましたが、コンテナ内やボリュームにログファイルを出すのは違う気がします。docker logs で見ることができるログはどういう扱いなんでしょう?

デフォルト設定のロギング・ドライバのログ(JSON形式)はホストの

/var/lib/docker/containers/(コンテナID)/(コンテナID)-json.log

にあって、これが docker logs の表示元になっています。

問題はどうやったらここに出力されるのか?ですが、標準出力、標準エラー出力へ出力すれば良いそうです。例えば、Nginx公式イメージのDockerfileでは

# forward request and error logs to docker log collector
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
    && ln -sf /dev/stderr /var/log/nginx/error.log

と、/dev/stdout と /dev/stderr へのシンボリックリンクを作成することで対応しているようです。

Jenkinsさんの場合は先ほどのJENKINS_OPTSでログファイルのパス指定が可能でしたので

--accessLoggerClassName=winstone.accesslog.SimpleAccessLogger --simpleAccessLogger.format=combined --simpleAccessLogger.file=/dev/stdout

と設定すると標準出力に出力されるようになり、コンテナのログにJenkinsさんへのアクセスログが記録されるようになりました。

これでDockerのロギング・ドライバでアクセスログも統一的にあつかえますね。
例えばFluentdロギング・ドライバに切り替えてログ集約するようにしたりする場合にも安心です。

Facebooktwittergoogle_pluslinkedintumblrmail

ConoHaでDocker Machine、2016年夏

Docker for Mac からベータが取れて久しいですが、インストールしたまま放置状態でしたので勉強中です。

とりいそぎ手元の開発環境構築で使いたいのですが、最近よく開発用に利用している ConoHa で Docker Machine していました。なぜか。

Docker for Mac vs. Docker Toolbox を読みますと、Docker for Mac を手元で使う分には Docker Machine を意識する必要すらなくなっているようですが、docker-machineコマンドも含まれてはいたので調べていたらそういうことになっていたのでした。

docker-machineコマンドでConoHa上にDockerのHostをVMレベルからcreateしたりstopしたりrmしたりできるようになったわけですがいまのところあまり使い道が思いつきません。ConoHaだとVM(サーバー)止めてても課金が止まらないんですよねぇ。

検証用や確認用に一時的にHostを立ち上げて(create)終わったらすぐ消す(rm)、とかでしょうか。

せっかくですし、すぐに忘れてしまいそうなので引っかかった箇所のメモを残しておこうと思います。

(さらに…)

Facebooktwittergoogle_pluslinkedintumblrmail

QNAP ファームウェア4.2.1 で起動スクリプトを追加して dhcpd を自由に設定する

「NAS(ナス)とNIC(肉)の相性は最高だネ」 夏野菜ジョークでハートキャッチ! muraveです。

QNAP TurboNAS で既存のファイルサーバー(Samba)を置き換えるための準備をしています。
このサーバーは DHCP Server の役割も担っているので NAS の選定時、機能が載っているという確認はしていました。

ところがいざ仕入れてセットアップと動作確認を進めると

「DHCP Server (dhcpd) の設定画面が NAS の IP より後ろしか range の設定できないという仕様でク◯だ!」

「設定した『既定のゲートウェイ』を再起動時に無視して自分のIPを設定しやがって◯ソとかいうレベルじゃなくバグってんじゃねーか!」

などありまして、大変困ってしまったのでした。 あまり使われてないんですかね。

置き換えするサーバーが居るネットワークはなぜか真ん中あたりにゲートウェイが居るし、range だって複数設定したいんだよ!

ということで、回避方法を調べていたところ dhcpd の設定ファイルを書き換えてから kill -HUP する独自の起動スクリプト(autorun.sh)を追加するという方法にたどり着きました。

(さらに…)

Facebooktwittergoogle_pluslinkedintumblrmail

『第9回 コンテナ型仮想化の情報交換会@福岡』メモ

4/23(土) に開催された『コンテナ型仮想化の情報交換会@福岡』に参加しました。

参加の動機は社内チャットで情報が流れてきた(重要)のと、仮想サーバーのイメージにディスクをぶん取られて汲々の開発機さんから「いいかげんDockerとかも検討したほうがいいんじゃないすか?」という囁きが聞こえてきたからです(幻聴)。

コンテナ型仮想化の情報交換会であってDockerの勉強会ではなかったわけですが、基礎から頭が整理できてよかったです。
目立ったキーワードは cgroup、capability、Docker、Namespace といった感じでしょうか。

(さらに…)

Facebooktwittergoogle_pluslinkedintumblrmail

Nginxのリバースプロキシを設定していてlocationの優先順位で大ハマリ

社内システムを動かしていたApacheなサーバーが廃止されたのでNginxなサーバーに移行しました。

他の社内システムに相乗りして、URLに特定のサブディレクトリがついていたら動作を分けて背後のアプリケーション・サーバーにリバースプロキシで接続という構成です。

server {
    #省略

    #リバースプロキシの設定を追加
    location /hoge/ {
        proxy_pass http://127.0.0.1:3000/;
    }

    #省略

    location ~* \.(jpg|jpeg|gif|png|css|js|ico|xml)$ {
        access_log        off;
        log_not_found     off;
    }
}

Nginxに上記のようなリバースプロキシの設定を入れたところプログラムは動作しているのに画像、css、jsが読み込めずに大ハマリしました。

勘がいい人は「他は省略しているのに残してある箇所」があやしいのに気づいてしまったと思いますが、その通り、アクセスログにも何も出ない、なぜだ?なぜなんだぜ?と調べていたらいらっしゃいましたよ、画像、css、jsの処理を全部もっていってるお方が。

location、正規表現の方が未指定の前方検索より強いので location ~* \.(jpg|jpeg|gif|png|css|js|ico|xml)$ に処理を全部もっていかれていたってわけです。Nginx で location の判定方法と優先順位を調べるがとても参考になりました。ありがとうございます。

正規表現よりも優先順位が高い書き方で指定すると解決しました。

server {
    #省略

    #リバースプロキシの設定を追加
    location ^~ /hoge/ {
        proxy_pass http://127.0.0.1:3000/;
    }

    #省略

    location ~* \.(jpg|jpeg|gif|png|css|js|ico|xml)$ {
        access_log        off;
        log_not_found     off;
    }
}
Facebooktwittergoogle_pluslinkedintumblrmail

OpenCOBOLとファイル操作(弊社拡張)

OpenCOBOLからのファイル操作ですが内部での定義によって幾つか種類があります。

  1. 固定長レコードのシーケンシャルファイル
  2. 固定長レコードのISAM形式ファイル
  3. 可変長レコードのシーケンシャルファイル

これらはCOBOLソース内でのSELECT句での定義でファイル名を直接または環境変数を経由して間接的に指定することが可能です。OpenCOBOLの素の状態ですと

          SELECT ADBF0320 ASSIGN TO "FILE0001"
                                ORGANIZATION  SEQUENTIAL
                                ACCESS  MODE  SEQUENTIAL.

と記述されている場合は、FILE0001または環境変数 DD_FILE0001または dd_FILE0001に設定されているファイル名のファイルのOPENが可能です。弊社ではこの環境変数渡しの機能を活用してperlからOpenCOBOL側へJCL中で使用しているファイル名を渡しています。
さて、JCL中ではSYSINと呼ばれる形式でファイルを作らずにその場で渡したいデータを記述することがあります。

\INPUT ACCEPT1,TYPE=DATASSF,LIST=YES
4241122 登録データ1
4241122 登録データ1追加分
\ENDINPUT;

ADAM2200:
\STEP PROG2000 FILE=USL.CAT1  DUMP=DATA SUBLM=NORMAL;
\ASSIGN FILE0010 USR.F001    SHARE=ALL HOLDMODE=NO;
\ASSIGN FILE0110 USR.F011-T  FILESTAT=TEMP   PUBLIC NORMAL=PASS;
\ALLOCATE FILE0110  USR.F011-T SIZE=05;
\DEFINE FILE0110  RECSIZE=57 BLOCKSZ=10260 INCRSZ=01
                     RELSP RECFORM=FB;
\ASSIGN SIN      ACCEPT1        FILESTAT=SYSIN;
\ENDSTEP;

上記ではJCL中で定義されたASSIGN1というSYSINの内容をSINというファイル識別名に割り当てています。これをperlに置き換える(この部分自動的に処理しています)と

INPUT "ACCEPT1,TYPE=DATASSF,LIST=YES",<<_EOT;
4241122 登録データ1
4241123 登録データ1追加分 
_EOT
ENDINPUT;

ADAM2200:
STEP "PROG2000 FILE=USL.CAT1  DUMP=DATA SUBLM=NORMAL";
ASSIGN "FILE0010 USR.F001    SHARE=ALL HOLDMODE=NO";
ASSIGN "FILE0110 USR.F011-T  FILESTAT=TEMP   PUBLIC NORMAL=PASS";
ALLOCATE "FILE0110  USR.F011-T SIZE=05";
DEFINE "FILE0110  RECSIZE=57 BLOCKSZ=10260 INCRSZ=01",
                     "RELSP RECFORM=FB";
ASSIGN "SIN      ACCEPT1        FILESTAT=SYSIN";

上記のように変換しています。さて、SYSINの内容ですがまず1レコードが何byteであるという情報がありません。そして1行毎に行の長さが異なっています。今回移植の対象となった対象機のCOBOLではこのような場合には「改行区切りで1レコード」とするようになっていました。つまり可変長レコードです。ところがOpenCOBOLで可変長レコードをファイルとして扱うためには:
レコード先頭1バイトまたは2バイトにレコード長+1レコード分のデータ
レコード先頭1バイトまたは2バイトにレコード長+1レコード分のデータ
・・・
という形式でデータを作成する必要があります(つまり1byte目がレコード長として正しくないと、メモリ上に過大な長さのデータが読み込まれて、あっという間にSegfault します)。OpenCOBOLの外側からファイルの形式について何らかの方法で指示を出す必要が在りましたので、弊社ではファイル名の先頭に「sysin://」という識別子を(URI的に)付けてファイル名を渡すようにしています。これをOpenCOBOL内のファイルハンドラに渡る前に処理し、改行区切りの可変長レコードとして処理しています。同じく、印刷用の中間データなど1行の長さが可変長となる場合について「sysout://」という識別子を付けて指定することができるようにしています。他、標準の固定長レコードのドライバと動作をちょっと変えたドライバを使いたい場合を考え「misam://」や「mseq://」さらにLinux他では/dev/nullに該当するものとして「nullfs://」という識別子を指定可能としています。
標準の固定長レコードのドライバと動作をちょっと変えたいというのは例えばレコード挿入、削除時の細かい振る舞い、二次キー指定時の動作、二次キーを持っているISAMファイルを主キーしか定義していないCOBOLソースから書き込みモードで開いた場合の動作(OpeCOBOLの標準の動作では、書き込みモードでISAMファイルを開くと、一旦削除されますので、最悪二次キーについての定義が欠落します)等々です。
また、「perlfs://CLASSNAME/param」という形式でファイル名を渡す事によりファイルハンドラとしてperlにて記述したものを呼び出すようにもしています。DBとCOBOL内の固定長レコードの編集用コードについてperlで記述できるため、大変柔軟にDBとの連携を図れるようになりました(つまり、DBD::PgやDBD:MySQL、Oracleなどとの連携も可能です。MySQLについては既に運用されていますし、KeyValue系のDBへの接続もそれほどの変更なしに実装できます)。
上記に加え、固定長レコードやキー定義などの情報を別ディレクトリ内の管理ファイルに登録しておくことで、ファイルオープン時に正しい形式のファイルを使用しているかどうかCOBOLプログラム内の定義と照らし合わせて動的にチェックできるようになり、また現在どのようなファイルがオープン状態であるか?を全てモニタできるようにしています。
これらの改造はOpenCOBOLがオープンソースとして配布されていたことで可能になりました。成果は随時コミュニティ等にフィードバックしていきたいと考えております。

Facebooktwittergoogle_pluslinkedintumblrmail

OpenCOBOLとSPECIAL-NAMES(拡張)

JCLからCOBOLを呼び出す際に外部パラメータを渡したいということがあります。某社JCLでは

\DCV  ACCEPT2,CHARACTER='04' ;

などDCVというコマンドで定義して、これをCOBOL側では

       SPECIAL-NAMES.
           CHAR002  IS  ACCEPT2.

のように記述して取り込んでいるようです(この場合CHAR002に’04’が入ります)。
ところがもちろんOpenCOBOLにはこのような機能はありません。実際 SPECIAL-NAMES. 部分の定義をcobc/parser.yから拾い上げると:

special_name:
  mnemonic_name_clause
| alphabet_name_clause
| symbolic_characters_clause
| locale_clause
| class_name_clause
| currency_sign_clause
| decimal_point_clause
| cursor_clause
| crt_status_clause
| screen_control
| event_status
;

となっており、mnemonic_name_clause 部分が使えそうなのですが、system_nameとして使用できるのは

} system_table[] = {
  {"SYSIN",     CB_DEVICE_NAME,  CB_DEVICE_SYSIN, NULL},
  {"SYSIPT",        CB_DEVICE_NAME,  CB_DEVICE_SYSIN, NULL},
  {"SYSOUT",        CB_DEVICE_NAME,  CB_DEVICE_SYSOUT, NULL},
  {"SYSLIST",       CB_DEVICE_NAME,  CB_DEVICE_SYSOUT, NULL},
  {"SYSLST",        CB_DEVICE_NAME,  CB_DEVICE_SYSOUT, NULL},
  {"PRINTER",       CB_DEVICE_NAME,  CB_DEVICE_SYSOUT, NULL},
  {"SYSERR",        CB_DEVICE_NAME,  CB_DEVICE_SYSERR, NULL},
  {"CONSOLE",       CB_DEVICE_NAME,  CB_DEVICE_CONSOLE, NULL},
  {"C01",       CB_FEATURE_NAME, CB_FEATURE_C01, NULL},
  {"C02",       CB_FEATURE_NAME, CB_FEATURE_C02, NULL},
  {"C03",       CB_FEATURE_NAME, CB_FEATURE_C03, NULL},
  {"C04",       CB_FEATURE_NAME, CB_FEATURE_C04, NULL},
  {"C05",       CB_FEATURE_NAME, CB_FEATURE_C05, NULL},
  {"C06",       CB_FEATURE_NAME, CB_FEATURE_C06, NULL},
  {"C07",       CB_FEATURE_NAME, CB_FEATURE_C07, NULL},
  {"C08",       CB_FEATURE_NAME, CB_FEATURE_C08, NULL},
  {"C09",       CB_FEATURE_NAME, CB_FEATURE_C09, NULL},
  {"C10",       CB_FEATURE_NAME, CB_FEATURE_C10, NULL},
  {"C11",       CB_FEATURE_NAME, CB_FEATURE_C11, NULL},
  {"C12",       CB_FEATURE_NAME, CB_FEATURE_C12, NULL},
  {"FORMFEED",      CB_FEATURE_NAME, CB_FEATURE_FORMFEED, NULL},
  {"SWITCH-1",      CB_SWITCH_NAME,  CB_SWITCH_1, NULL},
  {"SWITCH-2",      CB_SWITCH_NAME,  CB_SWITCH_2, NULL},
  {"SWITCH-3",          CB_SWITCH_NAME,  CB_SWITCH_3, NULL},
  {"SWITCH-4",      CB_SWITCH_NAME,  CB_SWITCH_4, NULL},
  {"SWITCH-5",      CB_SWITCH_NAME,  CB_SWITCH_5, NULL},
  {"SWITCH-6",      CB_SWITCH_NAME,  CB_SWITCH_6, NULL},
  {"SWITCH-7",      CB_SWITCH_NAME,  CB_SWITCH_7, NULL},
  {"SWITCH-8",      CB_SWITCH_NAME,  CB_SWITCH_8, NULL},
  {NULL, 0, 0, NULL}
};

上記以外の場合は”Unknown system-name ‘何々'” のメッセージが出力されてエラーとなります。弊社にて対応した際、当初は外部からの値を環境変数経由で渡す事を検討しましたが、COBOL内部で値をセットしてJCLへ返す場合があると言うことで思い切って機能拡張しました。DCVにて渡された値については環境変数(DCV_varname=値)を定義して渡し、値を書き替える場合も環境変数を書き替え、プロセス終了時にDCV_varname環境変数の値を全てファイルへ書き出しています。書き替えた値をJCLへ返す際にこの(環境変数 SYSTEM_STATUS_FILE に定義されたファイル名の)ファイルを読み込んでいます。
これらの拡張とJCLをperlに変換した*.jclスクリプトを使用することで DCV機能は

DCV  "ACCEPT2,CHARACTER='04'";

という記述のperlスクリプトと

       SPECIAL-NAMES.
           CHAR002  IS  ACCEPT2.

元のCOBOLプログラムをそのまま解釈できる拡張したOpenCOBOLにて処理できるようになりました。移植対象のコード中にDCVを使用している箇所が4桁箇所以上もありましたが、かなり簡単に移植ができました。

Facebooktwittergoogle_pluslinkedintumblrmail

OpenCOBOLで印刷機能拡張(コード側)

OpenCOBOL-1.1は印刷に関しては改行または改ページ程度しかケアしてくれません。そのままでは業務では使いづらいので色々と拡張して弊社では使っています。印刷機能というとREPORT caluse でREPORT IS hogeなどで定義して使うのが一般的ですが、このあたりは各社拡張しているようです。

弊社で対応した先では、定義にCHARACTER TYPE fontname COLUMN pos 形式で書ける拡張がされていましたのでcobc/parser.y 他に手を入れて同様の構文が処理できるように拡張しています(つまり、元の汎用機機種のCOBOLソースによってはそのままコンパイルが通ります)。

           02  FILLER                  PIC N(13) CHARACTER TYPE KM-12P
                                VALUE  "ダンプリスト" .
           02  FILLER                  PIC X(14) VALUE SPACE.
           02  FILLER                  PIC N(04) CHARACTER TYPE KM-7P
                                           VALUE "作成日" .
           02  H01-YY                  PIC Z9.
           02  FILLER                  PIC N(02) CHARACTER TYPE KM-7P
                                           VALUE "年 " .
           02  H01-MM                  PIC Z9.
           02  FILLER                  PIC N(02) CHARACTER TYPE KM-7P
                                           VALUE "月 " .

上記ではKM-7PまたはKM-12Pというフォント名で対応する文字列を印字するように定義されています。
また、

       01  W-WORK.
           02  W-SEIRNO                PIC ZZZZZZZ9         COLUMN    4.
           02  W-YMD                   PIC X(08)            COLUMN   13.
           02  W-KIN                   PIC ZZZZ,ZZZ,ZZZ,ZZ9 COLUMN   22.

上記では印字位置を COLUMNで指定しています。
さて、これらを内部的にどのように処理しているのかと言いますと、当初(2010〜2011年頃)取りかかった頃は文字フォント指定や文字位置指定をコンパイル中に「隠れFILLER」として定義を埋め込んでいたのですが、GROUP項目をMOVEするときにサイズが異なってしまうという問題にあたりました。そこで、WRITE時にCHARACTER TYPEまたはCOLUMNに対応する情報にしたがって別のバッファに値を詰め直すようにしています。これは印刷に係わる出力へのWRITE時だけ効くようにしていますので、通常のMOVE時や印刷に関連しない出力では影響を受けないようになっています。

この印刷機能は印刷用のWRITEを呼び出す前に、以下の様な関数を呼び出すことで実現しており、この関数は元の印刷定義にしたがってコンパイル時に自動生成されています。

cob_move_with_W_WORK (cob_field *from, cob_field *to, const int size)
{
    unsigned char *fp = from->data;
    unsigned char *tp = to->data;
    unsigned int i1   = 0;
    unsigned int i2   = 0;
    unsigned int i3   = 0;
    // name= W-SEIRNO 
    memcpy(tp, "\x1b[4G", 4);
    tp += 4;
    memcpy(tp, fp, 8);
    tp += 8;
    fp += 8;
    // name= W-YMD 
    memcpy(tp, "\x1b[13G", 5);
    tp += 5;
    memcpy(tp, fp, 8);
    tp += 8;
    fp += 8;
    // name= W-KIN 
    memcpy(tp, "\x1b[22G", 5);
    tp += 5;
    memcpy(tp, fp, 16);
    tp += 16;
    fp += 16;
// 以下略

「\x1b [ col G」のようなエスケープシーケンス(多分若い人はご存じない?)を印刷出力用の中間データに吐き出すことで「colカラム目に移動」のような指示を仮想ラインプリンタ(PDF出力用)に送り出しています。仮想ラインプリンタ側ではこのようなエスケープシーケンス以外にも、プリンタのカーソル位置を直接指定するようなバイナリコードにも対応させましたので、印刷帳票に関連するCOBOLプログラムの移植についてかなり柔軟に対応出来るようになりました。
そして、「何行出力したら改ページ」「データが続いていたら連続出力、変わったら改ページ」のような印刷帳票にありがちな処理についてロジックに手を入れること無くそのまま移植出来てしまうと言う利点があり、COBOLで帳票を利用しているプログラムの移植にお困りの方には大変有用なソリューションだと思います。

Facebooktwittergoogle_pluslinkedintumblrmail

OpenCOBOLで任意の日付を返す機能追加

月末のバッチを今のうちに流して検証したい、過去に流したバッチを再現させたい。そんな事ありますよね?しかし、プログラム中に現在日時を取得する処理が入ってるプログラムがあると色々面倒です。例えば、検証したい日時にサーバの日付を設定し直して流す…とかだと他の色々なサービスが正しく動作しなくなったりします。実行時の日付から処理年度を推測しているプログラムがあったりして、処理結果が以前バッチを流したときと違う…という事があるわけです。

また、対応するプログラム箇所を探し出して、都度書き替えていく…だと大変な工数がかかりますし元のロジックと変わったり変更漏れもあるかもしれません。なるべくプログラムでスマートに対応したいところです。

実際に、汎用機などでは同様の機能を持つ物があり、JCL中で処理DATEを設定しておくとその設定にしたがって以降の日付処理をおこなってくれます。

そこで、OpenCOBOLに任意の日付を返す機能を追加してみました。コードの概略としては、libc の time関数をフックしておいてCOB_DATE環境変数に値がセットされていたらこの値を現在日付として返すという内容になります。time関数を使っている箇所が libcob中に結構多く散らばっていますし、サブルーチンでも有効に効かせたいのでこのような方法を採ってみました。

static time_t (*time0)(time_t *t);
time_t time(time_t *t);

#define COB_DATE_ENVNAME "COB_DATE"
time_t time(time_t *t)
{
    if (t == NULL && getenv(COB_DATE_ENVNAME)) {
        char cob_date_envval[512];
        long fake_t = 0; 
        struct tm tm;
        strncpy(&cob_date_envval[0],
            getenv(COB_DATE_ENVNAME),
            sizeof(cob_date_envval));
        fake_t = atol(&cob_date_envval[0]);
        tm.tm_sec  = 0; 
        tm.tm_min  = 0; 
        tm.tm_hour = 0; 
        tm.tm_mday = fake_t % 100; 
        tm.tm_mon  = ((fake_t / 100) % 100) - 1; 
        tm.tm_year = fake_t / 10000 - 1900;
        tm.tm_wday = 0; 
        tm.tm_yday = 0; 
        tm.tm_isdst   = 0; 
        /* you must set validated value */
        fake_t = (long)mktime(&tm);
        return (time_t)fake_t; 
    }else{
        return time0(t);
    }    
}

int
main (int argc, char **argv)
{
    /* 略 */
    time0 = dlsym(RTLD_NEXT, "time");
    /* 略 */    
}

上記コードでは値のチェック等色々とサボってて荒いのですが、やりたい内容はだいたい伝わると思います。COB_DATE環境変数に20121231等設定してある状態で上記コードが走ると、time関数は常に 2012年12月31日 00時00分00秒を返します。
これで年末年始での処理を先に検証しておくことが出来ますね。

Facebooktwittergoogle_pluslinkedintumblrmail