Skip to content

Instantly share code, notes, and snippets.

@sentenza
Last active November 10, 2021 15:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sentenza/e8f8003e2581d52f12f8c89d663c00d7 to your computer and use it in GitHub Desktop.
Save sentenza/e8f8003e2581d52f12f8c89d663c00d7 to your computer and use it in GitHub Desktop.
Calculate date of easter based on Year passed. Based on Anonymous Gregorian Algorithm, also known as the Meeus/Jones/Butcher algorithm: https://en.wikipedia.org/wiki/Computus#Gauss_algorithm
CREATE OR REPLACE FUNCTION anonymous_easter(xyear integer) RETURNS date AS $$
DECLARE
a int;
b int;
c int;
d int;
e int;
f int;
g int;
h int;
i int;
k int;
l int;
m int;
day_ int;
month_ int;
BEGIN
a := mod(xyear, 19);
b := floor(xyear / 100);
c := mod(xyear, 100);
d := floor(b / 4);
e := mod (b, 4);
f := floor((b + 8) / 25);
g := floor((b - f + 1)/3);
h := mod ((19*a + b - d - g + 15), 30);
i := floor(c / 4);
k := mod (c, 4);
l := mod((32 + 2*e + 2*i - h - k), 7);
m := floor((a + 11*h + 22*l) / 451);
month_ := floor((h + l - 7*m + 114) / 31);
day_ = mod((h + l - 7*m + 114), 31) + 1;
RETURN to_date(
to_char(day_, '00') || '.' ||
to_char(month_, '00') || '.' ||
to_char(xyear, '0000'),
'DD.MM.YYYY'
);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION generate_italian_holidays("start_year" integer, "end_date" integer) RETURNS void
DECLARE
anno int;
easter_date date;
BEGIN
CREATE TABLE holidays_calendar (
holiday date NOT NULL,
description character varying NOT NULL,
id integer NOT NULL
);
FOR anno IN start_year..end_date
LOOP
easter_date := planner.anonymous_easter(anno);
INSERT INTO planner.holidays_calendar(holiday, description) VALUES (to_date( anno ||'-01-01', 'YYYY-MM-DD'), 'Capodanno');
INSERT INTO planner.holidays_calendar(holiday, description) VALUES (to_date( anno ||'-01-06', 'YYYY-MM-DD'), 'Epifania');
IF (easter_date = to_date( anno ||'-04-25', 'YYYY-MM-DD')) THEN
INSERT INTO planner.holidays_calendar(holiday, description) VALUES (easter_date, 'Pasqua e Festa della Liberazione');
INSERT INTO planner.holidays_calendar(holiday, description) VALUES (easter_date + 1, 'Pasquetta');
ELSIF (easter_date + 1 = to_date( anno ||'-04-25', 'YYYY-MM-DD')) THEN
INSERT INTO planner.holidays_calendar(holiday, description) VALUES (easter_date, 'Pasqua');
INSERT INTO planner.holidays_calendar(holiday, description) VALUES (easter_date + 1, 'Pasquetta e Festa della Liberazione');
ELSE
INSERT INTO planner.holidays_calendar(holiday, description) VALUES (easter_date, 'Pasqua');
INSERT INTO planner.holidays_calendar(holiday, description) VALUES (easter_date + 1, 'Pasquetta');
INSERT INTO planner.holidays_calendar(holiday, description) VALUES (to_date( anno ||'-04-25', 'YYYY-MM-DD'), 'Festa della Liberazione');
END IF;
INSERT INTO planner.holidays_calendar(holiday, description) VALUES (to_date( anno ||'-05-01', 'YYYY-MM-DD'), 'Festa dei lavoratori');
INSERT INTO planner.holidays_calendar(holiday, description) VALUES (to_date( anno ||'-06-02', 'YYYY-MM-DD'), 'Festa della Repubblica');
INSERT INTO planner.holidays_calendar(holiday, description) VALUES (to_date( anno ||'-08-15', 'YYYY-MM-DD'), 'Ferragosto');
INSERT INTO planner.holidays_calendar(holiday, description) VALUES (to_date( anno ||'-11-01', 'YYYY-MM-DD'), 'Ognissanti');
INSERT INTO planner.holidays_calendar(holiday, description) VALUES (to_date( anno ||'-12-08', 'YYYY-MM-DD'), 'Immacolata');
INSERT INTO planner.holidays_calendar(holiday, description) VALUES (to_date( anno ||'-12-25', 'YYYY-MM-DD'), 'Natale');
INSERT INTO planner.holidays_calendar(holiday, description) VALUES (to_date( anno ||'-12-26', 'YYYY-MM-DD'), 'Santo Stefano');
END LOOP;
END;
$$ LANGUAGE plpgsql;
-- Function: is_business_day(date)
-- DROP FUNCTION is_business_day(date);
CREATE OR REPLACE FUNCTION is_business_day(p_date date)
RETURNS boolean AS
$BODY$DECLARE
is_business_day boolean;
dow int;
BEGIN
dow := extract('dow' from p_date);
IF dow = 6 OR dow = 0 OR EXISTS (SELECT 1 FROM holidays_calendar WHERE holiday = p_date)
THEN
is_business_day := FALSE;
ELSE
is_business_day := TRUE;
END IF;
RETURN is_business_day ;
END;$BODY$
LANGUAGE plpgsql IMMUTABLE
COST 100;
ALTER FUNCTION planner.is_business_day(date)
OWNER TO support;
COMMENT ON FUNCTION planner.is_business_day(date) IS 'Returns true if day is IN(1,5) OR if is IN holidays_calendar';
-- Function: planner.working_date(date, integer)
-- DROP FUNCTION planner.working_date(date, integer);
CREATE OR REPLACE FUNCTION planner.working_date(start_date date, duration integer)
RETURNS date AS
$BODY$DECLARE
ret_date date;
loop_date date;
BEGIN
--add days
ret_date := start_date + (duration - 1);
loop_date := start_date + 1;
--add extra day for each no business day between start_date and ret_date
WHILE loop_date <= ret_date LOOP
IF not planner.is_business_day(loop_date) THEN
ret_date := ret_date + 1;
END IF;
loop_date := loop_date + 1;
END LOOP;
--add day if ret_date is no business day
WHILE not planner.is_business_day(ret_date) LOOP
ret_date := ret_date + 1;
END LOOP;
RETURN ret_date;
END;$BODY$
LANGUAGE plpgsql VOLATILE SECURITY DEFINER
COST 100;
ALTER FUNCTION planner.working_date(date, integer)
OWNER TO support;
COMMENT ON FUNCTION planner.working_date(date, integer) IS 'Returns the corresponding end date based on a duration made only of working days';
@sentenza
Copy link
Author

sentenza commented Sep 9, 2016

To test the function over a time range you could use proficiently generate_series():

SELECT to_char(gday,'DD-MM') , is_business_day(gday::date) AS isbusiness
FROM generate_series('2016-12-01'::date, '2016-12-31'::date, '1 day') gday

@sentenza
Copy link
Author

sentenza commented Jan 4, 2017

In order to retrieve the result using Symfony and Doctrine you could use something like that:

  $em = $this->getDoctrine()->getManager();
  $query = $em->getConnection()->prepare('SELECT planner.working_date(:start_date::date, :duration::integer)');
  $query->bindValue(':start_date', '04/01/2017' );
  $query->bindValue(':duration', 100);
  $query->execute();
  $end_date = $query->fetchAll();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment