C#のStreamWriterではClose()呼び出しを忘れるな!

やあ子供たち。おじさんはつい先日、C#StreamWriterを使っているプログラムが0バイトのファイルしか出力してくれなくなってしまう現象に出くわしたよ。
これがよくありがちなやつで、あるPCではちゃんと出力されるのに、別のPCでは0バイトになるというもの。

で、ここで、ま、誰でも思いつくことではあるんだが、StreamWriterインスタンスについて、Close()を明示的に呼ぶようにしてみたんだ。するとどうだ、たちどころに問題は解決したぞ。

はいはいはい、もうおわかりですね。StreamWriterを使った場合は、必ずClose()を明示的に呼ぶようにしようというここは教訓だな!

おじさんはね、ここで原理的なところに立ち入ったりするつもりはないよ。あるPCでは動いた、あるPCでは動かない、そんなことが起きないようにするよりよいプログラムの記述方法が見つかりましたとなれば、そうするまで!という内容の、今日はメモでした。
じゃ今日はこんなところで。チャオ!

水槽ネタ:水交換でも二酸化炭素補給できるのか?

水交換しただけでなにやら水草が元気に!なぜなぜ?

やあ子供たち。水交換をした後にやけに水草に気泡がついているなと思って調べてみたら、どうも水道水の中には二酸化炭素がそれなりに入っていて、水交換をすると、交換前の状態よりも二酸化炭素濃度が上がり、光合成が開始されるという説もあるということを知ったよ。

まその説を半ばそのまま信じてだね、いくらね、マグネシウムを投入したりだとか照明を明るくしたりしていてもね、二酸化炭素がないと光合成は起こらないんだな、っていうことが実体験として観察できた気がしたので今日は一応メモっておいたぞ。

なのでな、そういう説を信じるのであればだがな、ボンベとかもいいのかも知れんけど、週に1,2回水交換するのが苦でなければ、それで二酸化炭素の供給っていかばかりかはできる、或いはそれで充分なケースもあるんじゃないかなってちょっと思ってしまいました。

でもねでもね、その水草についている気泡が、果たして本当に光合成によるものなのかどうかは、諸説あるということも書いておくよ。水道水の中には水道管の中の温度圧力体積の中でいろいろな気体が溶け込んでいたものが、水槽の中で条件が変わり、そうしたいろいろな気体が単に葉っぱの上についているだけなのではという説もあるので、そこは各自で考え、判断するしかないところだ。(やっぱりボンベ導入かな?)
じゃ今日はこんなところで。チャオ!

なんだっけ、createReadStreamがこけないようにするためのエラー・表示処理

やあ子供たち。
NodeJSで、ファイルをダウンロードさせたい場合なんかに使うcreateReadStream()関数。容量の大きいファイルもストリーミングでリクエスト元にお届けしてくれるとてもありがたいやつだが、指定したファイルが見つからない場合はあっさりこけてサーバーが止まってしまうという問題がある。

ブラウザのプロキシサーバ設定を行うと、httpリクエストのヘッダのメソッド行のURIフィールドが、絶対URLになるということのようだ。

WireSharkというツールを使ってプロトコルの中を見る。
見ようとしたのだが、外部URLなんてのっけからHTTPSなサイトばかりでなかなかHTTPのリクエス送信先というものが見つからない。そこで、ローカルで適当なnodejsサーバを立ち上げ、WireSharkの「Adapter for loopback traffic capture」というインタフェース?を選択してキャプチャをしたところ、ついにHTTPリクエストヘッダがキャプチャできたのだった!
がしかし、「http サイト」で検索すれば、httpサイトが出てこないわけではない。このような場合はWireShark上でインタフェースとして普通に「WiFi」を選べば、それでもHTTPリクエストヘッダはキャプチャできた。

Windowsでもgrepしたい!findstr コマンドをもっと使おう!

やあ子供たち。今日はちょっとCEDECの講演タイトルばりのキャッチ―なタイトルにしてみたよ。
Windowsのフォルダの中に、テキストファイルがたくさんあって、(そう、例えば何かのアプリが出力したログだったりね)、でその中に特定の文字列を含むものがあれば、該当するファイルのファイル名と、出現行を、あますところなく教えてほしい。


そんな時は、LinuxなどのUnix系システムではgrepコマンドを当たり前のように使うわけなのであるが、じゃあWindowsでは?ということになってくると、おじさんは今までエクスプローラの右上の方にある検索窓に何かを打ち込むようなことしか思いつかなかったんだけれども、この検索窓は何やら気まぐれで、うまく働いてくれる時とそうでない時とがあったりする印象だったんだよな。


そこで今日は「windows grep equivalent」という検索ワードでググってみたよ。するとどうだ、なんとなんと
Windowsにはfindstrというコマンドプロンプト上で使えるコマンドがあって、この日記もおじさん自身の備忘録メモという位置づけなのでここにメモしておこうかなということ。
さて使用方法は、コマンドプロンプトを立ち上げて以下を実行するとよくわかりますよと。

findstr /?|more

そしてその出力は以下のようだ。使用方法だね。

ファイルから文字列を検索します。

FINDSTR [/B] [/E] [/L] [/R] [/S] [/I] [/X] [/V] [/N] [/M] [/O] [/P]
        [/F:ファイル] [/C:文字列] [/G:ファイル] [/D:ディレクトリ一覧]
        [/A:色属性] [/OFF[LINE]] 文字列 [[ドライブ:][パス]ファイル名[ ...]]

  /B           行の先頭にあるパターンを検索します。
  /E           行の末尾にあるパターンを検索します。
  /L           検索文字列をリテラルとして使用します。
  /R           検索文字列を正規表現として使用します。
  /S           現在のディレクトリとすべてのサブディレクトリから一致する
               ファイルを検索します。
  /I           検索するときに大文字と小文字を区別しません。
  /X           完全に一致する行を出力します。
  /V           一致しない行のみを出力します。
  /N           一致する各行の前に行番号を出力します。
  /M           ファイルに一致する行があるときに、ファイル名のみを出力します。
  /O           一致する各行の前に文字オフセットを出力します。
  /P           印刷不可能な文字を含むファイルをスキップします。
  /OFF[LINE]   オフライン属性が設定されたファイルをスキップしません。
  /A:属性      2 桁の 16 進数で色属性を指定します。"color /?" を参照してくだ
               さい。
  /F:ファイル  指定したファイルからファイル一覧を読み取ります (/ を指定する
               とコンソール)。
 /C:文字列    指定された文字列をリテラル検索文字列として使用します。
  /G:ファイル  指定されたファイルから検索文字列を取得します (/ を指定する
               とコンソール)。
  /D:ディレクトリ
               セミコロンで区切られた検索されるディレクトリ文字列テキストの
               一覧を検索します。
  [ドライブ:][パス]ファイル名
               検索するファイルを指定します。
複数の文字列を検索する場合には、引数 /C を使わず、各文字列をスペースで区切り
ます。
たとえば、FINDSTR "hello there" x.y と指定した場合は、ファイル x.y で "hello"
または "there" が検索されます。
これに対して、FINDSTR /C:"hello there" x.y と指定した場合はファイル x.y で
"hello there" が検索されます。

正規表現クイック リファレンス:
  .            ワイルドカード: 任意の文字
  *            繰り返し: ゼロ個以上の直前の文字またはクラス
  ^            行位置: 行頭
  $            行位置: 行末
  [class]      文字クラス: セットの任意の 1 文字
  [^class]     逆クラス: セット以外の任意の 1 文字
  [x-y]        範囲: 指定した範囲の任意の文字
 \x           エスケープ: メタ文字 x のリテラル使用
  \<xyz        単語位置: 単語の先頭
  xyz\>        単語位置: 単語の終わり

FINDSTR の詳細な正規表現に関しては、オンライン ヘルプのコマンド リファレンスを
参照してください。

どうだいこれは。とくにサブフォルダ構造を階層的にたどってくれる/Sオプションなっていう便利そうなものもあるじゃないですかー。
そして例えばの使用方法はこう。

findstr /R /C:"Taro" .\*.csv

これはカレントフォルダの中にある、あまねく.csvファイルに対し、その中に"Taro"という文字を含む.csvファイルをすべて、その出現行とともに出力してくれるよ。

どうだこれ一行でだぞ。Windowsなんかだと何かそういうフリーソフトとか探しに行っちゃいそうだけどそんなもの一切必要ないからねもう。このfindstrはいやいや色々オプションがたくさんあって使えそうな気がするなー。
じゃあ今日はこんなところだ。チャオ!

レシート打ち込みはGoogleドキュメント音声入力とGoogleSpreadSheetにお任せ☆彡

やあ子供たち元気でやっているか。夏休みで慣れないレンタカーで行かなきゃならないからって、怖気づいていたりしやしないかい。今日はちょっとした小技を覚えたので紹介するぞ。

おじさんは月に一回財布の中にたまったレシートをSpreadSheetに打ち込むということをもう何年もやっているのだけど、これがまあ面倒だったんだよ。テンキーを導入した方が少しは楽になるだろうかとか、あまり助けにはならなさそうな発想しかこれについては出せずに結局毎月手動打ち込みをし続けてきてしまった。
しかし、「サボろうとすることは技術進化や作業効率化の父」とよく言われるように(この語句通りではないにしても同じ意味のことがよく言われているよね)、もうめんどくさいから音声入力とかでせめてできないものかとちょっと調べてみた。

で、ここがポイントなんだが、Windowsとか、まPC環境なんでね、Windowsとかで何かツールを、、っていう切り口で調べてもそれは何も良い結果がヒットしません。有料のものだったり、OSの機能としてもあるようだが何だかわかりにくかったり動かなかったりね。動かねーじゃねーか>ディクテーション!現在の言語では対応してません?おいおい今どんな時代だよ!?

ところが、答えはすべてブラウザの中にあった。そう、Chromeブラウザ+Googleドキュメントね。Googeドキュメントのツール>音声入力とやると、とても性能のいい音声解釈エンジン(というか最近はSiriとかGoogleHomeとかですっかり慣れているけどね)が、そのままマイクに喋った内容をテキスト化してくれるんだ。

まずは日付。
日付は、例えば「〇月□日」だったら、そのまま話しかけてはだめさ。のちにSpreadSheetで、DateValue()関数でちゃんとした日付型にするために、「〇、スラッシュ、□」と話しかけよう。そうすれば「〇/□」というテキストができて、これをそのままDateValue()関数にながしこめば、現在の西暦年の入ったちゃんとした日付型に変換できるぞ。

次にキーボードからタブを打とう。
スペースでもいいが。これはのちにSpreadSheetで、テキストを列に分割するために必要だ。

次に、お店の名前を話しかけよう。これはそのままテキストになる。

次にキーボードからまたタブを打とう。スペースでもいいが。これはのちにSpreadSheetで、テキストを列に分割するために必要だ。

次に、金額を
話しかけよう。でも数字だけでいいぞ「~円」とか言ってしまうと文字列になってしまうからな。

これでレシート1枚分の行が完成したので、キーボードでリターンキーを押して改行し、また次の行に2枚目のレシート情報を上記の容量で入力していこう。

N枚分のレシートを音声入力できたら、N行のGoogleドキュメントテキストができているはずだ。
それをまるごとコピーして、GoogleSpreadSheetに貼付けよう。(とまあここからは話もうExcelでも何でもよいのだがな)
そして、データ>テキストを列に分解を実行することで、
あっ!というまに、「日付、お店の名前、支払った金額」という、3列のスプレッドシートデータが完成だ。

以上!じゃ今日のお話はこれでおしまいだよ。
チャオ!

TimeNeoを作ったよ

やあ子供たち。
time_tのオレオレラッパーを作ってみたので紹介するぞ。最後に簡単なカレンダーの作り方もあるよ。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <iomanip>
#include <ctime>
#include <string>



using namespace std;


#define TIMETNEO_LINEMAX 80
#define TIMETNEO_FORMAT_DATE "%d/%02d/%02d"
#define TIMETNEO_FORMAT_TIME "%02d:%02d:%02d"
class TimeNeo
{
public:
	TimeNeo(void) {
		this->LoadNow();
	}
	TimeNeo(const TimeNeo& t)
	{
		_time = t._time;
	}
	void LoadNow(void)
	{
		_time = time(nullptr);
	}
	string getDateAsText(void)
	{
		int y, m, d;
		this->getDate(&y, &m, &d);
		char ss[TIMETNEO_LINEMAX];
		sprintf(ss, TIMETNEO_FORMAT_DATE, y, m, d);
		return string(ss);
	}
	string getDateTimeAsText(void)
	{
		int y, m, d, h, min, s;
		this->getDateTime(&y, &m, &d, &h, &min, &s);
		char ss[TIMETNEO_LINEMAX];
		sprintf(ss, TIMETNEO_FORMAT_DATE " " TIMETNEO_FORMAT_TIME, y, m, d, h, min, s);
		return string(ss);
	}
	void getDate(int* pyear, int* pmonth, int* pdate)
	{
		int h, m, s;// dummies
		getDateTime(pyear, pmonth, pdate, &h, &m, &s);
		return;
	}
	void getDateTime(int* pyear, int* pmonth, int* pdate, int* phour, int* pmin, int* psec)
	{
		tm tm0;
		localtime_s(&tm0, &_time);
		*pyear = tm0.tm_year + 1900;// 年次は1900年からの数え年!
		*pmonth = tm0.tm_mon + 1;   // 月は0から11!
		*pdate = tm0.tm_mday;
		*phour = tm0.tm_hour;
		*pmin = tm0.tm_min;
		*psec = tm0.tm_sec;
	}
	int getYear()
	{
		tm tm0;
		localtime_s(&tm0, &_time);
		return tm0.tm_year + 1900;// 年次は1900年からの数え年!
	}
	int getMonth()
	{
		tm tm0;
		localtime_s(&tm0, &_time);
		return tm0.tm_mon + 1;// 月は0から11!
	}
	int getDay()
	{
		tm tm0;
		localtime_s(&tm0, &_time);
		return tm0.tm_mday;
	}
	int getWeekDay()
	{
		tm tm0;
		localtime_s(&tm0, &_time);
		return tm0.tm_wday;
	}
	void setDate(const int year, const int month, const int date)
	{
		// 人間が読める形式に変換
		tm tm0;
		localtime_s(&tm0, &_time);
		// 必要な内容のみ更新
		tm0.tm_year = year - 1900;// 年次は1900年からの数え年!
		tm0.tm_mon = month - 1;// 月は0から11!
		tm0.tm_mday = date;
		_time = mktime(&tm0);
		return;
	}
	void setDate(const int date)
	{
		// 人間が読める形式に変換
		tm tm0;
		localtime_s(&tm0, &_time);
		// 必要な内容のみ更新
		tm0.tm_mday = date;
		_time = mktime(&tm0);
		return;
	}
	void setDateTime(const int year, const int month, const int date, const int hour, const int min, const int sec)
	{
		// 人間が読める形式に変換
		tm tm0;
		localtime_s(&tm0, &_time);
		// 必要な内容のみ更新
		tm0.tm_year = year - 1900;// 年次は1900年からの数え年!
		tm0.tm_mon = month - 1;// 月は0から11!
		tm0.tm_mday = date;
		tm0.tm_hour = hour;
		tm0.tm_min = min;
		tm0.tm_sec = sec;
		_time = mktime(&tm0);
		return;
	}
	friend double operator-(const TimeNeo& a, const TimeNeo& b)
	{
		return difftime(a._time, b._time);
	}
	time_t operator=(const TimeNeo& a)
	{
		_time = a._time;
		return _time;
	}
	bool operator<(const TimeNeo& a)
	{
		return (a - *this) >= 0;
	}
	TimeNeo& advanceDate(const int dday)
	{
		int y, m, d, h, min, s;
		this->getDateTime(&y, &m, &d, &h, &min, &s);
		d += dday;
		this->setDateTime(y, m, d, h, min, s);
		return *this;
	}
	TimeNeo& advanceMonth(const int dmon)
	{
		int y, m, d, h, min, s;
		this->getDateTime(&y, &m, &d, &h, &min, &s);
		m += dmon;
		this->setDateTime(y, m, d, h, min, s);
		return *this;
	}
	TimeNeo& incDate(void)
	{
		return advanceDate(1);
	}
	TimeNeo& decDate(void)
	{
		return advanceDate(-1);
	}
	TimeNeo& incMonth(void)
	{
		return advanceMonth(1);
	}
	TimeNeo& decMonth(void)
	{
		return advanceMonth(-1);
	}
	time_t _time;
};

int main(int argc, char* argv[])
{
	TimeNeo tn0;
	TimeNeo tn1;
	tn1.setDate(1971, 2, 17);
	cout << tn0.getDateTimeAsText() << endl;
	cout << tn1.getDateTimeAsText() << endl;
	{
		int y, m, d, h, min, s;
		tn0.getDateTime(&y, &m, &d, &h, &min, &s);
		d += 30;
		h += 12;
		tn0.setDateTime(y, m, d, h, min, s);
		cout << tn0.getDateTimeAsText() << endl;
	}
	TimeNeo tn2 = tn0;
	{
		int y, m, d, h, min, s;
		tn2.getDateTime(&y, &m, &d, &h, &min, &s);
		s += 30;
		tn2.setDateTime(y, m, d, h, min, s);
		cout << tn2.getDateTimeAsText() << endl;
		cout << "difftime: " << tn2 - tn1 << endl;
	}
	TimeNeo tn3;
	{
		int y, m, d, h, min, s;
		tn3.getDateTime(&y, &m, &d, &h, &min, &s);
		s-=1.6e9;
		tn3.setDateTime(y, m, d, h, min, s);
		cout << tn3.getDateTimeAsText() << endl;
	}
	cout<< ( tn3 < tn0) <<endl;

	/////////////////////////////////
	// TimeNeoをつかってカレンダーを作ろう。
	//
	TimeNeo idate;
	idate.setDate(2022, 8, 17);// ターゲット月の設定
	cout << "**** Calendar "
		<< setw(4) << to_string(idate.getYear()) 
		<<"/"<< setw(2) << setfill('0') << to_string(idate.getMonth()) 
		<<" ****" << endl;
	cout << "Sun Mon Tue Wed Thr Fri Sat" << endl;
	{
		// ターゲット月の左上の日付を得よう。
		idate.setDate(1);
		while (idate.getWeekDay() != 0)
			idate.decDate();
		// ターゲット月の右下の日付の次の日を得よう。
		TimeNeo edate = idate;
		{
			edate.incMonth();
			while (edate.getWeekDay() != 0)
				edate.incDate();
			edate.decDate();
			//cout << edate.getDateAsText() << endl;;
		}
		// ターゲット月を打ち出そう
		//cout << idate.getDateAsText() << endl;
		int col = 0;
		while (idate < edate)
		{
			cout << setw(2) << to_string(idate.getDay()) << "  ";
			if (++col == 7)
			{
				cout << endl;
				col = 0;
			}
			idate.incDate();
		}//while
	}
	return 0;
}