Rebuild note

Spotify's timestamp is the end of a play, not the start

A field that every document described the same way, and that nothing in the app could tell you was wrong, because every number it produced still looked entirely reasonable.

What this is based on 137,153 back-to-back plays from a real 160,634-record Spotify export, and the 83.5 per cent against 0.3 per cent that settled which end of the song the timestamp marks.
In this piece
  1. What the export actually contains
  2. Everything we had written said it was a start time
  3. Why nothing looked broken
  4. How it was settled
  5. What changed
  6. Why this particular bug mattered more than most
  7. What we are not claiming

Quilore reads the data export Spotify gives you and turns it into your complete listening history. It is live at quilore.com, it is free, and it runs entirely in your browser.

This is the bug that took longest to see, because nothing about it looked like a bug. Every screen worked. Every number was plausible. All of them were wrong by about the length of a song.

What the export actually contains

Ask Spotify for your extended streaming history and you get a zip of JSON. One record per play, going back to the day you joined. Each record carries the track, the artist, how many milliseconds were played, why playback ended, and a field called ts.

ts is the timestamp. That is all Spotify says about it.

Everything we had written said it was a start time

The product spec said so. The data engine spec said so. The technical contract the parser was written against said so. So the parser read ts as the moment the track started, put the play in the day that timestamp fell in, and built sessions by measuring the gap to the next one.

It is the moment the track stopped.

Why nothing looked broken

This is the part worth sitting with, because it is the failure mode the whole product exists to avoid.

Reading the wrong end of a play does not produce an error. It shifts every derived number by roughly one track length, and one track length is small enough that everything downstream stays believable. Sessions were slightly wrong. Streaks were slightly wrong. A play just after midnight was filed under the wrong day. The hour-of-day chart leaned a few minutes late.

Nothing in that list announces itself. You would have to already know the answer to notice the question.

How it was settled

Not by reading the documentation, which does not cover it, and not by eye.

Take every pair of plays that ran back to back, the way autoplay does, and measure the gap between the two timestamps. If ts is a start time, that gap should match the duration of the previous track. If it is an end time, it should match the current one.

Across 137,153 such pairs in one real export:

ReadingGap matched
ts is the end of the play83.5%
ts is the start of the play0.3%

The second measurement is the one that removes any doubt. If you treat ts as a start time, plays begin overlapping each other: the export claims you were listening to two things at once for 13.6% of your total listening time. Read as an end time, apparent overlap falls to 0.42%, which is about what you would expect from rounding and from a device handing over to another device. One person has one pair of ears. A model that says otherwise for an eighth of their life is not a model, it is a defect.

What changed

startEpochSec(tsIso, msPlayed) is now the only supported way to get a start time anywhere in the engine, and it is the single place the subtraction happens. A track that finishes at four minutes past midnight is attributed to the previous day, which is what the person who played it would say happened.

Why this particular bug mattered more than most

The app exists because of a specific complaint about services in this category: their numbers are wrong in ways that look right.

That is not a criticism of the people who build them so much as a description of what they are working with. Spotify’s Web API will tell you that a track played. It will not tell you for how long, so listening time has to be inferred. And the endpoint that reports recent plays is capped. Spotify’s own developer documentation says so:

This endpoint has only ever returned 50 items total.

Poll it more slowly than the person listens and the plays in between are gone for good. The export has neither problem: it carries the true millisecond duration of every play and it is complete back to the beginning.

So accuracy is the entire claim this product makes. Shipping it with the timestamps read off the wrong end of every song would have been the same defect we set out to avoid, arrived at by a different route.

We have not published the complaint record behind that yet. The corpus exists as themes rather than as counted, dated, quotable reviews, and until it is gathered properly this stays a stated reason rather than an evidenced one. That teardown is owed before anything on this site treats it as settled.

What we are not claiming

Spotify does not document which end of a play ts marks, so this is a measured finding rather than a confirmed one. It rests on one export of 160,634 records from one account. The agreement is strong and the overlap check is hard to argue with, but if someone has an export where the gaps line up with the previous track, we would genuinely like to see it.

Quilore is at quilore.com. It is free, it does not ask for an account, and your listening history does not leave your browser. You need your own Spotify export to use it, and that takes a few days to arrive, which is the honest cost of the accuracy this post is about.

Written by Adeboye Oluwatimileyin and Moses-Azuoru George, the two people who are Emberfig. We make money from AdMob inside the apps. This site carries no ads yet. You never pay us and we never sell your data.