【Qt入門】時間処理の基本!QTimerとQDateTimeの使い方を解説

はい、承知いたしました。QtのQTimerQDateTimeに関する詳細な解説記事を作成します。約5000語のボリュームで、初心者から中級者までを対象とした実践的な内容を盛り込みます。


【Qt入門】時間処理の基本!QTimerとQDateTimeの使い方を徹底解説

序文

現代のアプリケーション開発において、「時間」という概念は避けて通れません。ユーザーインターフェース(UI)の応答性を保ちながらバックグラウンドで処理を行ったり、定期的にデータを更新したり、あるいは正確な時刻を記録・表示したりと、その用途は多岐にわたります。クロスプラットフォームなアプリケーションフレームワークであるQtは、こうした時間に関する処理をエレガントかつ効率的に実装するための強力なクラス群を提供しています。

その中でも、QTimerQDateTimeは、Qtにおける時間処理の根幹をなす2つの重要なクラスです。QTimerは「一定時間後に何かをする」「定期的に何かを繰り返す」といった時間ベースのイベントを管理し、QDateTimeは「今、何時何分か」「この日時からどれだけ時間が経過したか」といった日付と時刻そのものを扱います。

この記事では、Qtを学び始めた初心者から、より実践的な知識を求める中級者を対象に、QTimerQDateTimeの基本から応用までを徹底的に解説します。シンプルな使い方から始まり、GUIアプリケーションでの具体的な実装例、さらにはスレッドやタイムゾーンといった高度なトピックまで、豊富なコード例と共に掘り下げていきます。

この記事を読み終える頃には、あなたは以下の知識を習得しているはずです。

  • Qtのイベント駆動モデルにおけるQTimerの役割と重要性
  • 繰り返しタイマー、シングルショットタイマーの適切な使い分け
  • QDateTimeを使った現在時刻の取得、フォーマット、計算方法
  • デジタル時計やストップウォッチといった実践的なアプリケーションの実装スキル
  • QElapsedTimerとの比較やタイムゾーンの扱いなど、より高度で堅牢な時間処理の実装ノウハウ

さあ、Qtで時間を操る旅を始めましょう。


第1章: なぜQtで時間処理が必要なのか?

具体的なクラスの解説に入る前に、そもそもなぜQtアプリケーションにおいて時間処理がこれほど重要なのかを理解しておきましょう。その理由は、主に以下の4点に集約されます。

1. GUIの応答性維持

デスクトップアプリケーションやモバイルアプリケーションにおいて、最も避けなければならない事態の一つが「UIのフリーズ」です。ユーザーがボタンをクリックしても反応がない、ウィンドウを動かせないといった状況は、ユーザーエクスペリエンスを著しく損ないます。

UIのフリーズは、多くの場合、時間のかかる処理(重い計算、ファイルI/O、ネットワーク通信など)をUIを管理しているメインスレッドで直接実行してしまうことが原因です。メインスレッドがその処理にかかりきりになっている間、UIの更新やユーザー入力の受付といった他のイベントを処理できなくなってしまうのです。

ここでQTimerが活躍します。QTimerを使うと、時間のかかる処理を小さなタスクに分割し、少しずつ実行したり、処理の開始をわずかに遅らせてメインスレッドがUIイベントを処理する隙を与えたりできます。これにより、アプリケーション全体の応答性を維持したまま、バックグラウンドでタスクを進める非同期処理が可能になります。これはQtのイベント駆動アーキテクチャの根幹をなす考え方です。

2. 定期的なタスクの実行

多くのアプリケーションでは、一定間隔で特定の処理を実行する必要があります。

  • データ更新: ニュースアプリが最新の記事を1分ごとに取得する。
  • アニメーション: UI要素を滑らかに動かすために、1秒間に60回(約16.7ミリ秒ごと)画面を再描画する。
  • 自動保存/バックアップ: テキストエディタが5分ごとに編集内容を自動保存する。
  • 監視: システムリソースやセンサーの値を定期的にチェックする。

これらの「定期的実行」は、QTimerの最も得意とするところです。指定した間隔でシグナルを発行し、接続されたスロット(処理関数)を自動的に呼び出すことで、こうしたタスクを驚くほど簡単に実装できます。

3. 正確な時間情報の取得と操作

アプリケーションが扱うデータには、しばしば時間情報が伴います。

  • 時刻表示: デジタル時計やカレンダーアプリ。
  • タイムスタンプ: ログファイルに「いつ」イベントが発生したかを記録する。
  • 経過時間: ファイルのダウンロードにかかった時間を計測する。
  • 有効期限: セッションやキャッシュの有効期限を管理する。

QDateTimeは、こうした日付と時刻に関するあらゆるニーズに応えるためのクラスです。現在時刻の取得はもちろん、特定の書式での文字列変換、日時の比較、加算・減算といった演算を正確かつ簡単に行う機能を提供します。特にタイムゾーンや夏時間を考慮した設計になっているため、グローバルな環境で動作するアプリケーションでも安心して利用できます。

4. イベント駆動アーキテクチャとの親和性

Qtは、シグナルとスロットメカニズムを中心としたイベント駆動アーキテクチャを採用しています。「何かが起きたら(シグナル)、これを実行する(スロット)」というモデルです。QTimerはこのモデルに完璧にフィットします。

QTimerは、指定した時間が経過するとtimeout()というシグナルを発行します。開発者は、このシグナルに実行したい処理を記述したスロットを接続するだけです。これにより、時間というイベントを他のUIイベント(ボタンクリックなど)と全く同じように扱うことができます。この一貫性のある設計が、Qtプログラミングの学習コストを下げ、コードの見通しを良くしているのです。

以上の理由から、QTimerQDateTimeは、単なる便利ツールではなく、高品質なQtアプリケーションを構築するための必須要素であると言えるのです。


第2章: 時を刻むタイマー – QTimerの基本

それでは、QTimerの具体的な使い方を見ていきましょう。QTimerは、Qtのイベントループを利用して、指定された時間間隔でシグナルを発行するオブジェクトです。

QTimerとは何か?

QTimerの最大の特徴は、Qtのイベントループ内で動作するという点です。これは、タイマーイベントが他のUIイベント(マウスクリック、キープレスなど)と同じキューで処理されることを意味します。そのため、QTimerのスロットからUI要素を直接操作しても、スレッドセーフティについて心配する必要がありません。これは、低レベルなスレッドベースのタイマーと比較した場合の大きな利点です。

ただし、イベントループが他の重い処理でブロックされていると、QTimertimeout()シグナルの発行が遅れる可能性があることも意味します。このトレードオフを理解しておくことが重要です。

基本的な使い方 (Hello, QTimer!)

まずは、最もシンプルなコンソールアプリケーションでQTimerの動作を確認してみましょう。1秒ごとにメッセージを出力するプログラムです。

main.cpp
“`cpp

include

include

include

int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);

// 1. QTimerオブジェクトを作成
QTimer *timer = new QTimer(&a);

// 2. timeout()シグナルに実行したい処理(ラムダ式)を接続
QObject::connect(timer, &QTimer::timeout, [](){
    qDebug() << "Timer ticked!";
});

// 3. タイマーを開始(1000ミリ秒 = 1秒間隔)
timer->start(1000);

qDebug() << "Timer started. Application will run for 5 seconds.";

// 5秒後にアプリケーションを終了するタイマーも設定
QTimer::singleShot(5000, &a, &QCoreApplication::quit);

// 4. アプリケーションのイベントループを開始
return a.exec();

}
“`

このコードをコンパイルして実行すると、コンソールに1秒ごとに “Timer ticked!” と表示され、5秒後にプログラムが自動的に終了します。

コードのポイントを解説します。
1. QTimer *timer = new QTimer(&a);: QTimerのインスタンスを生成します。コンストラクタに親オブジェクト(ここではQCoreApplicationインスタンス)を渡すことで、親が破棄されるときにタイマーも自動的に破棄されるようになり、メモリリークを防げます。
2. QObject::connect(...): QTimertimeout()シグナルと、実行したい処理を接続します。ここではC++11のラムダ式を使っていますが、クラスのメンバ関数をスロットとして指定することも可能です。
3. timer->start(1000);: タイマーを開始します。引数はミリ秒単位の間隔です。この呼び出しにより、タイマーは1000ミリ秒ごとにtimeout()シグナルを発行し始めます。
4. a.exec();: Qtアプリケーションの心臓部であるイベントループを開始します。このループが、タイマーイベントを含むあらゆるイベントを監視し、適切なスロットを呼び出します。イベントループがなければ、タイマーは決して発火しません。

タイマーの種類

QTimerには、大きく分けて2つの動作モードがあります。

繰り返しタイマー (Repeating Timer)

これがデフォルトの動作です。start(msec)を呼び出すと、タイマーは指定されたmsecミリ秒ごとにtimeout()シグナルを発行し続けます。タイマーを停止するにはstop()メソッドを呼び出します。

“`cpp
QTimer *repeatingTimer = new QTimer(this);
connect(repeatingTimer, &QTimer::timeout, this, &MyClass::onTimeout);
repeatingTimer->start(2000); // 2秒ごとにonTimeoutが呼ばれる

// …どこかでタイマーを停止
repeatingTimer->stop();
“`

シングルショットタイマー (Single-shot Timer)

一度だけtimeout()シグナルを発行して自動的に停止するタイマーです。「N秒後にこの処理を実行する」といった遅延実行に非常に便利です。

シングルショットタイマーを設定するには2つの方法があります。

1. setSingleShot(true) を使う方法
cpp
QTimer *singleShotTimer = new QTimer(this);
singleShotTimer->setSingleShot(true); // シングルショットモードに設定
connect(singleShotTimer, &QTimer::timeout, this, &MyClass::doSomethingLater);
singleShotTimer->start(3000); // 3秒後に一度だけdoSomethingLaterが呼ばれる

2. 静的メソッド QTimer::singleShot() を使う方法 (推奨)
こちらの方がはるかに簡潔で、最も一般的に使われる方法です。QTimerのインスタンスを自分で作成・管理する必要がありません。

“`cpp
// 3秒後にthis->doSomethingLater()を呼び出す
QTimer::singleShot(3000, this, &MyClass::doSomethingLater);

// C++11のラムダ式も使える
QTimer::singleShot(500, this, {
qDebug() << “This is printed after 500ms.”;
});
``QTimer::singleShot()は、内部的にQTimer`オブジェクトを作成し、タイムアウト後に自動的に破棄してくれるため、非常に手軽で安全です。

タイマーの精度 (Timer Accuracy)

QTimerの精度は、setTimerType()メソッドで設定できます。これはQt::TimerTypeという列挙型を受け取ります。

  • Qt::CoarseTimer (デフォルト): 最も一般的なタイマー。OSに許される範囲で、他のタイマーとイベントをグループ化して処理しようとします。これにより、CPUのウェイクアップ回数が減り、電力消費を抑えることができます。精度はミリ秒単位ですが、数%の誤差が生じる可能性があります。一般的なUIの更新などにはこれで十分です。
  • Qt::PreciseTimer: フレームワークができるだけ正確なタイミング(ミリ秒単位)でタイマーを発行しようとします。ただし、イベントループの負荷が高い場合や、OSのスケジューリングの都合で、それでも遅延は発生し得ます。マルチメディアの再生など、より正確なタイミングが求められる場合に使用します。
  • Qt::VeryCoarseTimer: 精度は秒単位です。タイマーイベントは、1秒に1回しか評価されません。バックグラウンドでの定期的なチェックなど、高い精度が不要で、電力効率を最優先したい場合に使用します。タイマーの間隔は、秒単位で設定する必要があります(例:start(5000))。

cpp
QTimer *preciseTimer = new QTimer(this);
preciseTimer->setTimerType(Qt::PreciseTimer);
preciseTimer->start(16); // 約60fpsのアニメーションを目指す場合など

通常はデフォルトのQt::CoarseTimerで問題ありません。不必要にQt::PreciseTimerを使用すると、特にバッテリー駆動のデバイスで電力消費が増加する可能性があるため注意が必要です。


第3章: 日付と時刻を操る – QDateTimeの基本

QTimerが「いつ」イベントを発生させるかを制御するのに対し、QDateTimeは「その時が何時何分か」という具体的な時間情報を扱うためのクラスです。

QDateTimeとは何か?

QDateTimeは、カレンダー上の日付(QDate)と、時計の時刻(QTime)を組み合わせたものです。単に日付と時刻を保持するだけでなく、タイムゾーンや夏時間(DST)も考慮できる高度な機能を備えています。これにより、世界中のユーザーが利用するアプリケーションでも、時間を正しく扱うことができます。

現在時刻の取得

現在時刻を取得するのは非常に簡単です。

  • QDateTime::currentDateTime(): 実行環境のローカルタイムゾーンにおける現在の日付と時刻を取得します。UIに表示するなど、ユーザーに見せる時刻として最も一般的です。
  • QDateTime::currentDateTimeUtc(): 協定世界時 (UTC) における現在の日付と時刻を取得します。

“`cpp
// ローカル時刻の取得
QDateTime localTime = QDateTime::currentDateTime();
qDebug() << “Local Time:” << localTime; // 例: “Local Time: QDateTime(2023-10-27 10:30:00.123 JST)”

// UTC時刻の取得
QDateTime utcTime = QDateTime::currentDateTimeUtc();
qDebug() << “UTC Time:” << utcTime; // 例: “UTC Time: QDateTime(2023-10-27 01:30:00.123 UTC)”
“`

なぜUTCが重要なのか?
ログファイルへのタイムスタンプ記録や、サーバーとのデータ交換、データベースへの保存など、内部的なデータ処理ではUTCを使用することが強く推奨されます。ローカルタイムはタイムゾーンや夏時間によって変動しますが、UTCは世界共通の基準であるため、曖昧さがなく、異なる地域のシステム間で時間をやり取りする際に混乱が生じません。データをUTCで保存しておき、ユーザーに表示する際にローカルタイムに変換するのがベストプラクティスです。

特定の日付・時刻の生成

QDateTimeは、特定の日付・時刻を表すオブジェクトを生成することもできます。

コンストラクタを使う方法

QDateオブジェクトとQTimeオブジェクトを組み合わせて生成するのが基本です。

“`cpp

include

include

QDate date(2025, 1, 1); // 2025年1月1日
QTime time(12, 0, 0); // 12時00分00秒

QDateTime newYear(date, time); // 2025-01-01 12:00:00
qDebug() << newYear;
“`

文字列からパースする方法 (fromString)

fromString()静的メソッドを使うと、特定の書式を持つ文字列をQDateTimeオブジェクトに変換できます。

“`cpp
QString strDateTime1 = “2023-10-27 11:22:33”;
QString format1 = “yyyy-MM-dd hh:mm:ss”;
QDateTime dt1 = QDateTime::fromString(strDateTime1, format1);
qDebug() << dt1; // QDateTime(2023-10-27 11:22:33.000)

// ISO 8601形式 (Web APIなどでよく使われる)
QString strDateTime2 = “2023-10-27T15:45:00Z”; // ZはUTCを示す
QDateTime dt2 = QDateTime::fromString(strDateTime2, Qt::ISODate);
qDebug() << dt2; // QDateTime(2023-10-27 15:45:00.000 UTC)
``
フォーマット文字列を正しく指定することが極めて重要です。間違っていると、パースに失敗し、無効な
QDateTimeオブジェクトが返されます (isValid()`で確認可能)。

日付・時刻のフォーマット (文字列への変換)

QDateTimeオブジェクトを人間が読める文字列に変換するにはtoString()メソッドを使います。

“`cpp
QDateTime now = QDateTime::currentDateTime();

// デフォルトのフォーマット (ロケールに依存)
qDebug() << now.toString();

// ISO 8601形式
qDebug() << now.toString(Qt::ISODate); // 例: “2023-10-27T10:30:00”

// カスタムフォーマット
qDebug() << now.toString(“yyyy/MM/dd ddd hh:mm:ss.zzz”);
// 例: “2023/10/27 金 10:30:00.123”
“`
主な書式指定子は以下の通りです。

指定子 説明
yyyy 年 (4桁) 2023
yy 年 (2桁) 23
MM 月 (2桁、0埋め) 0112
M 月 (1-2桁) 112
dd 日 (2桁、0埋め) 0131
d 日 (1-2桁) 131
ddd 曜日 (短い形式、ロケール依存) , Mon
dddd 曜日 (長い形式、ロケール依存) 月曜日, Monday
hh 時 (12時間制 or 24時間制、0埋め) 0023
h 時 (1-2桁) 023
mm 分 (2桁、0埋め) 0059
ss 秒 (2桁、0埋め) 0059
zzz ミリ秒 (3桁、0埋め) 000999
AP 午前/午後 (大文字) AM, PM
ap 午前/午後 (小文字) am, pm
t タイムゾーン名 JST

日付・時刻の比較と演算

QDateTimeオブジェクト同士は、直感的な比較演算子 (<, >, ==, !=, <=, >=) で比較できます。

cpp
QDateTime dt1 = QDateTime::currentDateTime();
QTimer::singleShot(100, [&](){
QDateTime dt2 = QDateTime::currentDateTime();
if (dt1 < dt2) {
qDebug() << "dt1 is earlier than dt2.";
}
});

時間の加算・減算も簡単です。

“`cpp
QDateTime now = QDateTime::currentDateTime();

QDateTime oneHourLater = now.addSecs(3600); // 3600秒 (1時間) 後
QDateTime oneDayAgo = now.addDays(-1); // 1日前
QDateTime oneMonthLater = now.addMonths(1); // 1ヶ月後
QDateTime oneYearLater = now.addYears(1); // 1年後

qDebug() << “One hour later:” << oneHourLater.toString(“yyyy-MM-dd hh:mm:ss”);
“`

2つの日時の差を計算することもできます。

“`cpp
QDateTime start = QDateTime::currentDateTime();
// … 何か時間のかかる処理 …
QDateTime end = QDateTime::currentDateTime();

qint64 diff_secs = start.secsTo(end); // 差を秒で取得
qint64 diff_msecs = start.msecsTo(end); // 差をミリ秒で取得

qDebug() << “Processing took” << diff_secs << “seconds.”;
qDebug() << “Processing took” << diff_msecs << “milliseconds.”;
``secsTo()`は、経過時間の計測に非常に便利です。


第4章: QTimerとQDateTimeの実践的応用

基本を学んだところで、QTimerQDateTimeを組み合わせた、より実践的なGUIアプリケーションの例を見ていきましょう。

ケーススタディ1: デジタル時計の作成

QLabelを使って現在の時刻を1秒ごとに更新する、シンプルなデジタル時計を作成します。これは、QTimerQDateTimeの最も典型的で分かりやすい組み合わせです。

mainwindow.h
“`cpp

ifndef MAINWINDOW_H

define MAINWINDOW_H

include

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class QTimer; // 前方宣言

class MainWindow : public QMainWindow
{
Q_OBJECT

public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();

private slots:
void updateTime(); // 時刻を更新するスロット

private:
Ui::MainWindow ui;
QTimer
m_timer; // QTimerのメンバ変数
};

endif // MAINWINDOW_H

“`

mainwindow.cpp
“`cpp

include “mainwindow.h”

include “ui_mainwindow.h”

include

include

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);

// 1. QTimerのインスタンスを作成
m_timer = new QTimer(this);

// 2. timeoutシグナルをupdateTimeスロットに接続
connect(m_timer, &QTimer::timeout, this, &MainWindow::updateTime);

// 3. タイマーを1000ミリ秒(1秒)間隔で開始
m_timer->start(1000);

// 4. 起動時に一度、時刻を表示しておく
updateTime();

// UIのフォントなどを調整(任意)
ui->timeLabel->setFont(QFont("Arial", 48, QFont::Bold));
ui->timeLabel->setAlignment(Qt::AlignCenter);
this->resize(600, 200);
this->setWindowTitle("Digital Clock");

}

MainWindow::~MainWindow()
{
delete ui;
}

// 5. 1秒ごとに呼び出されるスロット
void MainWindow::updateTime()
{
// 現在時刻を取得
QDateTime currentTime = QDateTime::currentDateTime();

// 時刻を"hh:mm:ss"形式の文字列に変換
QString timeText = currentTime.toString("hh:mm:ss");

// QLabelにテキストを設定
ui->timeLabel->setText(timeText);

}
**`main.cpp`**cpp

include “mainwindow.h”

include

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
``
**UIファイル (
.ui)**:QLabelを一つ配置し、objectNametimeLabel`に設定しておきます。

このプログラムの動作は明快です。
1. MainWindowのコンストラクタでQTimerをセットアップします。
2. timer->start(1000)で、1秒ごとのtimeout()シグナル発行が始まります。
3. timeout()シグナルが発生するたびに、接続されたupdateTime()スロットが呼び出されます。
4. updateTime()内では、QDateTime::currentDateTime()で最新の時刻を取得し、toString("hh:mm:ss")で見やすい形式に変換して、QLabelのテキストを更新します。

この処理は全てメインスレッドのイベントループ内で安全に行われるため、UIの更新もスムーズです。

ケーススタディ2: ストップウォッチの実装

次に、開始・停止・リセット機能を持つストップウォッチを作ります。ここでは、経過時間を計測する方法としてQElapsedTimerというもう一つのクラスも紹介します。

QElapsedTimerとは?
QElapsedTimerは、単純な経過時間を高精度に計測することに特化したクラスです。QDateTimeとの大きな違いは、モノトニッククロック(単調増加クロック)を使用する点です。これは、システムの時刻設定がユーザーによって変更されても影響を受けない、連続的に増加するタイマーです。ストップウォッチのように純粋な経過時間が知りたい場合には、QDateTime::secsTo()よりもQElapsedTimerの方が堅牢で適しています。

ここでは、両方の実装を比較できるように、QElapsedTimerを使った実装を示します。

stopwatch.h
“`cpp

ifndef STOPWATCH_H

define STOPWATCH_H

include

include

class QLabel;
class QPushButton;
class QTimer;

class StopWatch : public QWidget
{
Q_OBJECT
public:
explicit StopWatch(QWidget *parent = nullptr);

private slots:
void onStartStop();
void onReset();
void onUpdateDisplay();

private:
void updateButtons();

QLabel *m_display;
QPushButton *m_startStopButton;
QPushButton *m_resetButton;
QTimer *m_timer;

QElapsedTimer m_elapsedTimer;
qint64 m_elapsedTime; // 停止時の経過時間を保持
bool m_isRunning;

};

endif // STOPWATCH_H

**`stopwatch.cpp`**cpp

include “stopwatch.h”

include

include

include

include

include

include

StopWatch::StopWatch(QWidget *parent)
: QWidget(parent), m_elapsedTime(0), m_isRunning(false)
{
// — UI Setup —
m_display = new QLabel(“00:00:00.000”);
m_display->setFont(QFont(“Courier”, 36));
m_display->setAlignment(Qt::AlignCenter);

m_startStopButton = new QPushButton("Start");
m_resetButton = new QPushButton("Reset");

QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(m_display);
QHBoxLayout *buttonLayout = new QHBoxLayout;
buttonLayout->addWidget(m_startStopButton);
buttonLayout->addWidget(m_resetButton);
mainLayout->addLayout(buttonLayout);

// --- Timer Setup ---
m_timer = new QTimer(this);
m_timer->setInterval(10); // 10ミリ秒ごとに更新
connect(m_timer, &QTimer::timeout, this, &StopWatch::onUpdateDisplay);

// --- Connections ---
connect(m_startStopButton, &QPushButton::clicked, this, &StopWatch::onStartStop);
connect(m_resetButton, &QPushButton::clicked, this, &StopWatch::onReset);

updateButtons();
setWindowTitle("Stopwatch");

}

void StopWatch::onStartStop()
{
m_isRunning = !m_isRunning;

if (m_isRunning) {
    m_elapsedTimer.start(); // タイマーを開始/再開
    m_timer->start();
} else {
    m_elapsedTime += m_elapsedTimer.elapsed(); // 停止までの時間を加算
    m_timer->stop();
}
updateButtons();

}

void StopWatch::onReset()
{
m_isRunning = false;
m_elapsedTime = 0;
m_timer->stop();
m_display->setText(“00:00:00.000”);
updateButtons();
}

void StopWatch::onUpdateDisplay()
{
// 現在のセッションでの経過時間 + 過去のセッションの合計時間
qint64 currentTotal = m_elapsedTime + m_elapsedTimer.elapsed();

// QTimeを使ってミリ秒を hh:mm:ss.zzz 形式にフォーマット
QTime time = QTime::fromMSecsSinceStartOfDay(currentTotal);
m_display->setText(time.toString("hh:mm:ss.zzz"));

}

void StopWatch::updateButtons()
{
m_startStopButton->setText(m_isRunning ? “Stop” : “Start”);
m_resetButton->setEnabled(!m_isRunning);
}
“`

この実装のポイント:
* 状態管理: m_isRunningフラグでストップウォッチが実行中か停止中かを管理します。
* QElapsedTimer: onStartStop()で実行状態になるとm_elapsedTimer.start()を呼び出します。これは内部のカウンタをリセットして計測を開始します。
* 累積時間: ストップ(一時停止)するたびに、m_elapsedTimer.elapsed()でそのセッションの経過時間を取得し、m_elapsedTimeに加算していきます。これにより、再開後も正しい経過時間が表示されます。
* UI更新タイマー: QTimer (m_timer) は純粋に画面表示を更新するためだけに使います。10ミリ秒ごとにonUpdateDisplayを呼び出し、滑らかな表示を実現します。
* フォーマット: onUpdateDisplayでは、合計経過時間(ミリ秒)をQTime::fromMSecsSinceStartOfDay()QTimeオブジェクトに変換し、toString("hh:mm:ss.zzz")で綺麗にフォーマットしています。QTimeをフォーマットのためだけに使う便利なテクニックです。

ケーススタディ3: 非同期データフェッチ

最後に、QTimerを使って定期的にバックグラウンドタスク(ここでは擬似的なデータ取得)を実行する例を考えます。これにより、UIをフリーズさせることなく、最新の情報を表示し続けることができます。

“`cpp
// あるWidgetクラス内での実装を想定

class DataFetcher : public QObject
{
Q_OBJECT
public:
explicit DataFetcher(QObject *parent = nullptr) : QObject(parent) {
m_fetchTimer = new QTimer(this);
connect(m_fetchTimer, &QTimer::timeout, this, &DataFetcher::fetchData);
}

void startFetching(int intervalMs) {
    m_fetchTimer->start(intervalMs);
    fetchData(); // 最初の一回はすぐに実行
}

void stopFetching() {
    m_fetchTimer->stop();
}

signals:
void dataFetched(const QString &data, const QDateTime &timestamp);

private slots:
void fetchData() {
// ここで実際のデータ取得処理(例:QNetworkAccessManagerを使用)を行う
// この例では、擬似的にデータを生成する
qDebug() << “Fetching data…”;

    // 擬似データ
    QDateTime now = QDateTime::currentDateTime();
    QString fetchedData = QString("Data fetched at %1").arg(now.toString("hh:mm:ss"));

    // 処理が終わったらシグナルを発行
    emit dataFetched(fetchedData, now);
}

private:
QTimer *m_fetchTimer;
};

// MainWindowなど、UIを持つクラス
class MainWindow : public QMainWindow
{
// …
private slots:
void onDataUpdated(const QString &data, const QDateTime &timestamp) {
ui->dataLabel->setText(data);
ui->timestampLabel->setText(“Last update: ” + timestamp.toString(Qt::ISODate));
}
private:
DataFetcher *m_fetcher;
};

// MainWindowのコンストラクタなどで
// m_fetcher = new DataFetcher(this);
// connect(m_fetcher, &DataFetcher::dataFetched, this, &MainWindow::onDataUpdated);
// m_fetcher->startFetching(30000); // 30秒ごとにデータを取得
``
この設計の利点は、データ取得ロジック (
DataFetcher) とUI表示ロジック (MainWindow) が疎結合になっている点です。DataFetcherはデータが取得できたという事実 (dataFetchedシグナル) を通知するだけで、UIがどうなっているかを知る必要はありません。MainWindowは、そのシグナルを受け取ってUIを更新します。QTimer`がこの2つを定期的に繋ぐ糊の役割を果たしています。


第5章: さらに高度なトピックとベストプラクティス

QTimerQDateTimeを使いこなすために、もう少し踏み込んだトピックを見ていきましょう。

QTimerとスレッド

前述の通り、QTimerはデフォルトで、それが作成されたスレッドのイベントループに所属します。しかし、moveToThread()を使うことで、QTimerとそのスロット処理をワーカースレッドで実行させることができます。

これは、メインスレッド(GUIスレッド)のイベントループをできるだけ軽く保ちたい場合に有効です。例えば、タイマー処理自体がCPUを少し消費する場合や、非常に高頻度のタイマーをGUIと並行して動かしたい場合などです。

“`cpp
// ワーカースレッドでタイマーを動かす例
QThread workerThread = new QThread(this);
MyWorker
worker = new MyWorker(); // QObjectを継承したクラス

// タイマーもワーカースレッドに移動
QTimer *threadTimer = new QTimer();
threadTimer->setInterval(100);
threadTimer->moveToThread(workerThread);

// workerオブジェクトをワーカースレッドに移動
worker->moveToThread(workerThread);

// 接続はmoveToThreadの前でも後でも良いが、スレッドをまたぐ接続になる
// (QueuedConnectionが自動で選択される)
connect(threadTimer, &QTimer::timeout, worker, &MyWorker::doWork);
connect(workerThread, &QThread::started, threadTimer, ={ threadTimer->start(); });
connect(workerThread, &QThread::finished, threadTimer, &QTimer::deleteLater);
connect(workerThread, &QThread::finished, worker, &MyWorker::deleteLater);

workerThread->start(); // スレッドを開始し、イベントループを起動
``
**注意点:**
QTimerをワーカースレッドで使うには、そのスレッドでイベントループが実行されている必要があります。QThread::run()をオーバーライドしてexec()を呼び出すか、単にQThread::start()を呼び出せば、QThread`が内部でイベントループを開始してくれます。

QElapsedTimer vs QTime::elapsed() vs QDateTime

経過時間を計測する方法として、いくつかの選択肢があります。

  • QElapsedTimer (推奨):

    • 用途: 純粋な経過時間の高精度計測(ベンチマーク、アニメーション、ストップウォッチなど)。
    • 利点: モノトニッククロックを使用するため、システム時刻の変更に影響されない。高分解能。
    • 欠点: 実世界の日時との関連はない。
  • QTime::start()/elapsed():

    • 用途: QElapsedTimerと同様の目的。
    • 利点: 古くからある方法で、手軽。
    • 欠点: QElapsedTimerに比べて機能が少なく、Qtのドキュメントでも新しいコードではQElapsedTimerの使用が推奨されている。内部実装はQElapsedTimerに似ているが、APIとしてQElapsedTimerの方が洗練されている。
  • QDateTime::msecsTo():

    • 用途: ある実世界の日時から、別の実世界の日時までの経過時間を計算する。
    • 利点: カレンダー上の日時として意味を持つ。
    • 欠点: システム時刻の変更(手動変更、NTP同期、夏時間の切り替わりなど)の影響を受ける。純粋な処理時間の計測には不向き。

使い分けの指針:
* プログラムの処理時間やユーザーの操作時間を測るなら QElapsedTimer
* 「2023年10月27日10:00」から「同日11:30」までの時間を知りたいなら QDateTime

タイムゾーンの取り扱い

グローバルなアプリケーションを作るなら、タイムゾーンの扱いは必須です。QDateTimeQTimeZoneクラスと連携してこれをサポートします。

“`cpp
// 現在のUTC時刻
QDateTime utcDateTime = QDateTime::currentDateTimeUtc();

// タイムゾーンIDでQTimeZoneオブジェクトを作成
QTimeZone tokyoZone(“Asia/Tokyo”);
QTimeZone newYorkZone(“America/New_York”);

// UTCを各タイムゾーンの時刻に変換
QDateTime tokyoDateTime = utcDateTime.toTimeZone(tokyoZone);
QDateTime newYorkDateTime = utcDateTime.toTimeZone(newYorkZone);

qDebug() << “UTC: ” << utcDateTime.toString(Qt::ISODate);
qDebug() << “Tokyo: ” << tokyoDateTime.toString(Qt::ISODate) << tokyoDateTime.toString(“t”);
qDebug() << “New York: ” << newYorkDateTime.toString(Qt::ISODate) << newYorkDateTime.toString(“t”);
“`

サーバーからUTCで時刻を受け取り、ユーザーの環境のタイムゾーンに合わせて表示する、といった処理がこれで可能になります。QTimeZone::availableTimeZoneIds()で利用可能なタイムゾーンIDのリストを取得できます。

パフォーマンスに関する考慮事項

  • タイマー間隔: 1msや0msといった非常に短い間隔でタイマーを設定しても、その通りに実行される保証はありません。OSのスケジューリングの粒度(通常は1ms~16ms程度)に依存します。intervalを0に設定すると、イベントループがアイドル状態になるたびに、可能な限り速くタイムアウトします。これはCPUを100%消費する可能性があるので注意が必要です。
  • 電力消費: Qt::CoarseTimerQt::VeryCoarseTimerを適切に使うことで、CPUのウェイクアップ回数を減らし、特にモバイルデバイスでのバッテリー消費を抑えることができます。精度が不要な場面では積極的に利用しましょう。
  • アニメーション: 滑らかなアニメーションを自前でQTimerを使って実装することも可能ですが、QtにはQPropertyAnimationを始めとする強力なアニメーションフレームワークがあります。これらは内部で最適なタイマー管理やイージングカーブの計算を行ってくれるため、UIアニメーションにはそちらの利用を優先的に検討すべきです。

まとめ

この記事では、Qtにおける時間処理の二大巨頭、QTimerQDateTimeについて、その基本から実践的な応用、そして高度なトピックまでを網羅的に解説しました。

  • QTimer は、Qtのイベントループとシームレスに連携し、定期実行や遅延実行といった非同期処理を驚くほど簡単に実装できる強力なツールです。シングルショットタイマーやタイマー精度の調整など、様々なニーズに応える柔軟性も持ち合わせています。
  • QDateTime は、日付と時刻を正確に扱うための必須クラスです。現在時刻の取得、文字列との相互変換、日時の比較や演算、さらには複雑なタイムゾーンの扱いまで、時間情報に関するあらゆる操作を堅牢にサポートします。
  • 両者の組み合わせにより、デジタル時計やストップウォッチ、データの定期更新といった、現代のアプリケーションに不可欠な機能を、応答性を損なうことなく構築できます。
  • さらに、QElapsedTimer のような特化したクラスを適切に使い分けることで、より正確で信頼性の高いコードを書くことができます。

時間処理は、一見地味ですが、アプリケーションの品質とユーザーエクスペリエンスを根底から支える重要な技術です。今回学んだ知識を土台として、ぜひあなたのQtアプリケーションに、より洗練された時間の概念を組み込んでみてください。

今後の学習としては、QTimerと密接に関連するQThreadQtConcurrentによるマルチスレッドプログラミング、あるいはQPropertyAnimationを使ったアニメーションフレームワークへと進んでいくと、さらに表現力豊かなアプリケーションが作れるようになるでしょう。

Happy coding with Qt!

参考資料:
* QTimer Official Documentation
* QDateTime Official Documentation
* QElapsedTimer Official Documentation
* QTimeZone Official Documentation

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール