مدیریت فرمت تاریخ و زمان در Oracle Database

اگر بخواهیم در مورد فرمت تاریخ و زمان در Oracle صحبت کنیم، فقط بحث SYSDATE نیست. معمولاً چند مفهوم با هم قاطی می‌شوند:

یکی SYSDATE، یکی SYSTIMESTAMP، یکی زمان Session، و یکی هم نحوه نمایش این مقادیر. برای همین اگر قرار است یک متن کامل و کاربردی داشته باشیم، بهتر است این‌ها را کنار هم توضیح بدهیم.

اول از همه باید این را روشن کنیم که در Oracle، تاریخ و زمان با یک فرمت متنی خاص ذخیره نمی‌شوند؛ یعنی وقتی شما یک ستون DATE یا TIMESTAMP دارید، Oracle آن را به صورت داخلی نگه می‌دارد. چیزی که شما در خروجی می‌بینید، نتیجه تنظیمات NLS است.

یعنی مثلاً اینکه SYSDATE به شکل 04-JUL-26 دیده شود یا 2026-07-04، مربوط به نحوه نمایش است، نه مقدار واقعی ذخیره‌شده.

بهترین روش این است که قبل از هر تغییر سراسری، اول در Session خودتان فرمت‌ها را تست کنید.

مثلاً اگر فقط می‌خواهید ببینید SYSDATE به صورت 2026-07-04 نمایش داده شود، این دستور را بزنید:

ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD';
SELECT SYSDATE FROM DUAL;

خروجی به این شکل می‌شود:

2026-07-04

اگر بخواهید تاریخ همراه با ساعت نمایش داده شود، می‌توانید این را تست کنید:

ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
SELECT SYSDATE FROM DUAL;

در این حالت خروجی چیزی شبیه این خواهد بود:

2026-07-04 11:25:40

برای SYSTIMESTAMP هم بهتر است فرمت جداگانه تنظیم شود، چون TIMESTAMP دقت بیشتری دارد و کسری ثانیه را هم نشان می‌دهد.

مثلاً:

ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF3';
SELECT SYSTIMESTAMP FROM DUAL;

و خروجی شبیه این می‌شود:

2026-07-04 11:26:12.348

اگر بخواهید timezone هم در خروجی مشخص باشد، بهتر است این فرمت را بگذارید:

ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF3 TZH:TZM';
SELECT SYSTIMESTAMP FROM DUAL;

خروجی مثلاً می‌شود:

2026-07-04 11:26:45.348 +03:30

اینجا می‌رسیم به تفاوت مهم SYSDATE و SYSTIMESTAMP.

SYSDATE فقط تاریخ و زمان سرور دیتابیس را برمی‌گرداند و نوع آن DATE است.

اما SYSTIMESTAMP زمان سرور را با دقت بیشتر و همراه timezone برمی‌گرداند و نوع آن TIMESTAMP WITH TIME ZONE است.

اگر بخواهم ساده بگویم:

  • SYSDATE = زمان فعلی سرور، ساده‌تر، بدون timezone در خروجی منطقی خودش
  • SYSTIMESTAMP = زمان فعلی سرور، دقیق‌تر، با timezone

برای اینکه تفاوت را بهتر ببینید، این دستور خیلی خوب است:

ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF3';
ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF3 TZH:TZM';

SELECT SYSDATE, SYSTIMESTAMP FROM DUAL;

خروجی نمونه:

SYSDATE                    SYSTIMESTAMP
-------------------------  ----------------------------------------
2026-07-04 11:30:10        2026-07-04 11:30:10.527 +03:30

حالا یک تفاوت مهم‌تر هم وجود دارد، و آن بین زمان سرور و زمان Session است.

خیلی‌ها فکر می‌کنند اگر از یک کلاینت دیگر وصل شوند، Oracle ساعت سیستم خود کلاینت را نشان می‌دهد. این برداشت دقیق نیست.

در Oracle چیزی که به رفتار کلاینت نزدیک است، معمولاً CURRENT_DATE و CURRENT_TIMESTAMP است؛ ولی این‌ها هم «ساعت سیستم‌عامل کلاینت» را مستقیم نمی‌خوانند، بلکه بر اساس timezone مربوط به Session کار می‌کنند.

برای مقایسه کامل، این دستور خیلی کمک می‌کند:

ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF3';
ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF3 TZH:TZM';

SELECT
    SYSDATE,
    SYSTIMESTAMP,
    CURRENT_DATE,
    CURRENT_TIMESTAMP,
    LOCALTIMESTAMP
FROM DUAL;

هر کدام از این‌ها معنی خودش را دارد:

  • SYSDATE → تاریخ و زمان سرور
  • SYSTIMESTAMP → تاریخ و زمان دقیق سرور با timezone
  • CURRENT_DATE → تاریخ و زمان Session
  • CURRENT_TIMESTAMP → timestamp مربوط به Session با timezone
  • LOCALTIMESTAMP → timestamp مربوط به Session بدون timezone

اگر timezone سشن را عوض کنید، تفاوت این‌ها خیلی بهتر دیده می‌شود.

مثلاً:

ALTER SESSION SET TIME_ZONE = '+03:30';

SELECT
    SYSTIMESTAMP,
    CURRENT_TIMESTAMP,
    LOCALTIMESTAMP
FROM DUAL;

بعد همین را با timezone دیگری تست کنید:

ALTER SESSION SET TIME_ZONE = '+00:00';

SELECT
    SYSTIMESTAMP,
    CURRENT_TIMESTAMP,
    LOCALTIMESTAMP
FROM DUAL;

نکته مهم اینجاست که با تغییر TIME_ZONE سشن:

  • SYSTIMESTAMP همچنان زمان سرور را نشان می‌دهد
  • CURRENT_TIMESTAMP بر اساس timezone سشن تغییر می‌کند
  • LOCALTIMESTAMP هم بر اساس سشن تغییر می‌کند، ولی timezone را در خروجی نشان نمی‌دهد

یعنی اگر بخواهید ببینید یک کاربر با timezone متفاوت چه زمانی را خواهد دید، باید بیشتر روی CURRENT_TIMESTAMP و CURRENT_DATE تمرکز کنید، نه SYSDATE.

اگر بخواهیم این موضوع را خیلی خلاصه کنیم، می‌شود گفت:

  • SYSDATE و SYSTIMESTAMP = زمان سرور
  • CURRENT_DATE و CURRENT_TIMESTAMP = زمان Session

برای بررسی timezone سشن هم می‌توانید این را بزنید:

SELECT SESSIONTIMEZONE FROM DUAL;

و اگر خواستید آن را تغییر دهید:

ALTER SESSION SET TIME_ZONE = 'Asia/Tehran';

یا مثلاً:

ALTER SESSION SET TIME_ZONE = 'Europe/London';

یا به صورت offset:

ALTER SESSION SET TIME_ZONE = '+03:30';

در مورد فرمت‌های مناسب، معمولاً بهترین انتخاب برای DATE همان فرمت استاندارد و خوانای زیر است:

YYYY-MM-DD

مثال:

2026-07-04

اگر ساعت هم لازم دارید:

YYYY-MM-DD HH24:MI:SS

یک سری فرمت دیگر هم بسته به نیاز قابل استفاده‌اند. مثلاً اگر بخواهید خروجی برای کاربر نهایی کمی خواناتر باشد:

DD Mon YYYY

مثال:

04 Jul 2026

یا اگر فرمت slashدار بخواهید:

YYYY/MM/DD

مثال:

2026/07/04

یا اگر بخواهید روز هم مشخص باشد:

YYYY-MM-DD DAY

مثال:

2026-07-04 SATURDAY

در مورد نمایش شمسی هم بهتر است global format دیتابیس را شمسی نکنید. راه امن و استاندارد‌تر این است که هر جا لازم شد، در query تبدیل انجام دهید.

مثلاً:

1405/04/13

اگر بخواهید ساعت و تایم زون هم همراهش باشد:

SELECT TO_CHAR(SYSTIMESTAMP, 'YYYY/MM/DD HH24:MI:SS TZH:TZM', 'NLS_CALENDAR=Persian') FROM DUAL;

این روش خیلی بهتر از این است که کل تنظیمات عمومی دیتابیس را برای همه کاربران به شمسی تغییر بدهید، چون در اکثر محیط‌های عملیاتی، مخصوصاً در سیستم‌های بانکی و migrationها، استاندارد میلادی برای storage و logging و troubleshooting امن‌تر و قابل‌پیش‌بینی‌تر است.

اگر بعد از تست، خواستید فرمت نمایش را برای کل سیستم پیش‌فرض کنید، می‌توانید در سطح system تنظیمش کنید:

ALTER SYSTEM SET NLS_DATE_FORMAT = 'YYYY-MM-DD' SCOPE=SPFILE;
ALTER SYSTEM SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF3' SCOPE=SPFILE;
ALTER SYSTEM SET NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF3 TZH:TZM' SCOPE=SPFILE;

و بعد ری‌استارت:

SHUTDOWN IMMEDIATE;
STARTUP;

البته این را هم باید در نظر داشت که بعضی کلاینت‌ها مثل SQL Developer، DBeaver، Toad یا PL/SQL Developer ممکن است تنظیمات NLS خودشان را اعمال کنند. یعنی حتی اگر در دیتابیس فرمت را عوض کرده باشید، باز در آن ابزار خروجی متفاوت ببینید.

در چنین شرایطی باید تنظیمات همان ابزار را هم چک کنید.

برای دیدن وضعیت فعلی پارامترها هم این دستورها مفیدند:

SHOW PARAMETER NLS_DATE_FORMAT;
SHOW PARAMETER NLS_TIMESTAMP_FORMAT;
SHOW PARAMETER NLS_TIMESTAMP_TZ_FORMAT;

و اگر خواستید سطح session و database را دقیق‌تر ببینید:

SELECT * FROM NLS_SESSION_PARAMETERS;
SELECT * FROM NLS_DATABASE_PARAMETERS;

اگر بخواهم کل حرف را به صورت عملی جمع‌بندی کنم:

اگر فقط هدفت این است که وقتی کسی SYSDATE می‌زند خروجی به شکل 2026-07-04 دیده شود، کافی است این را داشته باشی:

ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD';

و اگر بخواهی به صورت سراسری پیش‌فرض کل سیستم هم همین باشد:

ALTER SYSTEM SET NLS_DATE_FORMAT = 'YYYY-MM-DD' SCOPE=SPFILE;

در جمع بندی

  • SYSDATE زمان سرور را می‌دهد
  • SYSTIMESTAMP زمان سرور را با دقت و timezone می‌دهد
  • CURRENT_DATE و CURRENT_TIMESTAMP وابسته به Session هستند
  • TIME_ZONE سشن روی توابع CURRENT_* اثر می‌گذارد
  • برای شمسی بهتر است از TO_CHAR(..., 'NLS_CALENDAR=Persian') استفاده شود
  • فرمت‌های NLS فقط ظاهر خروجی را عوض می‌کنند، نه مقدار ذخیره شده را

 

یک نکتهٔ عملی که احتمالاً برای خیلی از تیم‌ها پیش می‌آید این است که مدت‌ها پیش در ایران تصمیم گرفته شد تغییر ساعت فصلی (Daylight Saving Time) برداشته شود. از آن زمان به بعد سیستم‌هایی که روی DST حساب باز کرده بودند گاهی رفتارهای غیرمنتظره نشان می‌دهند.

ما دقیقاً یک مورد مشابه داشتیم:

در یک سیستم قدیمی، بعضی Job‌ها به صورت ناگهانی یک ساعت زودتر اجرا می‌شدند.

اول فکر کردیم مشکل از Scheduler یا از سرور است، ولی بررسی دقیق‌تر نشان داد که مشکل از یک تنظیم دستی بود که سال‌ها قبل داخل همان Job‌ها گذاشته شده بود.

داخل تنظیمات اجرای Job، به جای اینکه Time Zone صحیح یعنی:

Asia/Tehran

استفاده شده باشد، به صورت دستی یک offset قدیمی تعریف شده بود:

+04:30

این offset مخصوص همان دوره‌ی زمانی بود که ساعت‌ها در ایران در نیم‌سال اول یک ساعت جلو کشیده می‌شد (DST).

بعد از حذف DST، این مقدار دیگر معتبر نبود و Oracle هم بر اساس همان offset رفتار می‌کرد؛ در نتیجه Job یک ساعت زودتر اجرا می‌شد.

بعد از اینکه Time Zone همهٔ Jobها را به شکل استاندارد:

Asia/Tehran

برگرداندیم، زمان اجرا کاملاً درست شد و مشکلات زمان‌بندی از بین رفت.