To answer your questions directly:
- If I try to access purchase_timestamp of the old accounts (before the upgrade), will I just get an error?
Correct, your program will fail to deserialize the old accounts, since your program expects additional data in the account, and it isn't there.
- And vice versa, if I try to access the new accounts that were created post-upgrade, I will succeed?
Yep! Assuming those accounts are created with the new field, of course.
- So to handle such an upgrade properly I would need to do some extra error handling?
That's one option certainly -- you check the size, and deserialize it as the old type if it's smaller, and as the new type if it's larger.
Your other options:
- introduce a totally new account type for the expanded accounts. This causes more churn since you probably need to add new instructions to handle the old and new types
- add a permissionless instruction to "upgrade" the old accounts to the new type. In that case, the instruction reallocates the account to the new size and transfers enough lamports to cover the higher rent-exemption on the expanded accounts. Once the instruction exists, you upgrade your program and quickly run your new instruction on all accounts.