I need to convert the sysdate and time to a particular timezone like EST. I can't assume my current time zone.
How to convert this in plsql? Please help me.
Assuming you have a TIMESTAMP WITH TIME ZONE (such as systimestamp), you can use the AT TIME ZONE syntax. For example, I can take the current systimestamp and convert it to UTC (GMT), Eastern, and Pacific time zones by specifying different time zone names.
SQL> ed
Wrote file afiedt.buf
1 select systimestamp at time zone 'UTC' current_time_in_utc,
2 systimestamp at time zone 'Us/Eastern' current_time_in_est,
3 systimestamp at time zone 'US/Pacific' current_time_in_pst
4* from dual
SQL> /
CURRENT_TIME_IN_UTC
---------------------------------------------------------------------------
CURRENT_TIME_IN_EST
---------------------------------------------------------------------------
CURRENT_TIME_IN_PST
---------------------------------------------------------------------------
26-APR-12 05.36.11.802000 PM UTC
26-APR-12 01.36.11.802000 PM US/EASTERN
26-APR-12 10.36.11.802000 AM US/PACIFIC
Oracle already has a timezone table:
SELECT tzname, tzabbrev from V$TIMEZONE_NAMES
SELECT TZ_OFFSET('US/Eastern') FROM DUAL;
Get the current time in EPT for use in a SQL statement:
select to_timestamp(to_char(TRUNC(LOCALTIMESTAMP - 5/1440, 'MI'),'mm/dd/yyyy hh24:mi'),'mm/dd/yyyy hh24:mi') FROM DUAL;
Oracle's 11g SQL reference is pretty good and available online here: http://docs.oracle.com/cd/B28359_01/server.111/b28286/toc.htm
You can quickly look up the following functions, which I'm sure you'll find helpful:
current_timestamp
dbtimezone
localtimestamp
trun(date)
sessiontimezone
sys_extract_utc
systimestamp
to_timestamp
to_timestamp_tz
tz_offset
ALTER SESSION SET TIME_ZONE = '+00:00'; -- you will now see times in UTC instead of EPT
Current date & time in varying timezones
Return current date & time (sysdate) in local prevailing time
Select sysdate from dual;
select LOCALTIMESTAMP FROM DUAL;
Return current date as UTC
select LOCALTIMESTAMP at time zone '+00:00' FROM DUAL
-- Instead of using sysdate and to_date, try using localtimestamp, to_timestamp and to_timestamp_tz. They are like sysdate and to_date but add on timezone functionality.
select LOCALTIMESTAMP at time zone '+00:00' FROM DUAL; –- returns essentially the same as sysdate but in UTC
or
ALTER SESSION SET TIME_ZONE = 'UTC';
select LOCALTIMESTAMP FROM DUAL –- after setting session time_zone to ‘UTC’ this will now return a UTC timestamp
Time zone conversion
Get the current time in UTC for use in a SQL statement.
-- Get current time in UTC format and subtract 5 minutes.
LOCALTIMESTAMP at time zone '+00:00' - 5/1440
-- Trunc the time to eliminate seconds
TRUNC(LOCALTIMESTAMP at time zone '+00:00' - 5/1440, 'MI')
-- Convert to characters then back to datetime.
to_timestamp(to_char(TRUNC(LOCALTIMESTAMP at time zone '+00:00' - 5/1440, 'MI'),'mm/dd/yyyy hh24:mi'),'mm/dd/yyyy hh24:mi')
-- Select from dual to show it works.
select to_timestamp(to_char(TRUNC(LOCALTIMESTAMP at time zone '+00:00' - 5/1440, 'MI'),'mm/dd/yyyy hh24:mi'),'mm/dd/yyyy hh24:mi') FROM DUAL;
Get the current time in EPT for use in a SQL statement.
-- Get current time in UTC format and subtract 5 minutes.
ALTER SESSION SET TIME_ZONE = 'US/Eastern'; -- set to EPT time
select LOCALTIMESTAMP from dual - 5/1440
-- Trunc the time to eliminate seconds
TRUNC(LOCALTIMESTAMP - 5/1440, 'MI')
-- Convert to characters then back to datetime.
to_timestamp(to_char(TRUNC(LOCALTIMESTAMP - 5/1440, 'MI'),'mm/dd/yyyy hh24:mi'),'mm/dd/yyyy hh24:mi')
-- Select from dual to show it works.
select to_timestamp(to_char(TRUNC(LOCALTIMESTAMP - 5/1440, 'MI'),'mm/dd/yyyy hh24:mi'),'mm/dd/yyyy hh24:mi') FROM DUAL;
Return DB and Session time zone functions
ALTER SESSION SET TIME_ZONE = '+00:00'; -- you will now see times in UTC instead of EPT
ALTER SESSION SET NLS_DATE_FORMAT = 'MM/DD/YYYY HH24:MI:SS';
ALTER SESSION SET TIME_ZONE = '+00:00'; -- you will now see times in UTC instead of EPT
ALTER SESSION SET TIME_ZONE = 'UTC'; -- set to UTC time same as command above
SELECT DBTIMEZONE , SESSIONTIMEZONE, CURRENT_TIMESTAMP, LOCALTIMESTAMP, systimestamp, sysdate FROM DUAL; -- see the results
ALTER SESSION SET TIME_ZONE = 'US/Eastern'; -- set to EPT time
SELECT DBTIMEZONE , SESSIONTIMEZONE, CURRENT_TIMESTAMP, LOCALTIMESTAMP, systimestamp, sysdate FROM DUAL; -- see the results
SELECT TO_TIMESTAMP_TZ('05/16/2014 11:26:48 -04:00',
'MM/DD/YYYY HH:MI:SS TZH:TZM') FROM DUAL;
SELECT tzname, tzabbrev from V$TIMEZONE_NAMES where tzabbrev = 'EPT';
SELECT TZ_OFFSET('US/Eastern') FROM DUAL;
-- The following example casts a null column in a UNION operation as TIMESTAMP WITH LOCAL TIME ZONE using the sample tables oe.order_items and oe.orders:
SELECT order_id, line_item_id,
CAST(NULL AS TIMESTAMP WITH LOCAL TIME ZONE) order_date
FROM order_items
UNION
SELECT order_id, to_number(null), order_date
FROM orders;
Date Time and Timezone (TO_TIMESTAMP_TZ)
TO_TIMESTAMP_TZ converts char of CHAR, VARCHAR2, NCHAR, or NVARCHAR2 datatype to a value of TIMESTAMP WITH TIME ZONEdatatype.
Examples:
The following example converts a character string to a value of TIMESTAMP WITH TIME ZONE:
SELECT TO_TIMESTAMP_TZ('1999-12-01 11:00:00 -8:00',
'YYYY-MM-DD HH:MI:SS TZH:TZM') FROM DUAL;
TO_TIMESTAMP_TZ('1999-12-0111:00:00-08:00','YYYY-MM-DDHH:MI:SSTZH:TZM')
The following example casts a null column in a UNION operation as TIMESTAMP WITH LOCAL TIME ZONE using the sample tablesoe.order_items and oe.orders:
SELECT order_id, line_item_id,
CAST(NULL AS TIMESTAMP WITH LOCAL TIME ZONE) order_date
FROM order_items
UNION
SELECT order_id, to_number(null), order_date
FROM orders;
Set Date & time formats
ALTER SESSION SET NLS_DATE_FORMAT = 'MM/DD/YYYY HH24:MI:SS';
SELECT CURRENT_TIMESTAMP, LOCALTIMESTAMP FROM DUAL;
Set current/local Timezone
ALTER SESSION SET TIME_ZONE = '-5:00';
ALTER SESSION SET NLS_DATE_FORMAT = 'MM/DD/YYYY HH24:MI:SS';
SELECT CURRENT_TIMESTAMP, LOCALTIMESTAMP FROM DUAL;
Local Time (LOCALTIMESTAMP)
LOCALTIMESTAMP returns the current date and time in the session time zone in a value of datatype TIMESTAMP. The difference between this function and CURRENT_TIMESTAMP is that LOCALTIMESTAMP returns a TIMESTAMP value while CURRENT_TIMESTAMPreturns a TIMESTAMP WITH TIME ZONE value.
ALTER SESSION SET TIME_ZONE = '-5:00';
ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MON-YYYY HH24:MI:SS';
SELECT CURRENT_TIMESTAMP, LOCALTIMESTAMP FROM DUAL;
The following statement uses the correct format mask to match the return type of LOCALTIMESTAMP:
INSERT INTO local_test VALUES
(TO_TIMESTAMP(LOCALTIMESTAMP, 'DD-MON-RR HH.MI.SSXFF PM'));
The code above is required to include the TIME ZONE portion of the return type of the function
Current Timestamp (CURRENT_TIMESTAMP)
CURRENT_TIMESTAMP returns the current date and time in the session time zone, in a value of datatype TIMESTAMP WITH TIMEZONE. The time zone offset reflects the current local time of the SQL session
ALTER SESSION SET TIME_ZONE = '-5:0';
ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MON-YYYY HH24:MI:SS';
SELECT SESSIONTIMEZONE, CURRENT_TIMESTAMP FROM DUAL;
How about this?
select to_timestamp_tz(to_char(sysdate,'YYYY-MM-DD HH24:MI:SS') || ' ' || 'FROM_TIME_ZONE', 'YYYY-MM-DD HH24:MI:SS TZR') at time zone 'TO_TIME_ZONE'
from dual;
Try this:
CREATE TABLE TIMEZONES (ZONE CHAR(1) PRIMARY KEY,
NAMES VARCHAR2(25) NOT NULL,
OFFSET_HOURS NUMBER NOT NULL);
Populate it as follows:
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('Z', 'GMT', 0);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('N', '-1', -1);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('O', '-2', -2);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('P', '-3', -3);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('Q', '-4 EDT', -4);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('R', 'EST CDT', -5);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('S', 'CST MDT', -6);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('T', 'MST PDT', -7);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('U', 'PST', -8);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('V', '-9', -9);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('W', '-10', -10);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('X', '-11', -11);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('Y', '-12', -12);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('A', '1', -1);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('B', '2', -2);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('C', '3', -3);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('D', '4', -4);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('E', '5', -5);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('F', '6', -6);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('G', '7', -7);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('H', '8', -8);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('I', '9', -9);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('K', '10', -10);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('L', '11', -11);
INSERT INTO TIMEZONES (ZONE, NAMES, OFFSET_HOURS) VALUES ('M', '12', -12);
Given the above you can then do
SELECT SYS_EXTRACT_UTC(SYSTIMESTAMP) + (tz.OFFSET_HOURS / 24)
FROM TIMEZONES tz
WHERE tz.NAMES LIKE '%EDT%';
or
WHERE tz.ZONE = 'Q'
to get the local time in the -4 timezone.
Share and enjoy.
The following will give you the current EST time (UTC - 5 hours) without taking into account daylight savings:
SELECT SYS_EXTRACT_UTC(SYSTIMESTAMP) FROM DUAL
In order to take daylight savings into account, you have 2 options:
Write a function to calculate which dates daylight saving time changes occur on
Populate a table containing these dates
If you only need to support the EST time zone then writing a function may be the way to go; otherwise I'd recommend populating a table containing these dates as they vary between time zones.
All of the methods so far work nicely when working with a current timestamp. I noticed however that Oracle's tz_offset will give you the prevailing time offset. For example, in July,
SELECT TZ_OFFSET('US/Eastern') FROM DUAL;
results in '-04:00.' In January, the same statement results in '-05:00.' So, if you want to convert a date stored in the database (as opposed to the current system or session time), you have to do some development.
You might note that
SELECT TZ_OFFSET('EST') FROM DUAL;
always returns '-05:00.' Unfortunately, a quick test of the other US standard times (Central, Mountain and Pacific) showed different results. Run the following to see for yourself. I ran the following on June 16, while DST was in effect.
SELECT TZ_OFFSET('US/Eastern'), TZ_OFFSET('EST'), TZ_OFFSET('EST5EDT') FROM DUAL;
SELECT TZ_OFFSET('US/Central'), TZ_OFFSET('CST'), TZ_OFFSET('CST6CDT') FROM DUAL;
SELECT TZ_OFFSET('US/Mountain'), TZ_OFFSET('MST'), TZ_OFFSET('MST7MDT') FROM DUAL;
SELECT TZ_OFFSET('US/Pacific'), TZ_OFFSET('PST'), TZ_OFFSET('PST8PDT') FROM DUAL;
-04:00 -05:00 -04:00
-05:00 -05:00 -05:00
-06:00 -07:00 -06:00
-07:00 -07:00 -07:00
The results are somewhat frustrating. From a quick sampling, almost all tz_offset queries appear to respond with the the current prevailing time. This useful when you are working with conversions for the current time, but falls flat when working with times that are pulled from the database.
Now, I can write code to determine if I'm in standard or prevailing time. However, there is no way that I see to consistently pull a tz_offset for standard or daylight times, just prevailing.
This leaves the developer with a need to create their own table as Bob Jarvis did above to hack around the problem as I did in the code below.
ALTER SESSION SET NLS_DATE_FORMAT = 'MM/DD/YYYY HH24:MI:SS';
Declare
-- ******** User declarations begin here ******** --
-- ******** User declarations end here ******** --
/******************* All Declarations of Variables and Functions below this point support Time Zone Conversion (Convert_TZ function) *******************/
-- TimeZone Conversion Procedure
-- User Input (Parameters)
input_date date := TO_TIMESTAMP_TZ('7/1/2009 18:00','mm/dd/yyyy hh24:mi'); -- Try: LocalTimestamp; or TO_TIMESTAMP('2/1/2009 13:00','mm/dd/yyyy hh24:mi')
--input_date date := LocalTimestamp;
input_TZ varchar(3) := 'GMT'; -- Exmaples: EST, EDT or EPT for Eastern Standard, Daylight or Prevailing, respectively.
output_TZ varchar(3) := 'EPT'; --
-- Variables
type date_array is table of date;
return_date date := localtimestamp;
temp_date date := to_date('10/27/1974 02:00','mm/dd/yyyy hh24:mi');
type str_array is table of varchar2(10);
dow_list str_array;
Function dst_start_stop (input_date DATE)
RETURN date_array
AS
year_part number(4);
start_week number(1) := 0; -- week of month: -1=last, 1=1st, 2=nd, 0=fixed date (like 1974 and 1975)
stop_week number(1) := 0; -- week of month: -1=last, 1=1st, 2=nd, 0=fixed date (like 1974 and 1975)
dst_start date := to_date('01/06/1974 23:59','mm/dd/yyyy hh24:mi');
dst_stop date := to_date('10/27/1974 23:59','mm/dd/yyyy hh24:mi');
dst_date date := dst_start;
dst_msg varchar2(500) := ' ';
inc_dec number := 0;
Cnt number(1) := 0;
dst_dow number(1) := 1; -- 1=Sunday, 2=Monday, etc.
i number;
dst_range date_array;
BEGIN
dst_range := date_array();
dst_range.extend(2);
dst_range(1) := temp_date;
dst_range(2) := temp_date;
DBMS_OUTPUT.PUT_LINE(' ** Start: dst_start_stop Func **');
--insert into dst_range values(dst_start,dst_stop);
--dst_range(1) := dst_start;
--dst_range(2) := dst_stop;
year_part := to_number(to_char(input_date,'YYYY'));
DBMS_OUTPUT.PUT_LINE(' Year: '||year_part);
-- Determine DST formula based on year of input_date
If year_part > 9999 Then -- Invalid TempYear > 9999
dst_msg := 'N/A. I can''t guess if DST will be applied after 9999. Standard Time returned. ';
Goto found_start_stop;
ElsIf year_part >= 2007 Then -- 2007 forward. Latest DST Rules used after 2007.
dst_msg := '2007 forward: Third National DST Standard. ';
--dst_msg := dst_msg || 'Spring Forward 2:00 AM second Sunday in March (skip 02:00:00 through 02:59:59, I.E. skip from 01:59:59 to 03:00:00). '
--dst_msg := dst_msg || 'Fall Back 2:00 AM first Sunday in November (repeat 02:00:00 to 02:59:59, I.E. jump back from 02:59:59 to 02:00:00 once). '
DBMS_OUTPUT.PUT_LINE(' '||dst_msg);
dst_start := to_date('03/01/'||year_part||' 02:00','mm/dd/yyyy hh24:mi');
start_week := 2; -- 2nd Sunday in March
dst_stop := to_date('11/01/'||year_part||' 02:00','mm/dd/yyyy hh24:mi');
stop_week := 1; -- 1st Sunday in November
ElsIf year_part >= 1987 Then -- 1987 thru 2006.
dst_msg := '1987 thru 2006: Second National DST Standard. ';
--dst_msg := dst_msg || 'Spring Forward 2:00 AM first Sunday in April (skip 02:00:00 through 02:59:59, I.E. skip from 01:59:59 to 03:00:00). ';
--dst_msg := dst_msg || 'Fall Back 2:00 AM last Sunday in October (repeat 02:00:00 to 02:59:59, I.E. jump back from 02:59:59 to 02:00:00 once). ';
DBMS_OUTPUT.PUT_LINE(' '||dst_msg);
start_week := 1;
dst_start := to_date('04/01/'||year_part||' 02:00','mm/dd/yyyy hh24:mi');
stop_week := -1;
dst_stop := to_date('10/31/'||year_part||' 02:00','mm/dd/yyyy hh24:mi');
ElsIf year_part >= 1976 Then -- 1976 thru 1986 OLD DST Rules used 1961 thru 1973.
dst_msg := '1976 thru 1986: First National DST Standard (resumed after 1974-1975 extended DST trials). ';
--dst_msg := dst_msg || 'Spring Forward 2:00 AM last Sunday in April (skip 02:00:00 through 02:59:59, I.E. skip from 01:59:59 to 03:00:00). ';
--dst_msg := dst_msg || 'Fall Back 2:00 AM last Sunday in October (repeat 02:00:00 to 02:59:59, I.E. jump back from 02:59:59 to 02:00:00 once). ';
DBMS_OUTPUT.PUT_LINE(' '||dst_msg);
start_week := -1;
dst_start := to_date('04/30/'||year_part||' 02:00','mm/dd/yyyy hh24:mi');
stop_week := -1;
dst_stop := to_date('10/31/'||year_part||' 02:00','mm/dd/yyyy hh24:mi');
ElsIf year_part = 1975 Then -- 1975 Trial.
dst_msg := '1975 Trial of Extended DST. ';
--dst_msg := dst_msg || 'Spring Forward 2:00 AM Feb 23 (skip 02:00:00 through 02:59:59, I.E. skip from 01:59:59 to 03:00:00). ';
--dst_msg := dst_msg || 'Fall Back 2:00 AM Oct 26, the last Sun in Oct (repeat 02:00:00 to 02:59:59, I.E. jump back from 02:59:59 to 02:00:00 once). ';
DBMS_OUTPUT.PUT_LINE(' '||dst_msg);
dst_start := to_date('02/23/1975 02:00','mm/dd/yyyy hh24:mi');
dst_stop := to_date('10/26/1974 02:00','mm/dd/yyyy hh24:mi');
Goto found_start_stop;
ElsIf year_part = 1974 Then -- 1974 Trial.
dst_msg := '1974 Trial of Extended DST. ';
--dst_msg := dst_msg || 'Spring Forward 2:00 AM Jan 6 (skip 02:00:00 through 02:59:59, I.E. skip from 01:59:59 to 03:00:00)). ';
--dst_msg := dst_msg || 'Fall Back 2:00 AM Oct 27 (repeat 02:00:00 to 02:59:59, I.E. jump back from 02:59:59 to 02:00:00 once). ';
DBMS_OUTPUT.PUT_LINE(' '||dst_msg);
dst_start := to_date('01/06/1974 02:00','mm/dd/yyyy hh24:mi');
dst_stop := to_date('10/27/1974 02:00','mm/dd/yyyy hh24:mi');
Goto found_start_stop;
ElsIf year_part >= 1961 Then -- 1961 thru 1973 First National DST Standard.
dst_msg := '1961 thru 1973: First National DST Standard. ';
--dst_msg := dst_msg || 'Spring Forward 2:00 AM last Sunday in April (skip 02:00:00 through 02:59:59, I.E. skip from 01:59:59 to 03:00:00). ';
--dst_msg := dst_msg || 'Fall Back 2:00 AM last Sunday in October (repeat 02:00:00 to 02:59:59, I.E. jump back from 02:59:59 to 02:00:00 once). ';
start_week := -1;
dst_start := to_date('04/30/'||input_date||' 02:00','mm/dd/yyyy hh24:mi');
stop_week := -1;
dst_stop := to_date('10/31/'||year_part||' 02:00','mm/dd/yyyy hh24:mi');
ElsIf year_part >=1900 Then -- DST was applied inconsistently or not at all
dst_msg := 'N/A. Before 1961, DST was applied inconsistently across states or not at all. Standard Time returned. ';
Goto found_start_stop;
ElsIf year_part < 1900 Then -- Invalid year_part
dst_msg := 'N/A. DST never active before 1900';
Goto found_start_stop;
Else -- Invalid year_part
dst_msg := 'N/A. Error. Invalid datetime value.';
Goto found_start_stop;
End If;
DBMS_OUTPUT.PUT_LINE(' The code specified the following DST rules for the input date ('||input_date||'). '||dst_msg);
if start_week > 0 then
DBMS_OUTPUT.PUT_LINE(' Start on '||dow_list(dst_dow)||' #'||start_week||' of '||trunc(dst_start,'W')||'. ');
else
DBMS_OUTPUT.PUT_LINE(' Starts '||start_week||' from the last '||dow_list(dst_dow)||' of '||trunc(dst_start,'W')||'. ');
end if;
if stop_week > 0 then
DBMS_OUTPUT.PUT_LINE(' End on '||dow_list(dst_dow)||' #'||stop_week||' of '||trunc(dst_stop,'W')||'. ');
else
DBMS_OUTPUT.PUT_LINE(' Ends '||stop_week||' from the last '||dow_list(dst_dow)||' of '||trunc(dst_stop,'W')||'. ');
end if;
DBMS_OUTPUT.PUT_LINE(' ');
/* Apply formula determined above to find dst start and stop times for the year of the input_date.
This section is skipped if start/stop already determined or indeterminant.
*/
-- DstStartDay
inc_dec := start_week/abs(start_week); -- results in +1 or -1
Cnt := 0; i:=0;
while (Cnt < abs(start_week) and i<20) loop
i:=i+1;
if (to_char(dst_start,'D') = dst_dow) then
Cnt := Cnt + 1;
--DBMS_OUTPUT.PUT_LINE(' Found '||dow_list(dst_dow))||' '||Cnt||': '||dst_start)
end if;
if (Cnt < abs(start_week)) then
dst_start := dst_start + inc_dec;
end if;
end loop;
case inc_dec
when 1 then
DBMS_OUTPUT.PUT_LINE(' Spring forward on '||dow_list(dst_dow)||' #'||Cnt||' of the month: '||dst_start);
else
DBMS_OUTPUT.PUT_LINE(' Spring forward on the last'||dow_list(dst_dow)||'of the month: '||dst_start);
end case;
-- DstStopDay
inc_dec := stop_week/abs(stop_week); -- results in +1 or -1
Cnt := 0; i :=0;
while (Cnt < abs(stop_week) and i <20) loop -- to_char(dst_stop,'D') > 1 loop
i:=i+1;
if (to_char(dst_stop,'D') = dst_dow) then
dst_stop := dst_stop + inc_dec;
Cnt := Cnt + 1;
end if;
if (Cnt < abs(stop_week)) then
dst_stop := dst_stop + inc_dec;
end if;
end loop;
case inc_dec
when 1 then
DBMS_OUTPUT.PUT_LINE(' Fall back on '||dow_list(dst_dow)||' #'||Cnt||' of the month: '||dst_stop);
else
DBMS_OUTPUT.PUT_LINE(' Fall back on the last'||dow_list(dst_dow)||'of the month: '||dst_stop);
end case;
<<found_start_stop>>
dst_range(1) := dst_start;
DBMS_OUTPUT.PUT_LINE(' dst_range(1): '||to_char(dst_range(1),'mm/dd/yyyy hh24:mi')||' = '||dst_start);
dst_range(2) := dst_stop;
DBMS_OUTPUT.PUT_LINE(' dst_range(2): '||to_char(dst_range(2),'mm/dd/yyyy hh24:mi')||' = '||dst_stop);
DBMS_OUTPUT.PUT_LINE(' ** Finish: dst_start_stop Func **');
Return dst_range;
END dst_start_stop;
Function is_dst_now
Return boolean
AS
--type date_array is table of date;
dst_range date_array;
curr_time date := LocalTimestamp;
Begin
dst_range := date_array();
dst_range.extend(2);
dst_range := dst_start_stop(curr_time);
If (dst_range(1) <= curr_time and curr_time < dst_range(2)) then
DBMS_OUTPUT.PUT_LINE('DST is active.');
Return True;
Else
DBMS_OUTPUT.PUT_LINE('DST is NOT active.');
Return False;
End If;
End;
FUNCTION dst_offset (prevailing_date DATE, dst_start DATE, dst_stop DATE)
RETURN number
AS
offset_days number :=0;
BEGIN
DBMS_OUTPUT.PUT_LINE(' Starting dst_offset sub-function:');
DBMS_OUTPUT.PUT_LINE(' where (input date, DST start, DST stop) = '||to_char(prevailing_date,'mm/dd/yyyy hh24:mi')
||', '||to_char(dst_start,'mm/dd/yyyy hh24:mi')||', '||to_char(dst_stop,'mm/dd/yyyy hh24:mi'));
If (dst_start <= prevailing_date and prevailing_date < dst_stop) then
offset_days :=1/24;
DBMS_OUTPUT.PUT_LINE(' input date is between dst start and stop');
Else
offset_days :=0;
DBMS_OUTPUT.PUT_LINE(' input date is not between dst start and stop');
End If;
DBMS_OUTPUT.PUT_LINE(' Result: DST Offset days = '||offset_days);
DBMS_OUTPUT.PUT_LINE(' hours = '||(offset_days*24));
Return offset_days;
END dst_offset;
-- Begin --move this down under the function -- ******************
FUNCTION Convert_TZ (input_date DATE, input_tz varchar2, output_tz varchar2)
RETURN date
AS
-- Variables
input_sz varchar(3) := substr(input_TZ,1,1)||'S'||substr(input_TZ,3,1);
input_sz varchar(3) := substr(output_TZ,1,1)||'S'||substr(output_TZ,3,1);
temp_str varchar2(1000);
dst_range date_array;
input_dst_offset number := 0;
input_tz_offset number := 0;
input_date_st date; --standard time
gmt_date date;
output_dst_offset number := 0;
output_tz_offset number := 0;
output_date date;
output_date_pt date; -- prevailing time
tz_offset_str varchar2(30);
--orig_nls_date_format varchar2(30) := NLS_DATE_FORMAT;
--TempYear number(4,0) := to_char(TempDate,'YYYY'); -- or := trunc(PrevailingTime, YYYY);
BEGIN
DBMS_OUTPUT.PUT_LINE('Starting Pl/sql procedure. ');
DBMS_OUTPUT.PUT_LINE('Input Date: '||to_char(input_date,'mm/dd/yyyy hh24:mi')||' '||input_tz||'. ');
-- Find DST start/stop dates
dst_range := date_array();
dst_range.extend(2);
dst_range := dst_start_stop(input_date);
DBMS_OUTPUT.PUT_LINE('DST date range determined. ');
DBMS_OUTPUT.PUT_LINE(' dst_range(1): '||to_char(dst_range(1),'mm/dd/yyyy hh24:mi')||' = '||dst_range(1));
DBMS_OUTPUT.PUT_LINE(' dst_range(2): '||to_char(dst_range(2),'mm/dd/yyyy hh24:mi')||' = '||dst_range(2));
-- Convert Input Date from input time zone to GMT
If upper(input_TZ) in ('GMT','UCT') then
-- If input TZ is GMT, we can skip this conversion!
DBMS_OUTPUT.PUT_LINE(' Input Time is ('||to_char(input_date,'mm/dd/yyyy hh24:mi')||' '
||input_tz||' GMT). No conversion required. ');
input_tz_offset := 0;
input_dst_offset := 0;
gmt_date := input_date;
Else
-- Convert from local prevailing to local standard time
-- Get input_dst_offset
Case upper(substr(input_TZ,2,1))
When 'S' then
-- already in standard time, not conversion needed.
input_dst_offset := 0; -- duplicative
DBMS_OUTPUT.PUT_LINE('Standard time ('||input_tz||') entered; no dst offset.' );
--input_tz_offset := input_tz_offset;
Else
-- run dst_offset function to convert from prevailing or daylight time to standard time.
input_dst_offset := dst_offset(input_date, dst_range(1), dst_range(2));
input_date_st := input_date - input_dst_offset;
DBMS_OUTPUT.PUT_LINE('Daylight Saving Time Effective ('||to_char(input_date,'mm/dd/yyyy hh24:mi')||' '
||input_tz||'); 1 hour offset; input DST offset = '||input_dst_offset);
DBMS_OUTPUT.PUT_LINE(' where (input_date, dst_start, dst_stop) = '
||to_char(input_date,'mm/dd/yyyy hh24:mi')
||' '||input_tz||', '||dst_range(1)||', '||dst_range(2)||', ');
DBMS_OUTPUT.PUT_LINE(' which adjusts '||to_char(input_date,'mm/dd/yyyy hh24:mi')||' '||input_tz
||' daylight to '||input_date_st||' standard time. ');
End Case;
-- Convert from local standard time to GMT
SELECT TZ_OFFSET((SELECT max(tzname) FROM V$TIMEZONE_NAMES where tzabbrev = input_TZ))
INTO tz_offset_str
FROM DUAL;
input_tz_offset := ( substr(tz_offset_str,1,3)/24 + substr(tz_offset_str,5,2)/1440 );
If is_dst_now then
input_tz_offset := input_tz_offset - 1/24;
End if;
DBMS_OUTPUT.PUT_LINE(' input_tz_offset (fractional days): '||input_tz_offset||'. ');
DBMS_OUTPUT.PUT_LINE(' input_tz_offset (hours): '||input_tz_offset*24||'. ');
gmt_date := input_date_st - input_tz_offset;
End If;
-- Convert input date from GMT to requested output time zone
DBMS_OUTPUT.PUT_LINE(' ');
DBMS_OUTPUT.PUT_LINE('Starting output_date analysis. ');
If upper(output_TZ) in ('GMT','UCT') then
-- If desired output TZ is GMT, we can skip this conversion!
output_tz_offset := 0;
output_dst_offset := 0;
output_date := gmt_date;
DBMS_OUTPUT.PUT_LINE(' Requested output format is GMT: ('||to_char(output_date,'mm/dd/yyyy hh24:mi')||' '||output_tz||'). No conversion required. ');
Else
-- Get output_dst_offset
Case upper(substr(output_TZ,2,1))
When 'S' then
output_dst_offset := 0; -- duplicative
DBMS_OUTPUT.PUT_LINE('Standard time ('||output_TZ||') entered; no dst offset.' );
Else
output_dst_offset := dst_offset(gmt_date + output_tz_offset, dst_range(1), dst_range(2));
End Case;
-- Convert from GMT to local standard time
SELECT TZ_OFFSET((SELECT max(tzname) FROM V$TIMEZONE_NAMES where tzabbrev = output_TZ)) INTO tz_offset_str FROM DUAL;
output_tz_offset := ( substr(tz_offset_str,1,3)/24 + substr(tz_offset_str,5,2)/1440 );
DBMS_OUTPUT.PUT_LINE(' output_tz_offset (fractional days): '||output_tz_offset||'. ');
DBMS_OUTPUT.PUT_LINE(' output_tz_offset (hours): '||output_tz_offset*24||'. ');
If is_dst_now then
output_tz_offset := output_tz_offset - 1/24;
DBMS_OUTPUT.PUT_LINE(' tz_offset correction... ');
DBMS_OUTPUT.PUT_LINE(' output_tz_offset (fractional days): '||output_tz_offset||'. ');
DBMS_OUTPUT.PUT_LINE(' output_tz_offset (hours): '||output_tz_offset*24||'. ');
End if;
output_date := gmt_date + output_tz_offset + output_dst_offset;
DBMS_OUTPUT.PUT_LINE(' gmt_date: '||gmt_date);
DBMS_OUTPUT.PUT_LINE(' output_tz_offset: '||output_tz_offset);
DBMS_OUTPUT.PUT_LINE(' output_dst_offset: '||output_dst_offset);
DBMS_OUTPUT.PUT_LINE('Daylight Saving Time ('||output_TZ||') offset = '||output_dst_offset);
DBMS_OUTPUT.PUT_LINE(' where (output_date, dst_start, DST_stop) = '||output_date||', '||dst_range(1)||', '||dst_range(2));
DBMS_OUTPUT.PUT_LINE(' which adjusts '||output_date||' standard to '||output_date_pt||' daylight time. ');
End If;
DBMS_OUTPUT.PUT_LINE('Output Date = '||to_char(output_date,'mm/dd/yyyy hh24:mi')||' '||output_tz||'. ');
Goto AllDone;
<<FoundError>>
<<AllDone>>
DBMS_OUTPUT.PUT_LINE(' ');
DBMS_OUTPUT.PUT_LINE('*** Results ***');
DBMS_OUTPUT.PUT_LINE(' ');
if input_dst_offset <> 0 then
temp_str := 'daylight saving';
else
temp_str := 'standard';
end if;
DBMS_OUTPUT.PUT_LINE(' Input Date: '||to_char(input_date,'mm/dd/yyyy hh24:mi')||' '||input_tz||', which falls in '||temp_str||' time. ');
DBMS_OUTPUT.PUT_LINE(' GMT Date: '||to_char(gmt_date,'mm/dd/yyyy hh24:mi')||' UTC/GMT. ');
DBMS_OUTPUT.PUT_LINE(' Output Date: '||to_char(output_date,'mm/dd/yyyy hh24:mi')||' UTC/GMT. ');
if output_dst_offset <> 0 then
temp_str := 'daylight saving';
else
temp_str := 'standard';
end if;
DBMS_OUTPUT.PUT_LINE(' All Done. Return Value: '||to_char(output_date,'mm/dd/yyyy hh24:mi')||' '||output_tz||'. ');
DBMS_OUTPUT.PUT_LINE(' ');
DBMS_OUTPUT.PUT_LINE('*** End of Results ***');
DBMS_OUTPUT.PUT_LINE(' ');
DBMS_OUTPUT.PUT_LINE('LocalTimestamp EPT: '||LocalTimestamp);
DBMS_OUTPUT.PUT_LINE('LocalTimestamp GMT:'||LOCALTIMESTAMP at time zone '+00:00');
Return output_date;
END;
/*********************** End of Declarations of Variables and Functions for Time Zone Conversion (Convert_TZ function) *********************/
Begin
/*********************** Start of Procedural Code for Time Zone Conversion (Convert_TZ function) *********************/
/* DOW list is required for Time Zone Conversion (Convert_TZ function) and should not be deleted. */
dow_list := str_array();
dow_list.extend(7);
dow_list(1) := 'Sun';
dow_list(2) := 'Mon';
dow_list(3) := 'Tue';
dow_list(4) := 'Wed';
dow_list(5) := 'Thu';
dow_list(6) := 'Fri';
dow_list(7) := 'Sat';
/* Next 2 lines are example of use of Time Zone Conversion (Convert_TZ function). */
return_date := Convert_TZ (input_date, input_tz, output_tz);
DBMS_OUTPUT.PUT_LINE('ta-dah! '||return_date);
/*********************** End of Procedural Code for Time Zone Conversion (Convert_TZ function) *********************/
-- ******** User coded begins here ******** --
End;
RTRIM( TO_CHAR( systimestamp at time zone 'GMT', 'DD-MON-YYYY HH24:MI:SS TZR' ))
RTRIM( TO_CHAR( systimestamp at time zone 'EST', 'DD-MON-YYYY HH24:MI:SS TZR' ))
RTRIM( TO_CHAR( systimestamp at time zone 'PST', 'DD-MON-YYYY HH24:MI:SS TZR' ))
output:
05-NOV-2014 17:20:10 GMT
05-NOV-2014 12:20:10 EST
05-NOV-2014 09:20:10 PST
substr('05-NOV-2014 09:20:10 PST', -3, 3) will return 'PST'
RTRIM (EXTRACT (HOUR from systimestamp ))
output: 17 (notice the 17 is in GMT time)
Related
Is there a way to add days hours months and minutes into a timestamp that i want to insert the value of into a variable
I have separate variables for minutes,hours,days and months that I want to add to the timestamp
the timestamp format goes like this '04-FEB-21 10.25.12.013000 AM'
I tried using SELECT TO_TIMESTAMP(datecreated,'dd-mon-yyyy hh.mi.ss AM') + daysvar into duedate FROM dual; but it returns the error AM/A.M. or PM/P.M. required
daysvar contains the amount of days to be added to the timestamp
Thank you!
I have separate variables for minutes,hours,days and months that I want to add to the timestamp
Use NUMTODSINTERVAL:
DECLARE
datecreated VARCHAR2(30) := '04-FEB-21 10.25.12.013000 AM';
days INT := 1;
hours INT := 2;
minutes INT := 42;
seconds INT := 3;
duedate VARCHAR2(30);
BEGIN
duedate := TO_CHAR(
TO_TIMESTAMP(
datecreated,
'DD-MON-RR HH12.MI.SS.FF6 AM',
'NLS_DATE_LANGUAGE=American'
)
+ NUMTODSINTERVAL(days, 'DAY')
+ NUMTODSINTERVAL(hours, 'HOUR')
+ NUMTODSINTERVAL(minutes, 'MINUTE')
+ NUMTODSINTERVAL(seconds, 'SECOND'),
'DD-MON-RR HH12.MI.SS.FF6 AM',
'NLS_DATE_LANGUAGE=American'
);
DBMS_OUTPUT.PUT_LINE(duedate);
END;
/
db<>fiddle here
If you want to add months then use ADD_MONTHS; however, it returns a DATE (and not a TIMESTAMP) so you would lose the fractional seconds. You can add those back though:
DECLARE
datecreated VARCHAR2(30) := '04-FEB-21 10.25.12.013000 AM';
months INT := 4;
days INT := 1;
hours INT := 2;
minutes INT := 42;
seconds INT := 3;
created_date TIMESTAMP;
duedate VARCHAR2(30);
BEGIN
created_date := TO_TIMESTAMP(
datecreated,
'DD-MON-RR HH12.MI.SS.FF6 AM',
'NLS_DATE_LANGUAGE=American'
);
duedate := TO_CHAR(
CAST(ADD_MONTHS(created_date, months) AS TIMESTAMP)
+ NUMTODSINTERVAL(days, 'DAY')
+ NUMTODSINTERVAL(hours, 'HOUR')
+ NUMTODSINTERVAL(minutes, 'MINUTE')
+ NUMTODSINTERVAL(seconds, 'SECOND')
+ (created_date - CAST(created_date AS DATE)), -- Fractional seconds
'DD-MON-RR HH12.MI.SS.FF6 AM',
'NLS_DATE_LANGUAGE=American'
);
DBMS_OUTPUT.PUT_LINE(duedate);
END;
/
db<>fiddle here
How to check if it's a weekend in SQL?
I know I can convert sysdate to a number using this
SQL> select to_char(sysdate, 'd') from dual;
TO_CHAR(SYSDATE,'D')
But I'm not really sure how to check if today is a 6 or 7.
Do not use TO_CHAR with the D format model for this as it is dependant on the NLS_TERRITORY session parameter.
For example, when SYSDATE = 2018-09-10 (a Monday):
ALTER SESSION SET NLS_TERRITORY = 'France';
SELECT TO_CHAR( SYSDATE, 'D' ) FROM DUAL;
Outputs 1 but the same query in a different territory:
ALTER SESSION SET NLS_TERRITORY = 'America';
SELECT TO_CHAR( SYSDATE, 'D' ) FROM DUAL;
Outputs 2.
Instead, you can use TRUNC and the IW format model:
SELECT TRUNC( SYSDATE ) - TRUNC( SYSDATE, 'IW' ) FROM DUAL
Outputs 0 for Monday (and 1 for Tuesday ... 6 for Sunday) and is independent of the NLS_TERRITORY setting.
So you could filter this to give weekends as:
SELECT *
FROM DUAL
WHERE TRUNC( SYSDATE ) - TRUNC( SYSDATE, 'IW' ) IN ( 5, 6 )
or
SELECT *
FROM DUAL
WHERE SYSDATE - TRUNC( SYSDATE, 'IW' ) >= 5
If you want the days 1-indexed for consistency with your expected output from TO_CHAR (rather then 0-indexed) then just add 1 to the value.
sysdate is a pseudo column. You don't need to query it, you can evaluate it directly:
IF TO_CHAR(SYSDATE, 'D') IN ('6', '7') THEN
-- Do something
END IF;
I would avoid the ambiguous 'D' format as this varies between territories (the week starts after the weekend where I live), and use
if to_char(sysdate,'fmDY','nls_date_language=English') like 'S%'
then
Regarding the 'D' format, unfortunately to_char doesn't let you specify nls_territory inline, so without an explicit alter session command, it will rely on the session settings at runtime. I've seen production bugs due to this, where the same code worked in London but failed in New York.
And here's the logic in a reusable function, but flipped to ask "Is it a weekday?" (not a weekend). You could add a parameter for the start day of the weekdays (2 is the default in Oracle; Sunday is day #1).
CREATE OR REPLACE FUNCTION is_weekday (date_in IN DATE)
RETURN BOOLEAN
IS
BEGIN
RETURN TO_CHAR (date_in, 'D') BETWEEN 2 AND 6;
END;
/
DECLARE
l_date DATE := DATE '2018-09-10';
BEGIN
DBMS_OUTPUT.put_line ('If your weekend is Saturday and Sunday....');
FOR indx IN 1 .. 7
LOOP
DBMS_OUTPUT.put_line (
TO_CHAR (l_date, 'FMDay, Month DD YYYY')
|| ' is '
|| CASE WHEN NOT is_weekday (l_date) THEN 'not ' END
|| 'a weekday');
l_date := l_date + 1;
END LOOP;
END;
/
Try it in LiveSQL:
https://livesql.oracle.com/apex/livesql/file/content_G8NQSY6NP48NPJX96RLQ51SUE.html
Can someone just tell me why this doesn't work. As far as I know, I've put the string into the variable and wanted to test it but it didn't seem to work and brought up errors.
SET SERVEROUTPUT ON;
DECLARE
DATETODAY DATE := SYSDATE;
DAYT VARCHAR2(10);
BEGIN
SELECT TO_CHAR(SYSDATE,'DAY') INTO DAYT FROM DUAL;
DBMS_OUTPUT.PUT_LINE('The date today is '||DATETODAY ||' and it is ' ||DAYT);
IF DAYT = 'SATURDAY' OR DAYT = 'SUNDAY' THEN
: DBMS_OUTPUT.PUT_LINE('Today is '||DAYT||' and its is a weekend');
ELSE
: DBMS_OUTPUT.PUT_LINE('Today is '||DAYT||' and its a week day');
END IF;
END;
/
SELECT TO_CHAR (SYSDATE, 'day') DayName,
TO_CHAR (SYSDATE, 'd') DayOfWeek,
TO_CHAR (SYSDATE, 'dd') DayOfMonth,
TO_CHAR (SYSDATE, 'ddd') DayOfYear
FROM DUAL;
DayName is bound to the language of your db. Better use DayOfWeek.
Anyway you should run the example-sql provided to compare your output. Perhaps a UPPER() could also help you.
TO_DATE( date_value, 'DAY' ) returns a fixed-length string (not a variable-length); this means that it is right-padded with space characters:
SQL Fiddle
Query 1:
SELECT TO_CHAR( DATE '2018-05-05', 'DAY' ) AS day,
DUMP( TO_CHAR( DATE '2018-05-05', 'DAY' ) ) As dump
FROM DUAL
Results:
| DAY | DUMP |
|-----------|-----------------------------------------|
| SATURDAY | Typ=1 Len=9: 83,65,84,85,82,68,65,89,32 |
Shows that the final character has ASCII code 32 - a space.
So your code should be:
SET SERVEROUTPUT ON;
DECLARE
DATETODAY DATE := SYSDATE;
DAYT VARCHAR2(10);
BEGIN
DAYT := TO_CHAR( DATETODAY ,'DAY');
DBMS_OUTPUT.PUT_LINE('The date today is '||DATETODAY ||' and it is ' ||DAYT);
IF DAYT = 'SATURDAY ' OR DAYT = 'SUNDAY ' THEN
DBMS_OUTPUT.PUT_LINE('Today is '||DAYT||' and its is a weekend');
ELSE
DBMS_OUTPUT.PUT_LINE('Today is '||DAYT||' and its a week day');
END IF;
END;
/
Which (for my NLS_DATE_FORMAT setting of YYYY-MM-DD HH24:MI:SS) outputs:
The date today is 2018-05-04 14:32:25 and it is FRIDAY
Today is FRIDAY and its a week day
Changing the initial assignment to DATETODAY DATE := DATE '2018-05-05'; then the output is:
The date today is 2018-05-05 00:00:00 and it is SATURDAY
Today is SATURDAY and its is a weekend
However, you could also write it as:
DECLARE
DATETODAY DATE := SYSDATE;
DAYT VARCHAR2(10);
BEGIN
DAYT := TO_CHAR( DATETODAY ,'DAY');
DBMS_OUTPUT.PUT_LINE('The date today is '||DATETODAY ||' and it is ' ||DAYT);
IF DATETODAY - TRUNC( DATETODAY, 'IW' ) >= 5 THEN
DBMS_OUTPUT.PUT_LINE('Today is '||DAYT||' and its is a weekend');
ELSE
DBMS_OUTPUT.PUT_LINE('Today is '||DAYT||' and its a week day');
END IF;
END;
/
As TRUNC( DATETODAY, 'IW' ) will truncate the date back to the start of the ISO week (always midnight on Monday) and this is independent of any NLS_DATE_LANGUAGE or NLS_TERRITORY settings that affect the TO_CHAR function.
I have MSSTAMP as "timestamp with milliseconds" in Oracle, format: 1483228800000. How can I cast that milliseconds timestamp into a date format "YYYY-MM", in order to get count of FINISHED rows per month for previous years.
I have tried different variations of TO_DATE, CAST, TO_CHAR - but I'm unable to get this working.
select
count(*) "EVENTS",
TO_DATE(MSSTAMP, 'YYYY-MM') "FINISHED_MONTH"
from
DB_TABLE
where
MSSTAMP < '1483228800000'
and
STATUS in ('FINISHED')
group by
FINISHED_MONTH ASC
Unix Time
If you just need to convert from milliseconds since epoch to a timestamp in the UTC timezone, then:
SELECT TIMESTAMP '1970-01-01 00:00:00.000 UTC'
+ NUMTODSINTERVAL( 1483228800000 / 1000, 'SECOND' )
AS TIME
FROM DUAL
Which outputs:
TIME
2017-01-01 00:00:00.000 +00:00
It you just want the year-month then use TRUNC( timestamp, 'MM' ) or TO_CHAR( timestamp, 'YYYY-MM' ).
Real Time (with leap seconds)
If you need to handle leap seconds then you can create a utility package that will adjust the epoch time to account for this:
CREATE OR REPLACE PACKAGE time_utils
IS
FUNCTION milliseconds_since_epoch(
in_datetime IN TIMESTAMP,
in_epoch IN TIMESTAMP DEFAULT TIMESTAMP '1970-01-01 00:00:00'
) RETURN NUMBER;
FUNCTION milliseconds_epoch_to_ts (
in_milliseconds IN NUMBER,
in_epoch IN TIMESTAMP DEFAULT TIMESTAMP '1970-01-01 00:00:00'
) RETURN TIMESTAMP;
END;
/
CREATE OR REPLACE PACKAGE BODY time_utils
IS
-- List of the seconds immediately following leap seconds:
leap_seconds CONSTANT SYS.ODCIDATELIST := SYS.ODCIDATELIST(
DATE '1972-07-01',
DATE '1973-01-01',
DATE '1974-01-01',
DATE '1975-01-01',
DATE '1976-01-01',
DATE '1977-01-01',
DATE '1978-01-01',
DATE '1979-01-01',
DATE '1980-01-01',
DATE '1981-07-01',
DATE '1982-07-01',
DATE '1983-07-01',
DATE '1985-07-01',
DATE '1988-01-01',
DATE '1990-01-01',
DATE '1991-01-01',
DATE '1992-07-01',
DATE '1993-07-01',
DATE '1994-07-01',
DATE '1996-01-01',
DATE '1997-07-01',
DATE '1999-01-01',
DATE '2006-01-01',
DATE '2009-01-01',
DATE '2012-07-01',
DATE '2015-07-01',
DATE '2016-01-01'
);
HOURS_PER_DAY CONSTANT BINARY_INTEGER := 24;
MINUTES_PER_HOUR CONSTANT BINARY_INTEGER := 60;
SECONDS_PER_MINUTE CONSTANT BINARY_INTEGER := 60;
MILLISECONDS_PER_SECOND CONSTANT BINARY_INTEGER := 1000;
MINUTES_PER_DAY CONSTANT BINARY_INTEGER := HOURS_PER_DAY * MINUTES_PER_HOUR;
SECONDS_PER_DAY CONSTANT BINARY_INTEGER := MINUTES_PER_DAY * SECONDS_PER_MINUTE;
MILLISECONDS_PER_MINUTE CONSTANT BINARY_INTEGER := SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND;
MILLISECONDS_PER_HOUR CONSTANT BINARY_INTEGER := MINUTES_PER_HOUR * MILLISECONDS_PER_MINUTE;
MILLISECONDS_PER_DAY CONSTANT BINARY_INTEGER := HOURS_PER_DAY * MILLISECONDS_PER_HOUR;
FUNCTION milliseconds_since_epoch(
in_datetime IN TIMESTAMP,
in_epoch IN TIMESTAMP DEFAULT TIMESTAMP '1970-01-01 00:00:00'
) RETURN NUMBER
IS
p_leap_milliseconds BINARY_INTEGER := 0;
p_diff INTERVAL DAY(9) TO SECOND(3);
BEGIN
IF in_datetime IS NULL OR in_epoch IS NULL THEN
RETURN NULL;
END IF;
p_diff := in_datetime - in_epoch;
IF in_datetime >= in_epoch THEN
FOR i IN 1 .. leap_seconds.COUNT LOOP
EXIT WHEN in_datetime < leap_seconds(i);
IF in_epoch < leap_seconds(i) THEN
p_leap_milliseconds := p_leap_milliseconds + MILLISECONDS_PER_SECOND;
END IF;
END LOOP;
ELSE
FOR i IN REVERSE 1 .. leap_seconds.COUNT LOOP
EXIT WHEN in_datetime > leap_seconds(i);
IF in_epoch > leap_seconds(i) THEN
p_leap_milliseconds := p_leap_milliseconds - MILLISECONDS_PER_SECOND;
END IF;
END LOOP;
END IF;
RETURN MILLISECONDS_PER_SECOND * EXTRACT( SECOND FROM p_diff )
+ MILLISECONDS_PER_MINUTE * EXTRACT( MINUTE FROM p_diff )
+ MILLISECONDS_PER_HOUR * EXTRACT( HOUR FROM p_diff )
+ MILLISECONDS_PER_DAY * EXTRACT( DAY FROM p_diff )
+ p_leap_milliseconds;
END milliseconds_since_epoch;
FUNCTION milliseconds_epoch_to_ts(
in_milliseconds IN NUMBER,
in_epoch IN TIMESTAMP DEFAULT TIMESTAMP '1970-01-01 00:00:00'
) RETURN TIMESTAMP
IS
p_datetime TIMESTAMP;
BEGIN
IF in_milliseconds IS NULL OR in_epoch IS NULL THEN
RETURN NULL;
END IF;
p_datetime := in_epoch
+ NUMTODSINTERVAL( in_milliseconds / MILLISECONDS_PER_SECOND, 'SECOND' );
IF p_datetime >= in_epoch THEN
FOR i IN 1 .. leap_seconds.COUNT LOOP
EXIT WHEN p_datetime < leap_seconds(i);
IF in_epoch < leap_seconds(i) THEN
p_datetime := p_datetime - INTERVAL '1' SECOND;
END IF;
END LOOP;
ELSE
FOR i IN REVERSE 1 .. leap_seconds.COUNT LOOP
EXIT WHEN p_datetime > leap_seconds(i);
IF in_epoch > leap_seconds(i) THEN
p_datetime := p_datetime + INTERVAL '1' SECOND;
END IF;
END LOOP;
END IF;
RETURN p_datetime;
END milliseconds_epoch_to_ts;
END;
/
Then you can do:
SELECT TIME_UTILS.milliseconds_epoch_to_ts(
in_milliseconds => 1483228800000,
in_epoch => TIMESTAMP '1970-01-01 00:00:00.000'
) AS time
FROM DUAL;
And get the output:
TIME
2016-12-31 23:59:33.000
Note: you will need to keep the package up-to-date when new leap-seconds are proposed.
db<>fiddle here
Update:
Your query would then be:
SELECT COUNT(*) "EVENTS",
TRUNC(
TIMESTAMP '1970-01-01 00:00:00.000'
+ NUMTODSINTERVAL( MSSTAMP / 1000, 'SECOND' ),
'MM'
) AS FINISHED_MONTH
FROM DB_TABLE
WHERE MSSTAMP < 1483228800000
AND STATUS = 'FINISHED'
GROUP BY
TRUNC(
TIMESTAMP '1970-01-01 00:00:00.000'
+ NUMTODSINTERVAL( MSSTAMP / 1000, 'SECOND' ),
'MM'
);
I have below PLSQL code which finds the epoch for last month same day, however it fails when I run it on month end for 31 and 01 days.
SET serveroutput ON
DECLARE
vDay VARCHAR2(30) := '&Enter_current_day';
vDate VARCHAR2(30);
vEpoch NUMBER;
BEGIN
vDate := TO_CHAR(sysdate, 'MM')||'-'||vDay||'-'||TO_CHAR(sysdate, 'YYYY');
vEpoch := (ADD_MONTHS(TO_DATE(vDate, 'MM-DD-YYYY HH24:MI:SS'), -1) - TO_DATE('01/01/1970 00:00:00', 'MM-DD-YYYY HH24:MI:SS')) * 24 * 60 * 60;
vDate := TO_DATE(vDate, 'MM-DD-YYYY HH24:MI:SS');
dbms_output.put_line('Current Date: '||to_number(vDay)||' Epoch: '||vEpoch||' Date: '||vDate);
END;
e.g. if todays date is,
A. 30-Sep and if I enter '31' then it should return epoch for the 01-Sep
B. 30-Sep and if I enter '01' then it should return epoch for the 01-Sep
C. 30-Mar and if I enter '31' then it should return epoch for the 01-Mar
D. 30-Mar and if I enter '01' then it should return epoch for the 01-Mar
Maybe something like this would do the trick?
DECLARE
vday VARCHAR2(30) := '&Enter_current_day';
vdate DATE;
vepoch NUMBER;
v_today DATE := SYSDATE - 30;
BEGIN
vdate := to_date(to_char(v_today, 'MM') || '-' ||
least(to_number(vday),
to_number(to_char(last_day(v_today), 'dd'))) || '-' ||
to_char(v_today, 'YYYY'),
'MM-DD-YYYY');
vepoch := (add_months(vdate, -1) -
to_date('01/01/1970 00:00:00', 'MM-DD-YYYY HH24:MI:SS')) * 24 * 60 * 60;
dbms_output.put_line('Current Date: ' || to_number(vday) || ' Epoch: ' ||
vepoch || ' Date: ' || to_char(vdate, 'mm-dd-yyyy'));
END;
Current Date: 1 Epoch: 1470009600 Date: 09-01-2016
Current Date: 15 Epoch: 1471219200 Date: 09-15-2016
Current Date: 30 Epoch: 1472601600 Date: 09-30-2016
Current Date: 31 Epoch: 1472601600 Date: 09-30-2016
This simply finds the last day of the month returned by the v_today date and compares it to the input vday and picks the lowest value. So, for September, if vday = 31, it would compare 31 with 30 (the last day in September) and output 30.
If you're dead-set on returning the first day of the month, then you'd need to change the least section to something like:
CASE WHEN to_number(to_char(last_day(v_today), 'dd')) < to_number(vday)
THEN '01'
ELSE vday
END
N.B. Note that I've made a few changes to your code to make it more robust - eg. You had vDate := TO_DATE(vDate, 'MM-DD-YYYY HH24:MI:SS'); which is a bit daft since you originally had vDate as a string, and by doing that, you're forcing Oracle to implicitly convert the string into a date and then back into a string.
Instead, I declared vDate as a date and have output the date in resultant string in the date format you used throughout the rest of the code - this means that it no longer relies on the NLS nls_date_format parameter to decide (this parameter is not necessarily the same for everyone!).