Skip to content

Commit 14a01f5

Browse files
devin-ai-integration[bot]octavia-squidington-iiiagarctfi
authored
fix(source-facebook-marketing): Fix Facebook Marketing UTC hardcoding with per-account timezone detection (#73281)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Octavia Squidington III <octavia-squidington-iii@users.noreply.github.com> Co-authored-by: Alfredo Garcia <alfredo.garcia@hallmark.edu>
1 parent 375612a commit 14a01f5

7 files changed

Lines changed: 39 additions & 9 deletions

File tree

airbyte-integrations/connectors/source-facebook-marketing/metadata.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ data:
1010
connectorSubtype: api
1111
connectorType: source
1212
definitionId: e7778cfc-e97c-4458-9ecb-b4f2bba8946c
13-
dockerImageTag: 5.0.0
13+
dockerImageTag: 5.0.1
1414
dockerRepository: airbyte/source-facebook-marketing
1515
documentationUrl: https://docs.airbyte.com/integrations/sources/facebook-marketing
1616
externalDocumentationUrls:

airbyte-integrations/connectors/source-facebook-marketing/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ requires = [ "poetry-core>=1.0.0",]
33
build-backend = "poetry.core.masonry.api"
44

55
[tool.poetry]
6-
version = "5.0.0"
6+
version = "5.0.1"
77
name = "source-facebook-marketing"
88
description = "Source implementation for Facebook Marketing."
99
authors = [ "Airbyte <contact@airbyte.io>",]

airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,4 @@ def get_account(self, account_id: str) -> AdAccount:
197197
@staticmethod
198198
def _find_account(account_id: str) -> AdAccount:
199199
"""Actual implementation of find account"""
200-
return AdAccount(f"act_{account_id}").api_get()
200+
return AdAccount(f"act_{account_id}").api_get(fields=["timezone_name"])

airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/streams/base_insight_streams.py

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
#
44

55
import logging
6-
from datetime import date, timedelta
6+
from datetime import date, datetime, timedelta
77
from functools import cache, cached_property
88
from typing import Any, Iterable, Iterator, List, Mapping, MutableMapping, Optional, Union
9+
from zoneinfo import ZoneInfo
910

1011
from facebook_business.exceptions import FacebookBadObjectError, FacebookRequestError
1112

@@ -82,6 +83,7 @@ def __init__(
8283
super().__init__(**kwargs)
8384
self._start_date = self._start_date.date()
8485
self._end_date = self._end_date.date()
86+
self._account_end_dates: dict[str, date] = {}
8587
self._custom_fields = fields
8688
if action_breakdowns_allow_empty:
8789
if action_breakdowns is not None:
@@ -282,14 +284,41 @@ def state(self, value: Mapping[str, Any]):
282284

283285
self._next_cursor_values = self._get_start_date()
284286

287+
def _get_end_date_for_account(self, account_id: str) -> date:
288+
"""Compute the effective end date for a specific account based on its timezone.
289+
290+
The Facebook Insights API interprets time_range dates in the ad account's timezone.
291+
For accounts ahead of UTC, the UTC date may lag behind the account's local date,
292+
causing the current day's data to be missed. This adjusts the end date per account.
293+
"""
294+
if account_id in self._account_end_dates:
295+
return self._account_end_dates[account_id]
296+
297+
today_utc = ab_datetime_now().date()
298+
if self._end_date < today_utc:
299+
self._account_end_dates[account_id] = self._end_date
300+
return self._end_date
301+
302+
account = self._api.get_account(account_id=account_id)
303+
timezone_name = account.get("timezone_name")
304+
if isinstance(timezone_name, str):
305+
account_today = datetime.now(tz=ZoneInfo(timezone_name)).date()
306+
end_date = max(self._end_date, account_today)
307+
else:
308+
end_date = self._end_date
309+
310+
self._account_end_dates[account_id] = end_date
311+
return end_date
312+
285313
def _date_intervals(self, account_id: str) -> Iterator[date]:
286314
"""Get date period to sync"""
287-
if self._end_date < self._next_cursor_values[account_id]:
315+
end_date = self._get_end_date_for_account(account_id)
316+
if end_date < self._next_cursor_values[account_id]:
288317
return
289318

290319
# Generate date intervals manually using standard datetime arithmetic
291320
current_date = self._next_cursor_values[account_id]
292-
while current_date <= self._end_date:
321+
while current_date <= end_date:
293322
yield current_date
294323
current_date += timedelta(days=self.time_increment)
295324

airbyte-integrations/connectors/source-facebook-marketing/unit_tests/integration/request_builder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717

1818
def get_account_request(account_id: Optional[str] = ACCOUNT_ID) -> RequestBuilder:
19-
return RequestBuilder.get_account_endpoint(access_token=ACCESS_TOKEN, account_id=account_id)
19+
return RequestBuilder.get_account_endpoint(access_token=ACCESS_TOKEN, account_id=account_id).with_fields(["timezone_name"])
2020

2121

2222
def get_ads_request(account_id: Optional[str] = ACCOUNT_ID) -> RequestBuilder:

airbyte-integrations/connectors/source-facebook-marketing/unit_tests/integration/response_builder.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ def build_response(
2121
return HttpResponse(body=json.dumps(body), status_code=status_code.value, headers=headers)
2222

2323

24-
def get_account_response(account_id: Optional[str] = ACCOUNT_ID) -> HttpResponse:
25-
response = {"account_id": account_id, "id": f"act_{account_id}"}
24+
def get_account_response(account_id: Optional[str] = ACCOUNT_ID, timezone_name: str = "Etc/UTC") -> HttpResponse:
25+
response = {"account_id": account_id, "id": f"act_{account_id}", "timezone_name": timezone_name}
2626
return build_response(body=response, status_code=HTTPStatus.OK)
2727

2828

docs/integrations/sources/facebook-marketing.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,7 @@ Facebook’s Ads Insights API dynamically aggregates and filters metrics. Purcha
412412

413413
| Version | Date | Pull Request | Subject |
414414
|:-----------|:-----------|:---------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
415+
| 5.0.1 | 2026-02-24 | [73281](https://github.com/airbytehq/airbyte/pull/73281) | fix(source-facebook-marketing): Fix Facebook Marketing UTC hardcoding with per-account timezone detection |
415416
| 5.0.0 | 2026-02-20 | [72779](https://github.com/airbytehq/airbyte/pull/72779) | Custom Insights streams now use level-based primary keys; removed deprecated `7d_view` and `28d_view` attribution window columns; removed `wish_bid` field. All users should refresh schema and reset affected streams. |
416417
| 4.2.1 | 2026-02-09 | [72952](https://github.com/airbytehq/airbyte/pull/72952) | fix(source-facebook-marketing): classify 'Invalid OAuth access token' as config_error (AI-Triage PR) |
417418
| 4.2.0 | 2026-01-07 | [71029](https://github.com/airbytehq/airbyte/pull/71029) | Add 98 missing fields for custom insights streams including objective_results transformation |

0 commit comments

Comments
 (0)