SQLiteは軽量で使いやすいデータベースとして人気ですが、
「現在時刻の取得方法がわからない」
「日時をどう保存すればよいかわからない」
といった疑問を持つ方も多いのではないでしょうか?
特に初心者の方は、
「なぜ時刻がずれるの?」
「どの形式で保存すればいいの?」
「アプリケーションで使うときの注意点は?」
といった悩みがあったりします。
この記事では、SQLiteで現在時刻を取得する方法とその使いどころ、保存形式の違いや注意点まで、実際のコード例を交えながらわかりやすく解説します。
SQLiteの日時機能の基本を理解しよう

SQLiteは、他のデータベースシステムとは少し異なる日時の扱いをします。
まずは基本的な特徴を押さえましょう。
SQLiteの日時の特徴
専用の日時型がない SQLiteには、MySQLのDATETIME
型やPostgreSQLのTIMESTAMP
型のような、専用の日時データ型がありません。代わりに、以下の方法で日時を扱います:
- TEXT型:文字列として保存
- INTEGER型:数値として保存
- REAL型:浮動小数点数として保存
組み込み関数が豊富 専用型がない代わりに、日時を扱うための関数が充実しています。こ
れらの関数を使って、現在時刻の取得や日時の計算を行います。
UTCベースの処理 SQLiteの日時関数は、基本的にUTC(協定世界時)で動作します。
ローカル時間が必要な場合は、明示的に指定する必要があります。
なぜこのような仕組みなの?
SQLiteが軽量で高速なのは、複雑な型システムを持たないことも理由の一つです。
シンプルな型システムと豊富な関数の組み合わせで、柔軟で効率的な日時処理を実現しています。
SQLiteで現在時刻を取得する方法

SQLiteには、現在の日時や時間を取得するための組み込み関数があります。用途に応じて使い分けましょう。
基本的な取得関数
CURRENT_TIMESTAMP 最もシンプルな現在時刻の取得方法です。
SELECT CURRENT_TIMESTAMP;
-- 結果例: 2025-06-17 12:34:56
datetime(‘now’) CURRENT_TIMESTAMP
とほぼ同じ結果を返します。
SELECT datetime('now');
-- 結果例: 2025-06-17 12:34:56
datetime(‘now’, ‘localtime’) ローカル時間(システムのタイムゾーン)で現在時刻を取得します。
SELECT datetime('now', 'localtime');
-- 結果例: 2025-06-17 21:34:56 (JST の場合)
時刻のみ、日付のみを取得する
time(‘now’):時刻のみ
SELECT time('now');
-- 結果例: 12:34:56
SELECT time('now', 'localtime');
-- 結果例: 21:34:56 (JST の場合)
date(‘now’):日付のみ
SELECT date('now');
-- 結果例: 2025-06-17
SELECT date('now', 'localtime');
-- 結果例: 2025-06-17
カスタムフォーマットで取得する
strftime()関数の活用 strftime()
関数を使えば、好きな形式で日時を取得できます。
-- 年月日を斜線区切りで
SELECT strftime('%Y/%m/%d', 'now', 'localtime');
-- 結果例: 2025/06/17
-- 時刻を12時間表記で
SELECT strftime('%I:%M %p', 'now', 'localtime');
-- 結果例: 09:34 PM
-- 曜日を含む日時
SELECT strftime('%Y年%m月%d日 %H:%M:%S', 'now', 'localtime');
-- 結果例: 2025年06月17日 21:34:56
-- Unixタイムスタンプ
SELECT strftime('%s', 'now');
-- 結果例: 1718624096
主要なフォーマット指定子
指定子 | 意味 | 例 |
---|---|---|
%Y | 4桁の年 | 2025 |
%m | 月(01-12) | 06 |
%d | 日(01-31) | 17 |
%H | 時(00-23) | 21 |
%M | 分(00-59) | 34 |
%S | 秒(00-59) | 56 |
%w | 曜日(0=日曜) | 2 |
%s | Unixタイムスタンプ | 1718624096 |
実際の使用例
アクセスログの記録
-- アクセス時刻をローカル時間で記録
INSERT INTO access_log (user_id, access_time)
VALUES (123, datetime('now', 'localtime'));
システムの現在状態を確認
-- システム時刻とローカル時刻の確認
SELECT
'UTC' as timezone,
datetime('now') as current_time
UNION ALL
SELECT
'LOCAL' as timezone,
datetime('now', 'localtime') as current_time;
これらの関数を使い分けることで、アプリケーションの要件に応じた適切な時刻取得ができます。
現在時刻をテーブルに保存する方法

データベースに現在時刻を保存する場面は非常に多くあります。
ユーザーの登録時刻、商品の注文時刻、ログの記録時刻など、様々な用途で活用されます。
基本的なテーブル設計
シンプルなログテーブルの例
CREATE TABLE access_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
action TEXT NOT NULL,
created_at TEXT NOT NULL
);
このテーブルに現在時刻を保存する方法をいくつか紹介します。
方法1:DEFAULT句を使った自動設定
テーブル作成時にデフォルト値を設定
CREATE TABLE logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
message TEXT NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
);
使用例
-- created_at は自動で現在時刻が設定される
INSERT INTO logs (message) VALUES ('ユーザーがログインしました');
-- 結果を確認
SELECT * FROM logs;
-- id | message | created_at | updated_at
-- 1 | ユーザーがログインしました | 2025-06-17 12:34:56 | 2025-06-17 12:34:56
ローカル時間をデフォルトにする場合
CREATE TABLE logs_local (
id INTEGER PRIMARY KEY AUTOINCREMENT,
message TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now', 'localtime'))
);
方法2:INSERT文で明示的に指定
UTC時刻で保存
INSERT INTO logs (message, created_at)
VALUES ('データを更新しました', CURRENT_TIMESTAMP);
-- または
INSERT INTO logs (message, created_at)
VALUES ('データを更新しました', datetime('now'));
ローカル時刻で保存
INSERT INTO logs (message, created_at)
VALUES ('ファイルをアップロードしました', datetime('now', 'localtime'));
カスタムフォーマットで保存
INSERT INTO logs (message, created_at)
VALUES (
'システムを再起動しました',
strftime('%Y-%m-%d %H:%M:%S', 'now', 'localtime')
);
方法3:トリガーを使った自動更新
更新時刻を自動で設定するトリガー
-- テーブル作成
CREATE TABLE products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price INTEGER NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
);
-- 更新時にupdated_atを自動更新するトリガー
CREATE TRIGGER update_products_updated_at
AFTER UPDATE ON products
BEGIN
UPDATE products
SET updated_at = datetime('now', 'localtime')
WHERE id = NEW.id;
END;
使用例
-- 商品を挿入
INSERT INTO products (name, price) VALUES ('ノートPC', 80000);
-- 価格を更新(updated_at が自動で更新される)
UPDATE products SET price = 75000 WHERE id = 1;
-- 結果確認
SELECT * FROM products;
-- id | name | price | created_at | updated_at
-- 1 | ノートPC | 75000 | 2025-06-17 12:34:56 | 2025-06-17 12:45:30
実践的な使用例
ユーザー管理テーブル
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now', 'localtime')),
last_login_at TEXT,
is_active INTEGER DEFAULT 1
);
-- ユーザー登録
INSERT INTO users (username, email, password_hash)
VALUES ('yamada_taro', 'yamada@example.com', 'hashed_password');
-- ログイン時刻を更新
UPDATE users
SET last_login_at = datetime('now', 'localtime')
WHERE username = 'yamada_taro';
注文履歴テーブル
CREATE TABLE orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
total_amount INTEGER NOT NULL,
status TEXT DEFAULT 'pending',
ordered_at TEXT DEFAULT (datetime('now', 'localtime')),
shipped_at TEXT,
delivered_at TEXT,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- 注文作成
INSERT INTO orders (user_id, total_amount) VALUES (1, 15000);
-- 発送時刻を記録
UPDATE orders
SET status = 'shipped', shipped_at = datetime('now', 'localtime')
WHERE id = 1;
-- 配達完了時刻を記録
UPDATE orders
SET status = 'delivered', delivered_at = datetime('now', 'localtime')
WHERE id = 1;
これらの方法を使い分けることで、アプリケーションの要件に応じた適切な時刻管理ができます。
日時の保存形式:どれを選ぶべき?

SQLiteでは複数の形式で日時を保存できます。それぞれにメリット・デメリットがあるため、用途に応じて選択しましょう。
保存形式の比較
形式 | データ型 | 例 | メリット | デメリット |
---|---|---|---|---|
ISO 8601 | TEXT | ‘2025-06-17 12:34:56’ | 人間が読みやすい、SQLで扱いやすい | 文字列なので若干メモリを使う |
Unixタイムスタンプ | INTEGER | 1718624096 | 計算が高速、メモリ効率が良い | 人間には読みにくい |
Julian日付 | REAL | 2451545.5 | 精度が高い、計算に適している | 一般的でない、理解しにくい |
TEXT型(ISO 8601形式)- 最も推奨
特徴
- 人間が読みやすい
- SQLの日時関数と相性が良い
- 他のデータベースとの互換性が高い
使用例
CREATE TABLE events (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
start_time TEXT NOT NULL, -- '2025-06-17 14:30:00'
end_time TEXT NOT NULL
);
-- データ挿入
INSERT INTO events (title, start_time, end_time) VALUES
('会議', '2025-06-17 14:30:00', '2025-06-17 15:30:00');
-- 日時での検索
SELECT * FROM events
WHERE start_time >= '2025-06-17 14:00:00'
AND start_time < '2025-06-17 16:00:00';
-- 期間の計算
SELECT
title,
start_time,
end_time,
(julianday(end_time) - julianday(start_time)) * 24 AS duration_hours
FROM events;
INTEGER型(Unixタイムスタンプ)- 高速処理が必要な場合
特徴
- 数値計算が高速
- メモリ効率が良い
- プログラミング言語との連携が簡単
使用例
CREATE TABLE sensor_data (
id INTEGER PRIMARY KEY,
sensor_id INTEGER NOT NULL,
value REAL NOT NULL,
timestamp INTEGER NOT NULL -- Unixタイムスタンプ
);
-- 現在時刻のUnixタイムスタンプで挿入
INSERT INTO sensor_data (sensor_id, value, timestamp) VALUES
(1, 23.5, strftime('%s', 'now'));
-- 時刻での検索(過去1時間のデータ)
SELECT
sensor_id,
value,
datetime(timestamp, 'unixepoch', 'localtime') AS readable_time
FROM sensor_data
WHERE timestamp >= strftime('%s', 'now', '-1 hour');
-- 時系列データの集計
SELECT
strftime('%Y-%m-%d %H:00:00', timestamp, 'unixepoch', 'localtime') AS hour,
AVG(value) AS avg_value,
COUNT(*) AS data_count
FROM sensor_data
GROUP BY strftime('%Y-%m-%d %H', timestamp, 'unixepoch', 'localtime')
ORDER BY hour;
REAL型(Julian日付)- 高精度が必要な場合
特徴
- 非常に高精度
- 天文学的計算に適している
- SQLiteの内部的な日時表現
使用例
CREATE TABLE astronomical_events (
id INTEGER PRIMARY KEY,
event_name TEXT NOT NULL,
julian_date REAL NOT NULL
);
-- Julian日付で挿入
INSERT INTO astronomical_events (event_name, julian_date) VALUES
('日食', julianday('2025-08-12 10:30:00'));
-- Julian日付から人間が読める形式に変換
SELECT
event_name,
julian_date,
datetime(julian_date) AS readable_date
FROM astronomical_events;
混在する場合の対処法
実際のプロジェクトでは、複数の形式が混在することがあります。そんな時の変換方法を紹介します。
異なる形式間の変換
-- サンプルデータ
CREATE TABLE mixed_dates (
id INTEGER PRIMARY KEY,
iso_date TEXT,
unix_timestamp INTEGER,
julian_date REAL
);
INSERT INTO mixed_dates VALUES
(1, '2025-06-17 12:34:56', 1718624096, 2460479.52423611);
-- 全て人間が読める形式に統一
SELECT
id,
iso_date,
datetime(unix_timestamp, 'unixepoch') AS unix_to_iso,
datetime(julian_date) AS julian_to_iso
FROM mixed_dates;
-- 全てUnixタイムスタンプに統一
SELECT
id,
strftime('%s', iso_date) AS iso_to_unix,
unix_timestamp,
strftime('%s', datetime(julian_date)) AS julian_to_unix
FROM mixed_dates;
どの形式を選ぶべきか?判断基準
TEXT型(ISO 8601)を選ぶべき場合
- 管理画面でデータを直接見る機会が多い
- 日時での検索や集計が多い
- 他のデータベースとの連携がある
- 一般的なWebアプリケーション
INTEGER型(Unix)を選ぶべき場合
- 大量のログデータを扱う
- 高速な時系列処理が必要
- IoTセンサーデータなど高頻度更新
- モバイルアプリとの連携
REAL型(Julian)を選ぶべき場合
- 天文学や科学計算のアプリケーション
- 非常に高精度な時刻が必要
- 複雑な日時計算を頻繁に行う
一般的なWebアプリケーションでは、**TEXT型(ISO 8601形式)**を推奨します。
タイムゾーンの扱いと注意点

SQLiteでの日時処理で最も注意すべきは、タイムゾーンの扱いです。
適切に理解していないと、思わぬトラブルの原因となります。
SQLiteのタイムゾーン処理の特徴
基本的にタイムゾーン情報を持たない
SQLiteは、保存された日時文字列にタイムゾーン情報を付与しません。'2025-06-17 12:34:56'
という文字列だけでは、これがUTCなのかローカル時間なのか判断できません。
関数はUTCベース datetime('now')
やCURRENT_TIMESTAMP
は、デフォルトでUTC時刻を返します。
ローカル時間は明示的に指定 ローカル時間が必要な場合は、'localtime'
修飾子を明示的に指定する必要があります。
UTCとローカル時間の使い分け
UTC時刻で保存するメリット
-- UTC で統一して保存
CREATE TABLE global_events (
id INTEGER PRIMARY KEY,
event_name TEXT NOT NULL,
event_time_utc TEXT NOT NULL -- UTC で統一
);
-- 世界各地からのイベント登録(全てUTCで保存)
INSERT INTO global_events (event_name, event_time_utc) VALUES
('東京オフィス会議', datetime('now')),
('ニューヨーク会議', datetime('now')),
('ロンドン会議', datetime('now'));
-- 表示時にローカル時間に変換
SELECT
event_name,
event_time_utc AS utc_time,
datetime(event_time_utc, 'localtime') AS local_time
FROM global_events;
ローカル時間で保存する場合
-- ローカル時間で保存(単一タイムゾーンのアプリケーション)
CREATE TABLE local_events (
id INTEGER PRIMARY KEY,
event_name TEXT NOT NULL,
event_time_local TEXT NOT NULL -- ローカル時間
);
-- ローカル時間で保存
INSERT INTO local_events (event_name, event_time_local) VALUES
('店舗営業開始', datetime('now', 'localtime'));
タイムゾーン変換の実践例
世界時計の実装
-- タイムゾーン変換のサンプル
WITH timezone_offsets AS (
SELECT 'UTC' as timezone, '+0 hours' as offset
UNION ALL SELECT 'JST', '+9 hours'
UNION ALL SELECT 'EST', '-5 hours'
UNION ALL SELECT 'PST', '-8 hours'
)
SELECT
timezone,
datetime('now', offset) as current_time
FROM timezone_offsets;
ユーザー別タイムゾーン設定
-- ユーザーテーブル
CREATE TABLE users (
id INTEGER PRIMARY KEY,
username TEXT NOT NULL,
timezone_offset TEXT DEFAULT '+0 hours' -- UTCからのオフセット
);
-- イベントテーブル(UTC で保存)
CREATE TABLE user_events (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
event_time_utc TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- ユーザーのタイムゾーンでイベント表示
SELECT
e.title,
e.event_time_utc,
datetime(e.event_time_utc, u.timezone_offset) as user_local_time
FROM user_events e
JOIN users u ON e.user_id = u.id
WHERE u.id = 1;
よくある問題と対処法
問題1:時刻がずれる
-- 問題のあるコード(UTC とローカル時間が混在)
INSERT INTO logs (message, created_at) VALUES
('ログ1', datetime('now')), -- UTC
('ログ2', datetime('now', 'localtime')); -- ローカル時間
-- 解決案:統一したルールで保存
-- 保存は常にUTC、表示時にローカル変換
INSERT INTO logs (message, created_at) VALUES
('ログ1', datetime('now')),
('ログ2', datetime('now'));
-- 表示時
SELECT
message,
created_at as utc_time,
datetime(created_at, 'localtime') as local_time
FROM logs;
問題2:夏時間の考慮不足
-- SQLite では夏時間を自動処理しないため、
-- アプリケーション側で対応が必要
-- 夏時間を考慮した時間変換(アプリケーション側での処理例)
CREATE TABLE timezone_rules (
timezone_name TEXT PRIMARY KEY,
standard_offset TEXT,
dst_offset TEXT,
dst_start TEXT,
dst_end TEXT
);
-- 基本的な解決策:UTCで統一して保存し、
-- 表示時にアプリケーション側でタイムゾーン変換
問題3:異なるシステム間での時刻同期
-- システム間でのデータ連携時の注意点
-- 送信側:必ずUTCで送信
SELECT
id,
title,
datetime(created_at_local, '-9 hours') as created_at_utc -- JSTをUTCに変換
FROM events_local;
-- 受信側:UTCで受信してローカル保存
INSERT INTO events (title, created_at_utc, created_at_local) VALUES
('イベント', ?, datetime(?, 'localtime'));
推奨されるベストプラクティス
データベース設計時のルール
-- 推奨:UTCで保存、カラム名で明示
CREATE TABLE best_practice_events (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
created_at_utc TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at_utc TEXT DEFAULT CURRENT_TIMESTAMP,
-- 必要に応じてローカル時間も保存
display_time_local TEXT
);
-- トリガーでローカル時間を自動計算
CREATE TRIGGER update_local_time
AFTER INSERT ON best_practice_events
BEGIN
UPDATE best_practice_events
SET display_time_local = datetime(NEW.created_at_utc, 'localtime')
WHERE id = NEW.id;
END;
適切なタイムゾーン処理により、グローバルなアプリケーションでも安定した時刻管理が実現できます。
よくあるトラブルと解決方法

SQLiteの日時処理では、初心者が陥りやすい罠がいくつかあります。
実際のトラブル例と解決方法を紹介します。
トラブル1:時刻が9時間ずれる(日本の場合)
症状
-- データを挿入
INSERT INTO events (title, event_time)
VALUES ('会議', datetime('now'));
-- 確認すると時刻が9時間早い
SELECT * FROM events;
-- title | event_time
-- 会議 | 2025-06-17 12:34:56 (実際は21:34のはずなのに...)
原因 datetime('now')
はUTC時刻を返すため、日本時間(UTC+9)とは9時間の差が生じます。
解決方法
-- 方法1:挿入時にローカル時間を指定
INSERT INTO events (title, event_time)
VALUES ('会議', datetime('now', 'localtime'));
-- 方法2:表示時にローカル時間に変換
SELECT
title,
datetime(event_time, 'localtime') as local_event_time
FROM events;
-- 方法3:テーブル設計時にデフォルト値をローカル時間に
CREATE TABLE events_local (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
event_time TEXT DEFAULT (datetime('now', 'localtime'))
);
トラブル2:文字列として保存した日時で正しく検索できない
症状
-- 日時を文字列として保存
CREATE TABLE logs (
id INTEGER PRIMARY KEY,
message TEXT,
log_time TEXT -- '2025/06/17 21:34:56' 形式で保存
);
-- このような形式で保存されている
INSERT INTO logs VALUES (1, 'エラー', '2025/6/17 9:34:56');
INSERT INTO logs VALUES (2, '警告', '2025/06/17 21:34:56');
-- 時刻での範囲検索がうまくいかない
SELECT * FROM logs
WHERE log_time >= '2025/06/17 10:00:00';
-- 期待通りの結果が得られない
原因 文字列の辞書順比較と日時の大小比較が異なるため。また、フォーマットが統一されていない。
解決方法
-- 方法1:正規化して比較
SELECT * FROM logs
WHERE datetime(log_time) >= datetime('2025-06-17 10:00:00');
-- 方法2:データを正しい形式で保存し直す
UPDATE logs
SET log_time = strftime('%Y-%m-%d %H:%M:%S',
datetime(substr(log_time, 1, 4) || '-' ||
substr(log_time, 6, 2) || '-' ||
substr(log_time, 9, 2) || ' ' ||
substr(log_time, 12, 8)));
-- 方法3:新しいテーブルで正しい形式を使用
CREATE TABLE logs_fixed (
id INTEGER PRIMARY KEY,
message TEXT,
log_time TEXT -- 'YYYY-MM-DD HH:MM:SS' 形式で統一
);
トラブル3:Unixタイムスタンプの精度問題
症状
-- ミリ秒を含むUnixタイムスタンプで保存
CREATE TABLE high_precision_logs (
id INTEGER PRIMARY KEY,
event_type TEXT,
timestamp_ms INTEGER -- ミリ秒単位のタイムスタンプ
);
INSERT INTO high_precision_logs VALUES
(1, 'click', 1718624096123); -- ミリ秒込み
-- 変換時に正しく表示されない
SELECT datetime(timestamp_ms, 'unixepoch') FROM high_precision_logs;
-- 結果が正しくない
原因 SQLiteのunixepoch
は秒単位なので、ミリ秒単位のタイムスタンプをそのまま使用できません。
解決方法
-- ミリ秒を秒に変換してから処理
SELECT
event_type,
timestamp_ms,
datetime(timestamp_ms / 1000, 'unixepoch') as readable_time,
datetime(timestamp_ms / 1000, 'unixepoch', 'localtime') as local_time
FROM high_precision_logs;
-- または、ミリ秒の精度を保持したい場合
SELECT
event_type,
timestamp_ms,
datetime(timestamp_ms / 1000, 'unixepoch') ||
'.' || (timestamp_ms % 1000) as precise_time
FROM high_precision_logs;
トラブル4:異なるタイムゾーンのデータが混在
症状
-- 複数の拠点からデータが登録される
CREATE TABLE multi_location_events (
id INTEGER PRIMARY KEY,
location TEXT,
event_name TEXT,
event_time TEXT
);
-- 各拠点からローカル時間で登録
INSERT INTO multi_location_events VALUES
(1, '東京', '朝会', '2025-06-17 09:00:00'),
(2, 'ニューヨーク', '朝会', '2025-06-17 09:00:00'),
(3, 'ロンドン', '朝会', '2025-06-17 09:00:00');
-- 実際は異なる時刻なのに同じ時刻として扱われる
解決方法
-- 方法1:UTC変換テーブルを作成
CREATE TABLE timezone_offsets (
location TEXT PRIMARY KEY,
utc_offset_hours INTEGER
);
INSERT INTO timezone_offsets VALUES
('東京', 9),
('ニューヨーク', -5),
('ロンドン', 0);
-- UTC時刻を計算して表示
SELECT
e.location,
e.event_name,
e.event_time as local_time,
datetime(e.event_time, '-' || t.utc_offset_hours || ' hours') as utc_time
FROM multi_location_events e
JOIN timezone_offsets t ON e.location = t.location;
-- 方法2:最初からUTCで保存
CREATE TABLE events_utc (
id INTEGER PRIMARY KEY,
location TEXT,
event_name TEXT,
event_time_utc TEXT,
event_time_local TEXT
);
トラブル5:日時の計算で予期しない結果
症状
-- 期間の計算が思った通りにならない
SELECT
'2025-06-17 10:30:00' as start_time,
'2025-06-17 15:45:00' as end_time,
('2025-06-17 15:45:00' - '2025-06-17 10:30:00') as duration;
-- 結果: 0 (文字列の引き算になってしまう)
解決方法
-- Julian日付を使った正しい計算
SELECT
'2025-06-17 10:30:00' as start_time,
'2025-06-17 15:45:00' as end_time,
(julianday('2025-06-17 15:45:00') - julianday('2025-06-17 10:30:00')) * 24 as hours,
(julianday('2025-06-17 15:45:00') - julianday('2025-06-17 10:30:00')) * 1440 as minutes;
-- より実用的な関数として
CREATE VIEW duration_calculator AS
SELECT
start_time,
end_time,
ROUND((julianday(end_time) - julianday(start_time)) * 24, 2) as duration_hours,
ROUND((julianday(end_time) - julianday(start_time)) * 1440, 0) as duration_minutes
FROM (
SELECT
'2025-06-17 10:30:00' as start_time,
'2025-06-17 15:45:00' as end_time
);
トラブル6:パフォーマンスの問題
症状
-- 大量のデータで日時検索が遅い
CREATE TABLE large_logs (
id INTEGER PRIMARY KEY,
message TEXT,
created_at TEXT
);
-- 100万件のデータがある状態で
SELECT * FROM large_logs
WHERE datetime(created_at) >= datetime('2025-06-01');
-- 非常に遅い
解決方法
-- 方法1:インデックスを作成
CREATE INDEX idx_logs_created_at ON large_logs(created_at);
-- 方法2:関数を使わずに文字列比較
-- ISO 8601形式なら文字列比較でも正しく動作
SELECT * FROM large_logs
WHERE created_at >= '2025-06-01 00:00:00';
-- 方法3:Unixタイムスタンプを使用
CREATE TABLE large_logs_optimized (
id INTEGER PRIMARY KEY,
message TEXT,
created_at_unix INTEGER,
created_at_readable TEXT
);
CREATE INDEX idx_logs_unix ON large_logs_optimized(created_at_unix);
-- 高速な検索
SELECT * FROM large_logs_optimized
WHERE created_at_unix >= strftime('%s', '2025-06-01');
実践的な使用例とベストプラクティス

ここまで学んだ知識を活用して、実際のアプリケーションでよく使われる日時処理パターンを紹介します。
使用例1:ログ管理システム
要件
- アクセスログを記録
- ログレベル別の検索
- 時間範囲での絞り込み
- 日別の集計
実装例
-- ログテーブルの設計
CREATE TABLE application_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
level TEXT NOT NULL CHECK(level IN ('DEBUG', 'INFO', 'WARN', 'ERROR')),
message TEXT NOT NULL,
module TEXT,
user_id INTEGER,
ip_address TEXT,
created_at_utc TEXT DEFAULT CURRENT_TIMESTAMP,
created_at_local TEXT DEFAULT (datetime('now', 'localtime'))
);
-- インデックスの作成
CREATE INDEX idx_logs_level ON application_logs(level);
CREATE INDEX idx_logs_created_at ON application_logs(created_at_utc);
CREATE INDEX idx_logs_user_id ON application_logs(user_id);
-- ログ挿入のサンプル
INSERT INTO application_logs (level, message, module, user_id, ip_address) VALUES
('INFO', 'ユーザーログイン', 'auth', 123, '192.168.1.100'),
('ERROR', 'データベース接続エラー', 'database', NULL, '192.168.1.101'),
('WARN', '不正なアクセス試行', 'security', NULL, '192.168.1.102');
-- 実用的なクエリ例
-- 1. 過去24時間のエラーログ
SELECT
level,
message,
module,
datetime(created_at_utc, 'localtime') as local_time
FROM application_logs
WHERE level = 'ERROR'
AND created_at_utc >= datetime('now', '-1 day')
ORDER BY created_at_utc DESC;
-- 2. 日別エラー件数の集計
SELECT
date(created_at_utc, 'localtime') as log_date,
level,
COUNT(*) as count
FROM application_logs
WHERE created_at_utc >= datetime('now', '-7 days')
GROUP BY date(created_at_utc, 'localtime'), level
ORDER BY log_date DESC, level;
-- 3. 時間別アクセス数(ヒートマップ用)
SELECT
strftime('%H', created_at_local) as hour,
COUNT(*) as access_count
FROM application_logs
WHERE date(created_at_local) = date('now', 'localtime')
GROUP BY strftime('%H', created_at_local)
ORDER BY hour;
使用例2:予約管理システム
要件
- 会議室の予約管理
- 重複チェック
- 今後の予約一覧
- 使用率の計算
実装例
-- 会議室テーブル
CREATE TABLE meeting_rooms (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
capacity INTEGER NOT NULL
);
-- 予約テーブル
CREATE TABLE reservations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
room_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
start_time TEXT NOT NULL,
end_time TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now', 'localtime')),
FOREIGN KEY (room_id) REFERENCES meeting_rooms(id)
);
-- 重複予約を防ぐためのインデックス
CREATE INDEX idx_reservations_time ON reservations(room_id, start_time, end_time);
-- サンプルデータ
INSERT INTO meeting_rooms VALUES (1, '会議室A', 10), (2, '会議室B', 6);
-- 予約の挿入(重複チェック付き)
-- 新しい予約前に重複をチェック
WITH new_reservation AS (
SELECT 1 as room_id,
'2025-06-17 14:00:00' as start_time,
'2025-06-17 15:00:00' as end_time
),
conflicts AS (
SELECT COUNT(*) as conflict_count
FROM reservations r, new_reservation n
WHERE r.room_id = n.room_id
AND NOT (r.end_time <= n.start_time OR r.start_time >= n.end_time)
)
SELECT
CASE
WHEN conflict_count = 0 THEN '予約可能'
ELSE '時間が重複しています'
END as status
FROM conflicts;
-- 予約が可能な場合の挿入
INSERT INTO reservations (room_id, user_id, title, start_time, end_time)
SELECT 1, 101, '企画会議', '2025-06-17 14:00:00', '2025-06-17 15:00:00'
WHERE NOT EXISTS (
SELECT 1 FROM reservations
WHERE room_id = 1
AND NOT (end_time <= '2025-06-17 14:00:00' OR start_time >= '2025-06-17 15:00:00')
);
-- 実用的なクエリ例
-- 1. 今日の予約一覧
SELECT
mr.name as room_name,
r.title,
r.start_time,
r.end_time,
(julianday(r.end_time) - julianday(r.start_time)) * 24 as duration_hours
FROM reservations r
JOIN meeting_rooms mr ON r.room_id = mr.id
WHERE date(r.start_time) = date('now', 'localtime')
ORDER BY r.start_time;
-- 2. 空き時間の検索
WITH time_slots AS (
SELECT
datetime('2025-06-17 09:00:00', '+' || (value * 1) || ' hours') as slot_start,
datetime('2025-06-17 09:00:00', '+' || ((value + 1) * 1) || ' hours') as slot_end
FROM generate_series(0, 8) -- 9:00-18:00 の9時間
),
available_slots AS (
SELECT
1 as room_id,
ts.slot_start,
ts.slot_end
FROM time_slots ts
WHERE NOT EXISTS (
SELECT 1 FROM reservations r
WHERE r.room_id = 1
AND NOT (r.end_time <= ts.slot_start OR r.start_time >= ts.slot_end)
)
)
SELECT
'会議室A' as room_name,
slot_start as available_from,
slot_end as available_to
FROM available_slots;
-- 3. 月別使用率の計算
SELECT
mr.name as room_name,
strftime('%Y-%m', r.start_time) as month,
SUM((julianday(r.end_time) - julianday(r.start_time)) * 24) as used_hours,
COUNT(DISTINCT date(r.start_time)) as used_days,
ROUND(SUM((julianday(r.end_time) - julianday(r.start_time)) * 24) /
(COUNT(DISTINCT date(r.start_time)) * 9) * 100, 1) as utilization_rate
FROM reservations r
JOIN meeting_rooms mr ON r.room_id = mr.id
WHERE r.start_time >= date('now', 'start of month', '-2 months')
GROUP BY mr.id, strftime('%Y-%m', r.start_time)
ORDER BY month DESC, mr.name;
使用例3:勤怠管理システム
要件
- 出退勤時刻の記録
- 労働時間の計算
- 残業時間の算出
- 月次集計
実装例
-- 従業員テーブル
CREATE TABLE employees (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
department TEXT NOT NULL
);
-- 勤怠記録テーブル
CREATE TABLE attendance (
id INTEGER PRIMARY KEY AUTOINCREMENT,
employee_id INTEGER NOT NULL,
work_date TEXT NOT NULL, -- YYYY-MM-DD 形式
clock_in_time TEXT, -- 出勤時刻
clock_out_time TEXT, -- 退勤時刻
break_minutes INTEGER DEFAULT 60, -- 休憩時間(分)
created_at TEXT DEFAULT (datetime('now', 'localtime')),
FOREIGN KEY (employee_id) REFERENCES employees(id)
);
-- 一意制約(1日1レコード)
CREATE UNIQUE INDEX idx_attendance_unique ON attendance(employee_id, work_date);
-- 出勤打刻
INSERT OR REPLACE INTO attendance (employee_id, work_date, clock_in_time) VALUES
(1, date('now', 'localtime'), time('now', 'localtime'));
-- 退勤打刻
UPDATE attendance
SET clock_out_time = time('now', 'localtime')
WHERE employee_id = 1 AND work_date = date('now', 'localtime');
-- 実用的なクエリ例
-- 1. 日次労働時間の計算
SELECT
e.name,
a.work_date,
a.clock_in_time,
a.clock_out_time,
CASE
WHEN a.clock_out_time IS NOT NULL THEN
ROUND(
(julianday(a.work_date || ' ' || a.clock_out_time) -
julianday(a.work_date || ' ' || a.clock_in_time)) * 24 * 60 -
a.break_minutes, 0
)
ELSE NULL
END as work_minutes,
CASE
WHEN a.clock_out_time IS NOT NULL THEN
ROUND(
(julianday(a.work_date || ' ' || a.clock_out_time) -
julianday(a.work_date || ' ' || a.clock_in_time)) * 24, 2
) - (a.break_minutes / 60.0)
ELSE NULL
END as work_hours
FROM attendance a
JOIN employees e ON a.employee_id = e.id
WHERE a.work_date = date('now', 'localtime');
-- 2. 残業時間の計算(所定労働時間8時間として)
WITH daily_work AS (
SELECT
employee_id,
work_date,
CASE
WHEN clock_out_time IS NOT NULL THEN
(julianday(work_date || ' ' || clock_out_time) -
julianday(work_date || ' ' || clock_in_time)) * 24 -
(break_minutes / 60.0)
ELSE 0
END as work_hours
FROM attendance
WHERE work_date >= date('now', 'start of month', 'localtime')
AND clock_out_time IS NOT NULL
)
SELECT
e.name,
dw.work_date,
ROUND(dw.work_hours, 2) as work_hours,
CASE
WHEN dw.work_hours > 8 THEN ROUND(dw.work_hours - 8, 2)
ELSE 0
END as overtime_hours
FROM daily_work dw
JOIN employees e ON dw.employee_id = e.id
ORDER BY dw.work_date DESC, e.name;
-- 3. 月次集計
SELECT
e.name,
e.department,
strftime('%Y-%m', a.work_date) as month,
COUNT(*) as work_days,
ROUND(SUM(
CASE
WHEN a.clock_out_time IS NOT NULL THEN
(julianday(a.work_date || ' ' || a.clock_out_time) -
julianday(a.work_date || ' ' || a.clock_in_time)) * 24 -
(a.break_minutes / 60.0)
ELSE 0
END
), 1) as total_work_hours,
ROUND(SUM(
CASE
WHEN a.clock_out_time IS NOT NULL AND
(julianday(a.work_date || ' ' || a.clock_out_time) -
julianday(a.work_date || ' ' || a.clock_in_time)) * 24 -
(a.break_minutes / 60.0) > 8 THEN
(julianday(a.work_date || ' ' || a.clock_out_time) -
julianday(a.work_date || ' ' || a.clock_in_time)) * 24 -
(a.break_minutes / 60.0) - 8
ELSE 0
END
), 1) as total_overtime_hours
FROM attendance a
JOIN employees e ON a.employee_id = e.id
WHERE a.work_date >= date('now', 'start of month', '-1 month', 'localtime')
AND a.work_date < date('now', 'start of month', 'localtime')
GROUP BY e.id, strftime('%Y-%m', a.work_date)
ORDER BY month DESC, e.name;
ベストプラクティスのまとめ
1. データ設計の原則
- UTC で保存、表示時にローカル変換
- カラム名でタイムゾーンを明示(
_utc
,_local
) - 必要に応じてインデックスを作成
2. パフォーマンス対策
- ISO 8601 形式なら文字列比較を活用
- 頻繁な検索にはUnixタイムスタンプを併用
- 適切なインデックス設計
3. 保守性の向上
- 一貫した日時フォーマット
- コメントでタイムゾーンの取り扱いを明記
- ビューを活用した共通処理の抽象化
これらの実例を参考に、あなたのアプリケーションに適した日時処理を実装してください。
まとめ:SQLiteでの日時処理をマスターしよう
ここまでSQLiteでの現在時刻取得と日時処理について詳しく解説してきました。最後に重要なポイントをまとめます。
核心的なポイントの復習
現在時刻の取得方法
CURRENT_TIMESTAMP
:シンプルで最も使いやすいdatetime('now')
:UTC時刻、関数の組み合わせに便利datetime('now', 'localtime')
:ローカル時間、ユーザー向け表示に最適
保存形式の選び方
- TEXT型(ISO 8601):一般的なアプリケーションに最適
- INTEGER型(Unix):高速処理や大量データに有効
- REAL型(Julian):科学計算や高精度が必要な場合
タイムゾーンの扱い
- 保存はUTC、表示時にローカル変換が基本
- カラム名でタイムゾーンを明示
- システム間連携では必ずUTCを使用
よくある質問への回答
Q: いつもUTC で保存すべきですか? A: グローバルなアプリケーションや複数システム連携がある場合はUTC推奨。単一地域のみなら、ローカル時間でも問題ありません。
Q: パフォーマンスが心配です A: ISO 8601形式なら文字列比較で十分高速。大量データの場合はUnixタイムスタンプとの併用を検討しましょう。
Q: 夏時間はどう対応すれば? A: SQLite単体では自動対応できません。アプリケーション側でタイムゾーンライブラリを使用することを推奨します。
次のステップ
SQLiteの日時処理をマスターしたら、以下の技術も学んでみましょう:
関連技術
- トリガーの活用:自動的な時刻更新処理
- ビューの作成:複雑な日時計算の抽象化
- プログラミング言語との連携:Python、JavaScript等での活用
発展的なトピック
- 時系列データベースの概念
- データ分析での日時処理
- マイクロサービスでの時刻同期
コメント