This doesn't really answer your question so much as explains why I don't think the alternatives aren't much better than block.timestamp
.
The block.timestamp
value, as you know, is set by the miner. However, the timestamp can't be too far out of whack or it will be rejected by the network. Suppose, then, that a miner's clock is running slow (so that the time on the timestamp is in the past) and mines a block. Most nodes that receives this block have no way of knowing whether they are simply late to hear about the block (because it took many hops to arrive) or if the other node's clock is slow. Only an entity that is able to monitor almost the entire network's nodes can tell where a block originated and whether the timestamp was a bit behind.
Conversely, if the miner's clock is a bit fast, some nodes may see the timestamp is in the future. But others will receive it by the time it is a bit stale. But miners who receive it quickly don't really know if they've received it quickly. So they'd have to query other nodes to know if they received it at a timestamp earlier than their own time. And so forth. By that logic, the node reporting the earliest sighting of the node is taken to be the one that received the block first. But that node's clock might have been running slow. So that becomes almost indistinguishable from the case where miners see that the block arrived in the past (except in this case, they know the miner's clock is fast).
Thus, some nodes, because they trust their own clocks, can only know that a miner's clock is fast, but not by how much. They can't know with certainty, without being omnipresent, whether a miner's clock is running slow. Unfortunately, this means that as a user of the timestamp provided by a third party, unless you can trust their clocks, you run into the exact same problem! The best you can do is put a greatest lower bound on the time a block was mined by running your own node.