【完全ガイド】Oracle DATE vs TIMESTAMP 違い|精度・タイムゾーン・INTERVAL 徹底解説

【完全ガイド】Oracle DATE vs TIMESTAMP 違い|精度・タイムゾーン・INTERVAL 徹底解説

Oracle 開発者が必ず一度は悩む問い:

CREATE TABLE events (
  event_id   NUMBER PRIMARY KEY,
  event_time DATE,          -- ← これ?
  event_ts   TIMESTAMP      -- ← それとも?
);

結論から言うと、両者は目的が違います:

  • DATE: 秒精度、7バイト、タイムゾーンなし
  • TIMESTAMP: ナノ秒精度、11-13バイト、タイムゾーン対応

しかし、実際は5種類のバリエーションがあり、選択が複雑:

  • DATE: 秒まで
  • TIMESTAMP: ナノ秒まで、TZ なし
  • TIMESTAMP(n): 精度指定
  • TIMESTAMP WITH TIME ZONE: TZ 含む
  • TIMESTAMP WITH LOCAL TIME ZONE: DB TZ に正規化

さらに、多くの Oracle 開発者が誤解している事実:

  • DATE は日付のみではない(時刻も持つ)
  • SYSDATESYSTIMESTAMP の返り値が違う
  • CURRENT_DATE はサーバーの日付ではない(セッションの日付)
  • TIMESTAMP WITH TIME ZONE は保存時に TZ を保持
  • TIMESTAMP WITH LOCAL TIME ZONE は保存時に UTC 変換
  • INTERVAL 型の存在を知らない
  • NLS_DATE_FORMAT がセッション毎

実務では:

  • 監査ログ: TIMESTAMP(順序保証)
  • 金融取引: TIMESTAMP WITH TIME ZONE
  • 国際サービス: TIMESTAMP WITH TIME ZONE
  • 業務日: DATE で十分
  • メール送信時刻: TIMESTAMP
  • DB マイグレーション: 型変換の考慮

さらに、日付演算・タイムゾーン計算・NLS 設定・INTERVAL 型・現在時刻関数の使い分けなど、日本語で体系的な解説がほぼないのが現状です。

本記事では、Oracle DATE vs TIMESTAMP完全ガイドを、リファレンスとして実用的に整理します。5種類の日付型、内部バイト構造、精度と範囲、7つの現在時刻関数、タイムゾーン計算、INTERVAL 型、日付演算、変換関数、NLS 設定、パフォーマンス、他 DB との比較、Rails/Java/Python 対応、実践シナリオ、FAQまで完全網羅。この1本で Oracle 日付型を根本から理解・選択できるようになります。


目次

結論:3つの型を覚える

時間がない方向けに、最速の理解を示します。

3つの主要型

精度サイズTZ用途
DATE7バイトなし業務日、日付管理
TIMESTAMPナノ秒11バイトなし高精度ログ、順序保証
TIMESTAMP WITH TIME ZONEナノ秒13バイトあり国際サービス、金融

DATE の実態

日付だけではない!時刻も持つ:

CREATE TABLE t (d DATE);
INSERT INTO t VALUES (SYSDATE);  -- 2026-06-15 14:30:45

DATE = 年月日時分秒(秒まで)。

5種類の全型

精度サイズTZ
DATE7なし
TIMESTAMPナノ秒11なし
TIMESTAMP(n)n桁7-11なし
TIMESTAMP WITH TIME ZONEナノ秒13明示
TIMESTAMP WITH LOCAL TIME ZONEナノ秒11暗黙UTC

7つの現在時刻関数

関数返り値参照時刻
SYSDATEDATEサーバー
SYSTIMESTAMPTIMESTAMP WITH TZサーバー
CURRENT_DATEDATEセッション
CURRENT_TIMESTAMPTIMESTAMP WITH TZセッション
LOCALTIMESTAMPTIMESTAMPセッション
DBTIMEZONEVARCHAR2DB の TZ
SESSIONTIMEZONEVARCHAR2セッション TZ

選択ガイド

日付だけ(時刻不要) → DATE
秒精度で十分         → DATE  
ミリ秒〜必要         → TIMESTAMP
国際サービス          → TIMESTAMP WITH TIME ZONE
DB TZ 統一で OK      → TIMESTAMP WITH LOCAL TIME ZONE
期間                  → INTERVAL

詳細は以下で解説します。


まず理解する:DATE 型の詳細

内部構造(7バイト)

バイト 1: 世紀(+100)
バイト 2: 年(+100)
バイト 3: 月
バイト 4: 日
バイト 5: 時(+1)
バイト 6: 分(+1)
バイト 7: 秒(+1)

例: 2026-06-15 14:30:45
バイト: 120, 126, 6, 15, 15, 31, 46

精度と範囲

範囲: 4712 BC 〜 9999 AD
精度: 1秒
サイズ: 7バイト(固定長)

DATE リテラル

-- ANSI 標準リテラル
SELECT DATE '2026-06-15' FROM DUAL;
-- 時刻は 00:00:00

-- TO_DATE 関数
SELECT TO_DATE('2026-06-15 14:30:45', 'YYYY-MM-DD HH24:MI:SS') FROM DUAL;

DATE の落とし穴

時刻を含む:

SELECT * FROM orders WHERE order_date = DATE '2026-06-15';
-- 2026-06-15 の 00:00:00 のみ!
-- 14:30 の注文は含まれない

-- 正しくは
SELECT * FROM orders 
WHERE order_date >= DATE '2026-06-15'
  AND order_date <  DATE '2026-06-16';

-- または
SELECT * FROM orders 
WHERE TRUNC(order_date) = DATE '2026-06-15';
-- ただしインデックス使えなくなる

TRUNC の問題は Oracle EXPLAIN PLAN や関数索引で解決可能。


TIMESTAMP 型の詳細

3種類のバリエーション

-- ① TIMESTAMP(TZ なし)
TIMESTAMP
TIMESTAMP(6)   -- 精度 6 (デフォルト、マイクロ秒)
TIMESTAMP(9)   -- 精度 9 (ナノ秒)

-- ② TIMESTAMP WITH TIME ZONE
TIMESTAMP WITH TIME ZONE
TIMESTAMP(6) WITH TIME ZONE

-- ③ TIMESTAMP WITH LOCAL TIME ZONE
TIMESTAMP WITH LOCAL TIME ZONE
TIMESTAMP(6) WITH LOCAL TIME ZONE

内部構造

TIMESTAMP(11バイト):

バイト 1-7: DATE と同じ(世紀・年・月・日・時・分・秒)
バイト 8-11: fractional seconds(ナノ秒対応)

TIMESTAMP WITH TIME ZONE(13バイト):

バイト 1-11: TIMESTAMP と同じ
バイト 12-13: タイムゾーンオフセット

TIMESTAMP WITH LOCAL TIME ZONE(11バイト):

TIMESTAMP と同じサイズ
値は UTC で保存、表示時に session TZ に変換

fractional_seconds_precision

0〜9 桁指定可能:

TIMESTAMP(0)   -- 秒まで(DATE と同等の精度)
TIMESTAMP(3)   -- ミリ秒
TIMESTAMP(6)   -- マイクロ秒(デフォルト)
TIMESTAMP(9)   -- ナノ秒(最高精度)

TIMESTAMP リテラル

-- ANSI 標準
SELECT TIMESTAMP '2026-06-15 14:30:45.123456' FROM DUAL;

-- TZ 付き
SELECT TIMESTAMP '2026-06-15 14:30:45 +09:00' FROM DUAL;

-- TZ 地域名
SELECT TIMESTAMP '2026-06-15 14:30:45 Asia/Tokyo' FROM DUAL;

TIMESTAMP WITH TIME ZONE の詳細

特徴

TZ 情報を保存:

CREATE TABLE international_events (
  event_id NUMBER,
  event_at TIMESTAMP WITH TIME ZONE
);

INSERT INTO international_events VALUES (
  1, TIMESTAMP '2026-06-15 09:00:00 America/New_York'
);
INSERT INTO international_events VALUES (
  2, TIMESTAMP '2026-06-15 22:00:00 Asia/Tokyo'
);

SELECT event_id, event_at FROM international_events;
-- 1: 2026-06-15 09:00:00 -04:00
-- 2: 2026-06-15 22:00:00 +09:00

両方とも UTC で同じ瞬間(09:00 EDT = 13:00 UTC = 22:00 JST)。

比較・演算

-- TZ を考慮して比較(UTC 換算)
SELECT * FROM international_events 
WHERE event_at = TIMESTAMP '2026-06-15 13:00:00 UTC';
-- 上記2件とも該当(同じ UTC 瞬間)

タイムゾーン変換

-- AT TIME ZONE で TZ 変換
SELECT event_at,
       event_at AT TIME ZONE 'Asia/Tokyo' AS tokyo_time,
       event_at AT TIME ZONE 'UTC' AS utc_time
FROM international_events;

タイムゾーン名 vs オフセット

-- 地域名(DST 対応)
TIMESTAMP '2026-07-15 09:00:00 America/New_York'
-- サマータイム時: -04:00
-- 冬時間:        -05:00

-- 固定オフセット
TIMESTAMP '2026-07-15 09:00:00 -05:00'
-- 常に -05:00

DST(サマータイム) を考慮するなら地域名。


TIMESTAMP WITH LOCAL TIME ZONE

動作

保存時: セッションTZ → DB TZ に変換して保存 取得時: DB TZ → セッションTZ に変換して表示

-- セッションTZ = 'Asia/Tokyo'
CREATE TABLE t (ts TIMESTAMP WITH LOCAL TIME ZONE);
INSERT INTO t VALUES (TIMESTAMP '2026-06-15 14:00:00');
-- DB内部(UTC): 2026-06-15 05:00:00

-- 別セッションが 'America/New_York'
SELECT ts FROM t;
-- 2026-06-15 01:00:00 (JST 14:00 の東部時間)

用途

  • 国際チーム全員が自分の TZ で見たい
  • DB は UTC 統一したい
  • DBTIMEZONE = ‘UTC’ 必須

制限

  • DBTIMEZONE を後から変更不可(データがある場合)
  • 保存サイズ 11バイト(TZ なし版と同じ)

7つの現在時刻関数

SYSDATE

サーバーの DATE:

SELECT SYSDATE FROM DUAL;
-- 2026-06-15 14:30:45 (秒まで)
  • 型: DATE
  • TZ: サーバーの OS TZ

SYSTIMESTAMP

サーバーの TIMESTAMP WITH TIME ZONE:

SELECT SYSTIMESTAMP FROM DUAL;
-- 2026-06-15 14:30:45.123456 +09:00
  • 型: TIMESTAMP(6) WITH TIME ZONE
  • TZ: サーバー OS TZ

CURRENT_DATE

セッションのDATE:

SELECT CURRENT_DATE FROM DUAL;
-- セッションTZ での現在日時
  • 型: DATE
  • TZ: SESSIONTIMEZONE

CURRENT_TIMESTAMP

セッションの TIMESTAMP WITH TIME ZONE:

SELECT CURRENT_TIMESTAMP FROM DUAL;
  • 型: TIMESTAMP WITH TIME ZONE
  • TZ: SESSIONTIMEZONE

LOCALTIMESTAMP

セッションのTIMESTAMP(TZ なし):

SELECT LOCALTIMESTAMP FROM DUAL;
  • 型: TIMESTAMP(TZなし)
  • 値: セッションTZ の時刻

DBTIMEZONE

DB の TZ:

SELECT DBTIMEZONE FROM DUAL;
-- '+00:00' または 'UTC' 等

SESSIONTIMEZONE

セッションの TZ:

SELECT SESSIONTIMEZONE FROM DUAL;
-- 'Asia/Tokyo' 等

-- 変更
ALTER SESSION SET TIME_ZONE = 'Asia/Tokyo';

使い分け表

用途関数
ログのタイムスタンプ(サーバー時刻)SYSDATE / SYSTIMESTAMP
ユーザー体験(クライアント時刻)CURRENT_DATE / CURRENT_TIMESTAMP
TZ 情報不要SYSDATE / LOCALTIMESTAMP
高精度SYSTIMESTAMP

日付演算

DATE の演算

-- ① 日数の加算
SELECT SYSDATE + 1 FROM DUAL;      -- 明日
SELECT SYSDATE + 1/24 FROM DUAL;   -- 1時間後
SELECT SYSDATE + 1/1440 FROM DUAL; -- 1分後

-- ② 日数の減算
SELECT SYSDATE - 7 FROM DUAL;      -- 1週間前

-- ③ 日付の差(日数)
SELECT DATE '2026-06-30' - DATE '2026-06-15' FROM DUAL;
-- 15

-- ④ 月加算
SELECT ADD_MONTHS(SYSDATE, 3) FROM DUAL;   -- 3ヶ月後

-- ⑤ 月数の差
SELECT MONTHS_BETWEEN(DATE '2026-12-31', DATE '2026-06-15') FROM DUAL;
-- 6.516...

TIMESTAMP の演算

INTERVAL 型が返る:

SELECT TIMESTAMP '2026-06-15 14:00:00' - TIMESTAMP '2026-06-15 12:00:00' FROM DUAL;
-- +000000000 02:00:00.000000  ← INTERVAL DAY TO SECOND

-- INTERVAL の加減
SELECT SYSTIMESTAMP + INTERVAL '1' HOUR FROM DUAL;
SELECT SYSTIMESTAMP + INTERVAL '30' MINUTE FROM DUAL;
SELECT SYSTIMESTAMP + INTERVAL '1' DAY FROM DUAL;

DATE と TIMESTAMP の混在

-- DATE + INTERVAL は可能
SELECT SYSDATE + INTERVAL '1' HOUR FROM DUAL;

INTERVAL 型

2種類の INTERVAL

INTERVAL YEAR TO MONTH:

INTERVAL '2-6' YEAR TO MONTH  -- 2年6ヶ月
INTERVAL '5' YEAR              -- 5年
INTERVAL '18' MONTH            -- 18ヶ月

INTERVAL DAY TO SECOND:

INTERVAL '10 12:30:45.5' DAY TO SECOND  -- 10日12時30分45.5秒
INTERVAL '3' DAY                         -- 3日
INTERVAL '2' HOUR                        -- 2時間
INTERVAL '30' MINUTE                     -- 30分
INTERVAL '45' SECOND                     -- 45秒

INTERVAL の使い方

-- テーブル定義
CREATE TABLE subscriptions (
  sub_id NUMBER,
  duration INTERVAL YEAR TO MONTH,
  trial_period INTERVAL DAY TO SECOND
);

-- 演算
SELECT SYSDATE + INTERVAL '1-3' YEAR TO MONTH FROM DUAL;
-- 1年3ヶ月後

変換関数

-- NUMTOYMINTERVAL(数値 → INTERVAL YEAR TO MONTH)
SELECT NUMTOYMINTERVAL(1.5, 'YEAR') FROM DUAL;  -- 1年6ヶ月

-- NUMTODSINTERVAL(数値 → INTERVAL DAY TO SECOND)
SELECT NUMTODSINTERVAL(2, 'HOUR') FROM DUAL;    -- 2時間

-- EXTRACT
SELECT EXTRACT(HOUR FROM INTERVAL '5:30' HOUR TO MINUTE) FROM DUAL;
-- 5

変換関数

TO_CHAR

-- DATE → VARCHAR2
SELECT TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS') FROM DUAL;

-- TIMESTAMP → VARCHAR2
SELECT TO_CHAR(SYSTIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.FF6') FROM DUAL;

-- TZ 付き
SELECT TO_CHAR(SYSTIMESTAMP, 'YYYY-MM-DD HH24:MI:SS TZR') FROM DUAL;

TO_DATE

SELECT TO_DATE('2026-06-15', 'YYYY-MM-DD') FROM DUAL;
SELECT TO_DATE('15/06/2026 14:30', 'DD/MM/YYYY HH24:MI') FROM DUAL;

日付変換のエラーは ORA-01843: not a valid month の記事、ORA-01830: date format ends before の記事、ORA-01858: non-numeric character の記事も参照してください。

TO_TIMESTAMP

SELECT TO_TIMESTAMP('2026-06-15 14:30:45.123', 'YYYY-MM-DD HH24:MI:SS.FF3') 
FROM DUAL;

TO_TIMESTAMP_TZ

SELECT TO_TIMESTAMP_TZ('2026-06-15 14:30:45 +09:00', 
                       'YYYY-MM-DD HH24:MI:SS TZH:TZM') 
FROM DUAL;

CAST

-- DATE → TIMESTAMP
SELECT CAST(SYSDATE AS TIMESTAMP) FROM DUAL;

-- TIMESTAMP → DATE(fractional 削除)
SELECT CAST(SYSTIMESTAMP AS DATE) FROM DUAL;

-- TIMESTAMP → TIMESTAMP WITH TIME ZONE
SELECT CAST(LOCALTIMESTAMP AS TIMESTAMP WITH TIME ZONE) FROM DUAL;

FROM_TZ

-- TIMESTAMP に TZ を追加
SELECT FROM_TZ(LOCALTIMESTAMP, 'Asia/Tokyo') FROM DUAL;

NLS 設定

NLS_DATE_FORMAT

-- 現在の設定
SELECT * FROM v$nls_parameters WHERE parameter LIKE 'NLS_DATE%';

-- 変更(セッション)
ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
SELECT SYSDATE FROM DUAL;
-- 2026-06-15 14:30:45(指定形式)

-- リセット
ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MON-YY';

NLS_TIMESTAMP_FORMAT

ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF6';

NLS_TIMESTAMP_TZ_FORMAT

ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT = 
  'YYYY-MM-DD HH24:MI:SS.FF6 TZR';

システムレベル設定

-- DB 全体
ALTER SYSTEM SET NLS_DATE_FORMAT = 'YYYY-MM-DD' SCOPE = SPFILE;
-- 再起動必要

パラメータ関連は Oracle パラメータ確認(V$PARAMETER)の記事も参照してください。

常に明示的に指定推奨

-- ❌ NLS 依存(環境で変わる)
SELECT TO_DATE('15/06/2026') FROM DUAL;

-- ✅ 明示的
SELECT TO_DATE('15/06/2026', 'DD/MM/YYYY') FROM DUAL;

パフォーマンス

ストレージ

サイズ100万行
DATE7B7 MB
TIMESTAMP(0)7B7 MB
TIMESTAMP(6)11B11 MB
TIMESTAMP WITH TZ13B13 MB

インデックス

通常の B-Tree インデックスが有効:

CREATE INDEX idx_order_date ON orders(order_date);

範囲検索

-- ✅ インデックス使用
WHERE order_date >= DATE '2026-06-01' 
  AND order_date <  DATE '2026-07-01'

-- ❌ 関数使用でインデックス無効化
WHERE TO_CHAR(order_date, 'YYYY-MM') = '2026-06'
WHERE EXTRACT(MONTH FROM order_date) = 6

-- ✅ 関数ベース索引で解決
CREATE INDEX idx_month ON orders(EXTRACT(MONTH FROM order_date));

TRUNC の問題

-- ❌ インデックス無効化
WHERE TRUNC(order_date) = DATE '2026-06-15'

-- ✅ 範囲で
WHERE order_date >= DATE '2026-06-15' 
  AND order_date <  DATE '2026-06-16'

パフォーマンス関連は Oracle 分析関数(OVER/PARTITION BY)の記事、EXPLAIN PLAN 系記事も参考になります。

パーティション化

-- 日付ベースパーティション
CREATE TABLE orders (
  id NUMBER,
  order_date DATE,
  ...
)
PARTITION BY RANGE (order_date)
INTERVAL (NUMTOYMINTERVAL(1, 'MONTH'))
(
  PARTITION p_start VALUES LESS THAN (DATE '2020-01-01')
);

大量データで必須。tablespace 関連は Oracle Tablespace 管理の記事も参照してください。


他 DB との比較

型マッピング

OraclePostgreSQLMySQLSQL Server
DATEDATEDATEDATE
DATE(時刻含む)TIMESTAMPDATETIMEDATETIME2
TIMESTAMPTIMESTAMPDATETIME(6)DATETIME2
TIMESTAMP WITH TZTIMESTAMPTZ(なし)DATETIMEOFFSET

重要な違い

PostgreSQL/MySQL の DATE:

  • 日付のみ(時刻なし)

Oracle の DATE:

  • 年月日 + 時分秒(一般の DATETIME 相当)

移行時の落とし穴:

PostgreSQL: DATE → 時刻情報なし
Oracle: DATE → 時刻情報あり
→ 混同すると誤動作

PostgreSQL からの移行

PostgreSQL DATE → Oracle DATE(時刻情報は 00:00:00)
PostgreSQL TIMESTAMP → Oracle TIMESTAMP or DATE
PostgreSQL TIMESTAMPTZ → Oracle TIMESTAMP WITH TIME ZONE

Rails / Java / Python 対応

Rails ActiveRecord

Rails のマイグレーション:

class CreateOrders < ActiveRecord::Migration[8.0]
  def change
    create_table :orders do |t|
      t.date :order_date       # DATE
      t.datetime :ordered_at    # TIMESTAMP(6)
      t.timestamps              # created_at, updated_at (TIMESTAMP)
    end
  end
end

oracle_enhanced adapter:

  • date → DATE
  • datetime → TIMESTAMP(6)
  • time → DATE(Oracle には TIME 型なし)

モデル:

class Order < ApplicationRecord
  # DateTime との変換は自動
  def formatted_time
    ordered_at.strftime("%Y-%m-%d %H:%M:%S")
  end
end

Rails 8 系の詳細は Rails 8 アップグレードガイドの記事、rails db:migrate 使い方の記事、find/find_by/where の記事も参照してください。

Java (JDBC)

// DATE
java.sql.Date sqlDate = rs.getDate("order_date");

// TIMESTAMP
java.sql.Timestamp ts = rs.getTimestamp("ordered_at");

// TIMESTAMP WITH TIME ZONE (Oracle 拡張)
oracle.sql.TIMESTAMPTZ tstz = rs.getObject("event_at", oracle.sql.TIMESTAMPTZ.class);

// Java 8+: LocalDateTime
LocalDateTime ldt = rs.getObject("ordered_at", LocalDateTime.class);
OffsetDateTime odt = rs.getObject("event_at", OffsetDateTime.class);

Python (oracledb)

import oracledb
from datetime import datetime, timezone

# DATE
cursor.execute("SELECT SYSDATE FROM DUAL")
d = cursor.fetchone()[0]
# type(d) == datetime

# TIMESTAMP
cursor.execute("SELECT SYSTIMESTAMP FROM DUAL")
ts = cursor.fetchone()[0]
# type(ts) == datetime (with tzinfo)

# INSERT
cursor.execute("""
    INSERT INTO orders VALUES (:1, :2, :3)
""", (1, datetime.now(), datetime.now(timezone.utc)))

実践シナリオ

シナリオ1:業務システムの標準設計

CREATE TABLE orders (
  order_id     NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  order_date   DATE NOT NULL DEFAULT SYSDATE,       -- 業務日
  created_at   TIMESTAMP(6) DEFAULT SYSTIMESTAMP,   -- 高精度
  updated_at   TIMESTAMP(6) DEFAULT SYSTIMESTAMP
);

-- 業務日: DATE(秒精度で十分)
-- 監査タイムスタンプ: TIMESTAMP

シナリオ2:国際サービス

CREATE TABLE international_events (
  event_id  NUMBER PRIMARY KEY,
  event_at  TIMESTAMP WITH TIME ZONE NOT NULL,
  location  VARCHAR2(100)
);

-- ユーザーのTZで検索
SELECT event_id, 
       event_at AT TIME ZONE 'Asia/Tokyo' AS tokyo_time
FROM international_events;

シナリオ3:ログ・監査

CREATE TABLE audit_log (
  log_id   NUMBER GENERATED ALWAYS AS IDENTITY,
  user_id  NUMBER,
  action   VARCHAR2(100),
  logged_at TIMESTAMP(9)  -- ナノ秒精度(順序保証)
);

ナノ秒精度 = 高並行性下でも順序保証。

シナリオ4:金融トランザクション

CREATE TABLE transactions (
  txn_id     NUMBER PRIMARY KEY,
  amount     NUMBER(15, 2),
  executed_at TIMESTAMP(6) WITH TIME ZONE NOT NULL
);

-- 24時間365日、複数タイムゾーンでの取引
INSERT INTO transactions VALUES 
  (1, 1000.00, TIMESTAMP '2026-06-15 14:30:45.123 Asia/Tokyo');

数値型(NUMBER(15, 2))は Oracle NUMBER型 vs INTEGER の違いの記事も参照してください。

シナリオ5:期間管理

CREATE TABLE subscriptions (
  sub_id NUMBER PRIMARY KEY,
  user_id NUMBER,
  start_date DATE,
  duration INTERVAL YEAR TO MONTH
);

-- 契約期限計算
SELECT sub_id, start_date, duration,
       start_date + duration AS end_date
FROM subscriptions;

シナリオ6:Rails での使い分け

class Event < ApplicationRecord
  # order_date: DATE
  # scheduled_at: TIMESTAMP
  
  scope :today, -> { where(order_date: Date.current) }
  scope :recent, -> { where(scheduled_at: 1.hour.ago..) }
end

シナリオ7:Cron 実行の TZ 処理

# バッチジョブ
class DailyBatch < ApplicationJob
  def perform
    # サーバー TZ に依存しない
    yesterday = Time.current.utc.beginning_of_day - 1.day
    Order.where(created_at: yesterday..(yesterday + 1.day)).each do |o|
      process(o)
    end
  end
end

Solid Queue の詳細は Solid Queue 使い方の記事も参照してください。

シナリオ8:Data Pump での TZ 移行

# TZ 情報保持でエクスポート
expdp system/pw dumpfile=data.dmp tables=events

# 別 DB へインポート
impdp system/pw dumpfile=data.dmp tables=events

注意: TIMESTAMP WITH LOCAL TIME ZONE は DB TZ が同じ必要。

Data Pump の詳細は Oracle Data Pump 使い方の記事も参照してください。

シナリオ9:Docker Oracle での TZ 設定

docker run -e TZ=Asia/Tokyo \
  -e ORACLE_TIMEZONE=Asia/Tokyo \
  gvenzl/oracle-xe:21

Docker 関連は docker daemon 接続エラーの記事、Docker no space left on device の記事も参照してください。

シナリオ10:AWS RDS Oracle での TZ

Parameter Group:
- timezone = Asia/Tokyo(設定)
- sessions がその TZ で開始

予防のベストプラクティス

1. DATE の落とし穴意識

-- DATE は時刻も含む
-- 日付だけ比較したい場合、範囲指定を
WHERE d >= DATE '2026-06-15' AND d < DATE '2026-06-16'

2. 常に NLS_DATE_FORMAT を明示

-- ❌ 環境依存
TO_DATE('01/02/2026')

-- ✅ 明示
TO_DATE('01/02/2026', 'DD/MM/YYYY')

3. 国際サービスは TIMESTAMP WITH TZ

event_at TIMESTAMP(6) WITH TIME ZONE

4. UTC 統一が現代の標準

-- DB TZ を UTC に
ALTER DATABASE SET TIME_ZONE = 'UTC';

5. インデックス設計

-- 範囲検索を活用
WHERE d >= X AND d < Y  -- インデックス有効

-- 関数使用は最小限
-- 必要なら関数ベース索引

6. INTERVAL 型の活用

-- 期間管理は INTERVAL
duration INTERVAL YEAR TO MONTH

7. パーティション化

-- 大量履歴データ
PARTITION BY RANGE (created_at)
INTERVAL (NUMTOYMINTERVAL(1, 'MONTH'))

8. TZ 変更の慎重な扱い

-- TIMESTAMP WITH LOCAL TIME ZONE を使用中は
-- DBTIMEZONE 変更禁止(データがあると不可)

9. Rails の Time.current 活用

Time.current  # config.time_zone の時刻
Time.now       # サーバー時刻(避ける)

10. 精度の意識

-- 必要以上の精度は無駄
TIMESTAMP(0)   -- 秒
TIMESTAMP(3)   -- ミリ秒(Web アプリ)
TIMESTAMP(6)   -- マイクロ秒(デフォルト)
TIMESTAMP(9)   -- ナノ秒(監査)

トラブルシューティング

ORA-01843: not a valid month

日付フォーマット不一致:

-- 明示的フォーマット指定
TO_DATE('15-06-2026', 'DD-MM-YYYY')

ORA-01843 の詳細は ORA-01843: not a valid month の記事を参照してください。

時刻がずれる

TZ 設定確認:

SELECT DBTIMEZONE, SESSIONTIMEZONE FROM DUAL;

DATE 検索でヒットしない

時刻が含まれている:

-- 修正
WHERE d >= DATE 'X' AND d < DATE 'X+1'

fractional_seconds が消える

DATE にキャストで消える:

CAST(SYSTIMESTAMP AS DATE)  -- fractional 消失

TIMESTAMP 演算の結果が INTERVAL

SELECT TIMESTAMP '...' - TIMESTAMP '...' FROM DUAL;
-- INTERVAL DAY TO SECOND

-- 日数に変換
SELECT EXTRACT(DAY FROM (t1 - t2)) FROM DUAL;

DST の落とし穴

-- サマータイム開始時、1時間ジャンプ
-- 存在しない時刻を指定するとエラー
TIMESTAMP '2026-03-08 02:30:00 America/New_York'
-- ORA-01878 (fractional seconds error)

UTC 統一で回避


よくある質問(FAQ)

Q1. DATE と TIMESTAMP どちらを使うべき?

  • 秒精度で十分: DATE
  • ミリ秒〜必要: TIMESTAMP
  • 国際サービス: TIMESTAMP WITH TZ

Q2. DATE は本当に日付だけ?

いいえ、年月日 + 時分秒(秒精度)。

Q3. SYSDATE と SYSTIMESTAMP の違い

  • SYSDATE: DATE(秒精度)
  • SYSTIMESTAMP: TIMESTAMP WITH TZ(マイクロ秒精度)

Q4. CURRENT_DATE と SYSDATE の違い

  • CURRENT_DATE: セッションTZ
  • SYSDATE: サーバーOS TZ

Q5. TIMESTAMP WITH TIME ZONE と LOCAL TIME ZONE の違い

  • WITH TIME ZONE: TZ を明示保存
  • WITH LOCAL TIME ZONE: DB TZ に正規化、表示時変換

Q6. INTERVAL 型はいつ使う?

期間管理(契約期間、経過時間等)。

Q7. NLS_DATE_FORMAT の変更

-- セッション
ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD';

-- 環境変数
export NLS_DATE_FORMAT="YYYY-MM-DD"

Q8. Rails での使い分け

  • t.date → DATE
  • t.datetime → TIMESTAMP
  • t.timestamps → TIMESTAMP × 2

Q9. PostgreSQL からの移行

  • PG DATE → Oracle DATE(時刻情報 00:00:00)
  • PG TIMESTAMPTZ → Oracle TIMESTAMP WITH TZ

Q10. 日付範囲検索の最適化

WHERE d >= X AND d < Y  -- インデックス有効

Q11. UTC 統一のメリット

  • TZ 変換不要
  • DST 問題回避
  • 国際対応

Q12. Autonomous DB のデフォルト

UTC 統一。TIMESTAMP WITH TIME ZONE 推奨。


参考リンク

Oracle 公式


まとめ

Oracle DATE vs TIMESTAMP の要点を再整理します。

5種類の日付型

精度サイズTZ
DATE7Bなし
TIMESTAMPナノ秒11Bなし
TIMESTAMP(n)n桁7-11Bなし
TIMESTAMP WITH TZナノ秒13B明示
TIMESTAMP WITH LOCAL TZナノ秒11B暗黙UTC

7つの現在時刻関数

SYSDATE            -- DATE、サーバー
SYSTIMESTAMP       -- TIMESTAMP WITH TZ、サーバー
CURRENT_DATE       -- DATE、セッション
CURRENT_TIMESTAMP  -- TIMESTAMP WITH TZ、セッション
LOCALTIMESTAMP     -- TIMESTAMP、セッション
DBTIMEZONE         -- DB の TZ
SESSIONTIMEZONE    -- セッション TZ

選択ガイド

業務日、日付管理    → DATE
高精度ログ          → TIMESTAMP
国際サービス、金融  → TIMESTAMP WITH TIME ZONE
DB TZ 統一で OK     → TIMESTAMP WITH LOCAL TIME ZONE
期間                → INTERVAL

演算

-- DATE
DATE + NUMBER = DATE      -- 日数加算
DATE - DATE = NUMBER      -- 日数差

-- TIMESTAMP
TIMESTAMP + INTERVAL = TIMESTAMP
TIMESTAMP - TIMESTAMP = INTERVAL DAY TO SECOND

-- 関数
ADD_MONTHS(d, n)
MONTHS_BETWEEN(d1, d2)
LAST_DAY(d)
NEXT_DAY(d, 'MONDAY')
TRUNC(d, 'MM')

INTERVAL 型

INTERVAL YEAR TO MONTH   -- 年月
INTERVAL DAY TO SECOND   -- 日時分秒

NUMTOYMINTERVAL(n, 'YEAR')
NUMTODSINTERVAL(n, 'HOUR')

タイムゾーン

event_at AT TIME ZONE 'Asia/Tokyo'
FROM_TZ(LOCALTIMESTAMP, 'Asia/Tokyo')
ALTER SESSION SET TIME_ZONE = 'Asia/Tokyo';

変換関数

TO_CHAR(d, 'YYYY-MM-DD')
TO_DATE('...', 'YYYY-MM-DD')
TO_TIMESTAMP('...', 'YYYY-MM-DD HH24:MI:SS.FF6')
TO_TIMESTAMP_TZ('...', '... TZH:TZM')
CAST(SYSDATE AS TIMESTAMP)

パフォーマンス

- 範囲検索を使う (>= AND <)
- TRUNC/関数を避ける(or 関数ベース索引)
- パーティション化
- 適切な精度選択

これらの知識は、Oracle でのアプリ設計・国際サービス・金融システム・ログ設計・監査ログ・パフォーマンスチューニング・Rails / Java / Python 開発・他 DB 移行など、あらゆる場面で活用できます。本記事をブックマークしておけば、Oracle 日付型を確実に理解・選択できるようになります。


本記事は2026年6月時点の情報をもとに、Oracle Database 19c〜23ai での動作確認・公式ドキュメントに基づき作成しています。Oracle のバージョンにより挙動が異なる場合があるため、最新の情報は Oracle 公式ドキュメント(docs.oracle.com)もあわせてご確認ください。