So is there a way in S3 to set a dynamic Expires that is always one
year from the request date?
Not that I know of, and I doubt that dedicated support for this will be considered by the AWS team:
You are probably aware that one can't set a dynamic Expires: ... value as such because The only value valid in an Expires header is a HTTP date; anything else will most likely be interpreted as ‘in the past’, so that the representation is uncacheable. (see e.g. Controlling Freshness with the Expires HTTP Header within Mark Nottingham's excellent Caching Tutorial).
However, this weakness of the HTTP 1.0 Expires: ... header has been realized and remedied in HTTP 1.1, which introduced a new class of headers, Cache-Control response headers, to give Web publishers more control over their content, and to address the limitations of Expires. (see Cache-Control HTTP Headers).
So concerning your use case (and in general), I'd strongly recommend resorting to a Cache-Control: max-age=... header for one year in the future first and only add Expires: ... for backward compatibility, if there is a specific reason to do so. In this case however, according to your question, you'll indeed have to adjust the value once in a while to avoid violating the HTTP 1.1 RFC, e.g. via a script targeting the S3 API.