Add 1 day to timezone aware timestamp with regards to daylight savings - sql

I am trying to add 1 day to a timezone aware timestamp.
In this example I expected + interval '1' day to add 23 hours because DST starts on 2021-03-28 02:00:00 in Europe/Berlin, but it behaves the same as + interval '24' hour:
select timestamp '2021-03-28 00:00:00 Europe/Berlin' as before_dst,
timestamp '2021-03-28 00:00:00 Europe/Berlin' + interval '1' day as plus_1_day,
timestamp '2021-03-28 00:00:00 Europe/Berlin' + interval '24' hour as plus_24_hour
from dual;
BEFORE_DST
PLUS_1_DAY
PLUS_24_HOUR
2021-03-28 00:00:00.000000000 +01:00
2021-03-29 01:00:00.000000000 +02:00
2021-03-29 01:00:00.000000000 +02:00
Is there a way to add a day to a timestamp so that the beginnings or ends of daylight saving times are respected? For the example above that means a way to have oracle automatically recognize that the day 2021-03-28 only has 23 hours in Europe/Berlin.
I attempted to solve this by converting the timestamp to a local timestamp using at local before adding a day, but that does not work because at local converts the timestamp to the local time zone and not to something like a LocalDateTime in java, resulting in the exact same outcome: + interval '1' day always adding exactly 24 hours.

You could cast the timestamp with time zone value to a plain timestamp, which discards the time zone information; then add the 1-day interval, and declare the result to be in the required time zone:
from_tz(cast(timestamp '2021-03-28 00:00:00 Europe/Berlin' as timestamp) + interval '1' day, 'Europe/Berlin') as plus_1_day
or cast to a date (which could be implicit), add a day, and cast back:
from_tz(cast(cast(timestamp '2021-03-28 00:00:00 Europe/Berlin' as date) + 1 as timestamp), 'Europe/Berlin')
Adapting your example and showing the intermediate values:
select timestamp '2021-03-28 00:00:00 Europe/Berlin' as before_dst,
cast(timestamp '2021-03-28 00:00:00 Europe/Berlin' as timestamp) as as_ts,
cast(timestamp '2021-03-28 00:00:00 Europe/Berlin' as timestamp) + 1 as plus_1_day_ts,
from_tz(cast(timestamp '2021-03-28 00:00:00 Europe/Berlin' as timestamp) + interval '1' day, 'Europe/Berlin') as plus_1_day
from dual;
BEFORE_DST
AS_TS
PLUS_1_DAY_TS
PLUS_1_DAY
2021-03-28 00:00:00 +01:00
2021-03-28 00:00:00
2021-03-29 00:00:00
2021-03-29 00:00:00 +02:00
db<>fiddle
This assumes that you're always dealing with a fixed known time zone region; if you actually have a variable or column value with an unknown time zone then you can extract the region from that and use that as the from_tz() argument.
You should also be aware that this will work for your example at midnight, but won't work for all times. For example if your starting value was timestamp '2021-03-27 02:30:00 Europe/Berlin' then it would fail with "ORA-01878: specified field not found in datetime or interval", because it would end up try to declare 2021-03-28 02:30:00 to be in zone Europe/Berlin - and there is no such time, as that falls into the 'lost' hour of 02:00-03:00. Simply adding a day interval handles that - but then doesn't work as you expect in your example...
And this is because of this line in the documentation:
Oracle performs all timestamp arithmetic in UTC time.
2021-03-28 00:00:00 Europe/Berlin is 2021-03-27 23:00:00 UTC; adding a day to that is 2021-03-28 23:00:00 UTC; which is 2021-03-29 02:00:00 Europe/Berlin

Related

How to convert UTC to EST without the offset and trailing zeros in PL/SQL

Below SQL converts UTC to EST:
-- EST (UTC-5hrs)
SELECT from_tz(to_timestamp('2011-02-01 00:00:00','yyyy-mm-dd hh24:mi:ss'), 'UTC')
AT TIME ZONE 'America/New_York'
FROM dual
However this results in 2011-01-31 19:00:00.000000000 -05:00. How can I get the result as 2011-01-31 19:00:00 without the trailing zeros and the offset?
-- EDT (UTC-4hrs)
SELECT from_tz(to_timestamp('2011-04-01 00:00:00','yyyy-mm-dd hh24:mi:ss'), 'UTC')
AT TIME ZONE 'America/New_York'
FROM dual
If the date falls under EDT then 2011-04-01 00:00:00 results in 2011-03-31 20:00:00 and not 2011-03-31 20:00:00.000000000 -04:00.

sysdate format not working in where clause with time

I am trying to run following query on oracle at PL/SQL developer to select a list of time slots between current time and end of the day:
SELECT T.VISIT_DATE
FROM REGISTRATION.VU_SCHEDULE T
WHERE T.VISIT_DATE BETWEEN TO_DATE(SYSDATE, 'DD-MON-YYYY HH24:MI:SS')
AND TO_DATE('27-MARCH-2020 23:59:59', 'DD-MON-YYYY HH24:MI:SS')
ORDER BY VISIT_DATE
but it gives me result of whole day instead of current time of day
VISIT_DATE
1 3/27/2020 9:00:00 AM
2 3/27/2020 9:15:00 AM
3 3/27/2020 9:30:00 AM
4 3/27/2020 9:45:00 AM
5 3/27/2020 10:00:00 AM
6 3/27/2020 10:15:00 AM
7 3/27/2020 10:30:00 AM
8 3/27/2020 10:45:00 AM
9 3/27/2020 11:00:00 AM
10 3/27/2020 11:15:00 AM
11 3/27/2020 11:30:00 AM
12 3/27/2020 11:45:00 AM
e.g if current time is 11:00 AM then it should give result from current time.
I've tried trunc(sysdate) but it doesn't work
NOTE:
The condition must have date and time from now to the end of the day with format.
must have date and time from now to the end of the day with format.
You could do:
where t.visit_date >= sysdate and t.visit_date < trunc(sysdate) + 1
Rationale:
sysdate gives you the current date/time, that represents the lower bound of the interval
trunc(sysdate) is the beginninig of the current day (today at midnight), to which you can add 1 to get the beginning of the next day; this is the (exclusive) upper bound of the range
Note that there is no point applying to_date() to function sysdate, that produces a date alreay.

How to generate series of 24hrs with 1 hour interval and display the last as 23:59:59

Project: BIRT
Datasource: Amazon Redshift
I want to generate a Data Set with value of:
00:00:00
1:00:00
2:00:00
3:00:00
4:00:00
5:00:00
6:00:00
7:00:00
8:00:00
9:00:00
10:00:00
11:00:00
12:00:00
13:00:00
14:00:00
15:00:00
16:00:00
17:00:00
18:00:00
19:00:00
20:00:00
21:00:00
22:00:00
23:00:00
23:59:59 //the last value should display like this
I was able to generate a series of 24hours with 1 hr interval, but I need to make the last one's value as 23:59:59
Query to generate 24 hours with 1 hour interval:
SELECT start_date + gs * interval '1 hour' as times
FROM (
SELECT '2019-05-21 00:00:00'::timestamp as start_date, generate_series(1,24, 1) as gs)
How is that?
Thanks
Updating your query, just adding a if for the last hour:
SELECT
start_date + gs * interval '1 hour'
- if(gs=24, interval '1 second', interval '0 second') as times
FROM (
SELECT
'2019-05-21 00:00:00'::timestamp as start_date
, generate_series(1,24, 1) as gs
)
I think too much about this, the simplest way to achieve this is just add a default value on the report parameter , if you're going to use the data set in the report parameter
or with this:
SELECT start_date + gs * interval '1 hour' as times
FROM (
SELECT '2020-01-01 00:00:00'::timestamp as start_date, generate_series(1,24, 1) as gs)
union
select '2020-01-01 23:59:59'::timestamp as start_date

Getting timezone based on the region

I have my timezone in Pacific time.I want to convert it to the local time based on the region .Below is the example
time region
2017-05-23 14:00:00 Central
2017-05-23 14:00:00 Eastern
2017-05-23 14:00:00 Mountain
What i am looking for
time region time_local
2017-05-23 14:00:00 Central 2017-05-23 16:00:00
2017-05-23 14:00:00 Eastern 2017-05-23 17:00:00
2017-05-23 14:05:00 Mountain 2017-05-23 15:05:00
You can join to the timezone system view to adjust the timestamps:
select
time,
region,
time - tz1.utc_offset + tz2.utc_offset
from Example ex
JOIN pg_timezone_names tz1
on tz1.name = 'US/Pacific'
JOIN pg_timezone_names tz2
on tz2.name = 'US/' || ex.region
Assuming your "time" column type is a correct "timestamp with time zone" (also called or "timestamptz" for short):
create table times (time timestamp with time zone not null, region text not null);
set timezone='US/Pacific';
insert into times (time, region) values
('2017-05-23 14:00:00','US/Central'),
('2017-05-23 14:00:00','US/Eastern'),
('2017-05-23 14:00:00','US/Mountain');
select *, time at time zone region as time_local from times;
time | region | time_local
------------------------+-------------+---------------------
2017-05-23 14:00:00-07 | US/Central | 2017-05-23 16:00:00
2017-05-23 14:00:00-07 | US/Eastern | 2017-05-23 17:00:00
2017-05-23 14:00:00-07 | US/Mountain | 2017-05-23 15:00:00
If your time columns is an ordinary timestamp instead then you need to think about changing it before you'll get insane. The timestamp type does not mean timestamp at all - it means what some clock somewhere will show, which means different thing depending on where you are and which date it is and in which country you check and your database client settings and current environment and after which changes to the clock will politicians do in the future. Do not go this path.
You may use such a conversion as below :
SET TIMEZONE TO 'US/Central';
SELECT now()::timestamp;
current_time
2018-07-25T14:01:50.608042Z
SET TIMEZONE TO 'US/Eastern';
SELECT now()::timestamp;
current_time
2018-07-25T15:01:50.608042Z
SET TIMEZONE TO 'US/Mountain';
SELECT now()::timestamp;
current_time
2018-07-25T13:01:50.608042Z
SQL Fiddle Demo 1
OR alternatively Use :
SET TIMEZONE TO 'US/Central';
SELECT concat(current_date,' ',localtime) as current_time;
current_time
2018-07-25 14:10:57.962193
SET TIMEZONE TO 'US/Eastern';
SELECT concat(current_date,' ',localtime) as current_time;
current_time
2018-07-25 15:10:57.962193
SET TIMEZONE TO 'US/Mountain';
SELECT concat(current_date,' ',localtime) as current_time;
current_time
2018-07-25 13:10:57.962193
SQL Fiddle Demo 2

Add nchar field to DateTime in Informix

I have a datetime field that stores times in UTC format. There's another nchar field that stores the time zone difference based on a location. I'm trying to combine the two for a report so that the time displayed matches the appropriate time zone.
time_stamp | time_zone
---------------------------------
2015-11-24 21:00:00 | -0500
2015-11-23 15:00:00 | -0600
Expected output:
2015-11-24 16:00:00
2015-11-23 09:00:00
I was able to get this to work by using:
extend(time_stamp, year to minute) + (CAST(LEFT(time_zone,3) as int)) units hour
While this technically works for the current situation, I really don't like using the CAST and LEFT functions on the time_zone field since it breaks if the value is not negative. Seems like there's a much better solution, possible something with TO_CHAR. In an informix database, what is the proper way to combine the dateime and nchar fields so that the output time is correct? Ideally I would like to output in non 24 hr format (4:00 PM, etc...) but at this point I'm mainly focused on getting the correct time.
Ideally, your time zone column would be an INTERVAL HOUR TO MINUTE type; you'd then simply add the two columns to get the desired result. Since it is a character type, substringing in some form will be necessary. Using LEFT is one option; SUBSTRING is another; using the Informix subscripting notation is another. The CAST isn't crucial; Informix is pretty good about coercing things.
Unless you actually want only hours and minutes in the result (which is a legitimate choice), your EXTEND operation is unnecessary and undesirable; it means your result won't include the seconds value from your data.
Note that some time zones include minutes values. Newfoundland is on UTC-04:30; India is on UTC+05:30; Nepal is on UTC+05:45. (See World Time Zone for more information.) Getting the minutes accurate is harder because the sign has to be carried through.
As to formatting in AM/PM notation, apart from the question 'why', the answer is to use the TO_CHAR() function and a ghastligram expressing the time format that you want.
TO_CHAR()
GL_DATETIME
GL_DATE
Demonstration:
create table zone_char(time_stamp datetime year to second, time_zone nchar(5));
insert into zone_char values('2015-11-24 21:00:00', '-0500');
insert into zone_char values('2015-11-23 15:00:00', '-0600');
insert into zone_char values('2015-11-22 17:19:21', '+0515');
insert into zone_char values('2015-11-21 02:56:31', '-0430');
Various ways to select the data:
select extend(time_stamp, year to minute) + LEFT(time_zone,3) units hour,
time_stamp + LEFT(time_zone,3) units hour,
time_stamp + time_zone[1,3] units hour,
time_stamp + time_zone[1,3] units hour + (time_zone[1] || time_zone[4,5]) units minute,
TO_CHAR(time_stamp + time_zone[1,3] units hour + (time_zone[1] || time_zone[4,5]) units minute,
'%A %e %B %Y %I.%M.%S %p')
from zone_char;
Sample output:
2015-11-24 16:00 2015-11-24 16:00:00 2015-11-24 16:00:00 2015-11-24 16:00:00 Tuesday 24 November 2015 04.00.00 PM
2015-11-23 09:00 2015-11-23 09:00:00 2015-11-23 09:00:00 2015-11-23 09:00:00 Monday 23 November 2015 09.00.00 AM
2015-11-22 22:19 2015-11-22 22:19:21 2015-11-22 22:19:21 2015-11-22 22:34:21 Sunday 22 November 2015 10.34.21 PM
2015-11-20 22:56 2015-11-20 22:56:31 2015-11-20 22:56:31 2015-11-20 22:26:31 Friday 20 November 2015 10.26.31 PM
And note how much easier it is when the time zone is represented as an INTERVAL HOUR TO MINUTE:
alter table zone_char add hhmm interval hour to minute;
update zone_char set hhmm = time_zone[1,3] || ':' || time_zone[4,5];
SELECT:
select time_stamp, hhmm, extend(time_stamp + hhmm, year to minute),
time_stamp + hhmm,
TO_CHAR(time_stamp + hhmm, '%A %e %B %Y %I.%M.%S %p')
from zone_char;
Result:
2015-11-24 21:00:00 -5:00 2015-11-24 16:00 2015-11-24 16:00:00 Tuesday 24 November 2015 04.00.00 PM
2015-11-23 15:00:00 -6:00 2015-11-23 09:00 2015-11-23 09:00:00 Monday 23 November 2015 09.00.00 AM
2015-11-22 17:19:21 5:15 2015-11-22 22:34 2015-11-22 22:34:21 Sunday 22 November 2015 10.34.21 PM
2015-11-21 02:56:31 -4:30 2015-11-20 22:26 2015-11-20 22:26:31 Friday 20 November 2015 10.26.31 PM