mySQL need timezone displayed in EST but cannot change global time settings - mysql

I am facing an odd issue. The global timezone setting in the mySQL is in UTC. There are multiple tables (sub databases) within a single instance of mySQL (I am using database.NET as the manager); so I can't change the global timezone.
The following is my query. All I need is to display the times in EST. I have seen a few solutions with ##sessionTimeZone but they didn't work. Furthermore, I am running into the issue of data being collected till 9PM EST, but in UTC that's 1AM on the next day.
My main confusion is that the data is already stored in the table in UTC; however, I would like to see those datetime fields displayed in EST.
cartId is a numerical field
createDate is a datetime field
I am just using http://fishcodelib.com/database.htm and connecting to the mySQL database.
SELECT DATE(createDate)
,DATE_FORMAT(createDate, '%l%p') as HourOfDay
,count(cartId) as numCarts_ALL
FROM carts
WHERE createDate >= '2014-09-24'
AND createDate < '2014-10-01'
AND HOUR(createDate) >= 10 AND HOUR(createDate) <21
GROUP by DATE(createDate),HOUR(createDate)
;
I will really appreciate any help.
Thanks again

DATETIME data types are not affected by either the global or connection-local timezone setting. That only works for TIMESTAMP data types. That explains why your attempts to muck around with ##session.TimeZone don't have any effect. NOW() and CURDATE() are affected, though.
You say your DATETIME data is stored in UTC. That's excellent. Life is much easier when you store your data that way.
Before you use the advice I'm about to give you, please make sure your MySQL server has its timezone tables loaded correctly. Do this command and make sure you don't get a NULL result.
SELECT CONVERT_TZ(NOW(), 'America/New_York', 'UTC')
If this doesn't work -- if you get a NULL or an error -- you need to get your server people to load the time zone tables. They should do this. They know how. (If they don't, you should get a new service provider.)
We need to use the timezone named 'America/New_York' because presumably you want to switch automatically between EDT and EST on the appropriate dates each year.
Now, to retrieve a correctly converted UTC DATETIME value from a table, you do this:
SELECT CONVERT_TZ(createDate, 'UTC', 'America/New_York') AS createDate
FROM yourTable
This is cool because you can make the timezone setting a user preference if you have users in various time zones.
To store a local time value as UTC just do it in reverse. For example.
INSERT INTO yourTable (createDate) VALUES (CONVERT_TZ(?, 'America/New_York', 'UTC'))
Now do
SELECT NOW()
and look to see whether NOW() is in local time or in UTC. If it is in UTC, then you should start your session by doing SET TIME_ZONE='America/New_York' . This will get your timezone set right so NOW() and CURDATE() do what you want.
Then, to fetch yesterday's (local time) rows from your table do this:
WHERE createDate >= CONVERT_TZ(CURDATE(),'America/New_York','UTC') - INTERVAL 1 DAY
AND createDate < CONVERT_TZ(CURDATE(),'America/New_York','UTC')
This will convert midnight local time to UTC and fetch the range of items.
To get all the data from 4pm today until 2am tomorrow, you could do this:
4pm today (localtime) is CURDATE() + INTERVAL 16 HOUR . 2am tomorrow is CURDATE() + INTERVAL 26 HOUR or you could write it CURDATE() + INTERVAL 1 DAY + INTERVAL 2 HOUR
So fetching that range would need this:
WHERE createDate >= CONVERT_TZ(CURDATE() + INTERVAL 16 HOUR,'America/New_York','UTC')
AND createDate < CONVERT_TZ(CURDATE() + INTERVAL 26 HOUR,'America/New_York','UTC')
You could also use ADDTIME(CURDATE(),'16:00') to obtain a DATETIME value for 4pm today, if you'd rather combine ordinary date objects and ordinary time objects. Similarly, you could get 2am tomorrow like this:
ADDTIME(CURDATE(),'02:00') + INTERVAL 1 DAY
Notice that this form of WHERE clause allows a range scan on an index on the createDate column. This is very good for performance.

To convert datetime from a timezone to another timezone:
SELECT DATE(CONVERT_TZ(createDate, '+00:00', '-04:00')) createdDate,
HOUR(CONVERT_TZ(createDate, '+00:00', '-04:00')) hourOfDay
FROM carts
WHERE
createDate BETWEEN CURRENT_DATE + INTERVAL 16 HOUR AND CURRENT_DATE + INTERVAL 26 HOUR
GROUP BY createdDate, hourOfDay
For different dates
SELECT DATE(CONVERT_TZ(createDate, '+00:00', '-04:00')) createdDate,
HOUR(CONVERT_TZ(createDate, '+00:00', '-04:00')) hourOfDay
FROM carts
WHERE
createDate BETWEEN '2014-09-24' AND '2014-10-01'
GROUP BY createdDate, hourOfDay
HAVING hourOfDay <= 2 OR hourOfDay >= 20
You might think "why not in WHERE clause?"
If functions are used in the where clause, indexes can't be used (which results in a full table scan). So just filter out the records within the daterange, and the hours in the HAVING clause
FYI
CURRENT_DATE + INTERVAL 16 HOUR
Is short for
DATE_ADD(CURRENT_DATE, INTERVAL 16 HOUR)

Related

MYSQL: Select statement that counts event in timestamp column for the current date adjusting for UTC

So this has been the biggest pita... My db is in UTC and I am trying to create a select statement that accurately displays the count of newly created customers today in my TZ. The first interval works as intended, but when the clock strikes 5pm PST (UTC -7) and the db switches over to the next day, the second part after the and clause fails, and I am left with an empty set. Tweeked the hell out of the second part but have had no success. Any help would be most appreciated!
SELECT count(*) FROM product36.associate where date(created_date - interval 7 hour) >= curdate() and date(created_date + interval 17 hour) >=curdate();

Select where unix timestamp falls on specified hour or day

I'm trying to build a dynamic where for a MySQL query. The user can select how many hours or days ago they want data for. I want the data to fall on the particular hours or days ago.
Here is the code I'm using right now which does not work:
$condition['dates'] = 'Specific';
$condition['date_operand'] = 'Hour(s) ago';
$condition['date_value'] = '3';
if ($conditions['dates'] == 'Specific' && !empty($conditions['date_value'])) {
if ($conditions['date_operand'] == 'Hour(s) ago') {
$where[] = "date_format(from_unixtime(l.date_updated), '%Y-%m-%d %H') = date_format(now() - interval ".$conditions['date_value']." hour, '%Y-%m-%d %H')";
}
else if ($conditions['date_operand'] == 'Day(s) ago') {
$where[] = "date_format(from_unixtime(l.date_updated), '%Y-%m-%d') = date_format(now() - interval ".$conditions['date_value']." day, '%Y-%m-%d')";
}
}
It does not seem to be working whatsoever. The l.date_updated is a unix timestamp. You can see what I'm trying to achieve, it just isn't working.
UPDATE
Here is the MySQL where statement from the example which is not working:
SELECT * FROM mytable l
WHERE DATE_FORMAT(FROM_UNIXTIME(l.date_updated), '%Y-%m-%d %H') = DATE_FORMAT(NOW() - INTERVAL 3 HOUR, '%Y-%m-%d %H')
It does not cause an error, it simply isn't selecting what I want.
FINAL UPDATE
My query was actually fine. It appears my PHP code was connecting to a development database which hasn't been updated in years. That's why when I selected 3 days ago it returned zero rows.
So please, a little respect, for I am Dion, lord of the idiots.
It seems to me you want to choose rows where your date_updated value lies in a range of time. For example, I think you mean, if NOW() is 2017-04-03 17:55:22, you want all the records timestamped between 2017-04-03 14:00:00 and 2017-04-03 14:59:59.99999 inclusive.
Here's how you do that in a sargable way: a way that can use an index on your date_updated column.
This expression truncates NOW() to the top of the hour: 17:55 to 17:00:
DATE_FORMAT(NOW(), '%Y-%m-%d %H:00:00')
This backs up three hours.
DATE_FORMAT(NOW(), '%Y-%m-%d %H:00:00') - INTERVAL 3 HOUR
The beginning of your timestamp range is:
UNIX_TIMESTAMP(DATE_FORMAT(NOW(), '%Y-%m-%d %H:00:00') - INTERVAL 3 HOUR)
The end of your timestamp range, then, is
UNIX_TIMESTAMP(DATE_FORMAT(NOW(), '%Y-%m-%d %H:00:00') - INTERVAL 2 HOUR)
So, this WHERE clause does the trick.
WHERE l.date_updated >= UNIX_TIMESTAMP(DATE_FORMAT(NOW(),'%Y-%m-%d %H:00:00')-INTERVAL 3 HOUR)
AND l.date_updated) < UNIX_TIMESTAMP(DATE_FORMAT(NOW(),'%Y-%m-%d %H:00:00')-INTERVAL 2 HOUR)
Notice the <, not <=, at the end of the time range.
There's some time zone stuff happening here. Unix timestamps are (or should be) always recorded with respect to UTC. The UNIX_TIMESTAMP() function always converts from local time to UTC, and the NOW() function always works in local time, so this all should work properly. But you might investigate all this timestamp junk if you're still getting the wrong rows, or no rows.
Please notice that your time precision would be the same and your life would be easier if your column had the TIMESTAMP data type rather than the INT data type.
you can use TO_SECONDS:
https://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_to-seconds
... where TO_SECONDS(l.date_updated) >= TO_SECONDS(NOW()-INTERVAL 3 HOUR)

Select all MySQL records in the last day that are between a time range of 7am and midnight

I have this query
SELECT * FROM `timeclock_timecard`
WHERE `clock_in_datetime` > DATE_SUB(CURDATE(), INTERVAL 1 DAY)
which can get record in the last day but I need to limit to records created after 7AM
Any help please?
SELECT * FROM `timeclock_timecard`
WHERE `clock_in_datetime` > DATE_SUB(CURDATE(), INTERVAL 1 DAY)
and hour(`clock_in_datetime`) > 7;
Added one more filter condition to check for the hour.
Your query was almost correct, because CURDATE() only gives the date you can just subtract 17 hours to get the correct result. fiddle.
SELECT * FROM `timeclock_timecard`
WHERE `clock_in_datetime` >= DATE_SUB(CURDATE(), INTERVAL 17 HOUR)
To get the entries of the current day, we can add 7 hours (CURDATE() has time 0:00).
SELECT * FROM `timeclock_timecard`
WHERE `clock_in_datetime` >= DATE_ADD(CURDATE(), INTERVAL 7 HOUR)
To get only rows from yesterday, with a time value of 7AM or later, we can add 7 hours to the expression.
If we only up until midnight of today (just rows from yesterday), we can add another condition, the datetime is less than midnight today.
For example:
SELECT t.*
FROM `timeclock_timecard` t
WHERE t.`clock_in_datetime` >= DATE(NOW()) + INTERVAL -1 DAY + INTERVAL 7 HOUR
AND t.`clock_in_datetime` < DATE(NOW())
If you want to exclude the exact 7:00:00 AM value, change the >= to just >.
FOLLOWUP
Q: What I actually want is between about 5-6am TODAY and mindnight TODAY so anytime during today that I run the report for today I will get only timeclock data from users who clocked in/out today only and not include yesterdays data.
A: The predicates are going to be of the form
WHERE t.`clock_in_datetime` >= expr1
AND t.`clock_in_datetime` < expr2
You just need to find the expressions expr1 and expr2 that return the appropriate datetime values.
Just use a simple SELECT statement to test:
SELECT DATE(NOW()) + INTERVAL 5 HOUR AS `start`
, DATE(NOW()) + INTERVAL 1 DAY AS `end`
Q: I also modified my select to take in account my datetime is in UTC and my result needs to get todays records using local timezone.
SELECT * , CONVERT_TZ( clock_in_datetime , '+00:00', '-4:00' ) FROM `timeclock_timecard`
A: Personally, I would do the timezone conversion on the exprN values, not the column values. Having predicates on bare columns allows MySQL to make effective use of an index; wrapping the columns in expressions prevents MySQL from using an index.
If the MySQL system clock is UTC, and your datetime values stored in the table are in a different timezone, yes, use the MySQL CONVERT_TZ function.
Again, using a simple SELECT statement to develop and test the expressions:
SELECT CONVERT_TZ( DATE(NOW()) + INTERVAL 5 HOUR, '+0:00', to_tz) AS `start`
, CONVERT_TZ( DATE(NOW()) + INTERVAL 1 DAY , '+0:00', to_tz) AS `end`
Where to_tz is the timezone of the values in the table.
Once you get expressions start and end returning the values you need, then use those expressions in the predicates of the query of the timecard table.

Value of time of the beginning of today

I am trying to get the number of times a user has logged in since the beginning of each day.
SELECT user_id
FROM users
WHERE TIMESTAMPDIFF(HOUR, user_login, NOW()) < 12
AND user_id = 1
This sql checks the difference between the login and the current time which is wrong.
What should go instead of NOW() to indicate midnight+1min of that day and check the logins throughout that day?
As far as I understand your code, the question is rather about mySQL and not PHP. Use
CURDATE()
instead of NOW() since TIMESTAMPDIFF will use this as 'Current Day, 00:00:00'
To get the end of the current day use
CURDATE() + INTERVAL 1 DAY
so your where-clause should be
WHERE user_login BETWEEN CURDATE() AND CURDATE() + INTERVAL 1 DAY
provided you have the user_login field in the DATE format, otherwise you would have to cast that accordingly.

MySQL Select rows <= now(), using separated time fields

I have a table 't' with date(yyyy-mm-dd), hour(1-12), minute(00-59), ampm(a/p), and timezone(pst/est) fields.
How can I select the rows that are <= now()? (ie. already happened)
Thank you for your suggestions!
edit: this does it without attention to the hour/minute/ap/tz fields:
SELECT * FROM t.date WHERE date <= now()
Here's one way to do it - combine all your seconds, minutes, etc into a date and compare to NOW(), making sure you do the comparison in the same time-zone. (Untested):
SELECT *
FROM t
LEFT JOIN y ON t.constant=y.constant
WHERE CONVERT_TZ(STR_TO_DATE(CONCAT(date,' ',hour,':',minute,' 'ampm),
'%Y-%m-%d %l:%i %p' ),
timezone,"SYSTEM") < NOW();
If your hour is 01 - 12 not 1-12 then use %h instead of %l in the STR_TO_DATE.
The STR_TO_DATE tries to stick your date and time columns together and convert them into a date.
The CONVERT_TZ(...,timezone,"SYSTEM") converts this date from whatever timezone is specified in the timezone column to system time.
This is then compared to NOW(), which is always in system time.
As an aside, perhaps you should make a single column date using MySQL's date datatype, as it's a lot easier to do arithmetic on that!
For reference, here is a summary of very useful mysql date functions where you can read up on those featuring in this answer.
Good luck!
SELECT * FROM t
WHERE `date`<=DATE_SUB(curdate(), INTERVAL 1 DAY)
OR (
`date`<=DATE_ADD(curdate(), INTERVAL 1 DAY)
AND
CONVERT_TZ(CAST(CONCAT(`date`,' ',IF(`hour`=12 AND ampm='a',0,if(ampm='a',`hour`,`hour`+12)),':',`minute`,':00') AS DATETIME),'GMT',`timezone`)<=NOW()
)
Rationale for date<=DATE_[ADD|SUB](curdate(), INTERVAL 1 DAY):
The fancy conversion is quite an expensive operation, so we don't want it to run on the complete table. This is why we pre-select against an UNCHANGED date field (possibly using an index). In no timezone can an event being more than a day in current timezone's past be in the future, and in no timezone can an event more than a day in the curent timezone's future be in the past.