Is Postgres or Ruby adding the timezone to my `timestamp without time zone` column? - ruby

If I query my database with SELECT current_setting('TIMEZONE') I get 'UTC' (as expected).
Using PgAdmin, I run the following query:
SELECT foo FROM bar
PgAdmin shows "2011-03-12 08:00:00". However, when I read the value from Ruby (using DataMapper which uses the 'org.postgresql.Driver' JDBC driver as far as I know), it shows "2011-03-12 08:00:00 -0700".
Question: Where in the whole stack is the timezone getting added? Although I realize a lot depends on the specifics of my stack, it would really help to understand what should happen so that I can rule things out. For example, for a timestamp without time zone column, should I expect that JDBC driver gives a 'raw' value with no timezone information?

Something in Ruby is making the timezone adjustment:
psql=> select current_setting('timezone');
current_setting
-----------------
Canada/Pacific
(1 row)
psql=> select min(created_at) from people;
min
----------------------------
2010-07-09 13:58:51.320659
(1 row)
psql=> set timezone = 'utc';
psql=> select current_setting('timezone');
current_setting
-----------------
UTC
(1 row)
psql=> select min(created_at) from people;
min
----------------------------
2010-07-09 13:58:51.320659
(1 row)
You can check this by doing a raw SQL query of a timestamp from within Ruby and seeing what string you get back.

The JDBC driver when reading a timestamp without timezone makes bold/reasonable assumption that this timestamp is expressed in the JVM timezone.

If you do not want timezone to be added, use type 'timestamp without timezone'.
That way, reader will always read same second/hour/minute/day/month/year as you inserted.
I used following procedure to reproduce that
create table t (
without_tz timestamp without time zone ,
with_tz timestamp with time zone
)
SET SESSION TIME ZONE default;
insert into t VALUES ( now(), now() )
select * from t;
SET SESSION TIME ZONE PST8PDT;
insert into t VALUES ( now(), now() )
select * from t;
SET SESSION TIME ZONE PST6PDT;
insert into t VALUES ( now(), now() )
select * from t;
Observing values from select, I come to conclusion that
timestamp without timezone is never converted. You read same second/hour/minute/day/month/year what you inserted, no matter what timezone you are in.
timestamp with timezone converts values you read to your timezone. they represent same instant (point in time) but hour (and sometimes days, sometimes even minutes) values will be diffrent.

Related

Why is DATE data type treated like TIMESTAMP(0) data type in Oracle mode in H2?

According to the H2 documentation, in the Oracle compatibility mode:
DATE data type is treated like TIMESTAMP(0) data type.
Meantime, DATE and TIMESTAMP(0) datatypes are not the same in Oracle. Compare:
SELECT CAST(SYSDATE AS TIMESTAMP(0)), CAST(SYSDATE AS DATE) from dual
gives
25-MAR-22 13.07.42.000000000 25-MAR-22
respectively.
In particular, this weird treating of DATE as TIMESTAMP(0) influences on how H2 calculates the difference between two dates.
Again, in Oracle:
SELECT CAST(TO_DATE('2022-01-05', 'YYYY-MM-DD') AS TIMESTAMP(0)) - CAST(TO_DATE('2022-01-01', 'YYYY-MM-DD') AS TIMESTAMP(0)) from dual
gives
+04 00:00:00.000000
and
SELECT CAST(TO_DATE('2022-01-05', 'YYYY-MM-DD') AS DATE) - CAST(TO_DATE('2022-01-01', 'YYYY-MM-DD') AS DATE) from dual
produces just:
4
Apparently, for H2 both above queries produce the result in nanoseconds and not days as expected.
So, it is an H2 bug or I am missing something?
Meantime, DATE and TIMESTAMP(0) datatypes are not the same in Oracle
Oracle differs from many other RDBMS in that its DATE data type ALWAYS contains both a date and a time component. Its implementation predates the ANSI standard.
In Oracle, if you have the table:
CREATE TABLE table_name (ts TIMESTAMP(0), dt DATE);
and insert the data:
INSERT INTO table_name (ts, dt) VALUES (SYSDATE, SYSDATE);
Then you can look at the binary data being stored using the DUMP function:
SELECT DUMP(ts) AS dump_ts,
DUMP(dt) AS dump_dt
FROM table_name;
Which outputs:
DUMP_TS
DUMP_DT
Typ=180 Len=7: 120,122,3,25,15,13,37
Typ=12 Len=7: 120,122,3,25,15,13,37
Then you can see that they are both stored as 7-byte binary values:
120 = Century + 100
122 = Year-of-century + 100
3 = Month
25 = Day
15 = Hour + 1
13 = Minutes + 1
37 = Seconds + 1
And the binary values are identical (the only difference is in the meta-data Typ where 180 = TIMESTAMP and 12 = DATE).
Effectively, they are stored identically.
db<>fiddle here
However
The side-effects of a TIMESTAMP vs. a DATE data type in Oracle may lead to different effects.
When you subtract a TIMESTAMP and either a TIMESTAMP or a DATE then the return value is an INTERVAL DAY TO SECOND data type.
When you subtract a DATE and a DATE then the default return value is a NUMBER representing the number of days difference.
When you display a TIMESTAMP then the client application you are using may default to using the NLS_TIMESTAMP_FORMAT session parameter to format the timestamp as a string and the default for this parameter will typically show date, time and fractional seconds.
When you display a DATE then the client application you are using may default to using the NLS_DATE_FORMAT session parameter to format the date as a string and the default for this parameter will show date but not time (and there will never be any fractional seconds to show). Just because the client application may chose not to show the time component does not mean that the time component does not exist.
If you set the session parameters using:
ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
Then, provided your client application is using those parameters to format them, they will display identically.
The problem you are seeing with the difference in Oracle is due to these side effects.
If the question is
So, it is an H2 bug or I am missing something?
than the the answer would be:
No, it is not a bug, and what you've missed is the fact, that compatibility modes in H2 are just that - attempt to reach with minimal efforts maximum compatibility with different databases.
H2 is not an emulator for any non-standard features (quirks) of those databases.
It that particular case, to achieve identical behaviour would require to introduce new non-standard data type, which goes beyond "minimal effort" level.
The different in the output of the values in the first query is down to the session's NLS settings. These control the display format for dates and timestamps:
sho parameter nls_date_format
NAME TYPE VALUE
--------------- ------ -----------
nls_date_format string DD-MON-YYYY
sho parameter nls_timestamp_format
NAME TYPE VALUE
-------------------- ------ -------------------------
nls_timestamp_format string DD-MON-YYYY HH24.MI.SSXFF
SELECT CAST(SYSDATE AS TIMESTAMP(0)), CAST(SYSDATE AS DATE) from dual;
CAST(SYSDATEASTIMESTAMP(0)) CAST(SYSDAT
------------------------------ -----------
25-MAR-2022 12.18.24.000000000 25-MAR-2022
If you change these to be the same format, both expressions return the same result:
alter session set nls_date_format = 'DD-MON-YYYY HH24:MI:SS';
alter session set nls_timestamp_format = 'DD-MON-YYYY HH24:MI:SS';
SELECT CAST(SYSDATE AS TIMESTAMP(0)), CAST(SYSDATE AS DATE) from dual;
CAST(SYSDATEASTIMEST CAST(SYSDATEASDATE)
-------------------- --------------------
25-MAR-2022 12:17:43 25-MAR-2022 12:17:43
So they both contain the full date + time with no fractional seconds.
Note that while date and timestamp(0) have the same precision, as your further examples show they work differently:
Subtracting one date from another returns the number of days between the values as a number
Subtracting a timestamp from a date or timestamp returns an interval
So the result of:
SELECT CAST(TO_DATE('2022-01-05', 'YYYY-MM-DD') AS DATE) - CAST(TO_DATE('2022-01-01', 'YYYY-MM-DD') AS DATE) from dual
Is 4 days.

Which time zone does the table's column of DATE data type reflect?

In Oracle database I have this table (the data type of column col is DATE):
col
2021-02-26 23:14:24
Question: in my case, assuming over time DB settings haven't changed, which time zone does the table's column of DATE data type reflect?
UTC or
Europe/Helsinki?
Following I provide current DB settings.
Database time zone
SELECT DBTIMEZONE FROM DUAL;
|DBTIMEZONE |
|============|
|+00:00 |
Session time zone
SELECT SESSIONTIMEZONE FROM DUAL;
|SESSIONTIMEZONE|
|===============|
|Europe/Helsinki|
SELECT CURRENT_DATE FROM DUAL;
|CURRENT_DATE |
|===================|
|2021-07-18 15:05:32|
The time zone of database server's operating system
SELECT SYSDATE FROM DUAL;
|SYSDATE |
|===================|
|2021-07-18 15:05:32|
SELECT SYSTIMESTAMP FROM DUAL;
|SYSTIMESTAMP |
|==============================|
|2021-07-18 15:05:32.984 +03:00|
The time zone is undefined by the column.
You can do:
CREATE TABLE table_name (col DATE);
ALTER SESSION SET TIME_ZONE = 'UTC';
INSERT INTO table_name (col) VALUES (CURRENT_DATE);
ALTER SESSION SET TIME_ZONE = 'Europe/Helsinki';
INSERT INTO table_name (col) VALUES (CURRENT_DATE);
ALTER SESSION SET TIME_ZONE = 'America/New_York';
INSERT INTO table_name (col) VALUES (CURRENT_DATE);
Then:
ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
SELECT * FROM table_name;
Outputs:
COL
2021-07-18 21:06:05
2021-07-19 00:06:05
2021-07-18 17:06:05
db<>fiddle here
Those values were all inserted in the same second by the same user in the same session using identical SQL statements; however there is no consistency in the time zone of the DATE value as the session settings were altered between each statement; so you cannot rely on a DATE having any particular time zone.
If you want to work out what time zone your data is in then check your application that is storing the data:
Is it always using SYSDATE? Then the time zone of the column is the time zone of the database's system.
Does the application specify the time zone? Then the data will have the time zone specified by the application.
Is it taking data from an external source? Then check that external source.
Is it taking data from the user? Then you have no guarantees.
If the data is of type DATE, then it doesn't reflect any time zone at all. It's just a date and time, with time resolved to the second. SYSDATE simply gets date and time from the host server OS, so to the degree that the time portion is reflective of any time zone, it would be that of the host OS.

oracle CURRENT_TIMESTAMP AT TIME ZONE 'GMT'-TO_DSINTERVAL

In my oracle database, I am setting one date field like this
update abc set
startdate = CURRENT_TIMESTAMP AT TIME ZONE 'GMT'-TO_DSINTERVAL('0 00:59:00');
but when I do
select * from abc w where
w.startdate < CURRENT_TIMESTAMP AT TIME ZONE 'GMT'-TO_DSINTERVAL('0 00:59:00');
after a few minutes then it does not return this row.
To my surprise
select * from abc w where
w.startdate > CURRENT_TIMESTAMP AT TIME ZONE 'GMT'-TO_DSINTERVAL('0 00:59:00');
returns this row.
How is it possible?
Please help me understand.
From your comment
the data type of startdate is 'Date'. There is certainly some implicit conversion happening. The session is in PST. Is there any way we can stop or handle the implicit conversion?
CAST the right-hand side of the expression to a DATE rather than allowing an implicit cast to occur (which would cast the DATE value on the left-hand side to a TIMESTAMP WITH TIME ZONE data type with your current session time zone).
select *
from abc
where startdate < CAST(CURRENT_TIMESTAMP AT TIME ZONE 'GMT'-TO_DSINTERVAL('0 00:59:00') AS DATE);
db<>fiddle here

Fetch Hours from Created date

I'm just trying to fetch Hour of my table from created date in Oracle 12c Database but it is showing error INVALID EXTRACT FIELD FOR EXTRACT FIELD. kindly guide me to fetch hour of my date my code is here...
SELECT
EXTRACT( HOUR FROM (TO_CHAR(CREATED_DATE,'RRRR-MM-DD HH:MI:SS')) ) HOUR
FROM
INVOICE_V;
my Date is stored as 6/1/2020 4:04:50 PM in this format and Extract function is not accept this function.
Do not store dates as strings.
But, since you have, convert it from a string to a date using TO_DATE:
SELECT EXTRACT( HOUR FROM TO_TIMESTAMP(CREATED_DATE,'DD/MM/YYYY HH12:MI:SS AM') ) AS HOUR
FROM INVOICE_V;
If, however, you meant that its just displaying in that format (and is actually a DATE data type) then CAST the date to a timestamp:
SELECT EXTRACT( HOUR FROM CAST( CREATED_DATE AS TIMESTAMP) ) AS HOUR
FROM INVOICE_V;
An hour can not be used in the EXTRACT function.
The only way to extract hour is to use TO_CHAR or subtract it from TRUNC date as follows:
TO_CHAR(created_date,'HH24') -- OR 'HH' as per your requirement
-- OR
FLOOR(24*(created_date- TRUNC(created_date)))
Please note that Oracle does not store dates in any format. It has its own binary representation. What you see while selecting from the table is based on the NLS_DATE_FORMAT parameter.
You can set it according to your requirement.
ALTER SESSION SET NLS_dATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'; -- like this
If you have a date column (or the-like), then:
select extract(hour from cast(created_date as timestamp)) as hr
from invoice_v
Alternatively:
select to_char(created_date, 'hh24') as hr
from invoice_v
The first expression returns an integer number, while the second produces a string.
Note that hour is a language keyword, hence not a good choice for an identifier (here, you used it as a column alias). I changed that.

Oracle date comparison in where clause

For eg I have a student table with a DOJ(date of joining) column with its type set as DATE now in that I have stored records in dd-mon-yy format.
I have an IN param at runtime with date passed as string and its in dd/mm/yyyy format. How do I compare and fetch results on date?
I want to fetch count of records of students who have DOJ of 25-AUG-92 per my database table student, but I am getting date as varchar in dd/mm/yyyy format in an IN param, kindly please guide.
I have tried multiple options such as trunc, to_date, to_char but, unfortunately nothing seems to work.
I have a student table with a DOJ(date of joining) column with its type set as DATE now in that I have stored records in dd-mon-yy format.
Not quite, the DATE data-type does not have a format; it is stored internally in tables as 7-bytes (year is 2 bytes and month, day, hour, minute and second are 1-byte each). The user interface you are using (i.e. SQL/PLUS, SQL Developer, Toad, etc.) will handle the formatting of a DATE from its binary format to a human readable format. In SQL/Plus (or SQL Developer) this format is based on the NLS_DATE_FORMAT session parameter.
If the DATE is input using only the day, month and year then the time component is (probably) going to be set to 00:00:00 (midnight).
I have an IN param at runtime with date passed as string or say varchar and its in dd/mm/yyyy format. How do I compare and fetch results on date.?
Assuming the time component for you DOJ column is always midnight then:
SELECT COUNT(*)
FROM students
WHERE doj = TO_DATE( your_param, 'dd/mm/yyyy' )
If it isn't always midnight then:
SELECT COUNT(*)
FROM students
WHERE TRUNC( doj ) = TO_DATE( your_param, 'dd/mm/yyyy' )
or:
SELECT COUNT(*)
FROM students
WHERE doj >= TO_DATE( your_param, 'dd/mm/yyyy' )
AND doj < TO_DATE( your_param, 'dd/mm/yyyy' ) + INTERVAL '1' DAY
The below should do what you've described. If not, provide more information on how "nothing seems to work".
-- Get the count of students with DOJ = 25-AUG-1992
SELECT COUNT(1)
FROM STUDENT
WHERE TRUNC(DOJ) = TO_DATE('25/AUG/1992','dd/mon/yyyy');
The above was pulled from this answer. You may want to look at the answer, because if performance is critical to you, there is a different way to write this query which doesn't use trunc, which will allow Oracle to use index on DOJ, if one is present.
Though I am bit late in posting this but I have been able to resolve this.
What I did was I converted both the dates to_char in similar formats and it worked here is my query condition that worked..
TO_CHAR(TO_DATE(C.DOB, 'DD-MON-YY'),'DD-MON-YY')=TO_CHAR(TO_DATE(P_Dob,'DD/MM/YYYY'),'DD-MON-YY'))
Thanks for the support all. :)

Resources