|
52 | 52 | 'target_date': string (YYYY-MM-DD), |
53 | 53 | 'target_date_start_utc': string (YYYY-MM-DD HH:MM:SS) - start of day in UTC, |
54 | 54 | 'target_date_end_utc': string (YYYY-MM-DD HH:MM:SS) - end of day in UTC, |
55 | | - 'day_of_week': string (e.g. 'Monday'), |
56 | | - 'day_of_month': integer (1-31) |
| 55 | + 'deadline_passed': boolean |
57 | 56 | } |
58 | 57 | #} |
59 | 58 | {% macro calculate_sla_deadline_utc(sla_hour, sla_minute, timezone) %} |
60 | 59 | {% set datetime = modules.datetime %} |
61 | 60 | {% set pytz = modules.pytz %} |
62 | 61 |
|
63 | 62 | {% if elementary.is_dbt_fusion() %} |
64 | | - {# dbt-fusion's pytz and timezone-aware datetime operations have known issues |
65 | | - (dbt-labs/dbt-fusion#143). Use naive UTC datetimes with manual offset |
66 | | - calculation to avoid broken localize() and datetime comparison. |
67 | | - Known limitation: on DST transition days, boundary times (midnight, |
68 | | - SLA deadline) may be off by ~1 hour since we reuse a single offset. |
69 | | - The dbt-core path below handles DST correctly. #} |
70 | | - {% set utc_tz = pytz.timezone("UTC") %} |
| 63 | + {# dbt-fusion's pytz.localize() is unreliable (dbt-labs/dbt-fusion#143). |
| 64 | + Use stdlib datetime.timezone.utc to create a proper UTC-aware datetime, |
| 65 | + then call astimezone(pytz_tz) which uses pytz's fromutc() internally — |
| 66 | + more reliable than localize(). #} |
71 | 67 | {% set target_tz = pytz.timezone(timezone) %} |
72 | 68 |
|
73 | | - {# Get current UTC time as naive datetime - reliable across environments #} |
74 | | - {% set now_utc = datetime.datetime.utcnow() %} |
| 69 | + {# Create a UTC-aware datetime using pytz.utc — avoids datetime.timezone which |
| 70 | + is not exposed in fusion's modules.datetime (dbt-labs/dbt-fusion#143) #} |
| 71 | + {% set now_utc_aware = datetime.datetime.now(pytz.utc) %} |
| 72 | + {% set now_local = now_utc_aware.astimezone(target_tz) %} |
| 73 | + {% set target_date_local = now_local.date() %} |
| 74 | + {% set tz_offset = now_local.utcoffset() %} |
75 | 75 |
|
76 | | - {# Determine today's date and UTC offset in target timezone. |
77 | | - Use localize+astimezone only to probe the offset, not for final values. #} |
78 | | - {% set probe = utc_tz.localize(now_utc).astimezone(target_tz) %} |
79 | | - {% set target_date_local = probe.date() %} |
80 | | - {% set tz_offset = probe.utcoffset() %} |
81 | | - {% set now_local = probe %} |
| 76 | + {# Keep a naive UTC datetime for final deadline comparison #} |
| 77 | + {% set now_utc = datetime.datetime.utcnow() %} |
82 | 78 |
|
83 | 79 | {# Build all datetimes as naive local, then convert to naive UTC |
84 | 80 | by subtracting the timezone offset. This avoids tz-aware comparison. #} |
|
103 | 99 | - tz_offset |
104 | 100 | ) %} |
105 | 101 |
|
| 102 | + {# Compare naive UTC datetimes #} |
| 103 | + {% set deadline_passed = now_utc > sla_deadline_utc %} |
106 | 104 | {% else %} |
107 | 105 | {# Standard dbt-core path using pytz.localize() #} |
108 | 106 | {% set utc_tz = pytz.timezone("UTC") %} |
|
132 | 130 | ) %} |
133 | 131 | {% set sla_deadline_utc = sla_deadline_local.astimezone(utc_tz) %} |
134 | 132 |
|
| 133 | + {% set deadline_passed = now_utc > sla_deadline_utc %} |
135 | 134 | {% endif %} |
136 | 135 |
|
137 | 136 | {# Format for SQL #} |
|
147 | 146 | "target_date": target_date_str, |
148 | 147 | "target_date_start_utc": day_start_utc_str, |
149 | 148 | "target_date_end_utc": day_end_utc_str, |
| 149 | + "deadline_passed": deadline_passed, |
150 | 150 | "day_of_week": now_local.strftime("%A"), |
151 | 151 | "day_of_month": now_local.day, |
152 | 152 | } |
|
0 commit comments