From 4c49f4216e33d50b4cbbe2f37055d21d30379a90 Mon Sep 17 00:00:00 2001 From: Tatiana Al-Chueyr Date: Wed, 16 Apr 2025 09:53:49 +0100 Subject: [PATCH] Add sample dbt_packages to validate incremental dbt deps Dependency for #1668 Related to: #1630 --- .gitignore | 2 +- .pre-commit-config.yaml | 1 + .../dbt_date/.circleci/config.yml | 181 ++++ .../.github/ISSUE_TEMPLATE/bug_report.md | 39 + .../simple/dbt_packages/dbt_date/.gitignore | 7 + .../dbt_date/.pre-commit-config.yaml | 27 + .../simple/dbt_packages/dbt_date/CHANGELOG.md | 173 ++++ .../dbt/simple/dbt_packages/dbt_date/LICENSE | 21 + .../simple/dbt_packages/dbt_date/README.md | 843 ++++++++++++++++++ .../dbt_packages/dbt_date/dbt_project.yml | 19 + .../images/xebia-logo-large-transparent.png | Bin 0 -> 83213 bytes .../integration_tests/ci/profiles.yml | 58 ++ .../integration_tests/dbt_project.yml | 39 + .../integration_tests/docker-compose.yml | 27 + .../integration_tests/docker-start.sh | 1 + .../dbt_date/integration_tests/docker-stop.sh | 1 + .../integration_tests/docker/hive-site.xml | 46 + .../docker/spark-defaults.conf | 9 + .../docker/trino/catalog/memory.properties | 2 + .../macros/expression_is_true.sql | 23 + .../macros/get_custom_schema.sql | 10 + .../macros/get_test_dates.sql | 206 +++++ .../integration_tests/models/dates.sql | 2 + .../integration_tests/models/dim_date.sql | 19 + .../models/dim_date_fiscal.sql | 11 + .../integration_tests/models/dim_hour.sql | 7 + .../integration_tests/models/dim_week.sql | 4 + .../integration_tests/models/test_dates.sql | 1 + .../integration_tests/models/test_dates.yml | 75 ++ .../dbt_date/integration_tests/packages.yml | 2 + .../dbt_date/integration_tests/test.sh | 14 + .../dbt_date/macros/_utils/date_spine.sql | 82 ++ .../macros/_utils/generate_series.sql | 55 ++ .../macros/_utils/modules_datetime.sql | 23 + .../macros/calendar_date/convert_timezone.sql | 52 ++ .../macros/calendar_date/date_part.sql | 15 + .../macros/calendar_date/day_name.sql | 56 ++ .../macros/calendar_date/day_of_month.sql | 5 + .../macros/calendar_date/day_of_week.sql | 100 +++ .../macros/calendar_date/day_of_year.sql | 21 + .../calendar_date/from_unixtimestamp.sql | 110 +++ .../macros/calendar_date/iso_week_end.sql | 16 + .../macros/calendar_date/iso_week_of_year.sql | 34 + .../macros/calendar_date/iso_week_start.sql | 32 + .../macros/calendar_date/last_month.sql | 1 + .../macros/calendar_date/last_month_name.sql | 3 + .../calendar_date/last_month_number.sql | 3 + .../macros/calendar_date/last_week.sql | 1 + .../macros/calendar_date/month_name.sql | 36 + .../macros/calendar_date/n_days_ago.sql | 5 + .../macros/calendar_date/n_days_away.sql | 3 + .../macros/calendar_date/n_months_ago.sql | 4 + .../macros/calendar_date/n_months_away.sql | 4 + .../macros/calendar_date/n_weeks_ago.sql | 4 + .../macros/calendar_date/n_weeks_away.sql | 4 + .../macros/calendar_date/next_month.sql | 1 + .../macros/calendar_date/next_month_name.sql | 3 + .../calendar_date/next_month_number.sql | 3 + .../macros/calendar_date/next_week.sql | 1 + .../dbt_date/macros/calendar_date/now.sql | 3 + .../macros/calendar_date/periods_since.sql | 3 + .../macros/calendar_date/round_timestamp.sql | 3 + .../macros/calendar_date/to_unixtimestamp.sql | 23 + .../dbt_date/macros/calendar_date/today.sql | 1 + .../macros/calendar_date/tomorrow.sql | 3 + .../macros/calendar_date/week_end.sql | 18 + .../macros/calendar_date/week_of_year.sql | 23 + .../macros/calendar_date/week_start.sql | 32 + .../macros/calendar_date/yesterday.sql | 3 + .../macros/fiscal_date/get_fiscal_periods.sql | 81 ++ .../fiscal_date/get_fiscal_year_dates.sql | 114 +++ .../dbt_date/macros/get_base_dates.sql | 109 +++ .../dbt_date/macros/get_date_dimension.sql | 180 ++++ .../simple/dbt_packages/dbt_date/packages.yml | 0 dev/dags/dbt/simple/package-lock.yml | 4 + dev/dags/dbt/simple/packages.yml | 3 + 76 files changed, 3149 insertions(+), 1 deletion(-) create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/.circleci/config.yml create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/.github/ISSUE_TEMPLATE/bug_report.md create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/.gitignore create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/.pre-commit-config.yaml create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/CHANGELOG.md create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/LICENSE create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/README.md create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/dbt_project.yml create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/images/xebia-logo-large-transparent.png create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/ci/profiles.yml create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/dbt_project.yml create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/docker-compose.yml create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/docker-start.sh create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/docker-stop.sh create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/docker/hive-site.xml create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/docker/spark-defaults.conf create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/docker/trino/catalog/memory.properties create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/macros/expression_is_true.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/macros/get_custom_schema.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/macros/get_test_dates.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/dates.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/dim_date.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/dim_date_fiscal.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/dim_hour.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/dim_week.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/test_dates.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/test_dates.yml create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/packages.yml create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/test.sh create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/_utils/date_spine.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/_utils/generate_series.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/_utils/modules_datetime.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/convert_timezone.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/date_part.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/day_name.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/day_of_month.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/day_of_week.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/day_of_year.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/from_unixtimestamp.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/iso_week_end.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/iso_week_of_year.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/iso_week_start.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/last_month.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/last_month_name.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/last_month_number.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/last_week.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/month_name.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_days_ago.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_days_away.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_months_ago.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_months_away.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_weeks_ago.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_weeks_away.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/next_month.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/next_month_name.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/next_month_number.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/next_week.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/now.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/periods_since.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/round_timestamp.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/to_unixtimestamp.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/today.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/tomorrow.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/week_end.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/week_of_year.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/week_start.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/yesterday.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/fiscal_date/get_fiscal_periods.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/fiscal_date/get_fiscal_year_dates.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/get_base_dates.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/macros/get_date_dimension.sql create mode 100644 dev/dags/dbt/simple/dbt_packages/dbt_date/packages.yml create mode 100644 dev/dags/dbt/simple/package-lock.yml create mode 100644 dev/dags/dbt/simple/packages.yml diff --git a/.gitignore b/.gitignore index 5991c231ce..0af8bcb446 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ docs/profiles/* # dbt_packages is a directory that gets created when you run dbt deps -dbt_packages/ +dev/dags/dbt/jaffle_shop/dbt_packages/ # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3ba224730e..a3d7822a9e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -91,6 +91,7 @@ repos: apache-airflow, ] files: ^cosmos +exclude: "dev/dags/dbt/simple/dbt_packages/.*" ci: autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/.circleci/config.yml b/dev/dags/dbt/simple/dbt_packages/dbt_date/.circleci/config.yml new file mode 100644 index 0000000000..8c4238f4d5 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/.circleci/config.yml @@ -0,0 +1,181 @@ +version: 2.1 + +jobs: + integration-tests-core: + docker: + - image: cimg/python:3.9.9 + - image: cimg/postgres:14.0 + + resource_class: small + + environment: + DBT_PROFILES_DIR: ./integration_tests/ci + DBT_PROJECT_DIR: ./integration_tests + BIGQUERY_SERVICE_KEY_PATH: "/home/circleci/bigquery-service-key.json" + DBT_VERSION: 1.8.* + + steps: + - checkout + - run: &pip-install-core + name: Install core Python packages & dbt-core + command: | + python3 -m venv venv + . venv/bin/activate + pip install -U pip setuptools wheel + pip install "dbt-core==$DBT_VERSION" + + - run: + name: Install dbt adapter packages + command: | + python3 -m venv venv + . venv/bin/activate + pip install "dbt-postgres==$DBT_VERSION" "dbt-bigquery==$DBT_VERSION" "dbt-snowflake==$DBT_VERSION" + pip install "dbt-duckdb==$DBT_VERSION" + + - run: &dbt-deps + name: Install dbt dependencies + command: | + . venv/bin/activate + dbt deps --project-dir $DBT_PROJECT_DIR + + - run: + name: "Run Tests - Postgres" + environment: + POSTGRES_HOST: localhost + POSTGRES_TEST_USER: postgres + POSTGRES_TEST_PASSWORD: "" + POSTGRES_TEST_PORT: 5432 + POSTGRES_TEST_DATABASE: circle_test + POSTGRES_TEST_SCHEMA: dbt_date_integration_tests + command: | + . venv/bin/activate + dbt build -t postgres --project-dir $DBT_PROJECT_DIR + + - run: + name: "Set up GCP credentials" + command: | + echo "Writing to $BIGQUERY_SERVICE_KEY_PATH" + echo $BIGQUERY_SERVICE_KEY > $BIGQUERY_SERVICE_KEY_PATH + FILESIZE=$(stat -c%s "$BIGQUERY_SERVICE_KEY_PATH") + echo "Size of $BIGQUERY_SERVICE_KEY_PATH = $FILESIZE bytes." + echo "BIGQUERY_TEST_DATABASE = $BIGQUERY_TEST_DATABASE" + + - run: + name: "Run Tests - BigQuery" + command: | + . venv/bin/activate + dbt build -t bigquery --project-dir $DBT_PROJECT_DIR + + - run: + name: "Run Tests - Snowflake" + command: | + . venv/bin/activate + dbt build -t snowflake --project-dir $DBT_PROJECT_DIR + + - run: + name: "Run Tests - DuckDB" + command: | + . venv/bin/activate + dbt build -t duckdb --project-dir $DBT_PROJECT_DIR + + - store_artifacts: + path: ./logs + + integration-tests-spark-thrift: + docker: + - image: cimg/python:3.9.9 + - image: godatadriven/spark:3.1.1 + environment: + WAIT_FOR: localhost:5432 + command: > + --class org.apache.spark.sql.hive.thriftserver.HiveThriftServer2 + --name Thrift JDBC/ODBC Server + - image: postgres:9.6.17-alpine + environment: + POSTGRES_USER: dbt + POSTGRES_PASSWORD: dbt + POSTGRES_DB: metastore + + resource_class: small + + environment: + DBT_PROFILES_DIR: ./integration_tests/ci + DBT_PROJECT_DIR: ./integration_tests + DBT_VERSION: 1.8.* + + steps: + - checkout + - run: + name: Install Ubuntu packages + command: | + sudo apt-get update + sudo apt-get install libsasl2-dev libsasl2-2 + - run: *pip-install-core + - run: + name: Install dbt adapter packages + command: | + python3 -m venv venv + . venv/bin/activate + pip install "dbt-spark==$DBT_VERSION" "dbt-spark[PyHive]==$DBT_VERSION" + - run: *dbt-deps + - run: + name: Wait for Spark-Thrift + command: dockerize -wait tcp://localhost:10000 -timeout 15m -wait-retry-interval 5s + - run: + name: "Run Tests - Spark" + command: | + . venv/bin/activate + dbt build -t spark --project-dir $DBT_PROJECT_DIR + + - store_artifacts: + path: ./logs + + integration-tests-trino: + docker: + - image: cimg/python:3.11 + - image: trinodb/trino:431 + + resource_class: small + + environment: + DBT_PROFILES_DIR: ./integration_tests/ci + DBT_PROJECT_DIR: ./integration_tests + DBT_VERSION: 1.8.* + + steps: + - checkout + - run: *pip-install-core + - run: + name: Install dbt adapter packages + command: | + python3 -m venv venv + . venv/bin/activate + pip install "dbt-trino==$DBT_VERSION" + - run: *dbt-deps + - setup_remote_docker + - run: + name: Run Trino server + command: | + docker run --name trino -p 8080:8080 -d -v `pwd`/integration_tests/docker/trino/catalog:/etc/trino/catalog trinodb/trino:431 + timeout 5m bash -c -- 'while ! docker logs trino 2>&1 | tail -n 1 | grep "SERVER STARTED"; do sleep 2; done' + - run: + name: "Run Tests - Trino" + command: | + . venv/bin/activate + dbt build -t trino --project-dir $DBT_PROJECT_DIR + +workflows: + version: 2 + test-all: + jobs: + - hold: + type: approval + - integration-tests-core: + requires: + - hold + - integration-tests-spark-thrift: + requires: + - hold + - integration-tests-trino: + requires: + - hold diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/.github/ISSUE_TEMPLATE/bug_report.md b/dev/dags/dbt/simple/dbt_packages/dbt_date/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..6e29388018 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,39 @@ +--- +name: Bug report +about: I think I've found a bug +title: "[BUG]" +labels: "" +assignees: "" +--- + +### Is this a new bug in dbt-date? + +- [ ] I believe this is a new bug in dbt-date +- [ ] I have searched the existing issues, and I could not find an existing issue for this bug + +### Current Behavior + +### Expected Behavior + +### Steps To Reproduce + +### Relevant log output + +### Environment + +```markdown +- OS: +- Python: +- dbt: +- dbt-expectations: +``` + +### Which database adapter are you using with dbt? + +Note: dbt-date currently does not support database adapters other than the ones listed below. + +- Postgres +- Snowflake +- BigQuery + +### Additional Context diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/.gitignore b/dev/dags/dbt/simple/dbt_packages/dbt_date/.gitignore new file mode 100644 index 0000000000..404fb93a8d --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/.gitignore @@ -0,0 +1,7 @@ + +target/ +dbt_packages/ +logs/ +.python-version +integration_tests/.spark-warehouse +integration_tests/.hive-metastore diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/.pre-commit-config.yaml b/dev/dags/dbt/simple/dbt_packages/dbt_date/.pre-commit-config.yaml new file mode 100644 index 0000000000..adac6499fe --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/.pre-commit-config.yaml @@ -0,0 +1,27 @@ +--- +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: check-json + - id: check-ast + - id: check-merge-conflict + - id: check-toml + - id: check-yaml + args: [--unsafe] + - id: debug-statements + - id: detect-private-key + - id: end-of-file-fixer + exclude: macros/calendar_date/week_of_year.sql + - repo: https://github.com/pre-commit/mirrors-prettier + rev: "v3.1.0" + hooks: + - id: prettier + types_or: [json, markdown, yaml] + - repo: https://github.com/tconbeer/sqlfmt + rev: v0.26.0 + hooks: + - id: sqlfmt + language_version: python + additional_dependencies: [".[jinjafmt]"] diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/CHANGELOG.md b/dev/dags/dbt/simple/dbt_packages/dbt_date/CHANGELOG.md new file mode 100644 index 0000000000..2dfc590223 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/CHANGELOG.md @@ -0,0 +1,173 @@ +# dbt-date v0.10.1 + +## Fixes + +- Change marco month_name.sql by @flixflop in https://github.com/calogica/dbt-date/pull/123 + +# dbt-date v0.10.0 + +## New Features + +- Added Trino support by @damian3031 in https://github.com/calogica/dbt-date/pull/115 + +# dbt-date v0.9.2 + +## New Features + +- Add date() and datetime() short hand macros by @gregology in https://github.com/calogica/dbt-date/pull/112 + +# dbt-date v0.9.1 + +## Fixes + +- Fixed dataspine interval by @clausherther in https://github.com/calogica/dbt-date/pull/109 + +# dbt-date v0.9.0 + +## New Features + +- Add Spark support by @clausherther in https://github.com/calogica/dbt-date/pull/108 + +**Full Changelog**: https://github.com/calogica/dbt-date/compare/0.8.1...0.9.0 + +# dbt-date v0.8.0 + +## New Features + +- Add support for dbt-duckdb to the dbt_date package by @jwills in https://github.com/calogica/dbt-date/pull/105 + +## Docs + +- Update README.md by @JordanZimmitti in https://github.com/calogica/dbt-date/pull/99 +- Update README.md by @tgmof in https://github.com/calogica/dbt-date/pull/104 + +**Full Changelog**: https://github.com/calogica/dbt-date/compare/0.7.2...0.8.0 + +# dbt-date v0.7.2 + +## Fixes + +- Update refs to dbt-core macros by @clausherther in https://github.com/calogica/dbt-date/pull/97 +- Add dbt prefix to current_timestamp macro by @GtheSheep in https://github.com/calogica/dbt-date/pull/98 + +# dbt-date v0.7.1 + +- Fix calls to last_month and next_month by @clausherther in https://github.com/calogica/dbt-date/pull/95 + +# dbt-date v0.7.0 + +## Breaking Changes + +- Removed dependency on dbt-utils by @clausherther in https://github.com/calogica/dbt-date/pull/91 + +# dbt-date v0.6.3 + +- Switch to dbt-core's implementation of current_timestamp() by @clausherther in https://github.com/calogica/dbt-date/pull/88 + +# dbt-date v0.6.2 + +- Simplify convert from `source_tz` by @clausherther in https://github.com/calogica/dbt-date/pull/86 +- Fix issue of convert_timezone converting timestamps twice for timestamp_tz date types (issue #85, #77, #76 ) + +# dbt-date v0.6.1 + +- New: added `round_timestamp` macro by @jpmmcneill in https://github.com/calogica/dbt-date/pull/84 + +# dbt-date v0.6.0 + +- Move to dbt-utils 0.9.0 +- Remove references to deprecated dbt-utils cross-db macros by @clausherther in https://github.com/calogica/dbt-date/pull/79 + +# dbt-date v0.5.7 + +- Add github actions workflow by @clausherther in https://github.com/calogica/dbt-date/pull/69 +- Fix Redshift timezone conversion macro by @wellykachtel in https://github.com/calogica/dbt-date/pull/71 + +# dbt-date v0.5.6 + +- Fix missing bracket by @clausherther in https://github.com/calogica/dbt-date/pull/66 + +# dbt-date v0.5.5 + +- Fix README table of contents' links by @coisnepe [#61](https://github.com/calogica/dbt-date/pull/61) +- Fix timezone conversion macro on redshift by @msnidal [#63](https://github.com/calogica/dbt-date/pull/63) + +# dbt-date v0.5.4 + +- Updated Documentation [#60](https://github.com/calogica/dbt-date/pull/60) + +# dbt-date v0.5.3 + +- Allow negative shift year for fiscal periods ([#57](https://github.com/calogica/dbt-date/issues/57) [@boludo00](https://github.com/boludo00)) + +# dbt-date v0.5.2 + +- Fix [#55](https://github.com/calogica/dbt-date/issues/55) by removing dead macro. + +# dbt-date v0.5.1 + +- Fix `week_start` and `week_end` on Snowflake ([#53](https://github.com/calogica/dbt-date/issues/53), [#54](https://github.com/calogica/dbt-date/pull/54)) + +# dbt-date v0.5.0 + +- Deprecates support for dbt < 1.0.0 + +# dbt-date v0.4.2 + +## Under the hood + +- Patch: adds support for dbt 1.x + +# dbt-date v0.4.1 + +## Under the hood + +- Support for dbt 0.21.x + +# dbt-date v0.4.0 + +## Breaking Changes + +- Updates calls to adapter.dispatch to support `dbt >= 0.20` (see [Changes to dispatch in dbt v0.20 #34](https://github.com/calogica/dbt-date/issues/34)) + +- Requires `dbt >= 0.20` + +## Under the hood + +- Adds tests for timestamp and timezone macros (previously untested, new dbt version highlighted that) + +# dbt-date v0.3.1 + +_Patch release_ + +## Fixes + +- Fixed a bug in `snowflake__from_unixtimestamp` that prevented the core functionaility from being called ([#38](https://github.com/calogica/dbt-date/pull/38) by @swanderz) + +## Under the hood + +- Simplified `join` syntax ([#36](https://github.com/calogica/dbt-date/pull/36)) + +# dbt-date v0.3.0 + +## Breaking Changes + +- Switched `day_of_week` column in `get_date_dimension` from ISO to _not_ ISO to align with the rest of the package. [#33](https://github.com/calogica/dbt-date/pull/33) (@davesgonechina) + +## Features + +- Added `day_of_week_iso` column to `get_date_dimension` [#33](https://github.com/calogica/dbt-date/pull/33) (@davesgonechina) + +- Added `prior_year_iso_week_of_year` column to `get_date_dimension` + +## Fixes + +- Refactored Snowflake's `day_name` to not be ISO dependent [#33](https://github.com/calogica/dbt-date/pull/33) (@davesgonechina) + +- Fixed data types for `day_of_*` attributes in Redshift ([#28](https://github.com/calogica/dbt-date/pull/28) by @sparcs) + +- Fixed / added support for date parts other than `day` in `get_base_dates` ([#30](https://github.com/calogica/dbt-date/pull/30)) + +## Under the hood + +- Making it easier to shim macros for other platforms ([#27](https://github.com/calogica/dbt-date/pull/27) by @swanderz) diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/LICENSE b/dev/dags/dbt/simple/dbt_packages/dbt_date/LICENSE new file mode 100644 index 0000000000..956ea65b7a --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Pádraic Slattery + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/README.md b/dev/dags/dbt/simple/dbt_packages/dbt_date/README.md new file mode 100644 index 0000000000..466662b5ee --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/README.md @@ -0,0 +1,843 @@ +
+ + + + + + + + + +
+ +# dbt-date + +`dbt-date` is an extension package for [**dbt**](https://github.com/dbt-labs/dbt) to handle common date logic and calendar functionality. + +Development of `dbt-date` is supported by [Xebia Data](https://xebia.com/digital-transformation/data-and-ai/) (formerly known as GoDataDriven): + +
+
+ Xebia logo +
+
+ +## Install + +Include in `packages.yml` + +```yaml +packages: + - package: godatadriven/dbt_date + version: 0.10.1 + # for the latest version tag +``` + +This package supports: + +- Postgres +- Snowflake +- BigQuery +- DuckDB +- Spark +- Trino + +## Variables + +The following variables need to be defined in your `dbt_project.yml` file: + +```yaml +vars: + "dbt_date:time_zone": "America/Los_Angeles" +``` + +You may specify [any valid timezone string](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) in place of `America/Los_Angeles`. +For example, use `America/New_York` for East Coast Time. + +## Available Macros + +### Date Dimension + +- [get_base_dates](#get_base_datesstart_datenone-end_datenone-n_datepartsnone-datepartday) +- [get_date_dimension](#get_date_dimensionstart_date-end_date) + +### Calendar Date + +- [convert_timezone](#convert_timezone-column-target_tznone-source_tznone) +- [date_part](#date_partdatepart-date) +- [day_name](#day_namedate-shorttrue) +- [day_of_month](#day_of_monthdate) +- [day_of_week](#day_of_weekdate-isoweektrue) +- [day_of_year](#day_of_yeardate) +- [from_unixtimestamp](#from_unixtimestampepochs-formatseconds) +- [iso_week_end](#iso_week_enddatenone-tznone) +- [iso_week_of_year](#iso_week_of_yeardatenone-tznone) +- [iso_week_start](#iso_week_startdatenone-tznone) +- [last_month_name](#last_month_nameshorttrue-tznone) +- [last_month_number](#last_month_numbertznone) +- [last_month](#last_monthtznone) +- [last_week](#last_weektznone) +- [month_name](#month_namedate-shorttrue-tznone) +- [n_days_ago](#n_days_agon-datenone-tznone) +- [n_days_away](#n_days_awayn-datenone-tznone) +- [n_months_ago](#n_months_agon-tznone) +- [n_months_away](#n_months_awayn-tznone) +- [n_weeks_ago](#n_weeks_agon-tznone) +- [n_weeks_away](#n_weeks_awayn-tznone) +- [next_month_name](#next_month_nameshorttrue-tznone) +- [next_month_number](#next_month_numbertznone) +- [next_month](#next_monthtznone) +- [next_week](#next_weektznone) +- [now](#nowtznone) +- [periods_since](#periods_sincedate_col-period_nameday-tznone) +- [round_timestamp](#round_timestamptimestamp) +- [to_unixtimestamp](#to_unixtimestamptimestamp) +- [today](#todaytznone) +- [tomorrow](#tomorrowdatenone-tznone) +- [week_end](#week_enddatenone-tznone) +- [week_of_year](#week_of_yeardatenone-tznone) +- [week_start](#week_startdatenone-tznone) +- [yesterday](#yesterdaydatenone-tznone) + +## Fiscal Date + +- [get_fiscal_periods](#get_fiscal_periodsdates-year_end_month-week_start_day-shift_year1) + +## Utils + +- [date](#dateyear-month-day) +- [datetime](#datetimeyear-month-day-hour0-minute0-second0-microsecond0-tznone) + +## Documentation + +### [get_base_dates](macros/get_base_dates.sql)(`start_date=None, end_date=None, n_dateparts=None, datepart="day"`) + +A wrapper around [`dbt_utils.date_spine`](https://github.com/dbt-labs/dbt-utils#date_spine-source) that allows you to specify either `start_date` and `end_date` for your date spine, or specify a number of periods (`n_dateparts`) in the past from today. + +Usage to build a daily date dimension for the years 2015 to 2022: + +```sql +{{ dbt_date.get_base_dates(start_date="2015-01-01", end_date="2023-01-01") }} +``` + +or to build a daily date dimension for the last 3 years: + +```sql +{{ dbt_date.get_base_dates(n_dateparts=365*3, datepart="day") }} +``` + +### [get_date_dimension](macros/get_date_dimension.sql)(`start_date, end_date`) + +Returns a query to build date dimension from/to specified dates, including a number of useful columns based on each date. +See the [example model](integration_tests/models/dim_date.sql) for details. + +Usage: + +```sql +{{ dbt_date.get_date_dimension("2015-01-01", "2022-12-31") }} +``` + +### Fiscal Periods + +### [get_fiscal_periods](macros/fiscal_date/get_fiscal_periods.sql)(`dates, year_end_month, week_start_day, shift_year=1`) + +Returns a query to build a fiscal period calendar based on the 4-5-4 week retail period concept. +See the [example model](integration_tests/models/dim_date_fiscal.sql) for details and this [blog post](https://calogica.com/sql/dbt/2018/11/15/retail-calendar-in-sql.html) for more context on custom business calendars. + +Usage: + +```sql +{{ dbt_date.get_fiscal_periods(ref("dates"), year_end_month, week_start_day) }} +``` + +Note: the first parameter expects a dbt `ref` variable, i.e. a reference to a model containing the necessary date dimension attributes, which can be generated via the `get_date_dimension` macro (see above). + +### Date + +### [convert_timezone](macros/calendar_date/convert_timezone.sql)( `column, target_tz=None, source_tz=None`) + +Cross-database implemention of convert_timezone function. + +Usage: + +```sql +{{ dbt_date.convert_timezone("my_column") }} +``` + +or, specify a target timezone: + +```sql +{{ dbt_date.convert_timezone("my_column", "America/New_York") }} +``` + +or, also specify a source timezone: + +```sql +{{ dbt_date.convert_timezone("my_column", "America/New_York", "UTC") }} +``` + +Using named parameters, we can also specify the source only and rely on the configuration parameter for the target: + +```sql +{{ dbt_date.convert_timezone("my_column", source_tz="UTC") }} +``` + +### [date_part](macros/calendar_date/date_part.sql)(`datepart, date`) + +Extracts date parts from date. + +Usage: + +```sql +{{ dbt_date.date_part("dayofweek", "date_col") }} as day_of_week +``` + +### [day_name](macros/calendar_date/day_name.sql)(`date, short=True`) + +Extracts name of weekday from date. + +Usage: + +```sql +{{ dbt_date.day_name("date_col") }} as day_of_week_short_name +``` + +```sql +{{ dbt_date.day_name("date_col", short=true) }} as day_of_week_short_name +``` + +```sql +{{ dbt_date.day_name("date_col", short=false) }} as day_of_week_long_name +``` + +### [day_of_month](macros/calendar_date/day_of_month.sql)(`date`) + +Extracts day of the month from a date (e.g. `2022-03-06` --> `6`). + +Usage: + +```sql +{{ dbt_date.day_of_month("date_col") }} as day_of_month +``` + +### [day_of_week](macros/calendar_date/day_of_week.sql)(`date, isoweek=true`) + +Extracts day of the week _number_ from a date, starting with **1**. +By default, uses `isoweek=True`, i.e. assumes week starts on _Monday_. + +Usage: + +```sql +{{ dbt_date.day_of_week("'2022-03-06'") }} as day_of_week_iso +``` + +returns: **7** (Sunday is the _last_ day of the ISO week) + +```sql +{{ dbt_date.day_of_week("'2022-03-06'", isoweek=False) }} as day_of_week +``` + +returns: **1** (Sunday is the _first_ day of the non-ISO week) + +### [day_of_year](macros/calendar_date/day_of_year.sql)(`date`) + +Extracts day of the year from a date (e.g. `2022-02-02` --> `33`). + +Usage: + +```sql +{{ dbt_date.day_of_year("date_col") }} as day_of_year +``` + +or + +```sql +{{ dbt_date.day_of_year("'2022-02-02'") }} as day_of_year +``` + +returns: **33** + +### [from_unixtimestamp](macros/calendar_date/from_unixtimestamp.sql)(`epochs, format="seconds"`) + +Converts an `epoch` into a timestamp. The default for `format` is `seconds`, which can overriden depending your data"s epoch format. + +Usage: + +```sql +{{ dbt_date.from_unixtimestamp("epoch_column") }} as timestamp_column +``` + +```sql +{{ dbt_date.from_unixtimestamp("epoch_column", format="milliseconds") }} as timestamp_column +``` + +See also: [to_unixtimestamp](#to_unixtimestamp) + +### [iso_week_end](macros/calendar_date/iso_week_end.sql)(`date=None, tz=None`) + +Computes the week ending date using ISO format, i.e. week starting **Monday** and ending **Sunday**. + +Usage: + +```sql +{{ dbt_date.iso_week_end("date_col") }} as iso_week_end_date +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.iso_week_end("date_col", tz="America/New_York") }} as iso_week_end_date +``` + +### [iso_week_of_year](macros/calendar_date/iso_week_of_year.sql)(`date=None, tz=None`) + +Computes the week of the year using ISO format, i.e. week starting **Monday**. + +Usage: + +```sql +{{ dbt_date.iso_week_of_year("date_col") }} as iso_week_of_year +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.iso_week_of_year("date_col", tz="America/New_York") }} as iso_week_of_year +``` + +### [iso_week_start](macros/calendar_date/iso_week_start.sql)(`date=None, tz=None`) + +Computes the week starting date using ISO format, i.e. week starting **Monday**. + +Usage: + +```sql +{{ dbt_date.iso_week_start("date_col") }} as iso_week_start_date +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.iso_week_start("date_col", tz="America/New_York") }} as iso_week_start_date +``` + +### [last_month_name](macros/calendar_date/last_month_name.sql)(`short=True, tz=None`) + +Extracts the name of the prior month from a date. + +```sql +{{ dbt_date.last_month_name() }} as last_month_short_name +``` + +```sql +{{ dbt_date.last_month_name(short=true) }} as last_month_short_name +``` + +```sql +{{ dbt_date.last_month_name(short=false) }} as last_month_long_name +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.last_month_name(tz="America/New_York") }} as last_month_short_name +``` + +### [last_month_number](macros/calendar_date/last_month_number.sql)(`tz=None`) + +Returns the number of the prior month. + +```sql +{{ dbt_date.last_month_number() }} +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.last_month_number(tz="America/New_York") }} +``` + +### [last_month](macros/calendar_date/last_month.sql)(`tz=None`) + +Returns the start date of the prior month. + +```sql +{{ dbt_date.last_month() }} as last_month_start_date +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.last_month(tz="America/New_York") }} as last_month_start_date +``` + +### [last_week](macros/calendar_date/last_week.sql)(`tz=None`) + +Convenience function to get the start date of last week (non-ISO) + +Wraps: + +```sql +{{ dbt_date.n_weeks_ago(1, tz) }} +``` + +Usage: + +```sql +{{ dbt_date.last_week()) }} as last_week_start_date +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.last_week(tz="America/New_York)) }} as last_week_start_date +``` + +### [month_name](macros/calendar_date/month_name.sql)(`date, short=True, tz=None`) + +Extracts the name of the month from a date. + +```sql +{{ dbt_date.month_name(date_col) }} as month_short_name +``` + +```sql +{{ dbt_date.month_name(date_col, short=true) }} as month_short_name +``` + +```sql +{{ dbt_date.month_name(date_col, short=false) }} as month_long_name +``` + +### [n_days_ago](macros/calendar_date/n_days_ago.sql)(`n, date=None, tz=None`) + +Gets date _n_ days ago, based on local date. + +Usage: + +```sql +{{ dbt_date.n_days_ago(7) }} +``` + +Alternatively, you can specify a date column instead of defaulting the local date: + +```sql +{{ dbt_date.n_days_ago(7, date="date_col") }} +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.n_days_ago(7, tz="America/New_York)) }} +``` + +### [n_days_away](macros/calendar_date/n_days_away.sql)(`n, date=None, tz=None`) + +Gets date _n_ days away, based on local date. + +Usage: + +```sql +{{ dbt_date.n_days_away(7) }} +``` + +Alternatively, you can specify a date column instead of defaulting the local date: + +```sql +{{ dbt_date.n_days_away(7, date="date_col") }} +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.n_days_away(7, tz="America/New_York)) }} +``` + +### [n_months_ago](macros/calendar_date/n_months_ago.sql)(`n, tz=None`) + +Gets date _n_ months ago, based on local date. + +Usage: + +```sql +{{ dbt_date.n_months_ago(12) }} +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.n_months_ago(12, tz="America/New_York)) }} +``` + +### [n_months_away](macros/calendar_date/n_months_away.sql)(`n, tz=None`) + +Gets date _n_ months away, based on local date. + +Usage: + +```sql +{{ dbt_date.n_months_ago(12) }} +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.n_months_away(12, tz="America/New_York)) }} +``` + +### [n_weeks_ago](macros/calendar_date/n_weeks_ago.sql)(`n, tz=None`) + +Gets date _n_ weeks ago, based on local date. + +Usage: + +```sql +{{ dbt_date.n_weeks_ago(12) }} +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.n_weeks_ago(12, tz="America/New_York)) }} +``` + +### [n_weeks_away](macros/calendar_date/n_weeks_away.sql)(`n, tz=None`) + +Gets date _n_ weeks away, based on local date. + +Usage: + +```sql +{{ dbt_date.n_weeks_away(12) }} +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.n_weeks_away(12, tz="America/New_York)) }} +``` + +### [next_month_name](macros/calendar_date/next_month_name.sql)(`short=True, tz=None`) + +Extracts the name of the next month from a date. + +```sql +{{ dbt_date.next_month_name() }} as next_month_short_name +``` + +```sql +{{ dbt_date.next_month_name(short=true) }} as next_month_short_name +``` + +```sql +{{ dbt_date.next_month_name(short=false) }} as next_month_long_name +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.next_month_name(tz="America/New_York") }} as next_month_short_name +``` + +### [next_month_number](macros/calendar_date/next_month_number.sql)(`tz=None`) + +Returns the number of the next month. + +```sql +{{ dbt_date.next_month_number() }} +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.next_month_number(tz="America/New_York") }} +``` + +### [next_month](macros/calendar_date/next_month.sql)(`tz=None`) + +Returns the start date of the next month. + +```sql +{{ dbt_date.next_month() }} as next_month_start_date +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.next_month(tz="America/New_York") }} as next_month_start_date +``` + +### [next_week](macros/calendar_date/next_week.sql)(`tz=None`) + +Convenience function to get the start date of next week (non-ISO) + +Wraps: + +```sql +{{ dbt_date.n_weeks_away(1, tz) }} +``` + +Usage: + +```sql +{{ dbt_date.next_week()) }} as next_week_start_date +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.next_week(tz="America/New_York") }} as next_week_start_date +``` + +### [now](macros/calendar_date/now.sql)(`tz=None`) + +Gets current timestamp based on local timezone (specified). Default is "America/Los_Angeles". + +Usage: + +```sql +{{ dbt_date.now() }} +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.now("America/New_York") }} +``` + +### [periods_since](macros/calendar_date/periods_since.sql)(`date_col, period_name='day', tz=None`) + +Returns the number of periods since a specified date or to `now`. + +Usage: + +```sql +{{ dbt_date.periods_since("my_date_column", period_name="day") }} +``` + +or, + +```sql +{{ dbt_date.periods_since("my_timestamp_column", period_name="minute") }} +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.periods_since("my_timestamp_column", period_name="minute", tz="UTC") }} +``` + +### [round_timestamp](macros/calendar_date/round_timestamp.sql)(`timestamp`) + +Rounds the given timestamp or date to the nearest date (return type is `timestamp`). + +```sql +select +{{ dbt_date.round_timestamp("timestamp_col") }} as nearest_date +... +``` + +A few examples: + +```sql +{{ dbt_date.round_timestamp("'2022-02-05 18:45:15'")}} +-- results in 2022-02-06 +``` + +```sql +{{ dbt_date.round_timestamp("'2022-02-05 11:45:15'")}} +-- results in 2022-02-05 +``` + +```sql +{{ dbt_date.round_timestamp("'2022-02-05 12:00:00'")}} +-- results in 2022-02-06 +``` + +```sql +{{ dbt_date.round_timestamp("'2022-02-05 00:00:00'")}} +-- results in 2022-02-05 +``` + +### [to_unixtimestamp](macros/calendar_date/to_unixtimestamp.sql)(`timestamp`) + +Gets Unix timestamp (epochs) based on provided timestamp. + +Usage: + +```sql +{{ dbt_date.to_unixtimestamp("my_timestamp_column") }} +``` + +```sql +{{ dbt_date.to_unixtimestamp(dbt_date.now()) }} +``` + +### [today](macros/calendar_date/today.sql)(`tz=None`) + +Gets date based on local timezone. + +Usage: + +```sql +{{ dbt_date.today() }} +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.today("America/New_York") }} +``` + +### [tomorrow](macros/calendar_date/tomorrow.sql)(`date=None, tz=None`) + +Gets tomorrow's date, based on local date. + +Usage: + +```sql +{{ dbt_date.tomorrow() }} +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.tomorrow(tz="America/New_York") }} as date_tomorrow +``` + +Alternatively, you can also override the anchor date from the default `today` to some other date: + +```sql +{{ dbt_date.tomorrow(date="date_col", tz="America/New_York") }} as date_tomorrow +``` + +### [week_end](macros/calendar_date/week_end.sql)(`date=None, tz=None`) + +Computes the week ending date using standard (US) format, i.e. week starting **Sunday**. + +Usage: + +If `date` is not specified, the date anchor defaults to `today`. + +```sql +{{ dbt_date.week_end() }} as week_end_date +``` + +or specify a date (column): + +```sql +{{ dbt_date.week_end("date_col") }} as week_end_date +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.week_end("date_col", tz="America/New_York") }} as week_end_date +``` + +### [week_of_year](macros/calendar_date/week_of_year.sql)(`date=None, tz=None`) + +Computes the week of the year using standard (US) format, i.e. week starting **Sunday** and ending **Saturday**. + +Usage: + +If `date` is not specified, the date anchor defaults to `today`. + +```sql +{{ dbt_date.week_of_year() }} as week_of_year +``` + +or specify a date (column): + +```sql +{{ dbt_date.week_of_year("date_col") }} as week_of_year +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.week_of_year("date_col", tz="America/New_York") }} as week_of_year +``` + +### [week_start](macros/calendar_date/week_start.sql)(`date=None, tz=None`) + +Computes the week starting date using standard (US) format, i.e. week starting **Sunday**. + +Usage: + +If `date` is not specified, the date anchor defaults to `today`. + +```sql +{{ dbt_date.week_start() }} as week_start +``` + +or specify a date (column): + +```sql +{{ dbt_date.week_start("date_col") }} as week_start +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.week_start("date_col", tz="America/New_York") }} as week_start +``` + +### [yesterday](macros/calendar_date/yesterday.sql)(`date=None, tz=None`) + +Gets yesterday's date, based on local date. + +Usage: + +If `date` is not specified, the date anchor defaults to `today`. + +```sql +{{ dbt_date.yesterday() }} as date_yesterday +``` + +or specify a date (column): + +```sql +{{ dbt_date.yesterday("date_col") }} as date_yesterday +``` + +or, optionally, you can override the default timezone: + +```sql +{{ dbt_date.yesterday(tz="America/New_York") }} as date_yesterday +``` + +### [date](macros/_utils/modules_datetime.sql)(`year`, `month`, `day`) + +Reduces the boilerplate syntax required to produce a `date` object. This is not converted to a string to allow pythonic manipulation. + +Usage: + +```sql +{% set date_object = dbt_date.date(1997, 9, 29) %} +``` + +### [datetime](macros/_utils/modules_datetime.sql)(`year`, `month`, `day`, `hour=0`, `minute=0`, `second=0`, `microsecond=0`, `tz=None`) + +Reduces the boilerplate syntax required to produce a `datetime` object. This is not converted to a string to allow pythonic manipulation. + +Usage: + +```sql +{% set datetime_object = dbt_date.datetime(1997, 9, 29, 6, 14) %} +``` + +or, optionally, you can override the default timezone: + +```sql +{% set datetime_object = dbt_date.datetime(1997, 9, 29, 6, 14, tz='America/New_York') %} +``` + +## Integration Tests (Developers Only) + +This project contains integration tests for all test macros in a separate `integration_tests` dbt project contained in this repo. + +To run the tests: + +1. You will need a profile called `integration_tests` in `~/.dbt/profiles.yml` pointing to a writable database. We only support postgres, BigQuery and Snowflake. +2. Then, from within the `integration_tests` folder, run `dbt build` to run the test models in `integration_tests/models/schema_tests/` and run the tests specified in `integration_tests/models/schema_tests/schema.yml` diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/dbt_project.yml b/dev/dags/dbt/simple/dbt_packages/dbt_date/dbt_project.yml new file mode 100644 index 0000000000..0f66aab9cb --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/dbt_project.yml @@ -0,0 +1,19 @@ +name: "dbt_date" +version: "0.5.0" + +config-version: 2 + +target-path: "target" +clean-targets: ["target", "dbt_packages"] +macro-paths: ["macros"] +log-path: "logs" + +require-dbt-version: [">=1.2.0", "<2.0.0"] +profile: integration_tests + +quoting: + identifier: false + schema: false + +vars: + "dbt_date:time_zone": "America/Los_Angeles" diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/images/xebia-logo-large-transparent.png b/dev/dags/dbt/simple/dbt_packages/dbt_date/images/xebia-logo-large-transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..0eae7b461a46103af538dc692c542ee4c4e3b6c6 GIT binary patch literal 83213 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%0D_?;&6 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 + --class org.apache.spark.sql.hive.thriftserver.HiveThriftServer2 + --name Thrift JDBC/ODBC Server + volumes: + - ./.spark-warehouse/:/spark-warehouse/ + - ./docker/hive-site.xml:/usr/spark/conf/hive-site.xml + - ./docker/spark-defaults.conf:/usr/spark/conf/spark-defaults.conf + environment: + - WAIT_FOR=dbt-hive-metastore:5432 + + dbt-hive-metastore: + image: postgres:9.6.17-alpine + volumes: + - ./.hive-metastore/:/var/lib/postgresql/data + environment: + - POSTGRES_USER=dbt + - POSTGRES_PASSWORD=dbt + - POSTGRES_DB=metastore diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/docker-start.sh b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/docker-start.sh new file mode 100644 index 0000000000..a8341a634f --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/docker-start.sh @@ -0,0 +1 @@ +docker-compose up -d diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/docker-stop.sh b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/docker-stop.sh new file mode 100644 index 0000000000..a20ef1ad14 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/docker-stop.sh @@ -0,0 +1 @@ +docker-compose down && rm -rf ./.hive-metastore/ && rm -rf ./.spark-warehouse/ diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/docker/hive-site.xml b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/docker/hive-site.xml new file mode 100644 index 0000000000..457d04f316 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/docker/hive-site.xml @@ -0,0 +1,46 @@ + + + + + + + + javax.jdo.option.ConnectionURL + jdbc:postgresql://dbt-hive-metastore/metastore + + + + javax.jdo.option.ConnectionDriverName + org.postgresql.Driver + + + + javax.jdo.option.ConnectionUserName + dbt + + + + javax.jdo.option.ConnectionPassword + dbt + + + + hive.metastore.schema.verification + false + + diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/docker/spark-defaults.conf b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/docker/spark-defaults.conf new file mode 100644 index 0000000000..30ec59591a --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/docker/spark-defaults.conf @@ -0,0 +1,9 @@ +spark.driver.memory 2g +spark.executor.memory 2g +spark.hadoop.datanucleus.autoCreateTables true +spark.hadoop.datanucleus.schema.autoCreateTables true +spark.hadoop.datanucleus.fixedDatastore false +spark.serializer org.apache.spark.serializer.KryoSerializer +spark.jars.packages org.apache.hudi:hudi-spark3-bundle_2.12:0.10.0 +spark.sql.extensions org.apache.spark.sql.hudi.HoodieSparkSessionExtension +spark.driver.userClassPathFirst true diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/docker/trino/catalog/memory.properties b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/docker/trino/catalog/memory.properties new file mode 100644 index 0000000000..56b3b5e6ae --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/docker/trino/catalog/memory.properties @@ -0,0 +1,2 @@ +connector.name=memory +memory.max-data-per-node=128MB diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/macros/expression_is_true.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/macros/expression_is_true.sql new file mode 100644 index 0000000000..18a97918ea --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/macros/expression_is_true.sql @@ -0,0 +1,23 @@ +{% test expression_is_true(model, expression, column_name=None, condition="1=1") %} + {# T-SQL has no boolean data type so we use 1=1 which returns TRUE #} + {# ref https://stackoverflow.com/a/7170753/3842610 #} + {{ + return( + adapter.dispatch("test_expression_is_true", "dbt_date_integration_tests")( + model, expression, column_name, condition + ) + ) + }} +{% endtest %} + +{% macro default__test_expression_is_true(model, expression, column_name, condition) %} + + with meet_condition as (select * from {{ model }} where {{ condition }}) + + select * + from meet_condition + {% if column_name is none %} where not ({{ expression }}) + {%- else %} where not ({{ column_name }} {{ expression }}) + {%- endif %} + +{% endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/macros/get_custom_schema.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/macros/get_custom_schema.sql new file mode 100644 index 0000000000..d514eb6184 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/macros/get_custom_schema.sql @@ -0,0 +1,10 @@ +{% macro generate_schema_name(custom_schema_name, node) -%} + + {%- set default_schema = target.schema -%} + {%- if custom_schema_name is none -%} {{ default_schema }} + + {%- else -%} {{ custom_schema_name | trim }} + + {%- endif -%} + +{%- endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/macros/get_test_dates.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/macros/get_test_dates.sql new file mode 100644 index 0000000000..388c06dc59 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/macros/get_test_dates.sql @@ -0,0 +1,206 @@ +{% macro get_test_dates() -%} + select + cast('2020-11-29' as date) as date_day, + cast('2020-11-28' as date) as prior_date_day, + cast('2020-11-30' as date) as next_date_day, + 'Sunday' as day_name, + 'Sun' as day_name_short, + 29 as day_of_month, + 1 as day_of_week, + 7 as iso_day_of_week, + 334 as day_of_year, + cast('{{ get_test_week_start_date()[0] }}' as date) as week_start_date, + cast('{{ get_test_week_end_date()[0] }}' as date) as week_end_date, + {{ get_test_week_of_year()[0] }} as week_of_year, + -- in ISO terms, this is the end of the prior week + cast('2020-11-23' as date) as iso_week_start_date, + cast('2020-11-29' as date) as iso_week_end_date, + 48 as iso_week_of_year, + 11 as month_number, + 'November' as month_name, + 'Nov' as month_name_short, + 1623076520 as unix_epoch, + cast( + '{{ get_test_timestamps()[0] }}' as {{ dbt.type_timestamp() }} + ) as time_stamp, + cast( + '{{ get_test_timestamps()[1] }}' as {{ dbt.type_timestamp() }} + ) as time_stamp_utc, + cast('2021-06-07' as {{ dbt.type_timestamp() }}) as rounded_timestamp, + cast('2021-06-08' as {{ dbt.type_timestamp() }}) as rounded_timestamp_utc, + -- These columns are here to make sure these macros get run during testing: + {{ dbt_date.last_month_number() }} as last_month_number, + {{ dbt_date.last_month_name(short=False) }} as last_month_name, + {{ dbt_date.last_month_name(short=True) }} as last_month_name_short, + {{ dbt_date.next_month_number() }} as next_month_number, + {{ dbt_date.next_month_name(short=False) }} as next_month_name, + {{ dbt_date.next_month_name(short=True) }} as next_month_name_short, + cast('{{ modules.datetime.date(1997, 9, 29) }}' as date) as datetime_date, + cast( + '{{ modules.datetime.datetime(1997, 9, 29, 6, 14, 0, tzinfo=modules.pytz.timezone(var("dbt_date:time_zone"))) }}' + as {{ dbt.type_timestamp() }} + ) as datetime_datetime + + union all + + select + cast('2020-12-01' as date) as date_day, + cast('2020-11-30' as date) as prior_date_day, + cast('2020-12-02' as date) as next_date_day, + 'Tuesday' as day_name, + 'Tue' as day_name_short, + 1 as day_of_month, + 3 as day_of_week, + 2 as iso_day_of_week, + 336 as day_of_year, + cast('{{ get_test_week_start_date()[1] }}' as date) as week_start_date, + cast('{{ get_test_week_end_date()[1] }}' as date) as week_end_date, + {{ get_test_week_of_year()[1] }} as week_of_year, + cast('2020-11-30' as date) as iso_week_start_date, + cast('2020-12-06' as date) as iso_week_end_date, + 49 as iso_week_of_year, + 12 as month_number, + 'December' as month_name, + 'Dec' as month_name_short, + 1623076520 as unix_epoch, + cast( + '{{ get_test_timestamps()[0] }}' as {{ dbt.type_timestamp() }} + ) as time_stamp, + cast( + '{{ get_test_timestamps()[1] }}' as {{ dbt.type_timestamp() }} + ) as time_stamp_utc, + cast('2021-06-07' as {{ dbt.type_timestamp() }}) as rounded_timestamp, + cast('2021-06-08' as {{ dbt.type_timestamp() }}) as rounded_timestamp_utc, + -- These columns are here to make sure these macros get run during testing: + {{ dbt_date.last_month_number() }} as last_month_number, + {{ dbt_date.last_month_name(short=False) }} as last_month_name, + {{ dbt_date.last_month_name(short=True) }} as last_month_name_short, + {{ dbt_date.next_month_number() }} as next_month_number, + {{ dbt_date.next_month_name(short=False) }} as next_month_name, + {{ dbt_date.next_month_name(short=True) }} as next_month_name_short, + cast('{{ modules.datetime.date(1997, 9, 29) }}' as date) as datetime_date, + cast( + '{{ modules.datetime.datetime(1997, 9, 29, 6, 14, 0, tzinfo=modules.pytz.timezone(var("dbt_date:time_zone"))) }}' + as {{ dbt.type_timestamp() }} + ) as datetime_datetime + +{%- endmacro %} + +{% macro get_test_week_of_year() -%} + {{ + return( + adapter.dispatch("get_test_week_of_year", "dbt_date_integration_tests")() + ) + }} +{%- endmacro %} + +{% macro default__get_test_week_of_year() -%} + {# weeks_of_year for '2020-11-29' and '2020-12-01', respectively #} + {{ return([48, 48]) }} +{%- endmacro %} + +{% macro snowflake__get_test_week_of_year() -%} + {# weeks_of_year for '2020-11-29' and '2020-12-01', respectively #} + {# Snowflake uses ISO year #} + {{ return([48, 49]) }} +{%- endmacro %} + +{% macro spark__get_test_week_of_year() -%} + {# weeks_of_year for '2020-11-29' and '2020-12-01', respectively #} + {# spark uses ISO year #} + {{ return([48, 49]) }} +{%- endmacro %} + +{% macro trino__get_test_week_of_year() -%} + {# weeks_of_year for '2020-11-29' and '2020-12-01', respectively #} + {# trino uses ISO year #} + {{ return([48, 49]) }} +{%- endmacro %} + + +{% macro get_test_week_start_date() -%} + {{ + return( + adapter.dispatch( + "get_test_week_start_date", "dbt_date_integration_tests" + )() + ) + }} +{%- endmacro %} + +{% macro default__get_test_week_start_date() -%} + {{ return(["2020-11-29", "2020-11-29"]) }} +{%- endmacro %} + +{% macro spark__get_test_week_start_date() -%} + {# spark does not support non-iso weeks #} + {{ return(["2020-11-23", "2020-11-30"]) }} +{%- endmacro %} + +{% macro trino__get_test_week_start_date() -%} + {# trino does not support non-iso weeks #} + {{ return(["2020-11-23", "2020-11-30"]) }} +{%- endmacro %} + + +{% macro get_test_week_end_date() -%} + {{ + return( + adapter.dispatch("get_test_week_end_date", "dbt_date_integration_tests")() + ) + }} +{%- endmacro %} + +{% macro default__get_test_week_end_date() -%} + {{ return(["2020-12-05", "2020-12-05"]) }} +{%- endmacro %} + +{% macro spark__get_test_week_end_date() -%} + {# spark does not support non-iso weeks #} + {{ return(["2020-11-29", "2020-12-06"]) }} +{%- endmacro %} + +{% macro trino__get_test_week_end_date() -%} + {# trino does not support non-iso weeks #} + {{ return(["2020-11-29", "2020-12-06"]) }} +{%- endmacro %} + + +{% macro get_test_timestamps() -%} + {{ + return( + adapter.dispatch("get_test_timestamps", "dbt_date_integration_tests")() + ) + }} +{%- endmacro %} + +{% macro default__get_test_timestamps() -%} + {{ + return( + [ + "2021-06-07 07:35:20.000000 America/Los_Angeles", + "2021-06-07 14:35:20.000000 UTC", + ] + ) + }} +{%- endmacro %} + +{% macro bigquery__get_test_timestamps() -%} + {{ return(["2021-06-07 07:35:20.000000", "2021-06-07 14:35:20.000000"]) }} +{%- endmacro %} + +{% macro snowflake__get_test_timestamps() -%} + {{ + return( + ["2021-06-07 07:35:20.000000 -0700", "2021-06-07 14:35:20.000000 -0000"] + ) + }} +{%- endmacro %} + +{% macro duckdb__get_test_timestamps() -%} + {{ return(["2021-06-07 07:35:20.000000", "2021-06-07 14:35:20.000000"]) }} +{%- endmacro %} + +{% macro spark__get_test_timestamps() -%} + {{ return(["2021-06-07 07:35:20.000000", "2021-06-07 14:35:20.000000"]) }} +{%- endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/dates.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/dates.sql new file mode 100644 index 0000000000..00b4e12af0 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/dates.sql @@ -0,0 +1,2 @@ +{{ config(materialized="table") }} +{{ dbt_date.get_date_dimension("2015-01-01", "2022-12-31") }} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/dim_date.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/dim_date.sql new file mode 100644 index 0000000000..a1f6fe4f56 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/dim_date.sql @@ -0,0 +1,19 @@ +{{ config(materialized="table") }} +with + date_dimension as (select * from {{ ref("dates") }}), + fiscal_periods as ( + {{ + dbt_date.get_fiscal_periods( + ref("dates"), year_end_month=1, week_start_day=1, shift_year=1 + ) + }} + ) +select + d.*, + f.fiscal_week_of_year, + f.fiscal_week_of_period, + f.fiscal_period_number, + f.fiscal_quarter_number, + f.fiscal_period_of_quarter +from date_dimension d +left join fiscal_periods f on d.date_day = f.date_day diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/dim_date_fiscal.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/dim_date_fiscal.sql new file mode 100644 index 0000000000..f7b5fe3463 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/dim_date_fiscal.sql @@ -0,0 +1,11 @@ +{{ config(materialized="table") }} +with + fp as ( + {{ + dbt_date.get_fiscal_periods( + ref("dates"), year_end_month=1, week_start_day=1 + ) + }} + ) +select f.* +from fp f diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/dim_hour.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/dim_hour.sql new file mode 100644 index 0000000000..3be8f92110 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/dim_hour.sql @@ -0,0 +1,7 @@ +{{ config(materialized="table") }} +with + periods_hours as ( + {{ dbt_date.get_base_dates(n_dateparts=24 * 28, datepart="hour") }} + ) +select d.* +from periods_hours d diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/dim_week.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/dim_week.sql new file mode 100644 index 0000000000..1cb8899f3b --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/dim_week.sql @@ -0,0 +1,4 @@ +{{ config(materialized="table") }} +with periods_weeks as ({{ dbt_date.get_base_dates(n_dateparts=52, datepart="week") }}) +select d.* +from periods_weeks d diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/test_dates.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/test_dates.sql new file mode 100644 index 0000000000..9f19a571c4 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/test_dates.sql @@ -0,0 +1 @@ +{{ config(materialized="table") }} {{ dbt_date_integration_tests.get_test_dates() }} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/test_dates.yml b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/test_dates.yml new file mode 100644 index 0000000000..3ab3edd04c --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/models/test_dates.yml @@ -0,0 +1,75 @@ +version: 2 +models: + - name: test_dates + tests: + - expression_is_true: + expression: "prior_date_day = {{ dbt_date.yesterday('date_day') }}" + - expression_is_true: + expression: "next_date_day = {{ dbt_date.tomorrow('date_day') }}" + - expression_is_true: + expression: "day_name = {{ dbt_date.day_name('date_day', short=False) }}" + - expression_is_true: + expression: "day_name_short = {{ dbt_date.day_name('date_day', short=True) }}" + - expression_is_true: + expression: "day_of_month = {{ dbt_date.day_of_month('date_day') }}" + - expression_is_true: + expression: "day_of_week = {{ dbt_date.day_of_week('date_day', isoweek=False) }}" + - expression_is_true: + expression: "iso_day_of_week = {{ dbt_date.day_of_week('date_day', isoweek=True) }}" + - expression_is_true: + expression: "day_of_year = {{ dbt_date.day_of_year('date_day') }}" + + - expression_is_true: + expression: "week_start_date = {{ dbt_date.week_start('date_day') }}" + - expression_is_true: + expression: "week_end_date = {{ dbt_date.week_end('date_day') }}" + - expression_is_true: + expression: "week_of_year = {{ dbt_date.week_of_year('date_day') }}" + - expression_is_true: + expression: "iso_week_start_date = {{ dbt_date.iso_week_start('date_day') }}" + - expression_is_true: + expression: "iso_week_end_date = {{ dbt_date.iso_week_end('date_day') }}" + - expression_is_true: + expression: "iso_week_of_year = {{ dbt_date.iso_week_of_year('date_day') }}" + - expression_is_true: + expression: "month_number = {{ dbt_date.date_part('month', 'date_day') }}" + - expression_is_true: + expression: "month_name = {{ dbt_date.month_name('date_day', short=False) }}" + - expression_is_true: + expression: "month_name_short = {{ dbt_date.month_name('date_day', short=True) }}" + - expression_is_true: + expression: "time_stamp_utc = {{ dbt_date.from_unixtimestamp('unix_epoch') }}" + - expression_is_true: + expression: "unix_epoch = {{ dbt_date.to_unixtimestamp('time_stamp_utc') }}" + - expression_is_true: + expression: "time_stamp = {{ dbt_date.convert_timezone('time_stamp_utc') }}" + - expression_is_true: + expression: "time_stamp = {{ dbt_date.convert_timezone('time_stamp_utc', source_tz='UTC') }}" + # - expression_is_true: + # expression: "time_stamp_utc = {{ dbt_date.convert_timezone('time_stamp', source_tz='America/Los_Angeles', target_tz='UTC') }}" + # - expression_is_true: + # expression: "time_stamp = {{ dbt_date.convert_timezone('time_stamp', source_tz='America/Los_Angeles', target_tz='America/Los_Angeles') }}" + - expression_is_true: + expression: "rounded_timestamp = {{ dbt_date.round_timestamp('time_stamp') }}" + - expression_is_true: + expression: "rounded_timestamp_utc = {{ dbt_date.round_timestamp('time_stamp_utc') }}" + - expression_is_true: + expression: "last_month_number = {{ dbt_date.last_month_number() }}" + - expression_is_true: + expression: "last_month_name = {{ dbt_date.last_month_name(short=False) }}" + - expression_is_true: + expression: "last_month_name_short = {{ dbt_date.last_month_name(short=True) }}" + - expression_is_true: + expression: "next_month_number = {{ dbt_date.next_month_number() }}" + - expression_is_true: + expression: "next_month_name = {{ dbt_date.next_month_name(short=False) }}" + - expression_is_true: + expression: "next_month_name_short = {{ dbt_date.next_month_name(short=True) }}" + - expression_is_true: + expression: "datetime_date = cast('{{ dbt_date.date(1997, 9, 29) }}' as date)" + - expression_is_true: + expression: "datetime_datetime = cast('{{ dbt_date.datetime(1997, 9, 29, 6, 14) }}' as {{ dbt.type_timestamp() }})" + + columns: + - name: date_day + - name: prior_date_day diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/packages.yml b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/packages.yml new file mode 100644 index 0000000000..4a6b9c1948 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/packages.yml @@ -0,0 +1,2 @@ +packages: + - local: ../ diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/test.sh b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/test.sh new file mode 100644 index 0000000000..790660c120 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/integration_tests/test.sh @@ -0,0 +1,14 @@ +if [[ $# -gt 0 ]] +then + +i=1; +for t in "$@" +do + + dbt build -t $t + +done + +else + echo "Please specify one or more targets as command-line arguments, i.e. test.sh bq snowflake" +fi diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/_utils/date_spine.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/_utils/date_spine.sql new file mode 100644 index 0000000000..f5eb93971b --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/_utils/date_spine.sql @@ -0,0 +1,82 @@ +{% macro get_intervals_between(start_date, end_date, datepart) -%} + {{ + return( + adapter.dispatch("get_intervals_between", "dbt_date")( + start_date, end_date, datepart + ) + ) + }} +{%- endmacro %} + +{% macro default__get_intervals_between(start_date, end_date, datepart) -%} + {%- call statement("get_intervals_between", fetch_result=True) %} + + select {{ dbt.datediff(start_date, end_date, datepart) }} + + {%- endcall -%} + + {%- set value_list = load_result("get_intervals_between") -%} + + {%- if value_list and value_list["data"] -%} + {%- set values = value_list["data"] | map(attribute=0) | list %} + {{ return(values[0]) }} + {%- else -%} {{ return(1) }} + {%- endif -%} + +{%- endmacro %} + + +{% macro date_spine(datepart, start_date, end_date) %} + {{ + return( + adapter.dispatch("date_spine", "dbt_date")(datepart, start_date, end_date) + ) + }} +{%- endmacro %} + +{% macro default__date_spine(datepart, start_date, end_date) %} + + {# call as follows: + +date_spine( + "day", + "to_date('01/01/2016', 'mm/dd/yyyy')", + "dbt.dateadd(week, 1, current_date)" +) #} + with + rawdata as ( + + {{ + dbt_date.generate_series( + dbt_date.get_intervals_between(start_date, end_date, datepart) + ) + }} + + ), + + all_periods as ( + + select + ( + {{ + dbt.dateadd( + datepart, + "(row_number() over (order by 1) - 1)", + start_date, + ) + }} + ) as date_{{ datepart }} + from rawdata + + ), + + filtered as ( + + select * from all_periods where date_{{ datepart }} <= {{ end_date }} + + ) + + select * + from filtered + +{% endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/_utils/generate_series.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/_utils/generate_series.sql new file mode 100644 index 0000000000..343d1c6c81 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/_utils/generate_series.sql @@ -0,0 +1,55 @@ +{% macro get_powers_of_two(upper_bound) %} + {{ return(adapter.dispatch("get_powers_of_two", "dbt_date")(upper_bound)) }} +{% endmacro %} + +{% macro default__get_powers_of_two(upper_bound) %} + + {% if upper_bound <= 0 %} + {{ exceptions.raise_compiler_error("upper bound must be positive") }} + {% endif %} + + {% for _ in range(1, 100) %} + {% if upper_bound <= 2**loop.index %} {{ return(loop.index) }}{% endif %} + {% endfor %} + +{% endmacro %} + + +{% macro generate_series(upper_bound) %} + {{ return(adapter.dispatch("generate_series", "dbt_date")(upper_bound)) }} +{% endmacro %} + +{% macro default__generate_series(upper_bound) %} + + {% set n = dbt_date.get_powers_of_two(upper_bound) %} + + with + p as ( + select 0 as generated_number + union all + select 1 + ), + unioned as ( + + select + + {% for i in range(n) %} + p{{ i }}.generated_number * power(2, {{ i }}) + {% if not loop.last %} + {% endif %} + {% endfor %} + + 1 as generated_number + + from + + {% for i in range(n) %} + p as p{{ i }} {% if not loop.last %} cross join {% endif %} + {% endfor %} + + ) + + select * + from unioned + where generated_number <= {{ upper_bound }} + order by generated_number + +{% endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/_utils/modules_datetime.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/_utils/modules_datetime.sql new file mode 100644 index 0000000000..7e5065ea15 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/_utils/modules_datetime.sql @@ -0,0 +1,23 @@ +{% macro date(year, month, day) %} + {{ return(modules.datetime.date(year, month, day)) }} +{% endmacro %} + +{% macro datetime( + year, month, day, hour=0, minute=0, second=0, microsecond=0, tz=None +) %} + {% set tz = tz if tz else var("dbt_date:time_zone") %} + {{ + return( + modules.datetime.datetime( + year=year, + month=month, + day=day, + hour=hour, + minute=minute, + second=second, + microsecond=microsecond, + tzinfo=modules.pytz.timezone(tz), + ) + ) + }} +{% endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/convert_timezone.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/convert_timezone.sql new file mode 100644 index 0000000000..01a381be0b --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/convert_timezone.sql @@ -0,0 +1,52 @@ +{%- macro convert_timezone(column, target_tz=None, source_tz=None) -%} + {%- set source_tz = "UTC" if not source_tz else source_tz -%} + {%- set target_tz = var("dbt_date:time_zone") if not target_tz else target_tz -%} + {{ adapter.dispatch("convert_timezone", "dbt_date")(column, target_tz, source_tz) }} +{%- endmacro -%} + +{% macro default__convert_timezone(column, target_tz, source_tz) -%} + convert_timezone( + '{{ source_tz }}', + '{{ target_tz }}', + cast({{ column }} as {{ dbt.type_timestamp() }}) + ) +{%- endmacro -%} + +{%- macro bigquery__convert_timezone(column, target_tz, source_tz=None) -%} + timestamp(datetime({{ column }}, '{{ target_tz}}')) +{%- endmacro -%} + +{% macro postgres__convert_timezone(column, target_tz, source_tz) -%} + cast( + cast({{ column }} as {{ dbt.type_timestamp() }}) + at time zone '{{ source_tz }}' at time zone '{{ target_tz }}' + as {{ dbt.type_timestamp() }} + ) +{%- endmacro -%} + +{%- macro redshift__convert_timezone(column, target_tz, source_tz) -%} + {{ return(dbt_date.default__convert_timezone(column, target_tz, source_tz)) }} +{%- endmacro -%} + +{% macro duckdb__convert_timezone(column, target_tz, source_tz) -%} + {{ return(dbt_date.postgres__convert_timezone(column, target_tz, source_tz)) }} +{%- endmacro -%} + +{%- macro spark__convert_timezone(column, target_tz, source_tz) -%} + from_utc_timestamp( + to_utc_timestamp({{ column }}, '{{ source_tz }}'), '{{ target_tz }}' + ) +{%- endmacro -%} + +{%- macro trino__convert_timezone(column, target_tz, source_tz) -%} + cast( + ( + at_timezone( + with_timezone( + cast({{ column }} as {{ dbt.type_timestamp() }}), '{{ source_tz }}' + ), + '{{ target_tz }}' + ) + ) as {{ dbt.type_timestamp() }} + ) +{%- endmacro -%} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/date_part.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/date_part.sql new file mode 100644 index 0000000000..9e6b201c7e --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/date_part.sql @@ -0,0 +1,15 @@ +{% macro date_part(datepart, date) -%} + {{ adapter.dispatch("date_part", "dbt_date")(datepart, date) }} +{%- endmacro %} + +{% macro default__date_part(datepart, date) -%} + date_part('{{ datepart }}', {{ date }}) +{%- endmacro %} + +{% macro bigquery__date_part(datepart, date) -%} + extract({{ datepart }} from {{ date }}) +{%- endmacro %} + +{% macro trino__date_part(datepart, date) -%} + extract({{ datepart }} from {{ date }}) +{%- endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/day_name.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/day_name.sql new file mode 100644 index 0000000000..6cb91b5ccb --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/day_name.sql @@ -0,0 +1,56 @@ +{%- macro day_name(date, short=True) -%} + {{ adapter.dispatch("day_name", "dbt_date")(date, short) }} +{%- endmacro %} + +{%- macro default__day_name(date, short) -%} + {%- set f = "Dy" if short else "Day" -%} to_char({{ date }}, '{{ f }}') +{%- endmacro %} + +{%- macro snowflake__day_name(date, short) -%} + {%- if short -%} dayname({{ date }}) + {%- else -%} + -- long version not implemented on Snowflake so we're doing it manually :/ + case + dayname({{ date }}) + when 'Mon' + then 'Monday' + when 'Tue' + then 'Tuesday' + when 'Wed' + then 'Wednesday' + when 'Thu' + then 'Thursday' + when 'Fri' + then 'Friday' + when 'Sat' + then 'Saturday' + when 'Sun' + then 'Sunday' + end + {%- endif -%} + +{%- endmacro %} + +{%- macro bigquery__day_name(date, short) -%} + {%- set f = "%a" if short else "%A" -%} + format_date('{{ f }}', cast({{ date }} as date)) +{%- endmacro %} + +{%- macro postgres__day_name(date, short) -%} + {# FM = Fill mode, which suppresses padding blanks #} + {%- set f = "FMDy" if short else "FMDay" -%} to_char({{ date }}, '{{ f }}') +{%- endmacro %} + +{%- macro duckdb__day_name(date, short) -%} + {%- if short -%} substr(dayname({{ date }}), 1, 3) + {%- else -%} dayname({{ date }}) + {%- endif -%} +{%- endmacro %} + +{%- macro spark__day_name(date, short) -%} + {%- set f = "E" if short else "EEEE" -%} date_format({{ date }}, '{{ f }}') +{%- endmacro %} + +{%- macro trino__day_name(date, short) -%} + {%- set f = "a" if short else "W" -%} date_format({{ date }}, '%{{ f }}') +{%- endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/day_of_month.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/day_of_month.sql new file mode 100644 index 0000000000..3028c3b860 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/day_of_month.sql @@ -0,0 +1,5 @@ +{%- macro day_of_month(date) -%} {{ dbt_date.date_part("day", date) }} {%- endmacro %} + +{%- macro redshift__day_of_month(date) -%} + cast({{ dbt_date.date_part("day", date) }} as {{ dbt.type_bigint() }}) +{%- endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/day_of_week.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/day_of_week.sql new file mode 100644 index 0000000000..62460fb3b0 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/day_of_week.sql @@ -0,0 +1,100 @@ +{%- macro day_of_week(date, isoweek=true) -%} + {{ adapter.dispatch("day_of_week", "dbt_date")(date, isoweek) }} +{%- endmacro %} + +{%- macro default__day_of_week(date, isoweek) -%} + + {%- set dow = dbt_date.date_part("dayofweek", date) -%} + + {%- if isoweek -%} + case + -- Shift start of week from Sunday (0) to Monday (1) + when {{ dow }} = 0 then 7 else {{ dow }} + end + {%- else -%} {{ dow }} + 1 + {%- endif -%} + +{%- endmacro %} + +{%- macro snowflake__day_of_week(date, isoweek) -%} + + {%- if isoweek -%} + {%- set dow_part = "dayofweekiso" -%} {{ dbt_date.date_part(dow_part, date) }} + {%- else -%} + {%- set dow_part = "dayofweek" -%} + case + when {{ dbt_date.date_part(dow_part, date) }} = 7 + then 1 + else {{ dbt_date.date_part(dow_part, date) }} + 1 + end + {%- endif -%} + +{%- endmacro %} + +{%- macro bigquery__day_of_week(date, isoweek) -%} + + {%- set dow = dbt_date.date_part("dayofweek", date) -%} + + {%- if isoweek -%} + case + -- Shift start of week from Sunday (1) to Monday (2) + when {{ dow }} = 1 then 7 else {{ dow }} - 1 + end + {%- else -%} {{ dow }} + {%- endif -%} + +{%- endmacro %} + + +{%- macro postgres__day_of_week(date, isoweek) -%} + + {%- if isoweek -%} + {%- set dow_part = "isodow" -%} + -- Monday(1) to Sunday (7) + cast({{ dbt_date.date_part(dow_part, date) }} as {{ dbt.type_int() }}) + {%- else -%} + {%- set dow_part = "dow" -%} + -- Sunday(1) to Saturday (7) + cast({{ dbt_date.date_part(dow_part, date) }} + 1 as {{ dbt.type_int() }}) + {%- endif -%} + +{%- endmacro %} + + +{%- macro redshift__day_of_week(date, isoweek) -%} + + {%- set dow = dbt_date.date_part("dayofweek", date) -%} + + {%- if isoweek -%} + case + -- Shift start of week from Sunday (0) to Monday (1) + when {{ dow }} = 0 then 7 else cast({{ dow }} as {{ dbt.type_bigint() }}) + end + {%- else -%} cast({{ dow }} + 1 as {{ dbt.type_bigint() }}) + {%- endif -%} + +{%- endmacro %} + +{%- macro duckdb__day_of_week(date, isoweek) -%} + {{ return(dbt_date.postgres__day_of_week(date, isoweek)) }} +{%- endmacro %} + + +{%- macro spark__day_of_week(date, isoweek) -%} + + {%- set dow = "dayofweek_iso" if isoweek else "dayofweek" -%} + + {{ dbt_date.date_part(dow, date) }} + +{%- endmacro %} + + +{%- macro trino__day_of_week(date, isoweek) -%} + + {%- set dow = dbt_date.date_part("day_of_week", date) -%} + + {%- if isoweek -%} {{ dow }} + {%- else -%} case when {{ dow }} = 7 then 1 else {{ dow }} + 1 end + {%- endif -%} + +{%- endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/day_of_year.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/day_of_year.sql new file mode 100644 index 0000000000..5ddb45f002 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/day_of_year.sql @@ -0,0 +1,21 @@ +{%- macro day_of_year(date) -%} + {{ adapter.dispatch("day_of_year", "dbt_date")(date) }} +{%- endmacro %} + +{%- macro default__day_of_year(date) -%} + {{ dbt_date.date_part("dayofyear", date) }} +{%- endmacro %} + +{%- macro postgres__day_of_year(date) -%} + {{ dbt_date.date_part("doy", date) }} +{%- endmacro %} + +{%- macro redshift__day_of_year(date) -%} + cast({{ dbt_date.date_part("dayofyear", date) }} as {{ dbt.type_bigint() }}) +{%- endmacro %} + +{%- macro spark__day_of_year(date) -%} dayofyear({{ date }}) {%- endmacro %} + +{%- macro trino__day_of_year(date) -%} + {{ dbt_date.date_part("day_of_year", date) }} +{%- endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/from_unixtimestamp.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/from_unixtimestamp.sql new file mode 100644 index 0000000000..b08545c526 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/from_unixtimestamp.sql @@ -0,0 +1,110 @@ +{%- macro from_unixtimestamp(epochs, format="seconds") -%} + {{ adapter.dispatch("from_unixtimestamp", "dbt_date")(epochs, format) }} +{%- endmacro %} + +{%- macro default__from_unixtimestamp(epochs, format="seconds") -%} + {%- if format != "seconds" -%} + {{ + exceptions.raise_compiler_error( + "value " + ~ format + ~ " for `format` for from_unixtimestamp is not supported." + ) + }} + {% endif -%} + to_timestamp({{ epochs }}) +{%- endmacro %} + +{%- macro postgres__from_unixtimestamp(epochs, format="seconds") -%} + {%- if format != "seconds" -%} + {{ + exceptions.raise_compiler_error( + "value " + ~ format + ~ " for `format` for from_unixtimestamp is not supported." + ) + }} + {% endif -%} + cast(to_timestamp({{ epochs }}) at time zone 'UTC' as timestamp) +{%- endmacro %} + +{%- macro snowflake__from_unixtimestamp(epochs, format) -%} + {%- if format == "seconds" -%} {%- set scale = 0 -%} + {%- elif format == "milliseconds" -%} {%- set scale = 3 -%} + {%- elif format == "microseconds" -%} {%- set scale = 6 -%} + {%- else -%} + {{ + exceptions.raise_compiler_error( + "value " + ~ format + ~ " for `format` for from_unixtimestamp is not supported." + ) + }} + {% endif -%} + to_timestamp_ntz({{ epochs }}, {{ scale }}) + +{%- endmacro %} + +{%- macro bigquery__from_unixtimestamp(epochs, format) -%} + {%- if format == "seconds" -%} timestamp_seconds({{ epochs }}) + {%- elif format == "milliseconds" -%} timestamp_millis({{ epochs }}) + {%- elif format == "microseconds" -%} timestamp_micros({{ epochs }}) + {%- else -%} + {{ + exceptions.raise_compiler_error( + "value " + ~ format + ~ " for `format` for from_unixtimestamp is not supported." + ) + }} + {% endif -%} +{%- endmacro %} + +{%- macro trino__from_unixtimestamp(epochs, format) -%} + {%- if format == "seconds" -%} + cast( + from_unixtime({{ epochs }}) at time zone 'UTC' as {{ dbt.type_timestamp() }} + ) + {%- elif format == "milliseconds" -%} + cast( + from_unixtime_nanos( + {{ epochs }} * pow(10, 6) + ) at time zone 'UTC' as {{ dbt.type_timestamp() }} + ) + {%- elif format == "microseconds" -%} + cast( + from_unixtime_nanos( + {{ epochs }} * pow(10, 3) + ) at time zone 'UTC' as {{ dbt.type_timestamp() }} + ) + {%- elif format == "nanoseconds" -%} + cast( + from_unixtime_nanos( + {{ epochs }} + ) at time zone 'UTC' as {{ dbt.type_timestamp() }} + ) + {%- else -%} + {{ + exceptions.raise_compiler_error( + "value " + ~ format + ~ " for `format` for from_unixtimestamp is not supported." + ) + }} + {% endif -%} + +{%- endmacro %} + + +{%- macro duckdb__from_unixtimestamp(epochs, format="seconds") -%} + {%- if format != "seconds" -%} + {{ + exceptions.raise_compiler_error( + "value " + ~ format + ~ " for `format` for from_unixtimestamp is not supported." + ) + }} + {% endif -%} + cast(to_timestamp({{ epochs }}) at time zone 'UTC' as timestamp) +{%- endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/iso_week_end.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/iso_week_end.sql new file mode 100644 index 0000000000..5598369457 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/iso_week_end.sql @@ -0,0 +1,16 @@ +{%- macro iso_week_end(date=None, tz=None) -%} + {%- set dt = date if date else dbt_date.today(tz) -%} + {{ adapter.dispatch("iso_week_end", "dbt_date")(dt) }} +{%- endmacro -%} + +{%- macro _iso_week_end(date, week_type) -%} + {%- set dt = dbt_date.iso_week_start(date) -%} {{ dbt_date.n_days_away(6, dt) }} +{%- endmacro %} + +{%- macro default__iso_week_end(date) -%} + {{ dbt_date._iso_week_end(date, "isoweek") }} +{%- endmacro %} + +{%- macro snowflake__iso_week_end(date) -%} + {{ dbt_date._iso_week_end(date, "weekiso") }} +{%- endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/iso_week_of_year.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/iso_week_of_year.sql new file mode 100644 index 0000000000..89f281c101 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/iso_week_of_year.sql @@ -0,0 +1,34 @@ +{%- macro iso_week_of_year(date=None, tz=None) -%} + {%- set dt = date if date else dbt_date.today(tz) -%} + {{ adapter.dispatch("iso_week_of_year", "dbt_date")(dt) }} +{%- endmacro -%} + +{%- macro _iso_week_of_year(date, week_type) -%} + cast({{ dbt_date.date_part(week_type, date) }} as {{ dbt.type_int() }}) +{%- endmacro %} + +{%- macro default__iso_week_of_year(date) -%} + {{ dbt_date._iso_week_of_year(date, "isoweek") }} +{%- endmacro %} + +{%- macro snowflake__iso_week_of_year(date) -%} + {{ dbt_date._iso_week_of_year(date, "weekiso") }} +{%- endmacro %} + +{%- macro postgres__iso_week_of_year(date) -%} + -- postgresql week is isoweek, the first week of a year containing January 4 of + -- that year. + {{ dbt_date._iso_week_of_year(date, "week") }} +{%- endmacro %} + +{%- macro duckdb__iso_week_of_year(date) -%} + {{ return(dbt_date.postgres__iso_week_of_year(date)) }} +{%- endmacro %} + +{%- macro spark__iso_week_of_year(date) -%} + {{ dbt_date._iso_week_of_year(date, "week") }} +{%- endmacro %} + +{%- macro trino__iso_week_of_year(date) -%} + {{ dbt_date._iso_week_of_year(date, "week") }} +{%- endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/iso_week_start.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/iso_week_start.sql new file mode 100644 index 0000000000..871086fada --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/iso_week_start.sql @@ -0,0 +1,32 @@ +{%- macro iso_week_start(date=None, tz=None) -%} + {%- set dt = date if date else dbt_date.today(tz) -%} + {{ adapter.dispatch("iso_week_start", "dbt_date")(dt) }} +{%- endmacro -%} + +{%- macro _iso_week_start(date, week_type) -%} + cast({{ dbt.date_trunc(week_type, date) }} as date) +{%- endmacro %} + +{%- macro default__iso_week_start(date) -%} + {{ dbt_date._iso_week_start(date, "isoweek") }} +{%- endmacro %} + +{%- macro snowflake__iso_week_start(date) -%} + {{ dbt_date._iso_week_start(date, "week") }} +{%- endmacro %} + +{%- macro postgres__iso_week_start(date) -%} + {{ dbt_date._iso_week_start(date, "week") }} +{%- endmacro %} + +{%- macro duckdb__iso_week_start(date) -%} + {{ return(dbt_date.postgres__iso_week_start(date)) }} +{%- endmacro %} + +{%- macro spark__iso_week_start(date) -%} + {{ dbt_date._iso_week_start(date, "week") }} +{%- endmacro %} + +{%- macro trino__iso_week_start(date) -%} + {{ dbt_date._iso_week_start(date, "week") }} +{%- endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/last_month.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/last_month.sql new file mode 100644 index 0000000000..f14370fa26 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/last_month.sql @@ -0,0 +1 @@ +{%- macro last_month(tz=None) -%} {{ dbt_date.n_months_ago(1, tz) }} {%- endmacro -%} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/last_month_name.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/last_month_name.sql new file mode 100644 index 0000000000..6f7a10164a --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/last_month_name.sql @@ -0,0 +1,3 @@ +{%- macro last_month_name(short=True, tz=None) -%} + {{ dbt_date.month_name(dbt_date.last_month(tz), short=short) }} +{%- endmacro -%} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/last_month_number.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/last_month_number.sql new file mode 100644 index 0000000000..32fef4dd9e --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/last_month_number.sql @@ -0,0 +1,3 @@ +{%- macro last_month_number(tz=None) -%} + {{ dbt_date.date_part("month", dbt_date.last_month(tz)) }} +{%- endmacro -%} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/last_week.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/last_week.sql new file mode 100644 index 0000000000..9cfa3675c3 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/last_week.sql @@ -0,0 +1 @@ +{%- macro last_week(tz=None) -%} {{ dbt_date.n_weeks_ago(1, tz) }} {%- endmacro -%} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/month_name.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/month_name.sql new file mode 100644 index 0000000000..4ae4452d68 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/month_name.sql @@ -0,0 +1,36 @@ +{%- macro month_name(date, short=True) -%} + {{ adapter.dispatch("month_name", "dbt_date")(date, short) }} +{%- endmacro %} + +{%- macro default__month_name(date, short) -%} + {%- set f = "MON" if short else "MONTH" -%} to_char({{ date }}, '{{ f }}') +{%- endmacro %} + +{%- macro bigquery__month_name(date, short) -%} + {%- set f = "%b" if short else "%B" -%} + format_date('{{ f }}', cast({{ date }} as date)) +{%- endmacro %} + +{%- macro snowflake__month_name(date, short) -%} + {%- set f = "MON" if short else "MMMM" -%} to_char({{ date }}, '{{ f }}') +{%- endmacro %} + +{%- macro postgres__month_name(date, short) -%} + {# FM = Fill mode, which suppresses padding blanks #} + {%- set f = "FMMon" if short else "FMMonth" -%} to_char({{ date }}, '{{ f }}') +{%- endmacro %} + + +{%- macro duckdb__month_name(date, short) -%} + {%- if short -%} substr(monthname({{ date }}), 1, 3) + {%- else -%} monthname({{ date }}) + {%- endif -%} +{%- endmacro %} + +{%- macro spark__month_name(date, short) -%} + {%- set f = "MMM" if short else "MMMM" -%} date_format({{ date }}, '{{ f }}') +{%- endmacro %} + +{%- macro trino__month_name(date, short) -%} + {%- set f = "b" if short else "M" -%} date_format({{ date }}, '%{{ f }}') +{%- endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_days_ago.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_days_ago.sql new file mode 100644 index 0000000000..807e7117cd --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_days_ago.sql @@ -0,0 +1,5 @@ +{%- macro n_days_ago(n, date=None, tz=None) -%} + {%- set dt = date if date else dbt_date.today(tz) -%} + {%- set n = n | int -%} + cast({{ dbt.dateadd("day", -1 * n, dt) }} as date) +{%- endmacro -%} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_days_away.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_days_away.sql new file mode 100644 index 0000000000..5308fef130 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_days_away.sql @@ -0,0 +1,3 @@ +{%- macro n_days_away(n, date=None, tz=None) -%} + {{ dbt_date.n_days_ago(-1 * n, date, tz) }} +{%- endmacro -%} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_months_ago.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_months_ago.sql new file mode 100644 index 0000000000..d95b86a591 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_months_ago.sql @@ -0,0 +1,4 @@ +{%- macro n_months_ago(n, tz=None) -%} + {%- set n = n | int -%} + {{ dbt.date_trunc("month", dbt.dateadd("month", -1 * n, dbt_date.today(tz))) }} +{%- endmacro -%} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_months_away.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_months_away.sql new file mode 100644 index 0000000000..15aaf932f2 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_months_away.sql @@ -0,0 +1,4 @@ +{%- macro n_months_away(n, tz=None) -%} + {%- set n = n | int -%} + {{ dbt.date_trunc("month", dbt.dateadd("month", n, dbt_date.today(tz))) }} +{%- endmacro -%} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_weeks_ago.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_weeks_ago.sql new file mode 100644 index 0000000000..d6c9675465 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_weeks_ago.sql @@ -0,0 +1,4 @@ +{%- macro n_weeks_ago(n, tz=None) -%} + {%- set n = n | int -%} + {{ dbt.date_trunc("week", dbt.dateadd("week", -1 * n, dbt_date.today(tz))) }} +{%- endmacro -%} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_weeks_away.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_weeks_away.sql new file mode 100644 index 0000000000..3e573c907c --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/n_weeks_away.sql @@ -0,0 +1,4 @@ +{%- macro n_weeks_away(n, tz=None) -%} + {%- set n = n | int -%} + {{ dbt.date_trunc("week", dbt.dateadd("week", n, dbt_date.today(tz))) }} +{%- endmacro -%} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/next_month.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/next_month.sql new file mode 100644 index 0000000000..a7ce7af39f --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/next_month.sql @@ -0,0 +1 @@ +{%- macro next_month(tz=None) -%} {{ dbt_date.n_months_away(1, tz) }} {%- endmacro -%} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/next_month_name.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/next_month_name.sql new file mode 100644 index 0000000000..fc1ecfdcf6 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/next_month_name.sql @@ -0,0 +1,3 @@ +{%- macro next_month_name(short=True, tz=None) -%} + {{ dbt_date.month_name(dbt_date.next_month(tz), short=short) }} +{%- endmacro -%} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/next_month_number.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/next_month_number.sql new file mode 100644 index 0000000000..87bffb048d --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/next_month_number.sql @@ -0,0 +1,3 @@ +{%- macro next_month_number(tz=None) -%} + {{ dbt_date.date_part("month", dbt_date.next_month(tz)) }} +{%- endmacro -%} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/next_week.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/next_week.sql new file mode 100644 index 0000000000..a4d58b9f63 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/next_week.sql @@ -0,0 +1 @@ +{%- macro next_week(tz=None) -%} {{ dbt_date.n_weeks_away(1, tz) }} {%- endmacro -%} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/now.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/now.sql new file mode 100644 index 0000000000..432b7d6d47 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/now.sql @@ -0,0 +1,3 @@ +{%- macro now(tz=None) -%} + {{ dbt_date.convert_timezone(dbt.current_timestamp(), tz) }} +{%- endmacro -%} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/periods_since.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/periods_since.sql new file mode 100644 index 0000000000..f98fec069a --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/periods_since.sql @@ -0,0 +1,3 @@ +{%- macro periods_since(date_col, period_name="day", tz=None) -%} + {{ dbt.datediff(date_col, dbt_date.now(tz), period_name) }} +{%- endmacro -%} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/round_timestamp.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/round_timestamp.sql new file mode 100644 index 0000000000..7fd17fdd48 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/round_timestamp.sql @@ -0,0 +1,3 @@ +{% macro round_timestamp(timestamp) %} + {{ dbt.date_trunc("day", dbt.dateadd("hour", 12, timestamp)) }} +{% endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/to_unixtimestamp.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/to_unixtimestamp.sql new file mode 100644 index 0000000000..1f61cd1963 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/to_unixtimestamp.sql @@ -0,0 +1,23 @@ +{%- macro to_unixtimestamp(timestamp) -%} + {{ adapter.dispatch("to_unixtimestamp", "dbt_date")(timestamp) }} +{%- endmacro %} + +{%- macro default__to_unixtimestamp(timestamp) -%} + {{ dbt_date.date_part("epoch", timestamp) }} +{%- endmacro %} + +{%- macro snowflake__to_unixtimestamp(timestamp) -%} + {{ dbt_date.date_part("epoch_seconds", timestamp) }} +{%- endmacro %} + +{%- macro bigquery__to_unixtimestamp(timestamp) -%} + unix_seconds({{ timestamp }}) +{%- endmacro %} + +{%- macro spark__to_unixtimestamp(timestamp) -%} + unix_timestamp({{ timestamp }}) +{%- endmacro %} + +{%- macro trino__to_unixtimestamp(timestamp) -%} + to_unixtime({{ timestamp }} at time zone 'UTC') +{%- endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/today.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/today.sql new file mode 100644 index 0000000000..db38bc5af5 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/today.sql @@ -0,0 +1 @@ +{%- macro today(tz=None) -%} cast({{ dbt_date.now(tz) }} as date) {%- endmacro -%} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/tomorrow.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/tomorrow.sql new file mode 100644 index 0000000000..a81d273e3a --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/tomorrow.sql @@ -0,0 +1,3 @@ +{%- macro tomorrow(date=None, tz=None) -%} + {{ dbt_date.n_days_away(1, date, tz) }} +{%- endmacro -%} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/week_end.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/week_end.sql new file mode 100644 index 0000000000..abf728b2ed --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/week_end.sql @@ -0,0 +1,18 @@ +{%- macro week_end(date=None, tz=None) -%} + {%- set dt = date if date else dbt_date.today(tz) -%} + {{ adapter.dispatch("week_end", "dbt_date")(dt) }} +{%- endmacro -%} + +{%- macro default__week_end(date) -%} {{ last_day(date, "week") }} {%- endmacro %} + +{%- macro snowflake__week_end(date) -%} + {%- set dt = dbt_date.week_start(date) -%} {{ dbt_date.n_days_away(6, dt) }} +{%- endmacro %} + +{%- macro postgres__week_end(date) -%} + {%- set dt = dbt_date.week_start(date) -%} {{ dbt_date.n_days_away(6, dt) }} +{%- endmacro %} + +{%- macro duckdb__week_end(date) -%} + {{ return(dbt_date.postgres__week_end(date)) }} +{%- endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/week_of_year.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/week_of_year.sql new file mode 100644 index 0000000000..e9c50551f6 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/week_of_year.sql @@ -0,0 +1,23 @@ +{%- macro week_of_year(date=None, tz=None) -%} + {%- set dt = date if date else dbt_date.today(tz) -%} + {{ adapter.dispatch("week_of_year", "dbt_date")(dt) }} +{%- endmacro -%} + +{%- macro default__week_of_year(date) -%} + cast({{ dbt_date.date_part("week", date) }} as {{ dbt.type_int() }}) +{%- endmacro %} + +{%- macro postgres__week_of_year(date) -%} + {# postgresql 'week' returns isoweek. Use to_char instead. + WW = the first week starts on the first day of the year #} + cast(to_char({{ date }}, 'WW') as {{ dbt.type_int() }}) +{%- endmacro %} + +{%- macro duckdb__week_of_year(date) -%} + cast(ceil(dayofyear({{ date }}) / 7) as int) +{%- endmacro %} + +{# {%- macro spark__week_of_year(date) -%} +weekofyear({{ date }}) +{%- endmacro %} #} + diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/week_start.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/week_start.sql new file mode 100644 index 0000000000..4f45d6bf07 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/week_start.sql @@ -0,0 +1,32 @@ +{%- macro week_start(date=None, tz=None) -%} + {%- set dt = date if date else dbt_date.today(tz) -%} + {{ adapter.dispatch("week_start", "dbt_date")(dt) }} +{%- endmacro -%} + +{%- macro default__week_start(date) -%} + cast({{ dbt.date_trunc("week", date) }} as date) +{%- endmacro %} + +{%- macro snowflake__week_start(date) -%} + {# + Get the day of week offset: e.g. if the date is a Sunday, + dbt_date.day_of_week returns 1, so we subtract 1 to get a 0 offset + #} + {% set off_set = dbt_date.day_of_week(date, isoweek=False) ~ " - 1" %} + cast({{ dbt.dateadd("day", "-1 * (" ~ off_set ~ ")", date) }} as date) +{%- endmacro %} + +{%- macro postgres__week_start(date) -%} + -- Sunday as week start date + cast( + {{ + dbt.dateadd( + "day", -1, dbt.date_trunc("week", dbt.dateadd("day", 1, date)) + ) + }} as date + ) +{%- endmacro %} + +{%- macro duckdb__week_start(date) -%} + {{ return(dbt_date.postgres__week_start(date)) }} +{%- endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/yesterday.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/yesterday.sql new file mode 100644 index 0000000000..3c55968cdb --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/calendar_date/yesterday.sql @@ -0,0 +1,3 @@ +{%- macro yesterday(date=None, tz=None) -%} + {{ dbt_date.n_days_ago(1, date, tz) }} +{%- endmacro -%} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/fiscal_date/get_fiscal_periods.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/fiscal_date/get_fiscal_periods.sql new file mode 100644 index 0000000000..c0f6cf1385 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/fiscal_date/get_fiscal_periods.sql @@ -0,0 +1,81 @@ +{% macro get_fiscal_periods(dates, year_end_month, week_start_day, shift_year=1) %} + {# +This macro requires you to pass in a ref to a date dimension, created via +dbt_date.get_date_dimension()s +#} + with + fscl_year_dates_for_periods as ( + {{ + dbt_date.get_fiscal_year_dates( + dates, year_end_month, week_start_day, shift_year + ) + }} + ), + fscl_year_w13 as ( + + select + f.*, + -- We count the weeks in a 13 week period + -- and separate the 4-5-4 week sequences + mod( + cast((f.fiscal_week_of_year - 1) as {{ dbt.type_int() }}), 13 + ) as w13_number, + -- Chop weeks into 13 week merch quarters + cast( + least( + floor((f.fiscal_week_of_year - 1) / 13.0), 3 + ) as {{ dbt.type_int() }} + ) as quarter_number + from fscl_year_dates_for_periods f + + ), + fscl_periods as ( + + select + f.date_day, + f.fiscal_year_number, + f.week_start_date, + f.week_end_date, + f.fiscal_week_of_year, + case + -- we move week 53 into the 3rd period of the quarter + when f.fiscal_week_of_year = 53 + then 3 + when f.w13_number between 0 and 3 + then 1 + when f.w13_number between 4 and 8 + then 2 + when f.w13_number between 9 and 12 + then 3 + end as period_of_quarter, + f.quarter_number + from fscl_year_w13 f + + ), + fscl_periods_quarters as ( + + select + f.*, + cast( + ( + (f.quarter_number * 3) + f.period_of_quarter + ) as {{ dbt.type_int() }} + ) as fiscal_period_number + from fscl_periods f + + ) + select + date_day, + fiscal_year_number, + week_start_date, + week_end_date, + fiscal_week_of_year, + dense_rank() over ( + partition by fiscal_period_number order by fiscal_week_of_year + ) as fiscal_week_of_period, + fiscal_period_number, + quarter_number + 1 as fiscal_quarter_number, + period_of_quarter as fiscal_period_of_quarter + from fscl_periods_quarters + order by 1, 2 +{% endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/fiscal_date/get_fiscal_year_dates.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/fiscal_date/get_fiscal_year_dates.sql new file mode 100644 index 0000000000..3fe83c9295 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/fiscal_date/get_fiscal_year_dates.sql @@ -0,0 +1,114 @@ +{% macro get_fiscal_year_dates( + dates, year_end_month=12, week_start_day=1, shift_year=1 +) %} + {{ + adapter.dispatch("get_fiscal_year_dates", "dbt_date")( + dates, year_end_month, week_start_day, shift_year + ) + }} +{% endmacro %} + +{% macro default__get_fiscal_year_dates( + dates, year_end_month, week_start_day, shift_year +) %} + -- this gets all the dates within a fiscal year + -- determined by the given year-end-month + -- ending on the saturday closest to that month's end date + with + fsc_date_dimension as (select * from {{ dates }}), + year_month_end as ( + + select + d.year_number - {{ shift_year }} as fiscal_year_number, d.month_end_date + from fsc_date_dimension d + where d.month_of_year = {{ year_end_month }} + group by 1, 2 + + ), + weeks as ( + + select + d.year_number, + d.month_of_year, + d.date_day as week_start_date, + cast({{ dbt.dateadd("day", 6, "d.date_day") }} as date) as week_end_date + from fsc_date_dimension d + where d.day_of_week = {{ week_start_day }} + + ), + -- get all the weeks that start in the month the year ends + year_week_ends as ( + + select + d.year_number - {{ shift_year }} as fiscal_year_number, d.week_end_date + from weeks d + where d.month_of_year = {{ year_end_month }} + group by 1, 2 + + ), + -- then calculate which Saturday is closest to month end + weeks_at_month_end as ( + + select + d.fiscal_year_number, + d.week_end_date, + m.month_end_date, + rank() over ( + partition by d.fiscal_year_number + order by + abs( + {{ + dbt.datediff( + "d.week_end_date", "m.month_end_date", "day" + ) + }} + ) + + ) as closest_to_month_end + from year_week_ends d + join year_month_end m on d.fiscal_year_number = m.fiscal_year_number + ), + fiscal_year_range as ( + + select + w.fiscal_year_number, + cast( + {{ + dbt.dateadd( + "day", + 1, + "lag(w.week_end_date) over(order by w.week_end_date)", + ) + }} as date + ) as fiscal_year_start_date, + w.week_end_date as fiscal_year_end_date + from weeks_at_month_end w + where w.closest_to_month_end = 1 + + ), + fiscal_year_dates as ( + + select + d.date_day, + m.fiscal_year_number, + m.fiscal_year_start_date, + m.fiscal_year_end_date, + w.week_start_date, + w.week_end_date, + -- we reset the weeks of the year starting with the merch year start + -- date + dense_rank() over ( + partition by m.fiscal_year_number order by w.week_start_date + ) as fiscal_week_of_year + from fsc_date_dimension d + join + fiscal_year_range m + on d.date_day + between m.fiscal_year_start_date and m.fiscal_year_end_date + join weeks w on d.date_day between w.week_start_date and w.week_end_date + + ) + select * + from fiscal_year_dates + order by 1 +{% endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/get_base_dates.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/get_base_dates.sql new file mode 100644 index 0000000000..cba16be4c5 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/get_base_dates.sql @@ -0,0 +1,109 @@ +{% macro get_base_dates( + start_date=None, end_date=None, n_dateparts=None, datepart="day" +) %} + {{ + adapter.dispatch("get_base_dates", "dbt_date")( + start_date, end_date, n_dateparts, datepart + ) + }} +{% endmacro %} + +{% macro default__get_base_dates(start_date, end_date, n_dateparts, datepart) %} + + {%- if start_date and end_date -%} + {%- set start_date = ( + "cast('" ~ start_date ~ "' as " ~ dbt.type_timestamp() ~ ")" + ) -%} + {%- set end_date = ( + "cast('" ~ end_date ~ "' as " ~ dbt.type_timestamp() ~ ")" + ) -%} + + {%- elif n_dateparts and datepart -%} + + {%- set start_date = dbt.dateadd( + datepart, -1 * n_dateparts, dbt_date.today() + ) -%} + {%- set end_date = dbt_date.tomorrow() -%} + {%- endif -%} + + with + date_spine as ( + + {{ + dbt_date.date_spine( + datepart=datepart, + start_date=start_date, + end_date=end_date, + ) + }} + + ) + select + cast(d.date_{{ datepart }} as {{ dbt.type_timestamp() }}) as date_{{ datepart }} + from date_spine d +{% endmacro %} + +{% macro bigquery__get_base_dates(start_date, end_date, n_dateparts, datepart) %} + + {%- if start_date and end_date -%} + {%- set start_date = "cast('" ~ start_date ~ "' as datetime )" -%} + {%- set end_date = "cast('" ~ end_date ~ "' as datetime )" -%} + + {%- elif n_dateparts and datepart -%} + + {%- set start_date = dbt.dateadd( + datepart, -1 * n_dateparts, dbt_date.today() + ) -%} + {%- set end_date = dbt_date.tomorrow() -%} + {%- endif -%} + + with + date_spine as ( + + {{ + dbt_date.date_spine( + datepart=datepart, + start_date=start_date, + end_date=end_date, + ) + }} + + ) + select + cast(d.date_{{ datepart }} as {{ dbt.type_timestamp() }}) as date_{{ datepart }} + from date_spine d +{% endmacro %} + + +{% macro trino__get_base_dates(start_date, end_date, n_dateparts, datepart) %} + + {%- if start_date and end_date -%} + {%- set start_date = ( + "cast('" ~ start_date ~ "' as " ~ dbt.type_timestamp() ~ ")" + ) -%} + {%- set end_date = ( + "cast('" ~ end_date ~ "' as " ~ dbt.type_timestamp() ~ ")" + ) -%} + + {%- elif n_dateparts and datepart -%} + + {%- set start_date = dbt.dateadd(datepart, -1 * n_dateparts, dbt_date.now()) -%} + {%- set end_date = dbt_date.tomorrow() -%} + {%- endif -%} + + with + date_spine as ( + + {{ + dbt_date.date_spine( + datepart=datepart, + start_date=start_date, + end_date=end_date, + ) + }} + + ) + select + cast(d.date_{{ datepart }} as {{ dbt.type_timestamp() }}) as date_{{ datepart }} + from date_spine d +{% endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/get_date_dimension.sql b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/get_date_dimension.sql new file mode 100644 index 0000000000..159c338dc7 --- /dev/null +++ b/dev/dags/dbt/simple/dbt_packages/dbt_date/macros/get_date_dimension.sql @@ -0,0 +1,180 @@ +{% macro get_date_dimension(start_date, end_date) %} + {{ adapter.dispatch("get_date_dimension", "dbt_date")(start_date, end_date) }} +{% endmacro %} + +{% macro default__get_date_dimension(start_date, end_date) %} + with + base_dates as ({{ dbt_date.get_base_dates(start_date, end_date) }}), + dates_with_prior_year_dates as ( + + select + cast(d.date_day as date) as date_day, + cast( + {{ dbt.dateadd("year", -1, "d.date_day") }} as date + ) as prior_year_date_day, + cast( + {{ dbt.dateadd("day", -364, "d.date_day") }} as date + ) as prior_year_over_year_date_day + from base_dates d + + ) + select + d.date_day, + {{ dbt_date.yesterday("d.date_day") }} as prior_date_day, + {{ dbt_date.tomorrow("d.date_day") }} as next_date_day, + d.prior_year_date_day as prior_year_date_day, + d.prior_year_over_year_date_day, + {{ dbt_date.day_of_week("d.date_day", isoweek=false) }} as day_of_week, + {{ dbt_date.day_of_week("d.date_day", isoweek=true) }} as day_of_week_iso, + {{ dbt_date.day_name("d.date_day", short=false) }} as day_of_week_name, + {{ dbt_date.day_name("d.date_day", short=true) }} as day_of_week_name_short, + {{ dbt_date.day_of_month("d.date_day") }} as day_of_month, + {{ dbt_date.day_of_year("d.date_day") }} as day_of_year, + + {{ dbt_date.week_start("d.date_day") }} as week_start_date, + {{ dbt_date.week_end("d.date_day") }} as week_end_date, + {{ dbt_date.week_start("d.prior_year_over_year_date_day") }} + as prior_year_week_start_date, + {{ dbt_date.week_end("d.prior_year_over_year_date_day") }} + as prior_year_week_end_date, + {{ dbt_date.week_of_year("d.date_day") }} as week_of_year, + + {{ dbt_date.iso_week_start("d.date_day") }} as iso_week_start_date, + {{ dbt_date.iso_week_end("d.date_day") }} as iso_week_end_date, + {{ dbt_date.iso_week_start("d.prior_year_over_year_date_day") }} + as prior_year_iso_week_start_date, + {{ dbt_date.iso_week_end("d.prior_year_over_year_date_day") }} + as prior_year_iso_week_end_date, + {{ dbt_date.iso_week_of_year("d.date_day") }} as iso_week_of_year, + + {{ dbt_date.week_of_year("d.prior_year_over_year_date_day") }} + as prior_year_week_of_year, + {{ dbt_date.iso_week_of_year("d.prior_year_over_year_date_day") }} + as prior_year_iso_week_of_year, + + cast( + {{ dbt_date.date_part("month", "d.date_day") }} as {{ dbt.type_int() }} + ) as month_of_year, + {{ dbt_date.month_name("d.date_day", short=false) }} as month_name, + {{ dbt_date.month_name("d.date_day", short=true) }} as month_name_short, + + cast({{ dbt.date_trunc("month", "d.date_day") }} as date) as month_start_date, + cast({{ last_day("d.date_day", "month") }} as date) as month_end_date, + + cast( + {{ dbt.date_trunc("month", "d.prior_year_date_day") }} as date + ) as prior_year_month_start_date, + cast( + {{ last_day("d.prior_year_date_day", "month") }} as date + ) as prior_year_month_end_date, + + cast( + {{ dbt_date.date_part("quarter", "d.date_day") }} as {{ dbt.type_int() }} + ) as quarter_of_year, + cast( + {{ dbt.date_trunc("quarter", "d.date_day") }} as date + ) as quarter_start_date, + cast({{ last_day("d.date_day", "quarter") }} as date) as quarter_end_date, + + cast( + {{ dbt_date.date_part("year", "d.date_day") }} as {{ dbt.type_int() }} + ) as year_number, + cast({{ dbt.date_trunc("year", "d.date_day") }} as date) as year_start_date, + cast({{ last_day("d.date_day", "year") }} as date) as year_end_date + from dates_with_prior_year_dates d + order by 1 +{% endmacro %} + +{% macro postgres__get_date_dimension(start_date, end_date) %} + with + base_dates as ({{ dbt_date.get_base_dates(start_date, end_date) }}), + dates_with_prior_year_dates as ( + + select + cast(d.date_day as date) as date_day, + cast( + {{ dbt.dateadd("year", -1, "d.date_day") }} as date + ) as prior_year_date_day, + cast( + {{ dbt.dateadd("day", -364, "d.date_day") }} as date + ) as prior_year_over_year_date_day + from base_dates d + + ) + select + d.date_day, + {{ dbt_date.yesterday("d.date_day") }} as prior_date_day, + {{ dbt_date.tomorrow("d.date_day") }} as next_date_day, + d.prior_year_date_day as prior_year_date_day, + d.prior_year_over_year_date_day, + {{ dbt_date.day_of_week("d.date_day", isoweek=true) }} as day_of_week, + + {{ dbt_date.day_name("d.date_day", short=false) }} as day_of_week_name, + {{ dbt_date.day_name("d.date_day", short=true) }} as day_of_week_name_short, + {{ dbt_date.day_of_month("d.date_day") }} as day_of_month, + {{ dbt_date.day_of_year("d.date_day") }} as day_of_year, + + {{ dbt_date.week_start("d.date_day") }} as week_start_date, + {{ dbt_date.week_end("d.date_day") }} as week_end_date, + {{ dbt_date.week_start("d.prior_year_over_year_date_day") }} + as prior_year_week_start_date, + {{ dbt_date.week_end("d.prior_year_over_year_date_day") }} + as prior_year_week_end_date, + {{ dbt_date.week_of_year("d.date_day") }} as week_of_year, + + {{ dbt_date.iso_week_start("d.date_day") }} as iso_week_start_date, + {{ dbt_date.iso_week_end("d.date_day") }} as iso_week_end_date, + {{ dbt_date.iso_week_start("d.prior_year_over_year_date_day") }} + as prior_year_iso_week_start_date, + {{ dbt_date.iso_week_end("d.prior_year_over_year_date_day") }} + as prior_year_iso_week_end_date, + {{ dbt_date.iso_week_of_year("d.date_day") }} as iso_week_of_year, + + {{ dbt_date.week_of_year("d.prior_year_over_year_date_day") }} + as prior_year_week_of_year, + {{ dbt_date.iso_week_of_year("d.prior_year_over_year_date_day") }} + as prior_year_iso_week_of_year, + + cast( + {{ dbt_date.date_part("month", "d.date_day") }} as {{ dbt.type_int() }} + ) as month_of_year, + {{ dbt_date.month_name("d.date_day", short=false) }} as month_name, + {{ dbt_date.month_name("d.date_day", short=true) }} as month_name_short, + + cast({{ dbt.date_trunc("month", "d.date_day") }} as date) as month_start_date, + cast({{ last_day("d.date_day", "month") }} as date) as month_end_date, + + cast( + {{ dbt.date_trunc("month", "d.prior_year_date_day") }} as date + ) as prior_year_month_start_date, + cast( + {{ last_day("d.prior_year_date_day", "month") }} as date + ) as prior_year_month_end_date, + + cast( + {{ dbt_date.date_part("quarter", "d.date_day") }} as {{ dbt.type_int() }} + ) as quarter_of_year, + cast( + {{ dbt.date_trunc("quarter", "d.date_day") }} as date + ) as quarter_start_date, + {# last_day does not support quarter because postgresql does not support quarter interval. #} + cast( + {{ + dbt.dateadd( + "day", + "-1", + dbt.dateadd( + "month", "3", dbt.date_trunc("quarter", "d.date_day") + ), + ) + }} as date + ) as quarter_end_date, + + cast( + {{ dbt_date.date_part("year", "d.date_day") }} as {{ dbt.type_int() }} + ) as year_number, + cast({{ dbt.date_trunc("year", "d.date_day") }} as date) as year_start_date, + cast({{ last_day("d.date_day", "year") }} as date) as year_end_date + from dates_with_prior_year_dates d + order by 1 +{% endmacro %} diff --git a/dev/dags/dbt/simple/dbt_packages/dbt_date/packages.yml b/dev/dags/dbt/simple/dbt_packages/dbt_date/packages.yml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/dev/dags/dbt/simple/package-lock.yml b/dev/dags/dbt/simple/package-lock.yml new file mode 100644 index 0000000000..9cf994ac02 --- /dev/null +++ b/dev/dags/dbt/simple/package-lock.yml @@ -0,0 +1,4 @@ +packages: + - package: godatadriven/dbt_date + version: 0.11.0 +sha1_hash: 1166423b7913acc5dbfae59492255eddd23a7db2 diff --git a/dev/dags/dbt/simple/packages.yml b/dev/dags/dbt/simple/packages.yml new file mode 100644 index 0000000000..9a7510e9c7 --- /dev/null +++ b/dev/dags/dbt/simple/packages.yml @@ -0,0 +1,3 @@ +packages: + - package: godatadriven/dbt_date + version: 0.11.0