diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..7b76473 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,20 @@ +name: CI +on: [push, pull_request] +jobs: + test: + strategy: + matrix: + pg: [15, 14, 13, 12, 11] + name: 🐘 PostgreSQL ${{ matrix.pg }} + runs-on: ubuntu-latest + container: pgxn/pgxn-tools + steps: + - name: Start PostgreSQL ${{ matrix.pg }} + run: pg-start ${{ matrix.pg }} + - name: Check out the repo + uses: actions/checkout@v3 + - name: Build project + run: | + chmod +x ./scripts/create_sql_file.sh + - name: Test on PostgreSQL ${{ matrix.pg }} + run: pg-build-test diff --git a/README.md b/README.md index 3e04c25..d1e7ebf 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,71 @@ variations and defines a default region. The above query would also produce the same output. +# Development + +For ease of use, instead of installing +[pgxn-client](https://pgxn.github.io/pgxnclient/) locally, you can use the +provided docker container. + +* Start the container (it will stay up for 10 days by default): + +```sh +docker-compose up -d +``` + +* Enter the container: + +```sh +docker-compose exec pgxn-tools bash +``` + +* Create the instance and the database: + +```sh +sudo pg-start 15 +createdb -U postgres contrib_regression +``` + +* Install the extension + +```sh +cd /repo && make install +``` + +* Then you can enter the PostgreSQL instance: + +```sh +psql -U postgres -d contrib_regression +``` + +* Create the extension: + +``` sql +create extension holidays; +\dn +``` + +* Retrieve some holidays: + +``` sql +SELECT * FROM holidays.by_country ('FR'::text, 2022, 2023); +``` + + +* To stop the container: + +```sh +docker-compose down +``` + +* To run the test on PostgreSQL 15: + +```sh +docker-compose up -d && \ + docker-compose exec pgxn-tools bash -c 'cd /repo && sudo pg-start 15 && pg-build-test' ; \ + docker-compose down +``` + # To Do Cross-port the knowledge from the npm / javascript libraries for the same diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c40354a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,7 @@ +version: '3.9' +services: + pgxn-tools: + image: pgxn/pgxn-tools + volumes: + - .:/repo/ + command: sleep 10d diff --git a/scripts/create_sql_file.sh b/scripts/create_sql_file.sh index e460643..502b2e9 100755 --- a/scripts/create_sql_file.sh +++ b/scripts/create_sql_file.sh @@ -111,7 +111,8 @@ COUNTRIES_FILES=( ) for sql_file in ${DBSETUP_FILES[*]} ${UTILS_FILES[*]} ${COUNTRIES_FILES[*]}; do - (cat "${sql_file}"; echo; echo) >> holidays.sql + (cat "${sql_file}"; echo; echo) >> holidays_orig.sql done -sed -e 's#\([ (\t]\)holidays\.#\1@extschema@.#g' -e 's#--\([ \t]*\)@extschema@\.#--\1holidays.#g' -i holidays.sql +sed -e 's#\([ (\t]\)holidays\.#\1@extschema@.#g' -e 's#--\([ \t]*\)@extschema@\.#--\1holidays.#g' holidays_orig.sql > holidays.sql +rm -f holidays_orig.sql diff --git a/sql/by_country.pgsql b/sql/by_country.pgsql index 354ff9c..5392784 100644 --- a/sql/by_country.pgsql +++ b/sql/by_country.pgsql @@ -224,3 +224,45 @@ BEGIN END; $$ LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION holidays.by_country( + p_country TEXT, + p_start_date date, + p_end_date date, + p_sub_region TEXT DEFAULT NULL +) +RETURNS SETOF holidays.holiday +AS $$ + SELECT + datestamp, + reference, + description, + authority, + day_off, + observation_shifted, + start_time, + end_time + FROM holidays.by_country( + p_country, + extract('year' from p_start_date)::int, + extract('year' from p_end_date)::int, + p_sub_region + ) + WHERE datestamp >= p_start_date AND datestamp <= p_end_date +$$ LANGUAGE sql; + +CREATE OR REPLACE FUNCTION holidays.by_country( + p_country TEXT, + p_daterange daterange, + p_sub_region TEXT DEFAULT NULL +) +RETURNS SETOF holidays.holiday +AS $$ + SELECT holidays.by_country( + p_country, + lower(p_daterange), + upper(p_daterange), + p_sub_region + ); +$$ LANGUAGE sql; diff --git a/sql/countries/france.pgsql b/sql/countries/france.pgsql index 17ff944..8689f13 100644 --- a/sql/countries/france.pgsql +++ b/sql/countries/france.pgsql @@ -47,9 +47,9 @@ DECLARE WEEKEND INTEGER[] := ARRAY[0, 6]; -- Provinces PROVINCES TEXT[] := ARRAY[ - 'MĂ©tropole', 'Alsace-Moselle', 'Guadeloupe', 'Guyane', 'Martinique', - 'Mayotte', 'Nouvelle-CalĂ©donie', 'La RĂ©union', 'PolynĂ©sie Française', - 'Saint-BarthĂ©lĂ©my', 'Saint-Martin', 'Wallis-et-Futuna' + 'MĂ©tropole', 'Alsace-Moselle', 'Guadeloupe', 'Saint-BarthĂ©lĂ©my', + 'Saint-Martin', 'Martinique', 'Guyane', 'La RĂ©union', 'Mayotte', + 'Nouvelle-CalĂ©donie', 'PolynĂ©sie Française', 'Wallis-et-Futuna' ]; -- Primary Loop t_years INTEGER[] := (SELECT ARRAY(SELECT generate_series(p_start_year, p_end_year))); @@ -61,6 +61,31 @@ DECLARE t_holiday holidays.holiday%rowtype; BEGIN + IF p_province ~ '^[0-9]{5}$' THEN + -- set province from postal code + IF LEFT(p_province, 2) IN ('57', '67', '68') THEN + p_province := 'Alsace-Moselle'; + ELSE + CASE LEFT(p_province, 3) + WHEN '971' THEN + IF p_province = '97133' + THEN p_province := 'Saint-BarthĂ©lĂ©my'; + ELSIF p_province = '97150' + THEN p_province := 'Saint-Martin'; + ELSE p_province := 'Guadeloupe'; + END IF; + WHEN '972' THEN p_province := 'Martinique'; + WHEN '973' THEN p_province := 'Guyane'; + WHEN '974' THEN p_province := 'La RĂ©union'; + WHEN '976' THEN p_province := 'Mayotte'; + WHEN '988' THEN p_province := 'Nouvelle-CalĂ©donie'; + WHEN '987' THEN p_province := 'PolynĂ©sie Française'; + WHEN '986' THEN p_province := 'Wallis-et-Futuna'; + ELSE p_province := 'MĂ©tropole'; + END CASE; + END IF; + END IF; + FOREACH t_year IN ARRAY t_years LOOP -- Defaults for additional attributes @@ -178,7 +203,7 @@ BEGIN t_holiday.description := 'DeuxiĂšme jour de NoĂ«l'; RETURN NEXT t_holiday; END IF; - + -- Citizenship Day t_holiday.reference := 'Citizenship Day'; IF p_province = 'Nouvelle-CalĂ©donie' THEN @@ -245,4 +270,4 @@ BEGIN END LOOP; END; -$$ LANGUAGE plpgsql; \ No newline at end of file +$$ LANGUAGE plpgsql; diff --git a/test/expected/base.out b/test/expected/base.out index 8f8cd97..4e10a40 100644 --- a/test/expected/base.out +++ b/test/expected/base.out @@ -27,4 +27,32 @@ SELECT * FROM holidays.by_country ('FR'::text, 2022, 2023); 12-25-2023 | Christmas Day | NoĂ«l | national | t | f | 00:00:00 | 24:00:00 (22 rows) +SELECT * FROM holidays.by_country ('FR'::text, '10/01/2022'::date, '05/31/2023'::date); + datestamp | reference | description | authority | day_off | observation_shifted | start_time | end_time +------------+-----------------------+--------------------+-----------+---------+---------------------+------------+---------- + 11-11-2022 | Remembrance Day | Armistice 1918 | national | t | f | 00:00:00 | 24:00:00 + 11-01-2022 | All Saints' Day | Toussaint | national | t | f | 00:00:00 | 24:00:00 + 12-25-2022 | Christmas Day | NoĂ«l | national | t | f | 00:00:00 | 24:00:00 + 01-01-2023 | New Year's Day | Jour de l'an | national | t | f | 00:00:00 | 24:00:00 + 05-01-2023 | Labour Day | FĂȘte du Travail | national | t | f | 00:00:00 | 24:00:00 + 05-08-2023 | Victory in Europe Day | Armistice 1945 | national | t | f | 00:00:00 | 24:00:00 + 04-10-2023 | Easter Monday | Lundi de PĂąques | national | t | f | 00:00:00 | 24:00:00 + 05-29-2023 | Pentecost | Lundi de PentecĂŽte | national | t | f | 00:00:00 | 24:00:00 + 05-18-2023 | Ascension | Ascension | national | t | f | 00:00:00 | 24:00:00 +(9 rows) + +SELECT * FROM holidays.by_country ('FR'::text, daterange('2022/10/01', '2023/05/31')); + datestamp | reference | description | authority | day_off | observation_shifted | start_time | end_time +------------+-----------------------+--------------------+-----------+---------+---------------------+------------+---------- + 11-11-2022 | Remembrance Day | Armistice 1918 | national | t | f | 00:00:00 | 24:00:00 + 11-01-2022 | All Saints' Day | Toussaint | national | t | f | 00:00:00 | 24:00:00 + 12-25-2022 | Christmas Day | NoĂ«l | national | t | f | 00:00:00 | 24:00:00 + 01-01-2023 | New Year's Day | Jour de l'an | national | t | f | 00:00:00 | 24:00:00 + 05-01-2023 | Labour Day | FĂȘte du Travail | national | t | f | 00:00:00 | 24:00:00 + 05-08-2023 | Victory in Europe Day | Armistice 1945 | national | t | f | 00:00:00 | 24:00:00 + 04-10-2023 | Easter Monday | Lundi de PĂąques | national | t | f | 00:00:00 | 24:00:00 + 05-29-2023 | Pentecost | Lundi de PentecĂŽte | national | t | f | 00:00:00 | 24:00:00 + 05-18-2023 | Ascension | Ascension | national | t | f | 00:00:00 | 24:00:00 +(9 rows) + ROLLBACK; diff --git a/test/expected/french_holiday_with_postal_code.out b/test/expected/french_holiday_with_postal_code.out new file mode 100644 index 0000000..6553ae1 --- /dev/null +++ b/test/expected/french_holiday_with_postal_code.out @@ -0,0 +1,109 @@ +BEGIN; +CREATE EXTENSION holidays; +SELECT * FROM holidays.by_country ('FR'::text, 2023, 2023, '14610'); + datestamp | reference | description | authority | day_off | observation_shifted | start_time | end_time +------------+-----------------------+--------------------+-----------+---------+---------------------+------------+---------- + 01-01-2023 | New Year's Day | Jour de l'an | national | t | f | 00:00:00 | 24:00:00 + 05-01-2023 | Labour Day | FĂȘte du Travail | national | t | f | 00:00:00 | 24:00:00 + 05-08-2023 | Victory in Europe Day | Armistice 1945 | national | t | f | 00:00:00 | 24:00:00 + 07-14-2023 | Bastille Day | FĂȘte nationale | national | t | f | 00:00:00 | 24:00:00 + 11-11-2023 | Remembrance Day | Armistice 1918 | national | t | f | 00:00:00 | 24:00:00 + 04-10-2023 | Easter Monday | Lundi de PĂąques | national | t | f | 00:00:00 | 24:00:00 + 05-29-2023 | Pentecost | Lundi de PentecĂŽte | national | t | f | 00:00:00 | 24:00:00 + 05-18-2023 | Ascension | Ascension | national | t | f | 00:00:00 | 24:00:00 + 08-15-2023 | Assumption | Assomption | national | t | f | 00:00:00 | 24:00:00 + 11-01-2023 | All Saints' Day | Toussaint | national | t | f | 00:00:00 | 24:00:00 + 12-25-2023 | Christmas Day | NoĂ«l | national | t | f | 00:00:00 | 24:00:00 +(11 rows) + +SELECT * FROM holidays.by_country ('FR'::text, 2023, 2023, 'Basse Normandie'); + datestamp | reference | description | authority | day_off | observation_shifted | start_time | end_time +------------+-----------------------+--------------------+-----------+---------+---------------------+------------+---------- + 01-01-2023 | New Year's Day | Jour de l'an | national | t | f | 00:00:00 | 24:00:00 + 05-01-2023 | Labour Day | FĂȘte du Travail | national | t | f | 00:00:00 | 24:00:00 + 05-08-2023 | Victory in Europe Day | Armistice 1945 | national | t | f | 00:00:00 | 24:00:00 + 07-14-2023 | Bastille Day | FĂȘte nationale | national | t | f | 00:00:00 | 24:00:00 + 11-11-2023 | Remembrance Day | Armistice 1918 | national | t | f | 00:00:00 | 24:00:00 + 04-10-2023 | Easter Monday | Lundi de PĂąques | national | t | f | 00:00:00 | 24:00:00 + 05-29-2023 | Pentecost | Lundi de PentecĂŽte | national | t | f | 00:00:00 | 24:00:00 + 05-18-2023 | Ascension | Ascension | national | t | f | 00:00:00 | 24:00:00 + 08-15-2023 | Assumption | Assomption | national | t | f | 00:00:00 | 24:00:00 + 11-01-2023 | All Saints' Day | Toussaint | national | t | f | 00:00:00 | 24:00:00 + 12-25-2023 | Christmas Day | NoĂ«l | national | t | f | 00:00:00 | 24:00:00 +(11 rows) + +SELECT * FROM holidays.by_country ('FR'::text, 2023, 2023, '67270'); + datestamp | reference | description | authority | day_off | observation_shifted | start_time | end_time +------------+-------------------------+-----------------------+-----------+---------+---------------------+------------+---------- + 01-01-2023 | New Year's Day | Jour de l'an | national | t | f | 00:00:00 | 24:00:00 + 05-01-2023 | Labour Day | FĂȘte du Travail | national | t | f | 00:00:00 | 24:00:00 + 05-08-2023 | Victory in Europe Day | Armistice 1945 | national | t | f | 00:00:00 | 24:00:00 + 07-14-2023 | Bastille Day | FĂȘte nationale | national | t | f | 00:00:00 | 24:00:00 + 11-11-2023 | Remembrance Day | Armistice 1918 | national | t | f | 00:00:00 | 24:00:00 + 04-07-2023 | Good Friday | Vendredi saint | national | t | f | 00:00:00 | 24:00:00 + 04-10-2023 | Easter Monday | Lundi de PĂąques | national | t | f | 00:00:00 | 24:00:00 + 05-29-2023 | Pentecost | Lundi de PentecĂŽte | national | t | f | 00:00:00 | 24:00:00 + 05-18-2023 | Ascension | Ascension | national | t | f | 00:00:00 | 24:00:00 + 08-15-2023 | Assumption | Assomption | national | t | f | 00:00:00 | 24:00:00 + 11-01-2023 | All Saints' Day | Toussaint | national | t | f | 00:00:00 | 24:00:00 + 12-25-2023 | Christmas Day | Premier jour de NoĂ«l | national | t | f | 00:00:00 | 24:00:00 + 12-26-2023 | Second Day of Christmas | DeuxiĂšme jour de NoĂ«l | national | t | f | 00:00:00 | 24:00:00 +(13 rows) + +SELECT * FROM holidays.by_country ('FR'::text, 2023, 2023, 'Alsace-Moselle'); + datestamp | reference | description | authority | day_off | observation_shifted | start_time | end_time +------------+-------------------------+-----------------------+-----------+---------+---------------------+------------+---------- + 01-01-2023 | New Year's Day | Jour de l'an | national | t | f | 00:00:00 | 24:00:00 + 05-01-2023 | Labour Day | FĂȘte du Travail | national | t | f | 00:00:00 | 24:00:00 + 05-08-2023 | Victory in Europe Day | Armistice 1945 | national | t | f | 00:00:00 | 24:00:00 + 07-14-2023 | Bastille Day | FĂȘte nationale | national | t | f | 00:00:00 | 24:00:00 + 11-11-2023 | Remembrance Day | Armistice 1918 | national | t | f | 00:00:00 | 24:00:00 + 04-07-2023 | Good Friday | Vendredi saint | national | t | f | 00:00:00 | 24:00:00 + 04-10-2023 | Easter Monday | Lundi de PĂąques | national | t | f | 00:00:00 | 24:00:00 + 05-29-2023 | Pentecost | Lundi de PentecĂŽte | national | t | f | 00:00:00 | 24:00:00 + 05-18-2023 | Ascension | Ascension | national | t | f | 00:00:00 | 24:00:00 + 08-15-2023 | Assumption | Assomption | national | t | f | 00:00:00 | 24:00:00 + 11-01-2023 | All Saints' Day | Toussaint | national | t | f | 00:00:00 | 24:00:00 + 12-25-2023 | Christmas Day | Premier jour de NoĂ«l | national | t | f | 00:00:00 | 24:00:00 + 12-26-2023 | Second Day of Christmas | DeuxiĂšme jour de NoĂ«l | national | t | f | 00:00:00 | 24:00:00 +(13 rows) + +SELECT * FROM holidays.by_country ('FR'::text, 2023, 2023, '97160'); + datestamp | reference | description | authority | day_off | observation_shifted | start_time | end_time +------------+-----------------------+--------------------------+-----------+---------+---------------------+------------+---------- + 01-01-2023 | New Year's Day | Jour de l'an | national | t | f | 00:00:00 | 24:00:00 + 05-01-2023 | Labour Day | FĂȘte du Travail | national | t | f | 00:00:00 | 24:00:00 + 05-08-2023 | Victory in Europe Day | Armistice 1945 | national | t | f | 00:00:00 | 24:00:00 + 07-14-2023 | Bastille Day | FĂȘte nationale | national | t | f | 00:00:00 | 24:00:00 + 11-11-2023 | Remembrance Day | Armistice 1918 | national | t | f | 00:00:00 | 24:00:00 + 04-07-2023 | Good Friday | Vendredi saint | national | t | f | 00:00:00 | 24:00:00 + 04-10-2023 | Easter Monday | Lundi de PĂąques | national | t | f | 00:00:00 | 24:00:00 + 05-29-2023 | Pentecost | Lundi de PentecĂŽte | national | t | f | 00:00:00 | 24:00:00 + 05-18-2023 | Ascension | Ascension | national | t | f | 00:00:00 | 24:00:00 + 08-15-2023 | Assumption | Assomption | national | t | f | 00:00:00 | 24:00:00 + 11-01-2023 | All Saints' Day | Toussaint | national | t | f | 00:00:00 | 24:00:00 + 12-25-2023 | Christmas Day | NoĂ«l | national | t | f | 00:00:00 | 24:00:00 + 05-27-2023 | Abolition of Slavery | Abolition de l'esclavage | national | t | f | 00:00:00 | 24:00:00 + 07-21-2023 | Abolition of Slavery | FĂȘte Victor Schoelcher | national | t | f | 00:00:00 | 24:00:00 +(14 rows) + +SELECT * FROM holidays.by_country ('FR'::text, 2023, 2023, 'Guadeloupe'); + datestamp | reference | description | authority | day_off | observation_shifted | start_time | end_time +------------+-----------------------+--------------------------+-----------+---------+---------------------+------------+---------- + 01-01-2023 | New Year's Day | Jour de l'an | national | t | f | 00:00:00 | 24:00:00 + 05-01-2023 | Labour Day | FĂȘte du Travail | national | t | f | 00:00:00 | 24:00:00 + 05-08-2023 | Victory in Europe Day | Armistice 1945 | national | t | f | 00:00:00 | 24:00:00 + 07-14-2023 | Bastille Day | FĂȘte nationale | national | t | f | 00:00:00 | 24:00:00 + 11-11-2023 | Remembrance Day | Armistice 1918 | national | t | f | 00:00:00 | 24:00:00 + 04-07-2023 | Good Friday | Vendredi saint | national | t | f | 00:00:00 | 24:00:00 + 04-10-2023 | Easter Monday | Lundi de PĂąques | national | t | f | 00:00:00 | 24:00:00 + 05-29-2023 | Pentecost | Lundi de PentecĂŽte | national | t | f | 00:00:00 | 24:00:00 + 05-18-2023 | Ascension | Ascension | national | t | f | 00:00:00 | 24:00:00 + 08-15-2023 | Assumption | Assomption | national | t | f | 00:00:00 | 24:00:00 + 11-01-2023 | All Saints' Day | Toussaint | national | t | f | 00:00:00 | 24:00:00 + 12-25-2023 | Christmas Day | NoĂ«l | national | t | f | 00:00:00 | 24:00:00 + 05-27-2023 | Abolition of Slavery | Abolition de l'esclavage | national | t | f | 00:00:00 | 24:00:00 + 07-21-2023 | Abolition of Slavery | FĂȘte Victor Schoelcher | national | t | f | 00:00:00 | 24:00:00 +(14 rows) + +ROLLBACK; diff --git a/test/sql/base.sql b/test/sql/base.sql index fb36c05..6f8d961 100644 --- a/test/sql/base.sql +++ b/test/sql/base.sql @@ -4,4 +4,8 @@ CREATE EXTENSION holidays; SELECT * FROM holidays.by_country ('FR'::text, 2022, 2023); +SELECT * FROM holidays.by_country ('FR'::text, '10/01/2022'::date, '05/31/2023'::date); + +SELECT * FROM holidays.by_country ('FR'::text, daterange('2022/10/01', '2023/05/31')); + ROLLBACK; diff --git a/test/sql/french_holiday_with_postal_code.sql b/test/sql/french_holiday_with_postal_code.sql new file mode 100644 index 0000000..80d9788 --- /dev/null +++ b/test/sql/french_holiday_with_postal_code.sql @@ -0,0 +1,17 @@ +BEGIN; + +CREATE EXTENSION holidays; + +SELECT * FROM holidays.by_country ('FR'::text, 2023, 2023, '14610'); + +SELECT * FROM holidays.by_country ('FR'::text, 2023, 2023, 'Basse Normandie'); + +SELECT * FROM holidays.by_country ('FR'::text, 2023, 2023, '67270'); + +SELECT * FROM holidays.by_country ('FR'::text, 2023, 2023, 'Alsace-Moselle'); + +SELECT * FROM holidays.by_country ('FR'::text, 2023, 2023, '97160'); + +SELECT * FROM holidays.by_country ('FR'::text, 2023, 2023, 'Guadeloupe'); + +ROLLBACK;