DEV Community

Zhuojun Li
Zhuojun Li

Posted on Fully Autonomous

SEC's API had Apple's pre-market 10-Q at 10:01 ET. This week it reads 06:01. Your cache doesn't.

From at least mid-September until at least October 22, 2026, acceptanceDateTime in SEC's submissions JSON (data.sec.gov/submissions/CIK##########.json) was wrong for a large share of filers, even though every value ends in Z. On October 28 every file I checked read true UTC. If your backtest cached that field before this week, the cache still holds the wrong values.

Three states, one per file

The value was in one of three states, and the whole file shared it:

  • True UTC. Microsoft, Tesla (until October) and Alphabet looked like this.
  • One Eastern offset late, +4 h in summer and +5 h in winter. Apple's 10-Q 0000320193-26-000020 was accepted at 06:01:02 ET on 2026-07-31, before the open. On October 21 the API returned 14:01:02Z, which is 10:01 ET, half an hour into the session.
  • Eastern wall time labelled Z. Arena Pharmaceuticals' earnings 8-K of 2016-05-09 was accepted at 16:18:17 ET, after the close. The API returned 16:18:17Z, which reads as 12:18 ET, mid-session. The stock opened 5.1% higher the next day.

Which state a file was in followed SEC's last rebuild of that file, not the company. On October 11 arhancanli compared 450 filings from 25 companies with their index pages: 12 files were right, and 13 were one offset late, including Apple, Nvidia, Amazon, Meta and JPMorgan. shahar-arbor found the third state in files for MMAT, SVB, Heliogen and Bed Bath & Beyond. The full thread is edgartools #1501.

My own numbers come from a post-earnings-drift audit I ran in September. 43 of 228 timed filings were Eastern time labelled Z, all from issuers no longer in company_tickers.json. Read as UTC, 26 filings accepted after the entry day's close would have looked intraday.

Why a few hours matter

An earnings backtest makes one decision per event: was the release before the open, during the session, or after the close? That decides whether the reaction is in today's open, today's close or tomorrow's open. Four or five hours is enough to move a release across 09:30 or 16:00, and nothing raises an error. In my PEAD backtest, timing alone moved net alpha per trade from 1.21% to about 0.25% (write-up).

What changed this week

On October 28 at 12:52 UTC I re-read all 228 filings from my audit. All 228 now read true UTC, the 43 included, and none of the 185 that were right in September had moved the other way.

At 12:55 UTC I checked the 13 files reported as one offset late, plus Microsoft, Tesla and Alphabet: three filings each (the newest, the newest before October 23, and one from February, so both sides of DST). 48 of 48 read true UTC. Apple's 10-Q now says 10:01:02Z, which is right. The last time anyone in the thread saw a late file was October 22.

I can't tell whether this is a fix or one more rebuild. SEC sends no Last-Modified or ETag for these files, so a change can't be dated from outside.

Check what you stored

Re-fetching won't tell you what your cache holds. Compare each cached value with the filing's -index-headers.html, which gives the acceptance time in Eastern time and doesn't change:

import re, urllib.request
from datetime import datetime
from zoneinfo import ZoneInfo

UA = {"User-Agent": "Your Name you@example.com"}  # SEC requires a real contact
ET, UTC = ZoneInfo("America/New_York"), ZoneInfo("UTC")

def accepted_utc(cik: int, accession: str) -> datetime:
    url = (f"https://www.sec.gov/Archives/edgar/data/{cik}/"
           f"{accession.replace('-', '')}/{accession}-index-headers.html")
    html = urllib.request.urlopen(urllib.request.Request(url, headers=UA)).read().decode("latin1")
    stamp = re.search(r"ACCEPTANCE-DATETIME>\s*(\d{14})", html).group(1)
    return datetime.strptime(stamp, "%Y%m%d%H%M%S").replace(tzinfo=ET).astimezone(UTC)

def convention(cached: str, truth: datetime) -> str:
    value = datetime.strptime(cached[:19], "%Y-%m-%dT%H:%M:%S").replace(tzinfo=UTC)
    hours = (value - truth).total_seconds() / 3600
    offset = -truth.astimezone(ET).utcoffset().total_seconds() / 3600
    if abs(hours) < 0.01:
        return "utc"
    if abs(hours - offset) < 0.01:
        return "one offset late"
    if abs(hours + offset) < 0.01:
        return "Eastern wall time labelled Z"
    return f"other ({hours:+.2f} h)"

truth = accepted_utc(320193, "0000320193-26-000020")
print(truth)                                          # 2026-07-31 10:01:02+00:00
print(convention("2026-07-31T14:01:02.000Z", truth))  # one offset late
Enter fullscreen mode Exit fullscreen mode

That is one small request per filing. At SEC's limit of 10 requests a second, 10,000 filings take under 20 minutes. Cache the result, since the index page doesn't change.

For new work, read the time from the index headers and keep the JSON value next to it. edgartools plans the same for 6.1 (filing.accepted_at). For earnings 8-Ks, earnings-session (MIT, pip install earnings-session) does this and records which convention the JSON used for each filing. Disclosure: I maintain it.

If your cache turns up a pattern different from these three, I'd like to see it.

Top comments (0)