How to remove the UTC info from a time stamp - sql

I have a postgresql table that has a time value but it's stored without time zone information. I'd like to convert it to UTC time, but format it a specific way.
I have a syntax error somewhere and I don't know how to fix it. I have the following sql query :
testdb=# select id, starttime at TIME ZONE 'UTC',endtime AT TIME ZONE 'UTC', dtc, etcd from fre order by id;
And it returns data like this:
id | timezone | timezone | dtc | etcd
-------------+-------------+-------------+-----------+-------------------------
143322 | 13:00:00+00 | 00:00:00+00 | 0000000 | 8899703
990222 | 05:00:00+00 | 05:00:00+00 | 0000000 | 45007
452256 | 05:00:00+00 | 05:00:00+00 | 0000000 | 33303
123118 | 05:08:00+00 | 00:00:00+00 | 1111100 | 8899701
I'd like to remove the "+00" reference in the starttime / endtime fields.
I found another post here on stackoverflow that suggested using the to_char() method, and used this example:
testdb=# select to_char(now(), 'HH24:MI:SS');
to_char
----------
09:55:48
(1 row)
So I adapted it and tried this:
testdb=# select to_char(now() AT TIME ZONE 'UTC', 'HH24:MI:SS');
to_char
----------
14:55:58
(1 row)
Now I'm trying to apply this to my original query like so:
testdb=# select to_char(starttime AT TIME ZONE 'UTC', 'HH24:MI:SS') from fre;
ERROR: function to_char(time with time zone, unknown) does not exist
LINE 1: select to_char(starttime AT TIME ZONE 'UTC', 'HH24:MI:SS') f...
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
I'm not sure what I'm doing wrong.
EDIT 1
In case it helps...
testdb=# \d+ fre;
Table "public.fre"
Column | Type | Modifiers | Storage | Description
-----------+------------------------+-----------+----------+-------------
id | character varying(40) | not null | extended |
etcd | character varying(40) | not null | extended |
starttime | time without time zone | | plain |
endtime | time without time zone | | plain |
startdate | date | | plain |
enddate | date | | plain |
dtc | bit(7) | | extended |

Cast it back to timestamp without time zone:
(starttime at TIME ZONE 'UTC')::timestamp without time zone

Related

Date arithmetic on values of type DATE without resulting in TIMESTAMP

Let's say I am doing similar query (where due_date is of type date) :
SELECT
(due_date + (7 * INTERVAL '1 DAY')) AS due_date_mod
FROM test_table
The resulting due_date_mod is type timestamp.
This makes sense as the result of the operation should be one type regardless of specific values and interval can have hours/minutes/seconds.
But is there a way to add days/months/years to a date without the result being time stamp and also obviously without casting? Or is casting the only way?
I know I can add days by using:
SELECT
(due_date + INTEGER '7') AS due_date_mod
And the result is type date.
But can I do something similar for months or years (without converting them to days)?
EDIT:
There seems to be no solution satisfying the requirements of the question. Proper way to get the required results is in the marked answer.
You already found that you can add integer to date to add days, returning date:
SELECT due_date + 7 AS due_date_mod -- type date
FROM test_table;
(Since date is an integer quantity inside, it's dead simple and cheap to add integer to it.)
As you can see in the table Date/Time Operators in the manual, there is no other operator that would return date (and no function, either).
And the unit "months" must be interpreted relative to the left operand (as months differ in their number of days). So you cannot simply extract the number of days from your months. You must use date + interval to get it right. Just add a cast, it's simple and very cheap:
SELECT (due_date + interval '3 months')::date AS due_date_mod
FROM test_table;
You are aware that (7 * interval '1 mon') = interval '7 mon', right?
Originally I misinterpreted the question so what is below is examples (including output) of what #mcc had already discovered. I'll leave this answer here as they may be useful to others looking for examples of date, timestamp, and interval arithmetic.
Adding a date to an interval:
select '2021-08-11 12:23:00'::date + '2 months'::interval;
?column?
---------------------
2021-09-11 00:00:00
Or, if the field is a timestamp you can to cast it to date, but you still get a datetime answer:
select ('2021-08-11 12:23:00'::timestamp)::date + '2 years'::interval;
?column?
---------------------
2023-08-11 00:00:00
As the above examples show, when adding an interval to a date or timestamp the results is always a timestamp. If you require the results to be a date you can cast after adding the interval:
select ('2021-08-11 12:23:00'::timestamp + '60 days'::interval)::date;
date
------------
2021-10-10
I've used intervals of days, months, and years to demonstrate how each time period can be added.
reference: https://www.postgresql.org/docs/current/catalog-pg-operator.html
SELECT
oprname,
p1.typname AS left_type,
p2.typname AS right_type,
p3.typname AS result_type,
oprcode
FROM
pg_operator po
JOIN pg_type p1 ON p1.oid = po.oprleft
JOIN pg_type p2 ON p2.oid = po.oprright
JOIN pg_type p3 ON p3.oid = po.oprresult
WHERE
oprname = '+'
AND p1.typname = 'date'::name;
So there is a row like :
oprname | left_type | right_type | result_type | oprcode
---------+-----------+------------+-------------+------------------
+ | date | int4 | date | date_pli
That's why select now()::date + 1; will work.
But is there a way to add days/months/years to a date without the
result being time stamp and also obviously without casting? Or is
casting the only way?
"add days/months/years" Means:
Is there a operator that right side (or left side) is interval data type then the return is date data type.
Does it exist? we can query the catalog schema and find out.
SELECT
oprname,
p1.typname AS left_type, --left side of a operator data type
p2.typname AS right_type, --right side of a operator data type
p3.typname AS result_type, --return data type
oprcode
FROM
pg_operator po
JOIN pg_type p1 ON p1.oid = po.oprleft
JOIN pg_type p2 ON p2.oid = po.oprright
JOIN pg_type p3 ON p3.oid = po.oprresult
WHERE
p1.typname = 'interval'::name
OR p2.typname = 'interval'::name;
oprname | left_type | right_type | result_type | oprcode
---------+-------------+-------------+-------------+-------------------------
+ | date | interval | timestamp | date_pl_interval
- | date | interval | timestamp | date_mi_interval
+ | timestamptz | interval | timestamptz | timestamptz_pl_interval
- | timestamptz | interval | timestamptz | timestamptz_mi_interval
= | interval | interval | bool | interval_eq
<> | interval | interval | bool | interval_ne
< | interval | interval | bool | interval_lt
<= | interval | interval | bool | interval_le
> | interval | interval | bool | interval_gt
>= | interval | interval | bool | interval_ge
+ | interval | interval | interval | interval_pl
- | interval | interval | interval | interval_mi
* | interval | float8 | interval | interval_mul
* | float8 | interval | interval | mul_d_interval
/ | interval | float8 | interval | interval_div
+ | time | interval | time | time_pl_interval
- | time | interval | time | time_mi_interval
+ | timetz | interval | timetz | timetz_pl_interval
- | timetz | interval | timetz | timetz_mi_interval
+ | interval | time | time | interval_pl_time
+ | timestamp | interval | timestamp | timestamp_pl_interval
- | timestamp | interval | timestamp | timestamp_mi_interval
+ | interval | date | timestamp | interval_pl_date
+ | interval | timetz | timetz | interval_pl_timetz
+ | interval | timestamp | timestamp | interval_pl_timestamp
+ | interval | timestamptz | timestamptz | interval_pl_timestamptz
<-> | interval | interval | interval | interval_dist
(27 rows)
As you can see the query column return type is {timestamp,timestamptz, bool, interval,time,timetz}. So it cannot return date.

Not getting results even with long datetime formats in SQL

I am trying to get some data that has datetime set, using the long format(ex: 2019-04-26 18:02:42).
When I use the following query I expected to find the following entry:
SELECT ip, cam_id
FROM test_table
WHERE ( date_time >= '2019-04-26 20:00:00' AND date_time <'2019-04-26 20:59:59' );
Entry:
id | ip | cam_id | date_time
-----+----+--------+---------------------
1 | 13 | 2 | 2019-04-26 20:46:06
However I am not getting any results. What am I doing wrong?
EDIT: Table schema
Kolon | Veri tipi | S²ralama (collation) | Bo■ (null) olabilir | Varsay²lan | Saklama | Stats hedefi | A²klama
-----------+------------------------+----------------------+---------------------+----------------------------------------+----------+--------------+----------
id | integer | | not null | nextval('test_table_id_seq'::regclass) | plain | |
ip | integer | | | | plain | |
cam_id | integer | | | | plain | |
date_time | character varying(255) | | | | extended | |
¦ndeksler:
"test_table_pkey" PRIMARY KEY, btree (id)
If you are new to postgresql, you should start first by reading postgresql manual and examples only. Dont use any kind of third party or unrelated code and sql generators, especialy unrelated to postgresql, those will only confuse you.
Currently, your query is comparing strings not datetime.
if you run this query, it will change date_time columns character varying(255) type to timestamp one, then your query will run properly:
alter table test_table alter column date_time TYPE timestamp without time zone using date_time::timestamp without time zone
You have to convert string to datetime format:
CONVERT(datetime, YOUR_COLUMN, 'yyyy-mm-dd hh:mm')

Extract date from datetime and calculate total minutes from duration

I have a table in Postgres as follows:
| id | start_time | end_time | duration |
|----|--------------------------|--------------------------|----------|
| 1 | 2018-05-11T00:00:20.631Z | 2018-05-11T01:03:14.496Z | 1:02:54 |
| 2 | 2018-05-11T00:00:04.877Z | 2018-05-11T00:00:14.641Z | 0:00:10 |
| 3 | 2018-05-11T01:03:28.063Z | 2018-05-11T01:04:36.410Z | 0:01:08 |
| 4 | 2018-05-11T00:00:20.631Z | 2018-05-11T02:03:14.496Z | 2:02:54 |
start_time and end_time are stored as varchar. Format is 'yyyy-mm-dd hh24:mi:ss.ms' (ISO format).
duration has been calculated as end_time - start_time. Format is hh:mi:ss.
I need result table output as follows:
| id | start_time | end_time | duration | start | end | duration_minutes |
|----|--------------------------|--------------------------|----------|-----------|-----------|------------------|
| 1 | 2018-05-11T00:00:20.631Z | 2018-05-11T01:03:14.496Z | 1:02:54 | 5/11/2018 | 5/11/2018 | 62 | -- (60+2)
| 2 | 2018-05-11T00:00:04.877Z | 2018-05-11T00:00:14.641Z | 0:00:10 | 5/11/2018 | 5/11/2018 | 0 |
| 3 | 2018-05-11T01:03:28.063Z | 2018-05-11T01:04:36.410Z | 0:01:08 | 5/11/2018 | 5/11/2018 | 1 |
| 4 | 2018-05-11T00:00:20.631Z | 2018-05-11T02:03:14.496Z | 2:02:54 | 5/11/2018 | 5/11/2018 | 122 | -- (2X60 +2)
start and end need to contain only the mm/dd/yyyy portion of start_time and end_time respectively.
duration_minutes should calculate total duration in minutes (eg, if duration is 1:02:54, duration in minutes should be 62 which is 60+2)
How can I do this using SQL?
Based in varchar input, this query produces your desired result, exactly:
SELECT *
, to_char(start_time::timestamp, 'FMMM/DD/YYYY') AS start
, to_char(end_time::timestamp, 'FMMM/DD/YYYY') AS end
, extract(epoch FROM duration::interval)::int / 60 AS duration_minutes
FROM tbl;
Major points:
Use timestamp and interval instead of varchar to begin with.
Or do not store the functionally dependent column duration at all. It can cheaply be computed on the fly.
For display / a particular text representation use to_char().
Be explicit and do not rely on locale settings that may change from session to session.
The FM pattern modifier is for (quoting the manual):
fill mode (suppress leading zeroes and padding blanks)
extract (epoch FROM interval_tpe) produces the number of contained seconds. You want to truncate fractional minutes? Integer division does just that, so cast to int like demonstrated. Related:
Get difference in minutes between times with timezone
The following appears to do what you want:
select v.starttime::timestamp::date, v.endtime::date,
extract(epoch from v.endtime::timestamp - v.starttime::timestamp)/60
from (values ('2018-05-11T00:00:20.631Z', '2018-05-11T01:03:14.496Z')) v(starttime, endtime)
If you want the dates in a particular format, then use to_char().

How can I calculate time difference(HH:MM:SS) from date_time (ISO format) in Postgres

I have a table T1 in Postgres which is as follows:
| event | Date_Time |
|-------|--------------------------|
| start | 2018-04-30T06:09:30.986Z |
| run | 2018-04-30T10:37:38.044Z |
| end | 2018-04-30T11:39:38.044Z |
The Date_Time is in ISO format (stored as varchar) and I need to calculate the difference in Date_Time so that my output is as follows:
| event | Date_Time | Time_Difference |
|-------|--------------------------|-----------------|
| start | 2018-04-30T06:09:30.986Z | 4:28:08 |
| run | 2018-04-30T10:37:38.044Z | 1:02:00 |
| end | 2018-04-30T11:39:38.044Z | |
(10: 37: 38 - 06: 09: 30 = 4:28:08)
How can I do this using SQL?
Unrelated to the question, but: you should never store timestamp (or date or number) values in a varchar.
You first have to convert the varchar value to a timestamp. If the values are indeed formatted correctly, you can simply cast them: Date_Time::timestamp - or maybe to a timestamptz.
As far as I can tell, you want the different to the next row in your result. This can be achieved with the window function lead()
select event,
Date_Time,
date_time::timestamp - lead(date_time::timestamp) over (order by date_time::timestamp) as time_difference
from the_table
order by date_time;
The result of subtracting one timestamp from another is an interval you can format if you want.

Why won't this simple SQL statement work?

dateposted is a MySQL TIMESTAMP column:
SELECT *
FROM posts
WHERE dateposted > NOW() - 604800
...SHOULD, if I am not mistaken, return rows where dateposted was in the last week. But it returns only posts less than roughly one day old. I was under the impression that TIMESTAMP used seconds?
IE: 7 * 3600 * 24 = 604800
Use:
WHERE dateposted BETWEEN DATE_ADD(NOW(), INTERVAL -7 DAY) AND NOW()
That is because now() is implicitly converted into a number from timestamp and mysql conversion rules create a number like YYYYMMDDHHMMSS.uuuuuu
from mysql docs:
mysql> SELECT NOW();
-> '2007-12-15 23:50:26'
mysql> SELECT NOW() + 0;
-> 20071215235026.000000
Internally perhaps. The way to do this is the date math functions. So it would be:
SELECT * FROM posts WHERE dateposted > DATE_ADD(NOW(), INTERVAL -7 DAY)
I think there is a DATE_SUB, I'm just used to using ADD everywhere.
No, you can't implicitly use integer arithmetic with TIMESTAMP, DATETIME, and other date-related data types. You're thinking of the UNIX timestamp format, which is an integer number of seconds since 1/1/1970.
You can convert SQL data types to a UNIX timestamp in MySQL and then use arithmetic:
SELECT * FROM posts WHERE UNIX_TIMESTAMP(dateposted)+604800 > NOW()+0;
NB: adding zero to NOW() makes it return a numeric value instead of a string value.
update: Okay, I'm totally wrong with the above query. Converting NOW() to a numeric output doesn't produce a number that can be compared to UNIX timestamps. It produces a number, but the number doesn't count seconds or anything else. The digits are just YYYYMMDDHHMMSS strung together.
Example:
CREATE TABLE foo (
id SERIAL PRIMARY KEY,
dateposted TIMESTAMP
);
INSERT INTO foo (dateposted) VALUES ('2009-12-4'), ('2009-12-11'), ('2009-12-18');
SELECT * FROM foo;
+----+---------------------+
| id | dateposted |
+----+---------------------+
| 1 | 2009-12-04 00:00:00 |
| 2 | 2009-12-11 00:00:00 |
| 3 | 2009-12-18 00:00:00 |
+----+---------------------+
SELECT *, UNIX_TIMESTAMP(dateposted) AS ut, NOW()-604800 AS wk FROM foo
+----+---------------------+------------+-----------------------+
| id | dateposted | ut | wk |
+----+---------------------+------------+-----------------------+
| 1 | 2009-12-04 00:00:00 | 1259913600 | 20091223539359.000000 |
| 2 | 2009-12-11 00:00:00 | 1260518400 | 20091223539359.000000 |
| 3 | 2009-12-18 00:00:00 | 1261123200 | 20091223539359.000000 |
+----+---------------------+------------+-----------------------+
It's clear that the numeric values are not comparable. However, UNIX_TIMSTAMP() can also convert numeric values in that format as it can convert a string representation of a timestamp:
SELECT *, UNIX_TIMESTAMP(dateposted) AS ut, UNIX_TIMESTAMP(NOW())-604800 AS wk FROM foo
+----+---------------------+------------+------------+
| id | dateposted | ut | wk |
+----+---------------------+------------+------------+
| 1 | 2009-12-04 00:00:00 | 1259913600 | 1261089774 |
| 2 | 2009-12-11 00:00:00 | 1260518400 | 1261089774 |
| 3 | 2009-12-18 00:00:00 | 1261123200 | 1261089774 |
+----+---------------------+------------+------------+
Now one can run a query with an expression comparing them:
SELECT * FROM foo WHERE UNIX_TIMESTAMP(dateposted) > UNIX_TIMESTAMP(NOW())-604800
+----+---------------------+
| id | dateposted |
+----+---------------------+
| 3 | 2009-12-18 00:00:00 |
+----+---------------------+
But the answer given by #OMGPonies is still better, because this expression in my query probably can't make use of an index. I'm just offering this as an explanation of how the TIMESTAMP and NOW() features work.
Try this query:
SELECT * FROM posts WHERE DATE_SUB(CURDATE(),INTERVAL 7 DAY) < dateposted;
I am assuming that you are using mySQL.