First, consider the data type timestamptz (timestamp with time zone) instead of timestamp (timestamp without time zone). Then the time zone of users does not matter. now() returns timestamptz, your problem stems from coercing that to timestamp. See:
"But I need the type timestamp!"
If your connections happen with database roles (logins) that can be tied to a timezone, then PostgreSQL offers a simple solution:
ALTER ROLE my_role SET TIMEZONE = '+1';
Every connection initiated with this role will operate in its preset timezone automatically (unless instructed otherwise).
Note that, quoting the manual:
This only happens at login time; executing SET ROLE or SET SESSION AUTHORIZATION does not cause new configuration values to be set.
And you may want to use a time zone name instead of the plain offset to follow DST rules and other political manipulation of the local time. More:
Alternatively, you could create a look-up table for your logins where you store the respective time zones (which might serve additional purposes):
CREATE TABLE usertime(
username text PRIMARY KEY -- careful, "user" is a reserved word
, timezone text -- ... but "timezone" is not
);
INSERT INTO usertime VALUES
('postgres', '+4')
, ('my_role' , '+3')
;
Write a tiny SQL function:
CREATE FUNCTION f_user_ts()
RETURNS timestamp
LANGUAGE sql STABLE AS
$func$
SELECT now() AT TIME ZONE u.timezone
FROM usertime u
WHERE u.username = current_user
$func$;
Now, this returns the local timestamp for the current role (user):
SELECT f_user_ts();
More info
See the fine manual for the AT TIME ZONE construct. Both of these syntax variants are valid:
SET TIME ZONE TO 'UTC';
SET TIMEZONE TO 'UTC';
But now() AT TIMEZONE foo; is not! It has to be:
SELECT now() AT TIME ZONE foo;
foo being a text variable (or column like in the function above) holding a time zone offset, abbreviation or name. You can also supply a string literal directly.