2

I have the following hexadecimal data that I want to convert using the postgres functions, the data they receive is the following:

   @AVLData_TimeStamp          @AVLData_GPSElement_Altitude
   00000174C0FA7EA0            0140
   

And I want to convert them to the following data

  @MOPO_FECHAHORA             @MOPO_ALTITUD
  2020-09-24 09:37:56.000     320

I have tried to do it using this function but have not succeeded

  SELECT to_timestamp(@AVLData_TimeStamp)

I have two functions in SQLserver that do this but in postgres I don't know how to do it

First Function:

CREATE FUNCTION FUN_Unix_To_Date(
    @FechaUnix CHAR(38)
)
RETURNS DATETIME
AS
BEGIN
    DECLARE
     @LocalTime BIGINT
    ,@Adjusted  BIGINT
    ,@seconds   BIGINT = @FechaUnix

    SET @LocalTime = DATEDIFF(MS,GETDATE(),GETUTCDATE())
    SET @Adjusted = @seconds - @LocalTime

    RETURN (DATEADD(SECOND,CONVERT(BIGINT, LEFT(CAST(@Adjusted AS VARCHAR(100)),13))/1000, CAST('1970-01-01 00:00:00' AS DATETIME)))

END

Two Function:

CREATE FUNCTION HexadecimalToDec_v2(
    @hexval CHAR(38)
)
RETURNS NUMERIC(38)
AS
BEGIN
    DECLARE  @i INT
            ,@digits INT
            ,@result NUMERIC
            ,@current_digit CHAR(1)
            ,@current_digit_dec NUMERIC
    SET @digits = LEN(@hexval)
    SET @i = 0
    SET @result =0

    WHILE @i <= @digits  
    BEGIN
        SET @current_digit = SUBSTRING(@hexval, @i, 1)
        
        IF  @current_digit = 'A' OR 
            @current_digit = 'B' OR
            @current_digit = 'C' OR
            @current_digit = 'D' OR
            @current_digit = 'E' OR
            @current_digit = 'F' 

            SET @current_digit_dec = ASCII(@current_digit) - ASCII('A') + 10

        ELSE
            SET @current_digit_dec = CONVERT(INT,@current_digit)

            SET @result = (@result * 16) + @current_digit_dec

        SET @i = @i + 1
    END
RETURN(@result)
END

and finally the function is used like this

@MOPO_FECHAHORA = (dbo.Unix_To_Date(dbo.FUN_HexadecimalToDec_v2(@AVLData_TimeStamp)))

And this is the result

 @MOPO_FECHAHORA             
  2020-09-24 09:37:56.000    

Thank you very much for your help.

Roller Fernandez
  • 171
  • 1
  • 3
  • 12

2 Answers2

3

Use the function from this answer to convert hexadecimal to decimal (you might want to use bigint rather than integer):

CREATE OR REPLACE FUNCTION hex_to_int(hexval text) RETURNS bigint
   LANGUAGE plpgsql IMMUTABLE STRICT AS
$$DECLARE
   result bigint;
BEGIN
   EXECUTE 'SELECT x' || quote_literal(hexval) || '::bigint'
   INTO result;
   RETURN result;
END;$$;

The timestamp can then be converted with

to_timestamp(hex_to_int('00000174C0FA7EA0') / 1000.0)
Laurenz Albe
  • 209,280
  • 17
  • 206
  • 263
  • I have used the function you mentioned CREATE OR REPLACE FUNCTION hex_to_int(hexval varchar) RETURNS integer AS $$ DECLARE result int; BEGIN EXECUTE 'SELECT x' || quote_literal(hexval) || '::int' INTO result; RETURN result; END; $$ LANGUAGE plpgsql IMMUTABLE STRICT; I have used the function you mentioned and I get the following error NOTICE: Yo this is good! --> integer out of range 22003 – Roller Fernandez Oct 13 '20 at 07:40
  • @LaurenzAlbe IMO this function can be implemented using `sql` language... – Abelisto Oct 13 '20 at 09:16
  • 2
    @LaurenzAlbe something like `select ('x' || lpad('FFAA', 16, '0'))::bit(64)::bigint` – Abelisto Oct 13 '20 at 09:54
  • Laurenz If I made the changes with bigint and I just got that error. – Roller Fernandez Oct 13 '20 at 14:35
  • Abelisto when I pass this hexadecimal 00000174C0FA7EA0 it throws me the following error. This is good! -> the time / date value is out of range: "1600965476000" 22008 – Roller Fernandez Oct 13 '20 at 14:37
  • I changed everything to bigint and it stopped giving me an error but now it gives me a different result than expected, the result should be 2020-09-24 09: 37: 56 but it will be returning 2020-09-24 11:37:56 – Roller Fernandez Oct 13 '20 at 15:15
  • Laurenz how could i do that? – Roller Fernandez Oct 13 '20 at 15:26
  • SET TIMEZONE TO 'UTC'; I changed to this and my result has varied even more 2020-09-24 16:37:56+00 – Roller Fernandez Oct 13 '20 at 15:32
  • Thanks Laurenz and I managed to correct it, also thanks Abelisto also your code served in the same way for this purpose – Roller Fernandez Oct 13 '20 at 16:35
  • Why there are pairs like `make_timestamp()` and `make_timestamptz()` but there is sad and alone `to_timestamp()`? – Abelisto Oct 13 '20 at 21:34
  • 1
    IMO it is impossible just because the naming conventions: `to_imestamp()` returns `timestamptz` and how I could to name the function that returns `timestamp without timezone`? `to_timestamp_wtz()`? It breaks all beautiful PG naming! In any case it is not the subject of the SO. Could you to provide me to the PG resource for it? (At the `https://www.postgresql.org/list/pgsql-hackers/` I lost a bit) – Abelisto Oct 13 '20 at 21:54
  • @Abelisto Yes, it is a historical burden, see [the commit](https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=b866d2e2d79416f8497e4dffa7e800298d018f6c). I couldn't find the message in the pgsql-patches archives, perhaps it got sent to Bruce off-list. – Laurenz Albe Oct 13 '20 at 23:08
  • @LaurenzAlbe I filling that it could be the patch of the `make_timestamp[tz]` functions with the only one parameter (epoch)... Just for now it produces error for `make_timestamp(0, 0, 0, 0, 0, 12345678)` but it should be some implementation like `make_timestamp(month=>11, sec=>2222)` or simply `make_timestamp(sec=>55555555)` ... Not obvious.... Damn! – Abelisto Oct 14 '20 at 04:11
  • 1
    @Abelisto Another consideration that I forgot about is that these PostgreSQL functions were expressly intended for Oracle compatibility (although they are not totally compatible). Perhaps the Oracle version returns `timestamp with time zone`? – Laurenz Albe Oct 14 '20 at 05:39
  • I said "good by!" to Oracle years ago and I using it just to port the Oracle DB to PG :) (or to MonetDB for analytics) I mean that I don't know what happens in the Oracle world for now :( – Abelisto Oct 14 '20 at 10:06
1

Just to clarify the source of issue.

The problem is that the your value(s) is the "local" epoch but to_timestamp() function returns timestamp with timezone value. Lets try some example:

with t(x) as (values('2020-09-24 09:37:56'))
  select
    x::timestamp as srcts,
    x::timestamptz as srctstz,
    to_timestamp(extract(epoch from x::timestamp)) as cnvts,
    to_timestamp(extract(epoch from x::timestamptz)) as cnvtstz
  from t;

┌─[ RECORD 1 ]─────────────────────┐
│ srcts   │ 2020-09-24 09:37:56    │
│ srctstz │ 2020-09-24 09:37:56+03 │
│ cnvts   │ 2020-09-24 12:37:56+03 │ <- Here is the issue in our case
│ cnvtstz │ 2020-09-24 09:37:56+03 │
└─────────┴────────────────────────┘

As you can see the data source type is critical. That's why you got the increased (actually converted from UTC to the local time zone) value in the first try using @LaurenzAlbe answer.

To fix this issue you need to perform some "reverse" calculations:

with t(x) as (values('2020-09-24 09:37:56'))
  select
    x::timestamp as srcts,
    x::timestamptz as srctstz,
    to_timestamp(extract(epoch from x::timestamp)) as cnvts,
    (to_timestamp(extract(epoch from x::timestamp)))::timestamptz at time zone 'utc' as cnvtsrecalc,
    to_timestamp(extract(epoch from x::timestamptz)) as cnvtstz
  from t;

┌─[ RECORD 1 ]┬────────────────────────┐
│ srcts       │ 2020-09-24 09:37:56    │
│ srctstz     │ 2020-09-24 09:37:56+03 │
│ cnvts       │ 2020-09-24 12:37:56+03 │ <- Here is the issue in our case
│ cnvtsrecalc │ 2020-09-24 09:37:56    │ <- Issue fixed
│ cnvtstz     │ 2020-09-24 09:37:56+03 │
└─────────────┴────────────────────────┘

The following function wraps all this logic including the conversion of the hex value to bigint:

create or replace function hex2ts(text)
  returns timestamp
  language sql
  immutable
  strict
as $$
  select 
    (to_timestamp(('x' || lpad($1, 16, '0'))::bit(64)::bigint / 1000.0))::timestamptz at time zone 'utc'
$$;
Abelisto
  • 14,826
  • 2
  • 33
  • 41