C言語で現在時刻を扱うとき、必ずといってよいほど登場するのがlocaltime関数です。
現在時刻を「年・月・日・時・分・秒」といった人間が読みやすい形式に変換するための重要な関数ですが、戻り値の扱い方やスレッドセーフでない点など、初心者がつまずきやすいポイントも多くあります。
この記事では、localtimeの基本から注意点、代替関数までを図解とサンプルコードで丁寧に解説します。
localtimeとは?C言語での基礎知識
localtime関数の概要と役割
localtimeとは何をする関数なのかを、まずは直感的なイメージから押さえていきます。

C言語のlocaltime関数は、時刻を表す数値(秒数)を、人間が理解しやすい「年月日時分秒」の形に分解する変換関数です。
ここでいう「時刻の数値」はtime_t型で表されており、多くの環境では「1970-01-01 00:00:00(UTC)からの経過秒数」です。
この関数の役割をまとめると、次のようになります。
「UNIX時間(time_t)」→「ローカル時刻の構造体(struct tm)」へ変換する
変換後のstruct tmには、年・月・日・時・分・秒だけでなく、曜日や年内通算日(DOY)なども含まれるため、日時関連の処理に広く利用できます。
time_tとstruct tmの関係
localtimeを正しく理解するためには、time_tとstruct tmの関係を押さえることが重要です。

time_tは多くの環境で整数(実際には符号付き整数かどうかは実装依存)として実装されており、「ある基準時刻からの経過秒数」を表します。
人間にとっては読みづらい形式ですが、計算機にとっては扱いやすい形式です。
一方、struct tmは次のような構造体で、人間が理解しやすい「カレンダー形式」の時刻を表します。
- 年
- 月
- 日
- 時
- 分
- 秒
- 曜日
- 年内通算日
- 夏時間フラグ
そのため、time_tからlocaltimeでstruct tmに変換したり、逆にstruct tmからmktimeでtime_tに戻したりすることで、計算しやすさと読みやすさを両立できます。
GMT(UTC)とローカル時刻の違い
localtimeを理解するうえで、「世界共通の時刻」と「ローカル時刻」の違いも重要です。

ポイントは次の通りです。
UTC(GMT) 世界共通の基準時刻で、タイムゾーンや地域に依存しない時刻です。
C言語ではgmtime関数を使うと、time_tをUTC基準のstruct tmに変換できます。
ローカル時刻 そのコンピュータや環境で設定されているタイムゾーン(日本なら通常JST=UTC+9)や夏時間設定を反映した時刻です。
localtimeは、このローカル時刻に変換します。
つまり、同じtime_tでもgmtimeとlocaltimeでは結果が異なるという点を覚えておく必要があります。
localtimeの基本的な使い方
localtimeの関数プロトタイプとヘッダファイル
まずはlocaltime関数の定義と、使用に必要なヘッダファイルを確認します。
/* localtimeのプロトタイプ宣言は time.h に定義されています */
#include <time.h>
/* C標準で定義されているlocaltimeの関数プロトタイプ */
struct tm *localtime(const time_t *timer);
この関数は、引数としてtime_t型のポインタを受け取り、ローカル時刻を表すstruct tmへのポインタを返します。
使用する際は、必ず#include <time.h>を記述してください。
time関数とlocaltimeの組み合わせ方
現在時刻を取得してローカル時刻として分解する、最も基本的な流れは次のようになります。

具体的には、次の3段階です。
time(NULL)で「現在時刻のtime_t」を取得するlocaltimeにtime_tのアドレスを渡してstruct tmを得る- 得られた
struct tmのtm_yearやtm_monなどのメンバを使って表示する
struct tm構造体の主なメンバ一覧
localtimeの戻り値であるstruct tmには、多くのメンバが定義されています。
主なメンバを表にまとめます。
| メンバ名 | 意味 | 値の範囲・注意点 |
|---|---|---|
tm_year | 年(1900年からの経過年数) | 実際の年 = tm_year + 1900 |
tm_mon | 月(0〜11) | 0が1月、11が12月なので、表示時は +1 する |
tm_mday | 日(1〜31) | カレンダー上の日に対応 |
tm_hour | 時(0〜23) | 24時間表記の時間 |
tm_min | 分(0〜59) | |
tm_sec | 秒(0〜60) | うるう秒のため最大60になる可能性がある |
tm_wday | 曜日(0〜6) | 0が日曜、1が月曜 … 6が土曜 |
tm_yday | 年内通算日(0〜365) | 0が1月1日、365が12月31日(うるう年の場合)など |
tm_isdst | 夏時間(DST)フラグ | 正:適用中、0:非適用、負:情報不明 |
特にtm_yearとtm_monは「そのままが年・月ではない」点に注意が必要です。
表示用に使う場合、年は1900を足し、月は1を足すというお約束を覚えておくとよいです。
サンプルコードで見るlocaltimeの使い方
それでは、実際にlocaltimeを使って現在時刻を表示するシンプルなサンプルコードを見てみます。
#include <stdio.h> /* printf */
#include <time.h> /* time, localtime, struct tm */
int main(void)
{
time_t now; /* 現在時刻を秒数で保持する変数 */
struct tm *lt; /* ローカル時刻を指すポインタ */
/* 現在時刻を取得(time_t 形式) */
now = time(NULL); /* 引数NULLで現在時刻を取得 */
if (now == (time_t)-1) {
/* 取得に失敗した場合の簡易エラーチェック */
fprintf(stderr, "time() による現在時刻の取得に失敗しました。\n");
return 1;
}
/* time_t をローカル時刻(struct tm)に変換 */
lt = localtime(&now); /* &now で time_t のアドレスを渡す */
if (lt == NULL) {
/* 変換に失敗した場合の簡易エラーチェック */
fprintf(stderr, "localtime() による変換に失敗しました。\n");
return 1;
}
/* struct tm のメンバを使って日時を表示 */
printf("現在時刻: %04d-%02d-%02d %02d:%02d:%02d\n",
lt->tm_year + 1900, /* tm_year は 1900 年からの経過年数 */
lt->tm_mon + 1, /* tm_mon は 0 が 1 月なので +1 する */
lt->tm_mday,
lt->tm_hour,
lt->tm_min,
lt->tm_sec);
return 0;
}
上記プログラムの実行結果の一例は次のようになります。
現在時刻: 2025-03-21 14:35:12
環境や実行したタイミングによって表示される日時は異なりますが、形式としてはこのように「YYYY-MM-DD HH:MM:SS」のような形で表示できます。
localtimeを使う時の注意点
localtimeは便利な反面、戻り値の扱いやスレッドセーフ性などの注意点があります。
ここを正しく理解していないと、バグや予期しない動作の原因になります。
戻り値のポインタと静的領域の注意点
localtimeの戻り値はstruct tm *ですが、このポインタがどこを指しているかが重要なポイントです。

localtimeは、関数内部で静的(static)なstruct tmオブジェクトを1つだけ保持し、そのアドレスを返す実装が一般的です。
これはC標準規格にも「静的に割り当てられた構造体へのポインタを返す」と記載されています。
そのため、次のような性質があります。
- 次に
localtimeを呼び出すと、前回の結果が上書きされる - freeしなくてよい(してはいけない)
- 複数の
struct tm *を保存しておくと、すべて同じ実体を指すことになる
複数回呼び出す場合、必要であれば自前でstruct tmをコピーして保持する必要があります。
#include <stdio.h>
#include <time.h>
int main(void)
{
time_t now = time(NULL);
struct tm *a = localtime(&now);
struct tm backup; /* 結果を退避するための構造体 */
/* 結果を自前の構造体にコピーする */
if (a != NULL) {
backup = *a; /* メンバを丸ごとコピー */
}
/* 別の時刻で再度 localtime を呼び出す */
time_t later = now + 3600; /* 1時間後 */
struct tm *b = localtime(&later);
printf("1回目の結果(backup): %02d:%02d:%02d\n",
backup.tm_hour, backup.tm_min, backup.tm_sec);
if (b != NULL) {
printf("2回目の結果( b ): %02d:%02d:%02d\n",
b->tm_hour, b->tm_min, b->tm_sec);
}
return 0;
}
このように、必要に応じて構造体ごとコピーしておくことで、後続のlocaltime呼び出しによる上書きを防げます。
スレッドセーフでない理由と問題点
localtimeは、スレッドセーフではありません。
その主な理由は、前項で述べた通り静的領域の1つのstruct tmを共有しているからです。

マルチスレッド環境で次のような事態が発生します。
- スレッドAが
localtimeを呼び出して結果を取得 - まだスレッドAが結果を使い終わる前に、スレッドBが
localtimeを呼び出して同じ領域を書き換える - スレッドAが参照している
struct tm *の中身が、Bによって上書きされた値になってしまう
そのため、マルチスレッドでの利用が想定される場合はlocaltime_rなどのスレッドセーフな代替関数を使うことが推奨されます。
この点については、後述の「localtime_rとの違いと使い分け」で詳しく説明します。
タイムゾーン変更の影響
localtimeは「その環境のタイムゾーン設定」に依存します。
つまり、タイムゾーンが変われば同じtime_tでも返ってくるstruct tmの内容が変わります。

多くのOSでは、環境変数TZを変更したり、システムのタイムゾーン設定を変更すると、localtimeやmktimeの挙動が変化します。
そのため、次のような影響があります。
- プログラム実行中にタイムゾーンを変更すると、
localtimeの結果が途中から変わる - ログなどで
time_tだけを保存しておき、後から違うタイムゾーン環境でlocaltimeすると、元の環境とは異なる日時が表示される
「time_tはあくまで絶対的な時刻を表し、localtimeはその解釈方法(タイムゾーン)に依存する」という点を意識しておくことが重要です。
夏時間(DST)とtm_isdstの扱い
夏時間(Daylight Saving Time, DST)を採用している地域では、localtimeの結果にtm_isdstが大きく関わってきます。

tm_isdstの値は次のような意味を持ちます。
- 正(通常は1): 夏時間が適用されている
- 0: 夏時間は適用されていない(標準時)
- 負値: 情報が不明
日本のように夏時間を採用していない国ではtm_isdstは常に0となることが多いですが、海外向けのシステムや国際化対応を行う場合、この値を考慮する必要があります。
また、夏時間の切り替えのタイミングでは、次のような注意点があります。
- 存在しない時刻(例: 2時〜3時が丸ごと飛ぶ)が発生することがある
- 同じ「見かけの時刻」が2回現れる(1時間戻す側)ことがある
このようなケースでは、mktimeとtm_isdstの組み合わせで時刻解決を行う必要があります。
localtimeを使う際も、夏時間が挟まる期間では「1時間の飛び」などが起き得る点を意識しておきましょう。
日付・時刻の範囲とオーバーフローの注意点
localtimeはtime_tの値を分解しますが、time_t自体に表現可能な範囲の限界があります。

多くの古い32ビット環境ではtime_tは32ビットの符号付き整数で実装されていました。
この場合、2038年1月19日以降の時刻が正しく表現できず、localtimeがNULLを返したり、異常な日時を返したりする可能性があります。
いわゆる2038年問題です。
現在の多くの環境では64ビットのtime_tが採用されてきていますが、組み込み機器や古い環境では依然として32ビットのtime_tが存在します。
そのため、次のような注意が必要です。
- 極端に過去または未来の
time_tをlocaltimeに渡すと、NULLが返る場合がある - オーバーフローによる不正な時刻を扱わないよう、範囲チェックを行う
localtimeの戻り値がNULLでないかを常に確認するのは、このような理由からも重要です。
localtimeの代替関数と応用
ここまででlocaltimeの基本と注意点を見てきました。
この章では、スレッドセーフな代替関数や、UTC変換・フォーマット出力との組み合わせについて解説します。
localtime_rとの違いと使い分け
localtimeのスレッドセーフでない問題を解決するために、POSIX環境ではlocaltime_rという関数が提供されています。

localtime_rの典型的なプロトタイプは次のようになります(POSIX定義)。
#include <time.h>
/*
* POSIX系OSで一般的な localtime_r のプロトタイプ
* 第1引数: 入力となる time_t のポインタ
* 第2引数: 出力先となる struct tm のポインタ(呼び出し側が用意)
* 戻り値 : 第2引数と同じポインタ(成功時)、または NULL(失敗時)
*/
struct tm *localtime_r(const time_t *timer, struct tm *result);
localtime_rは、呼び出し側がstruct tmの領域を用意し、そこに結果を書き込ませるスタイルです。
これにより、スレッドごと・用途ごとに別々のstruct tmを使えるため、スレッドセーフになります。
使用例を示します。
#include <stdio.h>
#include <time.h>
int main(void)
{
time_t now = time(NULL);
struct tm lt; /* 結果を書き込ませる領域 */
struct tm *ret;
ret = localtime_r(&now, <); /* スレッドセーフな変換 */
if (ret == NULL) {
fprintf(stderr, "localtime_r() による変換に失敗しました。\n");
return 1;
}
printf("現在時刻(localtime_r): %04d-%02d-%02d %02d:%02d:%02d\n",
lt.tm_year + 1900,
lt.tm_mon + 1,
lt.tm_mday,
lt.tm_hour,
lt.tm_min,
lt.tm_sec);
return 0;
}
出力例は通常のlocaltimeと同様です。
現在時刻(localtime_r): 2025-03-21 14:35:12
注意点として、localtime_rはPOSIX標準であり、厳密なC言語標準(C11など)には含まれていません。
Windowsなどでは別名の関数(localtime_sなど)を使用する必要があります。
localtime_sを使用#include <stdio.h>
#include <time.h>
int main(void) {
time_t now = time(NULL);
struct tm lt;
errno_t err; // エラーコードを受け取る変数
/* Windows向けの localtime_s は引数の順序が (struct tm*, const time_t*) */
err = localtime_s(<, &now);
if (err != 0) {
fprintf(stderr, "localtime_s() による変換に失敗しました。コード: %d\n", err);
return 1;
}
printf("現在時刻(localtime_s): %04d-%02d-%02d %02d:%02d:%02d\n",
lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday, lt.tm_hour, lt.tm_min,
lt.tm_sec);
return 0;
}
現在時刻(localtime_s): 2025-03-21 14:35:12
gmtimeとの違いと用途
gmtimeは、UTC(協定世界時)でtime_tを分解する関数です。
localtimeとの違いを整理します。
| 関数名 | 基準となる時刻 | タイムゾーン・夏時間の反映 | 典型的な用途 |
|---|---|---|---|
localtime | ローカル時刻(端末設定依存) | 反映する | ユーザー向け表示、ログ表示など |
gmtime | UTC(世界共通基準時刻) | 反映しない(常にUTC) | システム内部処理、国際的な時刻管理 |

「ローカル表示用か、世界共通基準か」によってどちらを使うかを選択します。
例えば、ログファイルをすべてUTCで記録しておき、表示時だけローカル時刻に変換する、という設計もよく採用されます。
gmtimeもlocaltimeと同様に静的領域を使っており、スレッドセーフではない点に注意が必要です。
スレッドセーフ版としてgmtime_rが用意されている環境もあります。
strftimeと組み合わせた日時フォーマット
struct tmをそのままprintfで表示するのはやや面倒です。
そこで便利なのが、日付・時刻を任意のフォーマット文字列に従って整形するstrftime関数です。

strftimeの典型的な使い方は次のとおりです。
#include <stdio.h>
#include <time.h>
int main(void)
{
time_t now = time(NULL);
struct tm *lt = localtime(&now);
char buf[64]; /* フォーマット済み日時を格納するバッファ */
if (lt == NULL) {
fprintf(stderr, "localtime() による変換に失敗しました。\n");
return 1;
}
/* "%Y-%m-%d %H:%M:%S" 形式で日時を文字列に変換 */
if (strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", lt) == 0) {
/* バッファが足りない場合などは 0 が返る */
fprintf(stderr, "strftime() によるフォーマットに失敗しました。\n");
return 1;
}
printf("現在時刻(フォーマット済み): %s\n", buf);
return 0;
}
現在時刻(フォーマット済み): 2025-03-21 14:35:12
主なフォーマット指定子の例をいくつか紹介します。
| 指定子 | 意味 | 例(2025年3月21日 14:35:12 の場合) |
|---|---|---|
%Y | 4桁の西暦 | 2025 |
%m | 2桁の月(01〜12) | 03 |
%d | 2桁の日(01〜31) | 21 |
%H | 2桁の時(00〜23) | 14 |
%M | 2桁の分(00〜59) | 35 |
%S | 2桁の秒(00〜60) | 12 |
%a | 短い曜日名 | Fri など |
%Z | タイムゾーン名 | JST, UTC など |
用途に応じてフォーマット文字列を変更することで、簡単にさまざまな日時表示が可能になります。
実用的な日時表示のサンプルコード
最後に、localtimeとstrftimeを組み合わせた、少し実用的なサンプルを示します。
ここでは、ログ用のタイムスタンプを標準出力に出すプログラムを例にします。

#include <stdio.h>
#include <time.h>
/* ログメッセージをタイムスタンプ付きで出力する関数 */
void log_message(const char *level, const char *message)
{
time_t now = time(NULL);
struct tm *lt;
char timestr[32];
lt = localtime(&now);
if (lt == NULL) {
/* localtime が失敗した場合は、時刻なしで出力する */
fprintf(stderr, "[unknown time] %s: %s\n", level, message);
return;
}
/* "YYYY-MM-DD HH:MM:SS" 形式で時刻を文字列に変換 */
if (strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S", lt) == 0) {
fprintf(stderr, "[time format error] %s: %s\n", level, message);
return;
}
/* ログレベルとメッセージを組み合わせて出力 */
printf("[%s] %s: %s\n", timestr, level, message);
}
int main(void)
{
log_message("INFO", "プログラムを開始しました。");
log_message("WARNING", "設定ファイルが見つかりません。デフォルトを使用します。");
log_message("ERROR", "ネットワーク接続に失敗しました。");
return 0;
}
[2025-03-21 14:35:12] INFO: プログラムを開始しました。
[2025-03-21 14:35:12] WARNING: 設定ファイルが見つかりません。デフォルトを使用します。
[2025-03-21 14:35:12] ERROR: ネットワーク接続に失敗しました。
このように、localtimeとstrftimeを組み合わせることで、簡潔かつ読みやすいログやメッセージを作ることができます。
まとめ
localtimeは、C言語でローカル時刻を扱ううえで欠かせない基本関数です。
time_tからstruct tmへの変換を行い、年・月・日・時・分・秒をはじめとする各種情報を取得できます。
しかし、静的領域を利用するためスレッドセーフではないことや、tm_year・tm_monの扱い、タイムゾーンや夏時間(DST)の影響、time_tの範囲といった注意点も存在します。
マルチスレッド環境ではlocaltime_rやlocaltime_sなどの代替を検討しつつ、strftimeと組み合わせることで、実用的で読みやすい日時表示を実現していきましょう。
