8.4 KiB
Timestamp Logic for Meta Insights Data
Overview
The system now uses intelligent timestamp assignment based on the date_preset and account timezone to ensure accurate day-by-day plotting while handling Meta's timezone-based data reporting.
Key Concepts
Meta's Timezone Behavior
Meta API reports data based on the ad account's timezone:
- "today" = today in the account's timezone
- "yesterday" = yesterday in the account's timezone
- An account in
America/Los_Angeles(PST/PDT) will have different "today" dates than an account inEurope/London(GMT/BST)
The Timestamp Challenge
When storing time-series data, we need timestamps that:
- Reflect the actual date of the data (not when we fetched it)
- Account for the ad account's timezone
- Allow for accurate day-by-day plotting
- Use current time for "today" (live, constantly updating data)
- Use historical timestamps for past data (fixed point in time)
Implementation
The _compute_timestamp() Method
Located in scheduled_grabber.py, this method computes the appropriate timestamp for each data point:
def _compute_timestamp(
self,
date_preset: str,
date_start_str: Optional[str],
account_timezone: str
) -> datetime:
"""
Compute the appropriate timestamp for storing insights data.
For 'today': Use current time (data is live, constantly updating)
For historical presets: Use noon of that date in the account's timezone,
then convert to UTC for storage
"""
Logic Flow
For "today" Data:
date_preset = "today"
↓
Use datetime.now(timezone.utc)
↓
Store with current timestamp
↓
Multiple fetches during the day overwrite each other
(database ON CONFLICT updates existing records)
Why: Today's data changes throughout the day. Using the current time ensures we can see when data was last updated.
For Historical Data (e.g., "yesterday"):
date_preset = "yesterday"
date_start = "2025-10-20"
account_timezone = "America/Los_Angeles"
↓
Create datetime: 2025-10-20 12:00:00 in PST
↓
Convert to UTC: 2025-10-20 19:00:00 UTC (PST is UTC-7 in summer)
↓
Store with this timestamp
↓
Data point will plot on the correct day
Why: Historical data is fixed. Using noon in the account's timezone ensures:
- The timestamp falls on the correct calendar day
- Timezone differences don't cause data to appear on wrong days
- Consistent time (noon) for all historical data points
Timezone Handling
Account timezones are:
- Cached during metadata collection in the
ad_accountstable - Retrieved from database using
_get_account_timezone() - Cached in memory to avoid repeated database queries
Example timezone conversion:
# Account in Los Angeles (PST/PDT = UTC-8/UTC-7)
date_start = "2025-10-20" # Yesterday in account timezone
account_tz = ZoneInfo("America/Los_Angeles")
# Create datetime at noon LA time
timestamp_local = datetime(2025, 10, 20, 12, 0, 0, tzinfo=account_tz)
# Result: 2025-10-20 12:00:00-07:00 (PDT)
# Convert to UTC for storage
timestamp_utc = timestamp_local.astimezone(timezone.utc)
# Result: 2025-10-20 19:00:00+00:00 (UTC)
Examples
Example 1: Same Account, Multiple Days
Ad Account: act_123 in America/New_York (EST = UTC-5)
Scenario:
- Fetch "yesterday" data on Oct 21, 2025
date_startfrom API:"2025-10-20"
Timestamp Calculation:
2025-10-20 12:00:00 EST (noon in NY)
↓ convert to UTC
2025-10-20 17:00:00 UTC (stored in database)
Result: Data plots on October 20 regardless of viewer's timezone
Example 2: Different Timezones
Account A: America/Los_Angeles (PDT = UTC-7)
Account B: Europe/London (BST = UTC+1)
Both fetch "yesterday" on Oct 21, 2025:
| Account | date_start | Local Time | UTC Stored |
|---|---|---|---|
| A (LA) | 2025-10-20 | 12:00 PDT | 19:00 UTC |
| B (London) | 2025-10-20 | 12:00 BST | 11:00 UTC |
Result: Both plot on October 20, even though stored at different UTC times
Example 3: "Today" Data Updates
Account: Any timezone Fetches: Every 2 hours
| Fetch Time (UTC) | date_preset | date_start | Stored Timestamp |
|---|---|---|---|
| 08:00 UTC | "today" | 2025-10-21 | 08:00 UTC (current) |
| 10:00 UTC | "today" | 2025-10-21 | 10:00 UTC (current) |
| 12:00 UTC | "today" | 2025-10-21 | 12:00 UTC (current) |
Result: Latest data always has the most recent timestamp, showing when it was fetched
Database Schema Implications
Primary Key Constraint
All insights tables use:
PRIMARY KEY (time, account_id) -- or (time, campaign_id), etc.
With ON CONFLICT DO UPDATE:
INSERT INTO account_insights (time, account_id, ...)
VALUES (...)
ON CONFLICT (time, account_id)
DO UPDATE SET
impressions = EXCLUDED.impressions,
spend = EXCLUDED.spend,
...
Behavior by Date Preset
"today" data:
- Multiple fetches in same day have different timestamps
- No conflicts (different
timevalues) - Creates multiple rows, building time-series
- Can see data evolution throughout the day
"yesterday" data:
- All fetches use same timestamp (noon in account TZ)
- Conflicts occur (same
timevalue) - Updates existing row with fresh data
- Only keeps latest version
Querying Data
Query by Day (Recommended)
-- Get all data for a specific date range
SELECT
DATE(time AT TIME ZONE 'America/Los_Angeles') as data_date,
account_id,
AVG(spend) as avg_spend,
MAX(impressions) as max_impressions
FROM account_insights
WHERE time >= '2025-10-15' AND time < '2025-10-22'
GROUP BY data_date, account_id
ORDER BY data_date DESC;
Filter by Date Preset
-- Get only historical (yesterday) data
SELECT * FROM account_insights
WHERE date_preset = 'yesterday'
ORDER BY time DESC;
-- Get only live (today) data
SELECT * FROM account_insights
WHERE date_preset = 'today'
ORDER BY time DESC;
Plotting Considerations
When creating day-by-day plots:
Option 1: Use date_start Field
SELECT
date_start, -- Already a DATE type
SUM(spend) as total_spend
FROM account_insights
GROUP BY date_start
ORDER BY date_start;
Option 2: Extract Date from Timestamp
SELECT
DATE(time) as data_date, -- Convert timestamp to date
SUM(spend) as total_spend
FROM account_insights
GROUP BY data_date
ORDER BY data_date;
For "Today" Data (Multiple Points Per Day)
-- Get latest "today" data for each account
SELECT DISTINCT ON (account_id)
account_id,
time,
spend,
impressions
FROM account_insights
WHERE date_preset = 'today'
ORDER BY account_id, time DESC;
Benefits
- Accurate Day Assignment: Historical data always plots on correct calendar day
- Timezone Aware: Respects Meta's timezone-based reporting
- Live Updates: "Today" data shows progression throughout the day
- Historical Accuracy: Yesterday data uses consistent timestamp
- Update Tracking: Can see when "yesterday" data was last refreshed
- Query Flexibility: Can query by date_start or extract date from time
Troubleshooting
Data Appears on Wrong Day
Symptom: Yesterday's data shows on wrong day in graphs
Cause: Timezone not being considered
Solution: Already handled! Our _compute_timestamp() uses account timezone
Multiple Entries for Yesterday
Symptom: Multiple rows for same account and yesterday's date Cause: Database conflict resolution not working Check:
- Primary key includes
timeandaccount_id - ON CONFLICT clause exists in insert statements
- Timestamp is actually the same (should be: noon in account TZ)
Timezone Errors
Symptom: ZoneInfo errors or invalid timezone names
Cause: Invalid timezone in database or missing timezone data
Solution: Code falls back to UTC if timezone is invalid
except Exception as e:
print(f"Warning: Could not parse timezone '{account_timezone}': {e}")
return datetime.now(timezone.utc)
Summary
The timestamp logic ensures:
- ✅ "Today" data uses current time (live updates)
- ✅ Historical data uses noon in account's timezone
- ✅ Timezone conversions handled automatically
- ✅ Data plots correctly day-by-day
- ✅ Account timezone cached for performance
- ✅ Fallback handling for missing/invalid timezones
This provides accurate, timezone-aware time-series data ready for visualization!