Handle date and time for different timezones - postgresql

I'm working on a feature of an app where users can create groups, and each group can have multiple posts. The groups have a start and end date. I'm using Node.js for the backend and PostgreSQL to model the data. The tables look like this (simplified):
CREATE TABLE IF NOT EXISTS public."group"
(
id uuid NOT NULL DEFAULT gen_random_uuid(),
start_date timestamp with time zone NOT NULL,
end_date timestamp with time zone NOT NULL,
owner_id uuid
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
)
CREATE TABLE IF NOT EXISTS public."post"
(
id uuid NOT NULL DEFAULT gen_random_uuid(),
user_id uuid NOT NULL,
group_id uuid,
created_at timestamp with time zone NOT NULL
)
A user can only post to a specific group once a day and the days in which they post should be recorded (users will be able to see in a "progress calendar" the days in which other people in the group and themselves created a post). Users from different countries may enter the same group.
How should I approach the following problems:
1. Make sure the user doesn't post more than once a day.
Here I could just create a query which checks if the user already
posted on day X. The problem is not knowing the user's
timezone, as the server's timezone is generally used. Should I just
store users' location in the database when they create an account
and use the timezone from there? What if they move? I also thought
about sending a timezone along with the request, but that doesn't
seem like a good idea, as a malicious user could send anything
there.
2. Sync the progress calendars for users with different time zones (e.g. user X from Romania, at 1 AM 22 August, should not see an x-mark in user's Y calendar for the date of 21 August, denoting that user Y hasn't posted on that date, as user Y still has time to post (user Y's date/time is 21 August 6 PM).
3. Not as important as the other 2 - What's a good practice for choosing the start / end date timezone for the group, so that users are not surprised when the group starts / ends. I would go with UTC, but I'm not sure people having UTC-9 and UTC+9 would have a pleasant experience with this.
Just to give an example, timestamp with timezone is represented as 2021-08-17 20:01:00.427+03 in postgres.

Related

How to deal with dates in global system

Let's say that I need to create 'chat-like' system. Of course I need to deal with dates somehow.
I read about it a lot and I have some knowledge but I really don't know how to use it.
I would like to store message date in UTC (postgres 12)
Each user should be able to select his time zone and this time zone should be saved into database (standard approach)
When message is retrieved from database I need to convert message date into valid local date based on user selected timezone.
This is really all I need to do and here problem starts:
In postgres date is stored with offset f.e 2020-05-01 00:00:00+02, but I want to store timezone in another table, not here
How can I store user timezone? I should use names like "EST5EDT" or use time offsets as integer?
Where can I find list of all timezones to present user? (Each global website f.e. facebook has list of timezones with offsets, where can I find list of all valid timezones?)
How can I select date with user appropriate timezone? f.e.
SELECT convert_to_user_date("Date", "timezonename??")
FROM "Messages"
Is this correct way to achieve my goal?
These days you don't have to resort to UTC. You can store full timestamps with time zone. This way e.g. you won't lose DST status at the moment the timestamp was recorded in the database.
https://www.postgresql.org/docs/current/datatype-datetime.html
You can easily select the timestamp stored with any time zone shifted to target user's time zone (assuming it's stored somewhere in user preferences). The syntax is SELECT ... AT TIME ZONE
https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-ZONECONVERT
You are very close to a workable setup.
First, use timestamp or timestamp without timezone column data type to store those UTC date/time stamps.
Second, store your users' preferred time zones in varchar(63) columns in the form Asia/Kolkata or America/Halifax.
Third, use postgresql's built in view pg_timezone_names to get a list of valid time zones. You can use it to populate a pulldown list of choices in your user-settings screen.
If you have time for some real excellence in your user-settings screen, you can suggest time zone settings you guess from the users' ip adresses and allow them to change them if your guess was wrong. Read this. How to get Time Zone through IP Address in PHP
Then, when your application starts using postgresql on behalf of any user, look up that user's chosen time zone in your users table, and use it in this SQL command. SET TIME ZONE 'America/Halifax'; (whatever the user's choice is).
Then when you retrieve your time stamps, they will be rendered in the user's local time, and when you store the they'll be in UTC.
The 'toobz are full of advice about this. Here's something that might be useful. How to get Time Zone through IP Address in PHP
Use the data type timestamp with time zone. Don't be worried by the name — that data type really represents an absolute point of time and does not store time zone information.
The only thing you have to do is to set the timezone parameter correctly for the time zone of the client connection, then the value will be represented correctly in that time zone. PostgreSQL does all the work for you.
If you don't like the string representation (e.g., you are disturbed by the time zone offset displayed), use to_char to format the output the way you like:
CREATE TABLE dates (x timestamp with time zone NOT NULL);
SET timezone = 'Europe/Vienna';
INSERT INTO dates VALUES ('2020-06-01 03:00:00');
SET timezone = 'Asia/Kolkata';
SELECT to_char(x, 'YYYY-MM-DD HH24:MI:SS') FROM dates;
to_char
---------------------
2020-06-01 06:30:00
(1 row)

PostgreSQL - extracting date ranges in local time?

CURRENT SITUATION:
I have a table of wildfire incidents with a timestamp with time zone (timestamptz) field to track when the observation occurred.
Everything in the data creation process is in UTC: the incoming data from the source, the app server that inserts the data, the insert python code (appends a "Z" to the time), and the database server are all in UTC.
The incidents' geographic extent spans several time zones in the US, Canada, and Mexico.
PROBLEM:
I've been querying on a day's worth of data in UTC time, but need to extract out data relative to local time. The midnight to midnight range will be different in each time zone.
My use case now is one day, but I was asked to consider arbitrary time ranges. E.g.: find all incidents in the hottest part of the day (say 10:00 to 18:00) local time.
This table is quite large and I have an index on the timestamptz field right now. Any changes I make will need to work with an index.
Account for daylight saving time.
I have a method to get the time zone for each record, so I don't need help with that.
I created a test table for this with a timestamptz field ts_with and a varchar field for the time zone tz. The following query returns what I want, so I feel like I'm making progress.
SELECT
name, test_tz.ts_with, test_tz.tz,
TIMEZONE(test_tz.tz, test_tz.ts_with) as timezone_with
FROM fire_info.test_tz
WHERE TIMEZONE(test_tz.tz, test_tz.ts_with) BETWEEN
'2018-08-07 00:00:00' AND '2018-08-07 23:59:59';
QUESTIONS:
Will this use my index? I'm thinking the timezone function will avoid it. Any solution for that? I'm considering adding another condition to the where clause that selects on timestamptz buffered by a day on either side. That would use the index and then the timezone function isn't sorting through too much data (~6k records per day during fire season). Would PG figure that out?
The timezone function is giving me DST offsets (e.g.: Denver is currently UTC-06). I assume I'll get standard time after DST ends. If I run a query in December for data in August, will it apply standard time or DST?
thanks!!!
The way you wrote the query, it cannot use an index on ts_with.
To use an index, the condition would have to be of the form ts_with <operator> <constant>, and there is no way to rewrite the query in that fashion.
So you should create a second index on timezone(test_tz.tz, test_tz.ts_with).

How to check whether a person is logged-in atleast once in a day and how to calculate the length of session of login in mysql-flask application?

I am using mysql and flask to develop a time-cards management system (which is basically an attendance taking software), in which I need to check the time a user is logged in and logged out and the interval of the session to calculate his working time and I should be able to update the database as a leave if he didn't login the whole day. And I need to store the data monthly-wise so that we can calculate the salary of the employee depending on his working hours and leaves in that month.
Can someone please help me how to:
Design my database such that we can store the monthly information of all
employees efficiently (like should i create a table for each month or some
other way?)
How to check the time of login and logout and the length of session of a
user?
How to check whether a user is logged in at least once in a day or not?
Thanks in advance.
Record the start and end time of each work period in a table.
create table time_record (
time_record_id int not null auto_increment primary key,
start_time datetime not null,
end_time datetime not null,
person_id int not null references person(person_id)
);
Then you can report on daily or monthly activity for each user with some simple queries. For instance, to roll up to a daily summary, you can join a set of calendar days against the time records.

Postgresql: Convert a date string '2016-01-01 00:00:00' to a datetime with specific timezone

I'm in a tricky situation. A client's database timezone was configured for America/Chicago instead of UTC.
From the app, we ask customers to enter useful dates, and sadly those dates are stored 'as-is', so if they entered '2001-01-01 00:00:00' in the text input, that same value will be stored in the DB, and we are ignoring the customer's timezone. We save that info separately.
The table column is of type TIMEZONETZ. So Postgresql will add the America/Chicago timezone offset at the end: Eg '2001-01-01 00:00:00-02'.
Naturally, most of the customers are not in Chicago.
The difficult part, is that, even knowing the customer's timezone, it's really hard to run calculations on the DB given that the datetime was not correctly pre-processed before storing it into the DB.
My attempted solution, is finding a way to extract the datetime string from the column value, and re-convert it to a date with the right timezone. Eg (pseudo-code):
// This is psuedo code
SELECT DATETIME((SELECT date_string(mycolumn) FROM mytable),
TIMEZONE('America/Managua'));
Which would be equivalent in PHP:
$customerInput = '2016-01-01 00:00:00';
$format = 'Y-m-d H:i:s';
$wrongDateStoredInDb = DateTime::createFromFormat($format, $customerInput, new DateTimezone('America/Chicago'));
// In order to fix that date, I'd extract the dateString and create a new DateTime but passing the correct timezone info.
$customerTimezone = new Timezone('America/Bogota');
$customerInput = $wrongDateStoredInDb->format($format); // Assuming we didn't have it already.
$actualDateTime = DateTime::createFromFormat($format, $customerInput, $customerTimezone);
With that kind of information, I'd be able to run calculations on date ranges, with the correct values, eg:
// Pseudo-code
SELECT * FROM myTable WHERE fix_date_time(columnWithInvalidDate, `correctTimezone`)::timestamp > `sometimestamp`;
I've read the Postgresql docs, and I've tried everything I could, but nothing seems to work.
Any suggestion is more than welcomed!
So you say that you have a timestamptz column. That is not stored as a string, but as an "instant" in microseconds since the epoch. But when you do an INSERT and give a string, Postgres converts your string into a time value automatically, before storing it. It's been assuming the strings you give it are in Chicago time, since that's the default timezone.
Now you want to reinterpret those times as being in the user's time zone instead. To do that, you can put them back into strings (in Chicago time), and then parse them again but with a different time zone.
Suppose you have data like this:
CREATE TABLE t (id int primary key, ts timestamptz, tz text);
SET TIMEZONE='America/Chicago';
INSERT INTO t
VALUES
(1, '2015-01-01 12:00:00', 'America/Managua'),
(2, '2015-01-01 12:00:00', 'America/Los_Angeles')
;
Then this will give you new times that are what the user really meant:
SET TIMEZONE='America/Chicago';
SELECT ts::text::timestamp AT TIME ZONE tz FROM t;
To break it down: ts::text stringifies the value into Chicago time, then we re-parse it, but into a bare timestamp with no timezone information yet. Then we attach a time zone---not Chicago time, but the user's own timezone.
From here you should be able to handle fixing the bad rows (and not the new ones, if you've already changed the server's default timezone).
One caveat is that if a user entered a time and then changed their timezone, there is no way to recover that, so this will interpret that old time incorrectly.

Query across timezones

I'm developing an app where a user can request that an email be sent to them at a specific time every day in their timezone. For example User A lives in London and schedules an email at 2pm every day London time and User B lives in New York and schedules an email at 2pm New York time.
I'm wondering what way I should configure my database postgres such that a scheduler can fire every minute and query for all emails to be sent at that minute regardless of what timezone their in.
The one thing I want to avoid is having to run multiple queries, once per timezone.
Due to the (rather idiotic, quite frankly) rules for daylight saving times (DST) across the world, a local time can mean all kind of things in absolute (UTC time).
Save a time (not timetz!) and the time zone name (not the abbreviation) for when to send the emails. Tricky details under this related question:
Time zone names with identical properties yield different result when applied to timestamp
CREATE TABLE event (
event_id serial PRIMARY KEY
, alarm_time time -- local alarm time
, tz text -- time zone name
, ...
);
Use the following expression to "cook" the exact daily point in time, taking local DST settings into account:
SELECT current_date + alarm_time AT TIME ZONE tz;
Example:
SELECT current_date + '2:30'::time AT TIME ZONE 'Europe/London' AS alarm_ts
Returns:
alarm_ts
2014-05-19 02:30:00+02
Use timestamp with time zone (timestamptz) across your whole application. Be sure to understand how it works. This comprehensive post may be of help (also explains the AT TIME ZONE construct:
Ignoring timezones altogether in Rails and PostgreSQL
Just to be clear, once you have "cooked" the daily UTC time, you can translate it to and work with any local time just as well. But it might be less confusing to do all the rest in UTC.