SQL Guide Chapter

21 – Managing Data in Different Time Zones (Or: Your Timestamps Are Lying to You)

And look, storing dates without time zones is like scheduling a global meeting and only writing “9 AM” in the invite. Whose 9 AM? Where? On which planet? This lesson is about not doing that.

oracle 19c mysql notes study chapter

And look, storing dates without time zones is like scheduling a global meeting and only writing “9 AM” in the invite. Whose 9 AM? Where? On which planet? This lesson is about not doing that.

After this chapter you should be able to:


1. Session Time Zone vs Database Time Zone

Scenario:

Key idea: Oracle maintains:

You can set the session time zone:

ALTER SESSION SET TIME_ZONE = '-07:00';  -- absolute offset
    ALTER SESSION SET TIME_ZONE = DBTIMEZONE;
    ALTER SESSION SET TIME_ZONE = LOCAL;
    ALTER SESSION SET TIME_ZONE = 'Australia/Sydney';  -- named region

Then inspect:

SELECT dbtimezone, sessiontimezone
    FROM   dual;

Database time zone = server’s reality. Session time zone = user’s reality. You need both.


2. SYSDATE vs CURRENT_DATE, CURRENT_TIMESTAMP, LOCALTIMESTAMP

This is where people get confused and blame Oracle when it was really their choice of function all along.

Example:

ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MON-YYYY HH24:MI:SS';
    ALTER SESSION SET TIME_ZONE = '-07:00';
    
    SELECT SYSDATE          AS sysdate,
           CURRENT_DATE     AS current_date,
           CURRENT_TIMESTAMP,
           LOCALTIMESTAMP
    FROM   dual;

Change the session time zone:

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

Run the same SELECT again:

Which is exactly what you want when Rick in Sydney is talking to a database in California.


3. Datetime Data Types: Beyond DATE

3.1 TIMESTAMP

Fields:

Example:

TIMESTAMP '2016-03-06 11:00:00.123456'

3.2 TIMESTAMP WITH TIME ZONE

All of TIMESTAMP plus:

Example:

TIMESTAMP '2016-03-06 11:00:00.123456 -08:00'

3.3 TIMESTAMP WITH LOCAL TIME ZONE

This is great for “always show this time in the user’s local zone” semantics.


4. Example: WEB_ORDERS with Timed Delivery

Imagine an online store:

CREATE TABLE web_orders (
      order_id      NUMBER,
      order_date    TIMESTAMP WITH TIME ZONE,
      delivery_time TIMESTAMP WITH LOCAL TIME ZONE
    );

Insert using current session’s date/time:

INSERT INTO web_orders (order_id, order_date, delivery_time)
    VALUES (
      1,
      CURRENT_TIMESTAMP,
      CURRENT_TIMESTAMP + INTERVAL '2' DAY  -- deliver in two days
    );

Query:

SELECT order_id,
           order_date,
           delivery_time
    FROM   web_orders;

order_date shows the exact instant + time zone. delivery_time is stored in database time zone, but displays in your session time zone.


5. INTERVAL Data Types: Storing Differences, Not Points

Two families:

5.1 INTERVAL YEAR TO MONTH

Example table:

CREATE TABLE warranty (
      product_id    NUMBER,
      warranty_time INTERVAL YEAR(3) TO MONTH
    );

Insert:

INSERT INTO warranty VALUES (123, INTERVAL '8' MONTH);
    INSERT INTO warranty VALUES (155, INTERVAL '200' YEAR(3));
    INSERT INTO warranty VALUES (678, INTERVAL '200-11' YEAR(3) TO MONTH);

Result:

SELECT * FROM warranty;
    -- 123  +00-08
    -- 155  +200-00
    -- 678  +200-11

5.2 INTERVAL DAY TO SECOND

Example table:

CREATE TABLE lab (
      experiment_id NUMBER,
      test_time     INTERVAL DAY(2) TO SECOND
    );

Insert:

INSERT INTO lab VALUES (100012, INTERVAL '90' DAY);
    INSERT INTO lab VALUES (56098, INTERVAL '6 03:30:16' DAY TO SECOND);

Result:

SELECT * FROM lab;
    -- 100012  +90 00:00:00
    -- 56098   +06 03:30:16

These types are ideal when you care about “how long” rather than “what exact timestamp.”


6. Useful Datetime Functions

6.1 EXTRACT

Pull a specific field (year, month, etc.) from a datetime:

SELECT last_name,
           hire_date,
           EXTRACT(YEAR  FROM hire_date) AS hire_year,
           EXTRACT(MONTH FROM hire_date) AS hire_month
    FROM   employees;

Filter by year:

SELECT last_name, hire_date
    FROM   employees
    WHERE  EXTRACT(YEAR FROM hire_date) > 2007;

6.2 SESSIONTIMEZONE, DBTIMEZONE, and TZ_OFFSET

Find out what your session thinks the time zone is:

SELECT sessiontimezone, dbtimezone
    FROM   dual;

Get offset for a named region:

SELECT tz_offset('US/Eastern')    AS us_eastern,
           tz_offset('Canada/Yukon')  AS canada_yukon,
           tz_offset('Europe/London') AS london
    FROM   dual;

6.3 FROM_TZ and TO_TIMESTAMP

Convert a local timestamp to TIMESTAMP WITH TIME ZONE:

SELECT FROM_TZ(
             TO_TIMESTAMP('06-MAR-2016 11:00:00',
                          'DD-MON-YYYY HH24:MI:SS'),
             'Australia/North'
           ) AS aussie_time
    FROM   dual;

Or just parse string → timestamp:

SELECT TO_TIMESTAMP('06-MAR-2016 11:00:00',
                        'DD-MON-YYYY HH24:MI:SS') AS ts
    FROM   dual;

6.4 TO_YMINTERVAL and TO_DSINTERVAL

Apply year/month or day/second offsets:

-- Add 1 year 2 months
    SELECT hire_date,
           hire_date + TO_YMINTERVAL('01-02') AS plus_1y2m
    FROM   employees;
    
    -- Add 100 days 10 hours
    SELECT hire_date,
           hire_date + TO_DSINTERVAL('100 10:00:00') AS plus_100d10h
    FROM   employees;

These functions are much clearer (and less error‑prone) than trying to remember “how many days is 18 months again?”


7. Daylight Saving Time (The Part Where Everything Gets Weird)

Daylight saving time introduces two kinds of special moments:

If you store proper TIMESTAMP WITH TIME ZONE values, Oracle can distinguish between those two different 1:30 AMs. If you just use DATE with no zone, good luck.


What You Should Be Able To Do Now

By the end of this lesson you should be able to:

In short, you can now timestamp things in a way that survives crossing time zones, DST changes, and users who insist on working from anywhere with decent Wi‑Fi. Which, given the state of the world, is pretty much everyone.

← previous view source markdown next →