Why your old posts show the wrong date
Three different wrong-date shapes in an Instagram export — one-off misdated posts, a block of identical mid-2015 stamps, and dates that render as 1970 — and what the parser's fallback chain actually does about them.
Somewhere in your archive is a post you remember making in one season that carries a
date from another. Maybe it is a single photo, maybe it is your entire back catalogue
before a certain year stamped with the same afternoon, and maybe — if you have opened
the file in a quick tool — it is a post from January 1970. None of these three shapes
means your export is broken, and none of them is fixed by requesting the export again.
They are three different absences in the source data, wearing three different costumes.
This post is the forensics: where a post's date comes from in the first place, what
this site's parser does when the field is missing, why a block of old posts can share
one migration-era timestamp, and what — honestly — can be reconstructed afterwards.
Folder structure and the six section families live in the "what's inside" post; this
one is about a single field and the trouble it causes, followed as far down as the
field's absence actually goes.
A post's date in the export is a Unix timestamp in one of a few fields — taken_at,creation_timestamp, or an enclosing record's timestamp — and when none of them
exists, the honest value is zero. Viewers render zero as 1970, an invalid date, or a
blank. A block of old posts sharing one mid-2015 date is a migration-era stamp from
Instagram's own records, not your archive failing. The parser reads fields in a fixed
order and stops at zero; a date that is absent from the export cannot be recovered by
any tool, including this one.
- 30swhich of the three wrong-date shapes you are looking at.
- 5minthe field order every parser (this one included) has to obey.
- ongoing — what evidence in the archive can bound a date, and what never can.
The three shapes of wrong
Sorting out which problem you have takes one look at the pattern, because the shapes do
not overlap:
| What you see | What it usually is | Recoverable? |
|---|---|---|
| One or a few posts on odd dates | A source field that never had the real date | Only by bounding it from neighbouring records |
| A large block of old posts sharing a single date | A migration-era stamp in the source | No — the per-post date was not kept |
| Dates in 1970, or a blank/invalid date | The field is absent; the value is zero | No — zero means "the file does not say" |
The third row is the one that looks most alarming and is the most simply understood:
1970-01-01 is not a date anyone's post has. It is what a computer shows when a date
value is exactly zero, which is the Unix epoch — the counting point every Unix-style
timestamp counts from. A zero in the file is a statement of ignorance, and a viewer
turning ignorance into 1970 is reporting the file's knowledge, not the post's history.
There is a fourth, rarer shape worth naming while we are sorting: a date so far in the
future it wraps off the calendar. That one is a units confusion rather than an absence
— seconds and milliseconds are both integers, both plausible, and a reader that
expects one and gets the other lands in the year fifty thousand. It is a bug in the
viewer, not in the archive, and the cure is checking the raw integer against the
field's documented unit before trusting any rendering. The three tabled shapes are the
ones you will actually meet in your own archive; this one you meet only in other
people's screenshots.
Where a post's date comes from
Inside posts.json (and the equivalent structures for other media sections), the date
is not a formatted string like "July 2016" — it is an integer counting seconds since
1970, stored under field names that vary by era and by section:
taken_at— the capture time, on records that have it. This is the field youwant, because it is the one that describes the moment the photo was taken rather than
the moment something was written down.creation_timestamp— the record-creation time. For carousel and album entriesit can exist per media element, and for older single-image records the media element
is the only place it appears: the parent post record carries the caption and theuri, while the timestamp lives onmedia[0]— which is why a naive reader that
only looks at the top of the record concludes the post has no date at all.timestamp— the enclosing or adjacent record's time, used when the elementitself carries none.
- Caption fields (
title,caption) — these hold text, never dates. A captionsaying "summer 2014" is prose; the file does not parse it, and neither should a
viewer.
Four names, one meaning: seconds since 1970, present or absent. Everything else in
this post is a consequence of that.
The single-image case deserves its own sentence, because it produces the most support
threads: on older records, the post-level object holds the caption and the media
reference, and the creation time exists only on the first media element. Read only the
top of the record and the post appears undated; read the chain properly and the date
is sitting one level down. A carousel makes the same structure obvious by contrast —
several media entries, each able to carry its own time — which is why the same export
can show careful per-photo dates on a 2018 album and a bare record on a 2013 photo.
The field order every reader has to obey
When a record has more than one of these fields, something must decide which wins.
There is no universal answer in the format itself, so every reader imposes an order —
and it is worth knowing this one's, because it is the chain your archive's dates are
actually produced by:
taken_at || creation_timestamp || timestamp || 0That is the posts parser's chain in packages/shared/src/parsers/content.ts — read
left to right, take the first value that is not zero, and if none is, stop at zero.
Two details of the real implementation matter for troubleshooting:1
- Per-media records are checked before their parent. For each media element the
chain is
media.creation_timestamp || media.taken_at || post.creation_timestamp || post.taken_at || inherited || 0— the element's own time wins, the post's time
serves as fallback, and only then does an inherited value from an enclosing record
get a chance. - Walking a nested record inherits its parent's time as a fallback. When the
parser descends into sub-objects, it carries the nearest enclosing timestamp down,
so a child record without its own date is dated by its container — and only by its
container if that container itself has a date.
The || 0 at the end is the honest part: after every field has been exhausted, the
value is zero rather than a guess. No parser — not this one, not a spreadsheet, not a
script you found — can read a date out of a field that does not contain one. If your
viewer shows 1970 or "invalid" or nothing, you are looking at the end of the chain.
The block of posts sharing one date
The second shape is the one that makes people suspect their archive: an entire stretch
of your oldest posts — often everything from before a point in the mid-2010s — carrying
the same date, down to the hour.2 This is not your export collapsing. It is
a pattern visible across many accounts' exports, and it points at the records
themselves: for media created before the platform's mid-2015 data migration, the
original per-post time was not carried into the current record structure, and what
remains is a stamp from the migration itself. Every legacy record touched by that
process inherits the same moment, the way every box in a shipment gets the same
shipping date when it enters a new warehouse.
The tell-tale is exactly that uniformity. Genuine posting history is irregular —
people post at different hours on different days — so a block sharing a single
timestamp to the second is a single event applied to many records, not thousands of
posts that coincidentally happened at once. Uniform means stamped; irregular means
recorded. The boundary of the block is usually legible too: records on one side of it
carry ordinary, scattered times, records on the other all carry the same one, and the
cut sits wherever the platform's migration drew it for your account's data — which is
why two people comparing exports do not necessarily share a boundary date.
Nothing downstream un-stamps them. The fields are what they are, the chain reads them
correctly, and the result is the migration date, correctly read. This is the difference
between a bug and an absence: a bug would put wrong values in fields that should be
right, and an absence leaves fields that were never given the values you want.
What can be bounded, and what cannot
"Honestly, can I get the real date back?" has an answer that splits on which shape you
have.
For a single misdated or undated post, the archive can sometimes bound it. A date
that is absent is absent, but dates that surround it are not, and a bound is not a
guess:
- Comments on the post carry their own timestamps — the earliest comment cannot
precede the post, and the conversation in them often fixes the season on its own.
- The posts immediately before and after it in
posts.jsonare dated, and a post sitsbetween its neighbours by definition of being a timeline.
- The conversation that shared or discussed it — DMs, if you forwarded it — may carry
the moment of sharing, which is at or after the post.
Three independent records agreeing on a window gives you a window, clearly labelled as
one. What none of them can do is produce the exact missing value — a bound is a
research result, not a repair, and honest readings say "somewhere between these two
dated records."
Worked, the method looks like this: a post's own timestamp is zero; the caption
references a trip you took in a documented month; the post above it in the file is
dated three days before that month and the post below it five days after; a comment on
it is timestamped within the month. Four weak signals, one answer — "that month" —
recorded in your notes as an inference with its evidence attached. The failure mode to
avoid is promoting the trip reference from evidence to value and writing the exact
day in as though the file had said it. It did not.
For the migration block, there is nothing to bound against. Every record in the
block carries the same stamp, so the usual surrounding evidence is stamped too — the
neighbours share the date rather than constraining it. Comments can still help for
posts inside the block that have them, which is a per-post salvage operation rather
than a batch fix. For the rest, the date is genuinely gone: the export describes the
account as the platform stores it, and the platform does not store those dates.
And for zero, zero is the answer. A timestamp: 0 in the file is the export
telling you it does not know. Any tool that puts a plausible-looking date there is
inventing one — which is precisely the behaviour this site's editorial rules forbid.
Reading a suspicious date yourself
You do not have to trust any viewer's rendering, including this one's. The check is
three steps and needs a text editor:
- Find the post in
posts.json— search for a distinctive fragment of its caption. - Look at the record's date fields:
taken_at,creation_timestamp, and the entriesunder
mediaif present. All are plain integers. - Run the chain by eye: first non-zero field wins. If you land on zero, the file said
nothing; if you land on a mid-2010s value identical to a thousand siblings, you have
the migration stamp.
A single line of code or a spreadsheet formula converts the integer to a calendar date
if you want one, but the conversion is cosmetic — it never adds information the
integer did not already have. The step that matters is step three, because that is
where "my viewer is wrong" stops being a suspicion and becomes "the field itself
contains this."
Two reading pitfalls while you are in there. First, check the field's unit: seconds
and milliseconds are both integers and both appear in export-adjacent data (DM records
in particular carry their time in milliseconds under timestamp_ms), and a unit
confusion moves dates by three orders of magnitude. Second, do not read a neighbouring
record's field by mistake — search results in a text editor land you on the line, not
on the object that owns it, and the timestamp you are looking at may belong to the
record above. Scroll until the braces balance; the chain only means something once you
are certain whose record you are running it on.
What this does not mean
Three misreadings are worth closing off, because each one wastes an evening:
- It does not mean your archive is corrupt. A corrupt archive fails to open or
parses with errors; a misdated post parses perfectly and carries a value the source
gave it. Different failure family entirely — the not-opening one has its own post. - It does not mean a better request fixes it. The date is not selected by export
settings. JSON versus HTML changes which fields exist at all (the half-life post
covers that trade), and in the HTML path dates are parsed back from what the page
rendered rather than read as integers3 — but neither format can carry a
time the source record never stored. - It does not mean the surrounding information is unreliable. The caption is real,
the media is real, the ordering is real — one field's absence does not contaminate
the rest of the record. Reading a misdated post as "posted sometime in the era its
neighbours describe" is using the file exactly as designed: the archive is a set of
records with varying completeness, and completeness was never uniform across a
twelve-year account. The posts you can date precisely are evidence about the ones you
cannot — that is the bound above, working in the direction the file actually supports.
Why do all my old posts have the same date?
The shared date is a migration-era stamp: records created before the platform's
mid-2015 data migration did not carry their original per-post time into the current
structure, so they all show the migration moment instead. Uniformity to the second is
the giveaway — real posting history is never that regular. The value is in the source
records, so no re-export changes it.
Why does one of my posts show a date in 1970?
Because its date field is zero, and zero is the Unix epoch — the number every Unix
timestamp counts from. The export is telling you the record has no time in it, and
your viewer is rendering the absence. Neighbouring dated records and the post's own
comment timestamps can bound when it happened; they cannot fill the field in.
Can I fix the dates in my export?
You can annotate your own copy — a notes file beside the archive, with bounded guesses
clearly marked as guesses. What you cannot do is make the export contain a value that
was never recorded, and any tool that silently overwrites the file's zero with a
plausible date is inventing history rather than repairing data.
Does requesting the export in a different format help?
No. Different formats carry different fields — HTML omits structure that JSON keeps,
which is the other post's subject — but neither format can carry a date the source
record lacks. The chain reads whatever exists and stops at zero in both cases.
Some posts show a date in the year 50000. Is my archive corrupted?
No — that is a units confusion in the viewer, not damage in the file. Seconds and
milliseconds are both integers, and reading one as the other inflates a date by three
orders of magnitude. Compare the raw integer in the record against the field name
(timestamp_ms versus timestamp) and the rendering resolves.
Where this was checked
1: packages/shared/src/parsers/content.ts — the posts chain
(item.taken_at || item.creation_timestamp || item.timestamp || 0), the per-media
chain that checks the media element before its parent record, and the walk that
inherits an enclosing timestamp as fallback before ending at zero.
2: The uniform mid-2010s stamp across pre-migration media records is an
observed pattern in exports of accounts with pre-2015 history; this post treats it as
a property of the source records, not of any particular export or tool.
3: In HTML-format exports the same absence appears differently — dates that
are rendered on the page are parsed back from the document, and a record with no
rendered time parses to the same zero the JSON path produces.
Footnotes
- code-chain
- migration
- html-time