MariaDBでUNIX_TIMESTAMPを使う際の落とし穴と解決策

2025-05-27

この関数は、主に以下の2つの方法で使用されます。

  1. 引数なしで呼び出す場合: UNIX_TIMESTAMP() この場合、現在のUnixタイムスタンプ(つまり、現在時刻の1970年1月1日00時00分00秒UTCからの経過秒数)を返します。

  2. 日付/時刻の引数を指定して呼び出す場合: UNIX_TIMESTAMP(date) ここでdateは、変換したい日付や時刻を表す文字列、DATE型、DATETIME型、TIMESTAMP型、またはYYMMDDYYYYMMDD形式の数値を指定します。関数は、指定された日付/時刻をUnixタイムスタンプに変換して返します。

主な特徴と利用場面

  • 32ビット制限: 32ビットシステムでは、Unixタイムスタンプの最大値に制限があり、2038年問題(2038年1月19日03時14分07秒UTC以降の日付が表現できない)が発生する可能性があります。MariaDBでは、これを超える日付の場合にNULLが返されることがあります。より新しい日付を扱う必要がある場合は、DATETIME型などの使用が推奨されます。
  • データ型としての効率性: 日付や時刻を数値として保存することで、データベースのストレージ効率が良くなる場合や、日付の比較や計算が高速になる場合があります。
  • FROM_UNIXTIME()との連携: UNIX_TIMESTAMP()の逆の操作を行う関数としてFROM_UNIXTIME()があります。これはUnixタイムスタンプを読みやすい日付/時刻形式に変換する際に使用されます。
  • 整数値: 基本的に整数値を返しますが、引数にマイクロ秒などの小数部が含まれる場合は、戻り値も小数部を持つ浮動小数点数になることがあります。
  • UTC基準: UNIX_TIMESTAMP()は、引数を現在のセッションのタイムゾーンの値として解釈し、内部的にUTCに変換してからUnixタイムスタンプを計算します。これにより、異なるタイムゾーンのシステム間でも時刻の比較や保存を一貫して行うことができます。


  • DATETIMEカラムの値をUnixタイムスタンプに変換:

    SELECT UNIX_TIMESTAMP(my_datetime_column) FROM my_table;
    
  • 特定の日付をUnixタイムスタンプに変換:

    SELECT UNIX_TIMESTAMP('2023-10-26 10:30:00');
    
  • 現在のUnixタイムスタンプを取得:

    SELECT UNIX_TIMESTAMP();
    


NULLが返される

UNIX_TIMESTAMP()関数に不正な日付/時刻形式の引数を与えた場合、または有効な日付範囲外の値を指定した場合、NULLが返されます。

一般的な原因

  • 存在しない日付: 例えば、2月30日や4月31日など、暦に存在しない日付を指定した場合。
  • 範囲外の日付:
    • UNIX_TIMESTAMPは、Unixエポック(1970年1月1日00時00分00秒UTC)より前の日付に対しては通常NULLを返します。
    • 32ビットシステムでは、2038年1月19日03時14分07秒UTCを超える日付に対してNULLが返される「2038年問題」が発生する可能性があります。MariaDBのバージョンやシステムアーキテクチャによっては、この上限が異なります。
  • 不正な日付形式の文字列: YYYY-MM-DD HH:MM:SSのような標準形式以外の文字列を使用した場合。例えば、'2023/10/26 10:30'のような形式は、設定によっては正しく解析されない場合があります。

トラブルシューティング

  • 入力値の確認: クエリに渡す変数の内容や、カラムに格納されているデータが有効な日付/時刻であることを確認します。
  • 日付の範囲確認: UNIX_TIMESTAMPの許容範囲内に日付が収まっているか確認します。特に、将来の日付を扱う場合は2038年問題を考慮し、DATETIME型などの使用も検討します。
  • 日付形式の確認: STR_TO_DATE()関数を使用して、指定した文字列がMariaDBによってどのように解釈されているかを確認します。
    SELECT STR_TO_DATE('2023/10/26 10:30', '%Y/%m/%d %H:%i');
    -- 必要に応じて、UNIX_TIMESTAMP()に渡す前に正しい形式に変換する
    SELECT UNIX_TIMESTAMP(STR_TO_DATE('2023/10/26 10:30', '%Y/%m/%d %H:%i'));
    

予期せぬ値が返される(タイムゾーンの問題)

UNIX_TIMESTAMP()は、引数を現在のセッションのタイムゾーンとして解釈し、内部的にUTCに変換してからUnixタイムスタンプを生成します。このタイムゾーンの設定が期待と異なる場合、予期せぬ結果になることがあります。

一般的な原因

  • 夏時間 (Daylight Saving Time - DST): 夏時間の切り替え時期に、特定の時刻が重複したりスキップされたりすることで、UNIX_TIMESTAMPが意図しない値を返すことがあります。
  • セッションのタイムゾーン設定: MariaDBサーバーのタイムゾーン(system_time_zonetime_zoneシステム変数)と、現在のセッションのタイムゾーン設定(time_zoneシステム変数)が異なる場合。

トラブルシューティング

  • タイムゾーンテーブルのロード: サーバーが正しいタイムゾーン情報を認識しているか確認します。MariaDBはデフォルトでタイムゾーンデータをインストールしていません。mysql_tzinfo_to_sqlツールを使ってロードする必要があります。
  • UTCを直接指定: タイムゾーンの問題を避けるために、引数を事前にUTCに変換してからUNIX_TIMESTAMPを使用するか、CONVERT_TZ()関数を利用します。
    -- JSTの時刻をUTCに変換してからUNIX_TIMESTAMP
    SELECT UNIX_TIMESTAMP(CONVERT_TZ('2023-10-26 10:30:00', '+09:00', '+00:00'));
    
  • セッションのタイムゾーンの設定: 必要に応じて、クエリの前にセッションのタイムゾーンを設定します。
    SET time_zone = '+09:00'; -- 日本標準時 (JST) に設定する例
    SELECT UNIX_TIMESTAMP('2023-10-26 10:30:00');
    SET time_zone = 'SYSTEM'; -- システムのタイムゾーンに戻す
    
  • 現在のタイムゾーンの確認:
    SELECT @@global.time_zone, @@session.time_zone, @@system_time_zone;
    

UNIX_TIMESTAMPをデフォルト値として設定できない

UNIX_TIMESTAMP()をカラムのDEFAULT値として直接指定しようとするとエラーになることがあります。

一般的な原因

  • MariaDBのバージョンによっては、UNIX_TIMESTAMP()のような関数をDEFAULT値として直接設定することが許可されていません。TIMESTAMP型にはCURRENT_TIMESTAMPを設定できますが、INT型のUnixタイムスタンプカラムには直接設定できません。

トラブルシューティング

  • アプリケーション側で設定する: 挿入や更新を行うアプリケーションコードで、UNIX_TIMESTAMP()を呼び出して取得した値を明示的に設定します。
  • トリガーを使用する: INT型でUnixタイムスタンプを保存したい場合は、BEFORE INSERTBEFORE UPDATEトリガーを使用して、UNIX_TIMESTAMP()の戻り値をカラムに設定します。
    CREATE TABLE my_table_int (
        id INT AUTO_INCREMENT PRIMARY KEY,
        created_at INT,
        updated_at INT
    );
    
    DELIMITER //
    CREATE TRIGGER set_unix_timestamps_on_insert
    BEFORE INSERT ON my_table_int
    FOR EACH ROW
    BEGIN
        IF NEW.created_at IS NULL THEN
            SET NEW.created_at = UNIX_TIMESTAMP();
        END IF;
        IF NEW.updated_at IS NULL THEN
            SET NEW.updated_at = UNIX_TIMESTAMP();
        END IF;
    END;
    //
    CREATE TRIGGER set_unix_timestamps_on_update
    BEFORE UPDATE ON my_table_int
    FOR EACH ROW
    BEGIN
        SET NEW.updated_at = UNIX_TIMESTAMP();
    END;
    //
    DELIMITER ;
    
  • TIMESTAMP型を使用する: Unixタイムスタンプを保存する目的であれば、TIMESTAMP型を使用し、DEFAULT CURRENT_TIMESTAMPON UPDATE CURRENT_TIMESTAMPを設定するのが最も簡単で推奨される方法です。これにより、値は自動的にUnixタイムスタンプとして扱われ、挿入時や更新時に自動的に設定されます。
    CREATE TABLE my_table (
        id INT AUTO_INCREMENT PRIMARY KEY,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    );
    
    取得時にはUNIX_TIMESTAMP(created_at)のように変換して使用できます。

UNIX_TIMESTAMP()は、引数にマイクロ秒などの小数部が含まれるDATETIMETIMESTAMP型の値が渡された場合、結果として浮動小数点数を返すことがあります。

一般的な原因

  • 結果を整数として扱いたいが、浮動小数点数が返されてしまう。
  • 入力データに小数秒が含まれている。
  • カラムの型: Unixタイムスタンプを格納するカラムの型がINT型である場合、小数部は自動的に切り捨てられるか、エラーになる場合があります。必要に応じてBIGINT型やDECIMAL型を検討します。
  • FLOOR()またはROUND()で丸める: 結果を整数にしたい場合は、FLOOR()(切り捨て)やROUND()(四捨五入)関数を使用して丸めます。
    SELECT FLOOR(UNIX_TIMESTAMP('2023-10-26 10:30:00.123456'));
    -- または
    SELECT ROUND(UNIX_TIMESTAMP('2023-10-26 10:30:00.123456'));
    


SQLクエリでのUNIX_TIMESTAMPの使用例

例1: 現在のUnixタイムスタンプを取得する

これは最も基本的な使用法です。

SELECT UNIX_TIMESTAMP();

出力例

+------------------+
| UNIX_TIMESTAMP() |
+------------------+
|       1732553237 |
+------------------+

(出力される値は、実行時の現在時刻によって異なります。)

例2: 特定の日付文字列をUnixタイムスタンプに変換する

正確な日付と時刻をUnixタイムスタンプに変換します。

SELECT UNIX_TIMESTAMP('2023-01-01 00:00:00');

出力例

+---------------------------------------+
| UNIX_TIMESTAMP('2023-01-01 00:00:00') |
+---------------------------------------+
|                            1672531200 |
+---------------------------------------+

例3: DATETIMEカラムの値をUnixタイムスタンプに変換する

テーブルに格納されているDATETIME型のデータをUnixタイムスタンプとして取得する場合です。

まず、サンプルテーブルを作成します。

CREATE TABLE events (
    id INT AUTO_INCREMENT PRIMARY KEY,
    event_name VARCHAR(255) NOT NULL,
    event_datetime DATETIME NOT NULL
);

INSERT INTO events (event_name, event_datetime) VALUES
('Meeting Start', '2023-05-15 09:00:00'),
('Project Deadline', '2024-03-31 17:00:00'),
('Presentation', '2023-11-10 14:30:00');

次に、変換クエリを実行します。

SELECT
    id,
    event_name,
    event_datetime,
    UNIX_TIMESTAMP(event_datetime) AS unix_timestamp_value
FROM
    events;

出力例

+----+------------------+---------------------+--------------------+
| id | event_name       | event_datetime      | unix_timestamp_value |
+----+------------------+---------------------+--------------------+
|  1 | Meeting Start    | 2023-05-15 09:00:00 |         1684122000 |
|  2 | Project Deadline | 2024-03-31 17:00:00 |         1711894800 |
|  3 | Presentation     | 2023-11-10 14:30:00 |         1699607400 |
+----+------------------+---------------------+--------------------+

例4: Unixタイムスタンプを使用して条件を絞り込む

特定の期間内のイベントを検索する場合など。FROM_UNIXTIME()関数と組み合わせて使用すると、可読性が向上します。

-- 2023年10月1日から2023年12月31日までのイベントを検索
SELECT
    id,
    event_name,
    event_datetime,
    UNIX_TIMESTAMP(event_datetime) AS unix_timestamp_value
FROM
    events
WHERE
    UNIX_TIMESTAMP(event_datetime) BETWEEN UNIX_TIMESTAMP('2023-10-01 00:00:00') AND UNIX_TIMESTAMP('2023-12-31 23:59:59');

出力例

+----+--------------+---------------------+--------------------+
| id | event_name   | event_datetime      | unix_timestamp_value |
+----+--------------+---------------------+--------------------+
|  3 | Presentation | 2023-11-10 14:30:00 |         1699607400 |
+----+--------------+---------------------+--------------------+

ウェブアプリケーションなどで、UNIX_TIMESTAMPをMariaDBと連携させる場合の基本的なプログラミング例です。

例: イベントの追加と表示

<?php

// データベース接続設定
$servername = "localhost";
$username = "your_username"; // あなたのデータベースユーザー名
$password = "your_password"; // あなたのデータベースパスワード
$dbname = "test_db";       // あなたのデータベース名

// データベース接続
$conn = new mysqli($servername, $username, $password, $dbname);

// 接続チェック
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}

// 例1: 新しいイベントを追加(DATETIME型で挿入)
$eventName = "New Year Party";
$eventDatetime = "2024-01-01 20:00:00";

$sqlInsert = "INSERT INTO events (event_name, event_datetime) VALUES (?, ?)";
$stmtInsert = $conn->prepare($sqlInsert);
$stmtInsert->bind_param("ss", $eventName, $eventDatetime);

if ($stmtInsert->execute()) {
    echo "New event added successfully!<br>";
} else {
    echo "Error: " . $sqlInsert . "<br>" . $conn->error . "<br>";
}
$stmtInsert->close();

echo "<hr>";

// 例2: イベント一覧とUnixタイムスタンプを表示
// FROM_UNIXTIME()も使って、Unixタイムスタンプから読みやすい形式に変換する例も含む
$sqlSelect = "
    SELECT
        id,
        event_name,
        event_datetime,
        UNIX_TIMESTAMP(event_datetime) AS unix_timestamp_value,
        FROM_UNIXTIME(UNIX_TIMESTAMP(event_datetime)) AS readable_from_unix_time
    FROM
        events
    ORDER BY
        event_datetime ASC;
";

$result = $conn->query($sqlSelect);

if ($result->num_rows > 0) {
    echo "<h2>Events List:</h2>";
    echo "<table border='1'>";
    echo "<tr><th>ID</th><th>Event Name</th><th>Event Datetime</th><th>Unix Timestamp</th><th>From UnixTime Readable</th></tr>";
    while ($row = $result->fetch_assoc()) {
        echo "<tr>";
        echo "<td>" . $row["id"] . "</td>";
        echo "<td>" . $row["event_name"] . "</td>";
        echo "<td>" . $row["event_datetime"] . "</td>";
        echo "<td>" . $row["unix_timestamp_value"] . "</td>";
        echo "<td>" . $row["readable_from_unix_time"] . "</td>";
        echo "</tr>";
    }
    echo "</table>";
} else {
    echo "No events found.";
}

// データベース接続を閉じる
$conn->close();

?>

実行結果の概念

ブラウザでこのPHPスクリプトにアクセスすると、以下のような出力が得られます(HTMLテーブルとして)。

New event added successfully!<br>
<hr>
<h2>Events List:</h2>
<table border="1">
    <tr>
        <th>ID</th><th>Event Name</th><th>Event Datetime</th><th>Unix Timestamp</th><th>From UnixTime Readable</th>
    </tr>
    <tr>
        <td>1</td><td>Meeting Start</td><td>2023-05-15 09:00:00</td><td>1684122000</td><td>2023-05-15 09:00:00</td>
    </tr>
    <tr>
        <td>3</td><td>Presentation</td><td>2023-11-10 14:30:00</td><td>1699607400</td><td>2023-11-10 14:30:00</td>
    </tr>
    <tr>
        <td>4</td><td>New Year Party</td><td>2024-01-01 20:00:00</td><td>1704148800</td><td>2024-01-01 20:00:00</td>
    </tr>
    <tr>
        <td>2</td><td>Project Deadline</td><td>2024-03-31 17:00:00</td><td>1711894800</td><td>2024-03-31 17:00:00</td>
    </tr>
</table>

(IDやUnixタイムスタンプの値は、実行時の挿入順序や時刻によって異なります。)

  • パフォーマンス: 大量のデータを扱う場合、Unixタイムスタンプ(INTまたはBIGINT)として保存すると、DATETIME型よりもインデックスの検索や比較が高速になることがあります。しかし、可読性やタイムゾーンの自動処理といったDATETIME型の利点とトレードオフになります。
  • FROM_UNIXTIME()での逆変換: アプリケーションでUnixタイムスタンプを持っている場合、それを人間が読みやすい形式で表示したい場合は、FROM_UNIXTIME()関数を使ってMariaDBで変換するか、PHPのdate()関数やJavaScriptのDateオブジェクトなど、各言語の機能を利用します。
  • クエリ時にUNIX_TIMESTAMP()で変換: データベースから取得する際に、必要に応じてUNIX_TIMESTAMP()関数を使ってUnixタイムスタンプに変換します。これにより、アプリケーション側での日付計算や比較が容易になります。
  • データベースにDATETIME型で保存: ほとんどの場合、日付と時刻の情報をDATETIME型またはTIMESTAMP型でデータベースに保存するのが最も良い方法です。これらの型は、MariaDBのタイムゾーン設定によって自動的に処理され、人間が読みやすい形式で表示されます。


DATETIME または TIMESTAMP 型を直接使用する

MariaDBにおける日付と時刻の情報を扱う最も一般的で推奨される方法は、DATETIMEまたはTIMESTAMPデータ型を直接使用することです。これらはUnixタイムスタンプの内部表現とは異なりますが、多くの利点があります。

特徴と利点

  • 範囲: DATETIME型は'1000-01-01 00:00:00'から'9999-12-31 23:59:59'までの広範な日付をサポートしており、2038年問題の懸念がありません。
  • タイムゾーン処理 (TIMESTAMPの場合): TIMESTAMP型は、保存時にセッションのタイムゾーンからUTCに変換され、取得時にセッションのタイムゾーンに変換されます。これにより、タイムゾーンをまたがるアプリケーションで一貫した時刻の扱いが可能になります。DATETIME型はタイムゾーンの影響を受けず、そのままの値が保存されます。
  • 日付/時刻関数: MariaDBの豊富​​な日付/時刻関数(DATE_FORMAT(), DATE_ADD(), DATEDIFF(), NOW(), CURDATE()など)を直接適用できます。
  • 可読性: データベース内で直接、人間が読みやすい形式 (YYYY-MM-DD HH:MM:SS) で日付と時刻が保存されます。

使用例

-- テーブル定義
CREATE TABLE logs (
    id INT AUTO_INCREMENT PRIMARY KEY,
    message TEXT,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP, -- 挿入時に自動で現在時刻を設定
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -- 更新時に自動で現在時刻を設定、タイムゾーン対応
);

-- データ挿入
INSERT INTO logs (message) VALUES ('Application started.');

-- データ取得(日付形式で)
SELECT id, message, created_at, updated_at FROM logs;

-- 特定の期間でフィルタリング
SELECT id, message FROM logs WHERE created_at BETWEEN '2023-01-01 00:00:00' AND '2023-01-31 23:59:59';

アプリケーション言語での日付/時刻処理

データベースにUnixタイムスタンプ形式で数値を保存する場合(INTまたはBIGINT型)、日付/時刻の変換や操作のほとんどをアプリケーション側の言語(PHP, Python, Java, JavaScriptなど)で行う方法です。

特徴と利点

  • 一貫したタイムゾーン処理: アプリケーション全体で統一されたタイムゾーン処理ロジックを適用しやすくなります。
  • アプリケーションの柔軟性: アプリケーション言語の豊富な日付/時刻ライブラリを最大限に活用できます。
  • データベースの負荷軽減: 複雑な日付/時刻の計算をアプリケーション側で行うことで、データベースサーバーのCPU負荷を軽減できます。

考慮点

  • クエリの複雑化: 日付によるフィルタリングやソートを行う際に、SQLクエリ内でFROM_UNIXTIME()を使用するか、アプリケーション側でUnixタイムスタンプを計算してSQLに渡す必要があります。
  • 可読性の低下: データベースを直接参照した際、Unixタイムスタンプの数値だけでは人間にとって日付を理解しにくいです。

PHPでの例

<?php

// Unixタイムスタンプを保存するテーブルを作成 (BIGINT型が安全)
// CREATE TABLE app_data (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255), created_unix_timestamp BIGINT);

// データベース接続(上記UNIX_TIMESTAMPの例と同じ設定)
$servername = "localhost";
$username = "your_username";
$password = "your_password";
$dbname = "test_db";

$conn = new mysqli($servername, $username, $password, $dbname);

if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}

// 例1: 現在のUnixタイムスタンプをPHPで取得し、データベースに挿入
$currentUnixTimestamp = time(); // PHPのtime()関数は現在のUnixタイムスタンプを返す
$dataName = "Some important event";

$sqlInsert = "INSERT INTO app_data (name, created_unix_timestamp) VALUES (?, ?)";
$stmtInsert = $conn->prepare($sqlInsert);
$stmtInsert->bind_param("si", $dataName, $currentUnixTimestamp);

if ($stmtInsert->execute()) {
    echo "Data inserted with PHP-generated Unix timestamp: " . $currentUnixTimestamp . "<br>";
} else {
    echo "Error: " . $conn->error . "<br>";
}
$stmtInsert->close();

echo "<hr>";

// 例2: データベースからUnixタイムスタンプを取得し、PHPで日付形式に変換して表示
$sqlSelect = "SELECT id, name, created_unix_timestamp FROM app_data ORDER BY id DESC LIMIT 1";
$result = $conn->query($sqlSelect);

if ($result->num_rows > 0) {
    $row = $result->fetch_assoc();
    $dbUnixTimestamp = $row["created_unix_timestamp"];

    // PHPのdate()関数を使ってUnixタイムスタンプをフォーマット
    $readableDate = date("Y-m-d H:i:s", $dbUnixTimestamp);

    echo "Retrieved data:<br>";
    echo "ID: " . $row["id"] . "<br>";
    echo "Name: " . $row["name"] . "<br>";
    echo "Stored Unix Timestamp: " . $dbUnixTimestamp . "<br>";
    echo "Readable Date (PHP): " . $readableDate . "<br>";
} else {
    echo "No data found.";
}

$conn->close();

?>

稀なケースですが、日付を2023102620231026103000のような整数としてINTまたはBIGINT型で保存する方法もあります。

特徴と利点

  • 特定の期間のフィルタリング: WHERE date_col BETWEEN 20230101 AND 20230131のように数値比較でフィルタリングできます。
  • ソートが容易: 数値としてソートできるため、日付順に並べ替えるのが非常に簡単です。

考慮点

  • 計算の複雑さ: 日付の加算・減算、曜日や月の取得などの操作が複雑になります。
  • タイムゾーンの問題: この形式自体にはタイムゾーン情報は含まれていません。
  • 日付/時刻関数との非互換性: MariaDBの標準の日付/時刻関数(DATE_ADD(), DATEDIFF()など)を直接適用できません。変換関数(STR_TO_DATE(), DATE_FORMAT())を介して行う必要があります。

使用例

CREATE TABLE sales (
    id INT AUTO_INCREMENT PRIMARY KEY,
    product_name VARCHAR(255),
    sale_date INT, -- YYYYMMDD 形式
    sale_datetime BIGINT -- YYYYMMDDHHMMSS 形式
);

INSERT INTO sales (product_name, sale_date, sale_datetime) VALUES
('Widget A', 20231026, 20231026103000),
('Gadget B', 20231027, 20231027154530);

-- 日付を読みやすい形式で取得
SELECT
    id,
    product_name,
    STR_TO_DATE(sale_date, '%Y%m%d') AS readable_date,
    STR_TO_DATE(sale_datetime, '%Y%m%d%H%i%s') AS readable_datetime
FROM sales;

-- 特定の月に販売されたものを検索
SELECT id, product_name
FROM sales
WHERE sale_date BETWEEN 20231001 AND 20231031;

ほとんどのケースでは、MariaDBのDATETIMEまたはTIMESTAMPを使用し、必要に応じてUNIX_TIMESTAMP()FROM_UNIXTIME()で変換するのが最もバランスの取れたアプローチです。これにより、データベースのデータが人間にとって読みやすく、MariaDBの強力な日付/時刻関数を利用でき、タイムゾーンの処理も適切に行われます。