-
Notifications
You must be signed in to change notification settings - Fork 41
FEAT/Calculate business days elapsed based on two inputs #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
cris-seaton
wants to merge
12
commits into
master
Choose a base branch
from
feat/business_day_udf
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 2 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
249d93a
calculate business days elapsed based on two inputs
cris-seaton dc5d2f3
add second udf to calculate elapsed business hours
cris-seaton ec66603
incorporate country and Alex comments
cris-seaton a732a55
fix edge cases
cris-seaton 73cba53
reinsert first_day_closed and last_day_open
cris-seaton b1157ee
iterate forward to next non-holiday at opening hour
cris-seaton 3253a67
change early return and accomodate negative calculations with max()
cris-seaton 0fa6a07
change start_datetime to opening hours after iterate forward
cris-seaton c6b0b33
add weekmask to have optional 4 day work weeks
cris-seaton 50c2d5a
update readme
cris-seaton 563dcda
hardcode overloading UDFs for defaulted parameters
cris-seaton e2b8b24
update documentation
cris-seaton File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| {% macro create_udfs() %} | ||
|
|
||
| create schema if not exists {{target.schema}}; | ||
|
|
||
| {{ create_udf_business_days() }}; | ||
| {{ create_udf_business_hours() }}; | ||
|
|
||
| {% endmacro %} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| {% macro create_udf_business_days() %} | ||
|
|
||
| create or replace function {{target.schema}}.udf_business_days(date1 TIMESTAMP_NTZ, date2 TIMESTAMP_NTZ, country STRING) | ||
| returns int | ||
| language python | ||
| runtime_version = 3.8 | ||
| packages = ('numpy','holidays') | ||
| handler = 'business_days_py' | ||
| as | ||
|
|
||
| $$ | ||
| import numpy as np | ||
| import holidays as holidays | ||
|
|
||
| def business_days_py(start_date, end_date, country='US'): | ||
|
|
||
| # calculate business days between two dates | ||
| start_date = start_date.date() | ||
| end_date = end_date.date() | ||
| years = [*range(1990,2030)] | ||
|
|
||
| US_holiday_list = list(holidays.(years=years)) | ||
|
cris-seaton marked this conversation as resolved.
Outdated
|
||
|
|
||
| return np.busday_count(start_date, end_date, holidays=US_holiday_list) | ||
|
|
||
| $$ | ||
|
|
||
| {% endmacro %} | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| version: 2 | ||
|
|
||
| macros: | ||
| - name: create_udf_business_days | ||
| description: > | ||
| This macro creates a UDF within the target schema that can be used to | ||
| calculate business days (excluding US holidays) between two date fields | ||
|
cris-seaton marked this conversation as resolved.
Outdated
|
||
| in a Snowflake table. The macro mearly creates the UDF within Snowflake, | ||
| and is called by the create_udfs macro. You must ensure that create_udfs | ||
| is called on-run-start in dbt_project.yml. | ||
|
|
||
| To utilize the UDF within your dbt project, proper syntax is: | ||
|
|
||
| select | ||
| *, | ||
| udf_business_days(start_date, end_date) as business_days_elapsed | ||
| from sample_table | ||
|
|
||
| Notes: | ||
| - The fields passed MUST be type == date. | ||
| - This UDF only considers holidays between Jan 1 1990 and Dec 31 2030. | ||
| - This UDF hard codes holiday date range (above) and holiday date country | ||
| (as US). Modify the macro to apply to different countries, define | ||
| regional (state or province) holidays or expand the date range. | ||
|
|
||
| docs: | ||
| show: false | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| {% macro create_udf_business_hours() %} | ||
|
|
||
| create or replace function {{target.schema}}.udf_business_hours(datetime1 TIMESTAMP_NTZ, datetime2 TIMESTAMP_NTZ) | ||
| returns int | ||
|
cris-seaton marked this conversation as resolved.
Outdated
|
||
| language python | ||
| runtime_version = 3.8 | ||
| packages = ('numpy','holidays') | ||
| handler = 'business_hours_py' | ||
| as | ||
|
|
||
| $$ | ||
| from datetime import datetime | ||
| from datetime import timedelta | ||
| import holidays | ||
| import numpy as np | ||
|
|
||
| def business_hours_py(datetime1, datetime2): | ||
|
cris-seaton marked this conversation as resolved.
Outdated
|
||
|
|
||
| years = [*range(2000,2030)] | ||
|
|
||
| US_holiday_list = list(holidays.US(years=years)) | ||
| days = np.busday_count( | ||
| datetime1.date(), | ||
| datetime2.date(), | ||
| holidays = US_holiday_list) | ||
| complete_days_hours = (days-1) * 8 | ||
|
cris-seaton marked this conversation as resolved.
Outdated
|
||
|
|
||
| # Jump forward to the next non-holiday | ||
| while datetime1 in US_holiday_list or datetime1.weekday() in [5,6]: | ||
| datetime1 = datetime1 + timedelta(days=1) | ||
| datetime1 = datetime1.replace(hour=9,minute=0,second=0) | ||
|
cris-seaton marked this conversation as resolved.
Outdated
|
||
|
|
||
| # Jump back to the last non-holiday | ||
| while datetime2 in US_holiday_list or datetime2.weekday() in [5,6]: | ||
| datetime2 = datetime2 - timedelta(days=1) | ||
| datetime2 = datetime2.replace(hour=9,minute=0,second=0) | ||
|
cris-seaton marked this conversation as resolved.
Outdated
|
||
|
|
||
| # Hard coded opening and closing times | ||
| biz_opened = datetime1.replace(hour=9, minute=0, second=0) | ||
| biz_closed = datetime1.replace(hour=17, minute=0, second=0) | ||
|
|
||
| if datetime1.date() == datetime2.date(): | ||
| return round((datetime2 - datetime1).seconds/60/60,2) | ||
| else: | ||
| duration_day_zero = round((biz_closed - datetime1 ).seconds/60/60, 2) | ||
| duration_day_n = round((datetime2 - biz_opened).seconds/60/60, 2) | ||
| return round(duration_day_zero + duration_day_n + complete_days_hours, 2) | ||
|
|
||
| $$ | ||
|
|
||
| {% endmacro %} | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.