As you already figured out yourself, the time zone is not saved at all with Postgres date / time types, not even with timestamptz. Its role is just an input modifier or output decorator, respectively. Only the value (the point in time) is saved. Ample details in this related answer:
Therefore, if you want to preserve that part of the input string, you have to extract it from the string and save it yourself. I would use a table like:
CREATE TABLE tstz
...
, ts timestamp -- without time zone
, tz text
)
tz, being text, can hold a numeric offset as well as a time zone abbreviation, or a time zone name.
The difficulty is to extract the time zone part according to all the various rules the parser follows and in a way that won't break easily. Instead of cooking up your own procedure, make the parser do the work. Consider this demo:
WITH ts_literals (tstz) AS (
VALUES ('2013-11-28 23:09:11.761166+03'::text)
, ('2013-11-28 23:09:11.761166 CET')
, ('2013-11-28 23:09:11.761166 America/New_York')
)
SELECT tstz
, tstz::timestamp AS ts
, right(tstz, -1 * length(tstz::timestamp::text)) AS tz
FROM ts_literals;
db<>fiddle here
Old sqlfiddle
Works with or without a T between date and time. The key logic is here:
right(tstz, -1 * length(tstz::timestamp::text)) AS tz
Take what's left of a timestamp string after trimming the length of what the parser identified as date / time component. This relies on the input being, as you stated:
validated ISO 8601 strings