C++ Builder XE4, 10.2 Tokyo > ログForm > ファイル書込み (ファイル名はN分ごとに切替える)

```txt:動作環境
C++ Builder XE4
RAD Studio 10.2 Tokyo Update 2 (追記: 2018/01/05)
```

### 処理

- 装置などからデータが適宜追加される
    - 例: 1000msecごとに1個ずつ
- データを適当な個数で取出しファイルに書出す
    - 例: 5秒ごとに5個ずつ 
- ファイル名はN分ごとに変更する

### 関連

- [【C#】現在時刻から直後のきりの良い時刻（5分、10分、15分・・・）を取得する。](https://qiita.com/kazu56/items/6ea0b85aa356a1afeebf)
- [c++ builder > TDateTime > 指定時刻に近い時刻を得る (10分区切り)](https://qiita.com/7of9/items/de690c703d2d3e758607)
- [C++ Builder > Form1:項目追加 + Form2:追加項目の取出し > TStringList実装](https://qiita.com/7of9/items/e41ad588d3c15ed3c945)

### code v0.1

```LoggerUnit.h
//---------------------------------------------------------------------------

#ifndef LoggerUnitH
#define LoggerUnitH
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <Vcl.ExtCtrls.hpp>
//---------------------------------------------------------------------------

/*
ログファイルを出力する。
ファイル名はN分ごとに切替わる。
*/

class TFormLogger : public TForm
{
__published:	// IDE で管理されるコンポーネント
	TTimer *TimerProcess;
	void __fastcall TimerProcessTimer(TObject *Sender);
private:	// ユーザー宣言
	TStringList *m_SL_log; // ログ保存用
	// 外部からの受取り用
	TStringList *m_SL_timeStamp; // 項目追加日時
	TStringList *m_SL_item; // 項目
	//
	void __fastcall saveAndRemove(void);
public:		// ユーザー宣言
	void __fastcall AddLogItem(TDateTime datDt, String item);
	__fastcall TFormLogger(TComponent* Owner);
	__fastcall ~TFormLogger();
};
//---------------------------------------------------------------------------
extern PACKAGE TFormLogger *FormLogger;
//---------------------------------------------------------------------------
#endif
```

```LoggerUnit.cpp
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include <DateUtils.hpp>
#include "LoggerUnit.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TFormLogger *FormLogger;
//---------------------------------------------------------------------------

/*
v0.1 2017/11/09
	- add [TimerProcess]
	- add saveAndRemove()
	- add getLogFileName()
	- add getRoundedDateTime_eachNmin()
	- add AddItem()
	- add [m_SL_log]
	- add [m_SL_timeStamp]
	- add [m_SL_item]
	- add ctor and dtor
*/

static const int kInterval_min = 5; // ログファイルの名前変更間隔
static const int kNumExtract = 5; // 取出す項目数
static const int kInterval_saveAndRemove_msec = 1000;
//---------------------------------------------------------------------------
// コンストラクタ・デストラクタ
//
__fastcall TFormLogger::TFormLogger(TComponent* Owner)
	: TForm(Owner)
{
	m_SL_item = new TStringList();
	m_SL_timeStamp = new TStringList();
	m_SL_log = new TStringList();

	TimerProcess->Enabled = false;
	TimerProcess->Interval = kInterval_saveAndRemove_msec; // msec
	TimerProcess->Enabled = true;
}
__fastcall TFormLogger::~TFormLogger()
{
	delete m_SL_item;
	m_SL_item = NULL;

	delete m_SL_timeStamp;
	m_SL_timeStamp = NULL;

	delete m_SL_log;
	m_SL_log = NULL;
}
//---------------------------------------------------------------------------
// Public関数
//
void __fastcall TFormLogger::AddLogItem(TDateTime datDt, String item)
{
	// 1. timestamp
	String tmstp = DateTimeToStr(datDt);
	m_SL_timeStamp->Add(tmstp);

	// 2. item
	m_SL_item->Add(item);
}

//---------------------------------------------------------------------------
// ファイルスコープstatic関数
//
static TDateTime getRoundedDateTime_eachNmin(TDateTime srcDt)
{
	int dt_min = srcDt.FormatString(L"nn").ToInt();
	int reduce_min = dt_min % kInterval_min; //

	TDateTime res = IncMinute( srcDt, -(reduce_min) );
	return res;
}

static String getLogFileName(TDateTime nowDt)
{
	TDateTime dt = getRoundedDateTime_eachNmin(nowDt);
	return L"log_t" + dt.FormatString(L"hhnn") + L".txt";
}

//---------------------------------------------------------------------------
// Private関数
//
void __fastcall TFormLogger::saveAndRemove(void)
{
	int exracted = 0; // 処理済み項目数
	String startfilename; // ファイル名変更のチェック用
	String savefilename;
	//
	for(int idx=0; idx < kNumExtract; idx++) {
		TDateTime dt = StrToDateTime(m_SL_timeStamp->Strings[idx]);
		if (idx == 0) {
			startfilename = getLogFileName(dt);
		}

		savefilename = getLogFileName(dt);
		if (startfilename != savefilename) { // ファイル名変更時にそれまでの分を書出す
			m_SL_log->SaveToFile(startfilename);
			m_SL_log->Clear();
			break;
		}

		String line = dt.FormatString(L"yyyy/mm/dd hh:nn:ss") + L"," + m_SL_item->Strings[idx];
		m_SL_log->Add(line);
		exracted++;
	}

	m_SL_log->SaveToFile(savefilename);

	for(int loop=0; loop < exracted; loop++) {
		m_SL_item->Delete(0);
		m_SL_timeStamp->Delete(0);
	}
}

//---------------------------------------------------------------------------
// IDE で管理されるコンポーネント
//
void __fastcall TFormLogger::TimerProcessTimer(TObject *Sender)
{
	String item = L"";

	if (m_SL_item->Count < kNumExtract) {
		return;
	}

	// TODO:資源のロック開始

	saveAndRemove();

	// TODO:資源のロック解除
}
//---------------------------------------------------------------------------
```

### 使用例

```Unit1.cpp
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
#include "LoggerUnit.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
	Timer1->Enabled = false;
	Timer1->Interval = 1000; // msec
	Timer1->Enabled = true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
	static int idx = 0;

	String itm = IntToStr(idx);
	FormLogger->AddLogItem(Now(), itm);
	idx++;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormShow(TObject *Sender)
{
	FormLogger->Show();
}
//---------------------------------------------------------------------------
```

### 結果例

```log_t1655.txt
2017/11/09 16:57:16,0
2017/11/09 16:57:17,1
2017/11/09 16:57:18,2
2017/11/09 16:57:19,3
2017/11/09 16:57:20,4
2017/11/09 16:57:21,5
2017/11/09 16:57:22,6
2017/11/09 16:57:23,7
(中略)
2017/11/09 16:59:55,157
2017/11/09 16:59:56,158
2017/11/09 16:59:57,159
2017/11/09 16:59:58,160
2017/11/09 16:59:59,161
```

```log_t1700.txt
2017/11/09 17:00:00,162
2017/11/09 17:00:01,163
2017/11/09 17:00:02,164
2017/11/09 17:00:03,165
(中略)
2017/11/09 17:02:49,328
2017/11/09 17:02:50,329
2017/11/09 17:02:51,330
2017/11/09 17:02:52,331
(ここでソフト停止)
```

### 備考

起動時に過去のログファイル読込みはしていないため、起動時に同じ名前の過去ログファイル内容は消えてしまう。

AddLogItem()は高頻度(1秒など)で実施されるという前提。低頻度(N分ごとなど）の場合は5回のAddLogItem()が実施される間隔が長くなる。データ消失に備えて保存処理のタイミングを変えることが望まれる。

保存ファイル名や保存先フォルダ指定などを追加すると、今後色々なプロジェクトで使い回ししやすくなるだろう。

static定義があるので、マルチインスタンスに対応していない。
クラスメンバー変数に移す方が役立つだろう。