I am looking for a simpler solution to find the nearest date (with respect to sysdate) for a given day of year value. Examples (in format "dd/mm/yyyy")
sysdate = "01/01/2012" input = 365 result = "31/12/2011"
sysdate = "01/01/2012" input = 366 result = "31/12/2012"
sysdate = "01/01/2012" input = 1 result = "01/01/2012"
sysdate = "31/12/2012" input = 1 result = "01/01/2013"
Basically the resulting date can be in current year, previous year or next year. Initially I wrote a small procedure as given below. Here I am using a reference date instead of sysdate to test the results. This works for cases where the input day of year is not 366. But when it is 366, this fails and one may need to travel further backwards and forward to find the nearest valid date. After adding the checks for leap year (all conditions 4,100,400 etc) the code became a real mess.
I would appreciate if you can suggest a simpler, better and foolproof solution (function or single query). Please do not use complex constructs too specific to Oracle as I will have to port the same to DB2 as well. Also, efficiency is of least concern as it is not going to be executed heavily.
CREATE OR REPLACE PROCEDURE test(ref_date_str varchar2, doy number) IS
ref_date date ;
nearest_date date ;
BEGIN
ref_date := to_date(ref_date_str, 'dd/mm/yyyy') ;
WITH choices AS
(
SELECT trunc(ref_date, 'yyyy') + doy - 1 AS choice_date FROM dual
UNION
SELECT trunc(trunc(ref_date, 'yyyy') - 1, 'yyyy') + doy - 1 AS choice_date FROM dual
UNION
SELECT add_months(trunc(ref_date, 'yyyy'), 12) + doy - 1 AS choice_date FROM dual
)
SELECT choice_date INTO nearest_date FROM choices WHERE abs(ref_date - choice_date) =
(SELECT min(abs(ref_date - choice_date)) FROM choices) AND rownum < 2 ;
dbms_output.put_line(to_char(nearest_date, 'dd/mm/yyyy')) ;
END ;
/
Logically the algorithm I am considering is
for each year backwards from current year
if a valid date found for the doy, and it is <= sysdate
first_date = this valid date
exit loop
for each year forward from current year
if a valid date found for the doy, and it is > sysdate
second_date = this valid date
exit loop
chosen_date = closest_to_sysdate_among(first_date, second_date)
EDIT 1 : Given below is the implementation of the algorithm I have given above (there is some redundancy in the code). I am still looking forward for alternatives or refinements to the solutions.
CREATE OR REPLACE FUNCTION GetNearestDate(reference_date DATE, day_of_year NUMBER) RETURN DATE IS
valid_date_1 DATE ;
valid_date_2 DATE ;
iter_date DATE ;
BEGIN
iter_date := trunc(reference_date, 'yyyy') ;
WHILE TRUE
LOOP
valid_date_1 := iter_date + day_of_year - 1 ;
IF valid_date_1 < add_months(iter_date, 12) AND valid_date_1 <= reference_date THEN
EXIT ;
END IF ;
iter_date := trunc(iter_date - 1, 'yyyy') ;
END LOOP ;
iter_date := trunc(reference_date, 'yyyy') ;
WHILE TRUE
LOOP
valid_date_2 := iter_date + day_of_year - 1 ;
IF valid_date_2 < add_months(iter_date, 12) AND valid_date_2 > reference_date THEN
EXIT ;
END IF ;
iter_date := add_months(iter_date, 12) ;
END LOOP ;
IF abs(valid_date_1 - reference_date) <= abs(valid_date_2 - reference_date) THEN
RETURN valid_date_1 ;
END IF ;
RETURN valid_date_2 ;
END ;
/
EDIT 2 : The check "valid_date_? < add_months" is to ensure that this date is on the same (iterated) year itself (otherwise, a value of 366, for a non-leap year will return next year start day). Also, the associated comparison with reference date is to guard against cases like (reference = "30/01/2012", input = 365). Here the valid_date_1 should be "31/12/2011" and not "30/12/2012", as the first one is the closest date to the reference, with day of year value as 365.