1

I have a MySQL database, which I cannot alter, where I read date from. The issue is that I have a varchar column that stores a date. The date is stored in the atomtime format eg. 2014-06-01T00:00:00+02:00.

I cannot figure how to specify the format in the STR_TO_DATE function. I tried STR_TO_DATE(Endtime, '%Y-%m-%dT%H:%i:%s+02:00'), but that doesn't work.

Do anyone have a solution for this?

I am trying to run the following query (which is not working properly):

SELECT *, COUNT(*) as antal 
 FROM ivocall_calls
WHERE Agentname LIKE 'Vinh Nguyen'
  AND Status1 = 'SALG'
  AND STR_TO_DATE(Endtime, '%Y-%m-%dT%H:%i:%s+02:00') 
        BETWEEN STR_TO_DATE('2014-06-01T00:00:00+02:00', '%Y-%m-%dT%H:%i:%s+02:00') 
            AND STR_TO_DATE('2014-06-30T00:00:00+02:00', '%Y-%m-%dT%H:%i:%s+02:00')

Thanks in advance.

Dan Lowe
  • 51,713
  • 20
  • 123
  • 112
denn
  • 35
  • 1
  • 5

2 Answers2

2

This is probably best-addressed using a stored function to parse and convert the timestamps from the stored format into MySQL's native format, using the built-in date time math functions to do the time zone conversions.

The function below will correctly handle two formats, YYYY-MM-DDTHH:MM:SSZ and YYYY-MM-DDTHH:MM:SS+/-HH:MM as well as correctly formed MySQL datetime literals, which will be passed through unmodified.

DELIMITER $$

DROP FUNCTION IF EXISTS `from_iso8601_subset` $$
CREATE FUNCTION `from_iso8601_subset`(in_ts TINYTEXT) RETURNS DATETIME
DETERMINISTIC
NO SQL
BEGIN

-- this function takes an input timestamp value in a suppported subset of iso8601 values, and
-- and converts it to the equivalent MySQL datetime value, expressed in the current session's
-- time zone.  Since this is also the timezone that columns in the TIMESTAMP data type expect,
-- this causes the input value to be stored correctly in the native TIMESTAMP format, which is.
-- UTC under the hood.

-- if you are taking the value here and stuffing it into a DATETIME column, you need to have your
-- session @@time_zone set to the same zone in which that column should be stored, or use
-- CONVERT(from_iso('input value'),'UTC','Your Desired Time Zone');

-- 2014-02-01T23:59:59Z --

IF (in_ts REGEXP '^[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}[T ][[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}(Z|[+-][[:digit:]]{2}:[[:digit:]]{2})$') THEN

  SET in_ts = REPLACE(REPLACE(in_ts, 'T', ' '), 'Z', '+00:00');
  RETURN CONVERT_TZ(SUBSTRING(in_ts FROM 1 FOR 19), SUBSTRING(in_ts FROM 20 FOR 24), @@time_zone);

-- unexpected format -- let MySQL's built-in functions do the best they can; this will throw warnings
-- if the input is not a yyyy-mm-dd hh:mm:ss datetime literal; alternately this could return NULL.

ELSE

  RETURN CAST(in_ts AS DATETIME);

END IF;

END $$

DELIMITER ;

Example output:

mysql> SET @@time_zone = 'America/New_York';
Query OK, 0 rows affected (0.08 sec)

mysql> SELECT from_iso8601_subset('2014-06-01T00:00:00+02:00');
+--------------------------------------------------+
| from_iso8601_subset('2014-06-01T00:00:00+02:00') |
+--------------------------------------------------+
| 2014-05-31 18:00:00                              |
+--------------------------------------------------+
1 row in set (0.08 sec)

mysql> set @@time_zone = 'UTC';
Query OK, 0 rows affected (0.08 sec)

mysql> SELECT from_iso8601_subset('2014-06-01T00:00:00+02:00');
+--------------------------------------------------+
| from_iso8601_subset('2014-06-01T00:00:00+02:00') |
+--------------------------------------------------+
| 2014-05-31 22:00:00                              |
+--------------------------------------------------+
1 row in set (0.08 sec)

We assume that if the input data matches one of the patterns, then the contents of the value passed in are also going to be sane; if you give nonsense input values, you'll get some nonsense output, such as if you are using a time zone of '+99:00' but it won't fail. This function does not have any SQL injection vulnerabilities.

The code could be further-optimized, but as written, this function is efficient enough that it can evaluate several thousand expressions per second on a moderately-powered machine.

darkwind
  • 33
  • 6
Michael - sqlbot
  • 169,571
  • 25
  • 353
  • 427
0

Use unix_timestamp() instead:

SELECT something, COUNT() as antal FROM ivocall_calls 
WHERE Agentname LIKE 'Vinh Nguyen' 
AND Status1 = 'SALG' 
AND unix_timestamp(Endtime) BETWEEN 
    unix_timestamp('2014-06-01T00:00:00+02:00' and unix_timestamp('2014-06-30T00:00:00+02:00');
Alex Monthy
  • 1,827
  • 1
  • 14
  • 24
  • That did the trick. Thanks! You are missing a bracket in your query, this query works: `SELECT *, COUNT(*) as antal FROM ivocall_calls WHERE Agentname LIKE 'Vinh Nguyen' AND Status1 = 'SALG' AND unix_timestamp(Endtime) BETWEEN unix_timestamp('2014-06-01T00:00:00+02:00') AND unix_timestamp('2014-06-30T00:00:00+02:00')` – denn Jun 13 '14 at 17:33
  • 1
    Interesting answer, but very wrong. The unix_timestamp function, at best, is ignoring the time zone information in its argument, and assuming the session's time zone. Run that query, and then `SHOW WARNINGS;`. – Michael - sqlbot Jun 13 '14 at 20:50
  • 1
    @Michael: I'm always happy to learn something new. Maybe my solution still is applicable for the OP, if all their entries are in the same time zone. But I have to concede that then it's not a recommendable solution. – Alex Monthy Jun 14 '14 at 08:39
  • Interesting point @Michael-sqlbot. In this particular situation the timezone is the same for all entries. Any clues about a proper solution? – denn Jun 14 '14 at 09:07
  • If you use day dates only and not the exact time, then you can use also the date() function instead of the unix_timestamp() function. But it seems to have the same flaw of not honouring the time zone. – Alex Monthy Jun 14 '14 at 09:24
  • @AlexMonthy thanks for the concession; humorously enough, if all the times are in the same format and time zone, a *lexical* comparison without any datetime hackery would be the simplest approach, given a design that can't be changed... in the sense that 'boy' `BETWEEN` 'apple' `AND` 'dog' correctly evaluates to `TRUE` and if the column is indexed, the index remains usable. – Michael - sqlbot Jun 14 '14 at 15:20
  • @Michael, you are right, of course. And who knows, my naive approach might even work for denn. We're still lacking a viable solution, though - wanna give it a try? – Alex Monthy Jun 14 '14 at 23:36
  • My concern with your approach is that it will give the appearance of working, but may have hidden edge cases. I do have a solution in the form of a user defined function, but I've forgotten which codebase it's hiding in. Still looking for it. – Michael - sqlbot Jun 15 '14 at 03:32