Wrong TimeZone Conversion in Ruby on Rails - mysql

I'm really stucked on this one.
Basically I want to Import a Excel-File. So I ended up using the CSV import from Rails.
CSV.open('[path-to-file]', 'r').each do |row|
Actually the Import is working fine, but there is among other things a Date-Column in the Excel/CSV-File and a DateTime-Column in the Database. I did following in the CSV.open-Method:
date = DateTime.strptime(row[0], "%Y-%m-%d").strftime("%Y-%m-%d")
start_datetime = DateTime.parse(date + " 6:30:00").utc
Event.create(:event_start => start_datetime)
This creates the correct Events with the correct DateTime in the Database. Looks for example like:
2008-11-18 09:30:00
My Problem:
If I choose to comment the line in the application.rb
config.time_zone = 'Berlin'
Rails uses UTC to display my Events and everything looks like the content of the Database.
If I choose to uncomment the config.time_zone part (what I definitely have to), Rails should add 1 hour (Berlin: UTC/GMT +1 hour). Actually it does add at least 1 hour, but sometimes 2 hours. There is no connection (for me), in which case Rails chooses to add 1 or 2 hours.
If I create an Event on the normal Websurface (in the Browser), everything is working fine (subtract on 1 hour to save UTC in DB and add 1 hour to display in correct TimeZone).
It would be really helpful, if you had some tips for me how I can try to localize this problem.
My System: Rails 3.2.3 on Ruby 1.9.3p194, MySQL on Suse Enterprise
Example:
CSV:
2012-01-08
2012-02-09
2012-03-10
2012-04-11
2012-05-12
2012-06-13
2012-07-14
2012-08-15
2012-09-16
2012-10-17
2012-11-18
2012-12-19
DB (MySQL):
| id | event_start
+----+---------------------
| 1 | 2012-01-08 06:30:00
| 2 | 2012-02-09 06:30:00
| 3 | 2012-03-10 06:30:00
| 4 | 2012-04-11 06:30:00
| 5 | 2012-05-12 06:30:00
| 6 | 2012-06-13 06:30:00
| 7 | 2012-07-14 06:30:00
| 8 | 2012-08-15 06:30:00
| 9 | 2012-09-16 06:30:00
| 10 | 2012-10-17 06:30:00
| 11 | 2012-11-18 06:30:00
| 12 | 2012-12-19 06:30:00
View (Browser) - Here I just used an .order("event_start ASC")
events.each do |ev|
ev.event_start
2012-12-19 07:30:00 +0100
2012-11-18 07:30:00 +0100
2012-10-17 08:30:00 +0200
2012-09-16 08:30:00 +0200
2012-08-15 08:30:00 +0200
2012-07-14 08:30:00 +0200
2012-06-13 08:30:00 +0200
2012-05-12 08:30:00 +0200
2012-14-11 08:30:00 +0200
2012-03-10 07:30:00 +0100
2012-02-09 07:30:00 +0100
2012-01-08 07:30:00 +0100

I'm pretty sure that the difference you're seeing is because of Daylight Saving Time (DST).
Note that all dates during summer are +0200, while all the others are +0100. That's the connection you didn't notice.
This is because Berlin is a timezone with DST. The dates you're importing are all UTC, they get written to the DB as UTC, but Rails (correctly) interprets them differently, depending on whether they're in summer or not.
That's why you don't get a constant 1-hour difference.
If you want the times to be 9:30 local time, no matter if in summer or winter, try using
Time.zone.parse("#{date} 09:30:00")
in your import code.
See ActiveSupport::TimeWithZone for details.

Related

Get End Time by Adding Number of Minutes in Start Time Keeping in mind the working hours

I have database table in which I have daily working hours of office.
What I need is to get End Time by Adding Number of minutes in Start Time keeping in mind the office hours. The working hours are from 09:00 to 17:30
I will be thankful if some one could help me to write query in mysql so that I could calculate end time.
my Sample Table is
+------------------+------------------+
| starttime | endtime |
+------------------+------------------+
| 2017-01-01 00:00 | 2017-01-01 00:00 |
| 2017-01-02 09:00 | 2017-01-02 17:30 |
| 2017-01-03 09:00 | 2017-01-03 17:30 |
| 2017-01-04 09:00 | 2017-01-04 17:30 |
| 2017-01-05 09:00 | 2017-01-05 17:30 |
| 2017-01-06 09:00 | 2017-01-06 17:30 |
| 2017-01-07 09:00 | 2017-01-07 14:30 |
| 2017-01-08 00:00 | 2017-01-08 00:00 |
| 2017-01-09 09:00 | 2017-01-09 17:30 |
+------------------+------------------+
Input time : 2017-01-02 16:52
adding minutes: 300
required time : 2017-01-03 12:22
If I understand clearly, you want:
SELECT CONCAT(FLOOR((540 + t.time)/60),'h ', MOD(540 + t.time, 60),'m') as HOURS
FROM table t;
Using DATE_ADD is what you want
SELECT DATE_ADD(starttime, INTERVAL 300 MINUTE) as endtime;
EDIT:
Okay, I think i understand you know. The code below could probably be condensed, however, I left it verbose for readability.
SELECT CASE WHEN
DATE_ADD(TIMESTAMP(starttime), INTERVAL 300 MINUTE)
BETWEEN TIMESTAMP(DATE(starttime),'09:00')
AND TIMESTAMP(DATE(starttime),'17:30')
THEN
## starttime plus 5 hours, is still in range of current business window
DATE_ADD(starttime, INTERVAL 300 MINUTE)
WHEN TIMESTAMP(starttime) > TIMESTAMP(DATE(starttime),'17:30') THEN
## startime falls after business hours, add it to the following day at 09:00
DATE_ADD(TIMESTAMP(DATE(starttime),'09:00'),
INTERVAL (1440 /*24 hours (i.e. next day)*/ + 300 ) MINUTE
)
ELSE
## if the starttime falls within business hours, but extends into the next business day,
## calculate the difference up to 17:30, add to the following day, after 09:00
DATE_ADD(TIMESTAMP(DATE(starttime),'09:00'),
INTERVAL (
1440 + /*24 hours (i.e. next day)*/
300 - /* standard working day */
ABS(
TIMESTAMPDIFF(MINUTE, TIMESTAMP(DATE(starttime),'17:30'), starttime)
) /* less yesterday's minutes */
) MINUTE
)
END AS endtime
;

Convert MySQL now() based on timezone of rows being searched

Suppose I have data in a table "events" structured like this:
eventid | datetime_start | datetime_end | timezone
001 | 2016-01-01 10:00:00 | 2016-01-01 14:00:00 | America/Los_Angeles
002 | 2016-01-03 19:00:00 | 2016-01-03 22:00:00 | America/Los_Angeles
003 | 2016-01-17 02:00:00 | 2016-01-17 06:00:00 | America/New_York
004 | 2016-01-31 23:00:00 | 2016-02-01 01:00:00 | America/Los_Angeles
The timezone column allows dates/times to be stored exactly as entered rather than normalized to UTC, GMT, etc.
I want to query the table to find eventids where "now()" falls between datetime_start and datetime_end:
SELECT eventid FROM events WHERE now() BETWEEN datetime_start AND datetime_end
However, since "now()" is based on a fixed timezone (UTC in my case), is there any way to convert "now()" to match the timezone column row by row as it searches? Maybe something in the spirit of the following:
SELECT eventid FROM events WHERE CONVERT_TZ(now(),'UTC',timezone) BETWEEN datetime_start
AND datetime_end

Select distinct and get sum of timestamp differences

I don't know if this is possible, but it'd be really awesome. I have a table of sign-ins for people who are logging time on different projects and I need to compile a report of time logged for each project for a given time period.
My table looks something like this:
id | project | time_in | time_out | break
----------------------------------------------------------------
1 | 1 | 2014-12-07 05:00:00 | 2014-12-07 10:00:00 | 30
2 | 2 | 2014-12-07 06:00:00 | 2014-12-07 13:00:00 | 15
3 | 1 | 2014-12-07 14:00:00 | 2014-12-07 18:00:00 | 0
4 | 3 | 2014-12-07 08:30:00 | 2014-12-07 18:45:00 | 75
5 | 2 | 2014-12-07 12:00:00 | 2014-12-07 16:30:00 | 0
What I'd like to be able to do is get a report of the time logged for each project given a date range, i.e. the total time, probably in seconds, logged for each project.
time_in and time_out are fields of type TIMESTAMP; break is an integer representing the number of minutes the person was on break. I need to get the sum of time_out - time_in - break for each project, e.g. for December 7:
project | time
---------------
1 | 34200
2 | 40500
3 | 34200
This is all I have so far:
SELECT DISTINCT
`project`
FROM `sign_ins`
WHERE
`time_in` >= '2014-12-07 00:00:00' AND
`time_out` <= '2014-12-08 00:00:00';
I appreciate your help on this, SO community. You guys are so brilliant.
You can get the difference in seconds by converting the date/time values to Unix time stamps. Then, just aggregate the differences using sum():
SELECT project,
SUM(UNIX_TIMESTAMP(time_out) - UNIX_TIMESTAMP(time_in) - (break * 60)) as DiffSecs
FROM `sign_ins`
WHERE `time_in` >= '2014-12-07 00:00:00' AND
`time_out` <= '2014-12-08 00:00:00'
GROUP BY project;

Group By sets of values

for a report I'm trying to query events from different shifts. The shifts start on 6 am, 2 pm, and 10 pm every day, and all of the data in the table is tagged with a datetime timestamp. Previously the graveyard shift wasn't doing anything important, so a simple group by DATE(stamp) was sufficient, but now it's 24/7 and I need to break it down into shifts.
Can anyone explain to me how to use a single group by clause to combine datetime values from a range or a set of values? The difficulty is that each graveyard shift spans two calendar days.
I've considered populating a table with 24 hours and shift numbers, then outer joining it and group by DATE(stamp), HOUR(stamp), but that seems hackish and possibly not even working, plus it would give 24 values for each day instead of 3, which then have to be combined in a superquery or script.
MySQL-specific is perfectly ok, that's all we ever use in the reporting.
Since they are all 8-hour shifts, offset by 6 hours from starting at midnight, you turn Stamp into the start-of-shift time like this:
select
stamp,
adddate(date(subdate(stamp, interval 6 hour)),
interval ((hour(subdate(stamp, interval 6 hour))
div 8) * 8) + 6 hour) as shift_start
from mytable;
This substracts 6 hours, then rounds the hour down to either 0 1 or 2 by using integer division, then expands it out again.
Here's the test code with some edge cases:
create table mytable (stamp datetime);
insert into mytable values ('2011-08-17 22:00:00'), ('2011-08-17 23:01:00'),
('2011-08-18 00:02:00'), ('2011-08-18 05:59:00'), ('2011-08-18 06:00:00'),
('2011-08-18 13:59:00'), ('2011-08-18 14:00:00'), ('2011-08-18 17:59:00');
Output of above query:
+---------------------+---------------------+
| stamp | shift_start |
+---------------------+---------------------+
| 2011-08-17 22:00:00 | 2011-08-17 22:00:00 |
| 2011-08-17 23:01:00 | 2011-08-17 22:00:00 |
| 2011-08-18 00:02:00 | 2011-08-17 22:00:00 |
| 2011-08-18 05:59:00 | 2011-08-17 22:00:00 |
| 2011-08-18 06:00:00 | 2011-08-18 06:00:00 |
| 2011-08-18 13:59:00 | 2011-08-18 06:00:00 |
| 2011-08-18 14:00:00 | 2011-08-18 14:00:00 |
| 2011-08-18 17:59:00 | 2011-08-18 14:00:00 |
+---------------------+---------------------+
Try this:
GROUP BY DATE(DATE_ADD(Stamp,INTERVAL -6 HOUR))
That should keep all your shifts on the same day
I think you should pursue your "table with hours and shift numbers" approach. Further, I think you should consider using a calendar table i.e. a table not just covering 24 hours but the whole past, present and future of your enterprise's expected needs. This is not hackish: rather, it is a tried and tested approach. The idea is that SQL is a declarative language designed to query data in tables so a declarative, data-driven solutions make a lot of sense.

finding date intervals start and end dates

I have a table like this (plus 10 more columns) containing more than 1 million of frequently updated records:
id pid start_date end_date
1 761 2011-07-25 00:00:00 2011-08-01 00:00:00
2 761 2011-08-01 00:00:00 2011-08-22 00:00:00
3 761 2011-08-22 00:00:00 2011-09-19 00:00:00
4 802 2011-08-22 00:00:00 2011-09-19 00:00:00
5 761 2011-06-05 00:00:00 2011-07-05 00:00:00
and would like to get result for a particular pid (761 in the example below) with all consecutive intervals combined:
id pid start_date end_date
1 761 2011-07-25 00:00:00 2011-09-19 00:00:00
5 761 2011-06-05 00:00:00 2011-07-05 00:00:00
Currently I am doing this in the code, but would like to move this functionality entirely to the db side.
Any ideas how to do this?
edit: start_date and end_date columns are of DATETIME type.
This is really much better done in code. Loop over the rows, when it's for the same product, update the end date, otherwise create a new array entry.
For an idea of how complex this is in SQL, see my attempt at solving this in SQL Server :)