Transactions don't have timestamps.
They kinda have. Each and every transaction has "nLockTime" field. Which means, that each and every transaction, can be confirmed after a given block, or a given timestamp. Of course, users could just set nSequence to "0xffffffff", and then "nLockTime" is ignored. Or they could simply use "0x00000000" as their locktime, and get it confirmed at any time, after the Genesis Block.
So, transactions don't have "reliable" timestamps, that you can "trust", but well, many wallets put the current block count inside locktime, to prevent an attack, where miners could constantly reorg a given block, and collect fees from transactions, which were made later. And by analyzing "nLockTime" field, maybe you cannot determine with 100% accuracy, when a given transaction was created, but you can at least assume, when a given user wanted to see it confirmed.
Also, when some transactions are
mined, then the more Proof of Work is put on top of it, the more you can be sure, that once some user decided to commit to some timestamp, it wasn't easy to change it, because it would require re-mining the whole transaction. Of course, users can also use locktime as a nonce, but then, it is not that different from what real miners do, by making their timestamps in a two hour window.
So, for many transactions, locktime is not reliable. But if you have a user, which uses the default settings, then "nLockTime" can be pretty much accurate, and commit at least to the block, which was the tip of the chain, when that transaction was made.