PHP / MySQL select by DATETIME field across DST change - php

Please forgive me if this unclear, but I am having a tough time trying to get what is in my mind down on paper.
Scenario:
We store a variety of jobs in a table. These are all timestamped using DATETIME set to UTC time zone.
Our users may have their timezones set in their preferences, so that we can transcribe system times to the users local times.
Now, assume that a user goes to select all jobs that were entered on October 25, 2012 (local user time):
+--------------------------------------+------------+---------+---------------------+
| id | project_id | amount | created |
+--------------------------------------+------------+---------+---------------------+
| 50889ba5-77b4-41e1-a942-1dea0ab761f6 | 15076850 | 50.00 | 2012-10-25 01:53:41 |
| 5088b9a3-8110-446e-81c8-75da341f3f95 | 15076850 | 2000.00 | 2012-10-25 04:01:39 |
| 5088c852-d434-41e6-ba5d-27560ab761f6 | 15076850 | 100.00 | 2012-10-25 05:04:18 |
| 50892a3b-ad9c-4a32-aebf-384c0ab761f6 | 15076850 | 500.00 | 2012-10-25 12:02:03 |
| 50893098-6b9c-4028-9a87-3eb20ab761f6 | 15076850 | 25.00 | 2012-10-25 12:29:12 |
| 50894b10-d260-4f61-8eb9-1d190ab761f6 | 15076850 | 25.00 | 2012-10-25 14:22:08 |
| 50895129-48c8-4bb4-928f-483b341f3f95 | 15076850 | 25.00 | 2012-10-25 14:48:09 |
| 50896019-7144-4e74-8037-4160341f3f95 | 15076850 | 50.00 | 2012-10-25 15:51:53 |
+--------------------------------------+------------+---------+---------------------+
8 rows in set (0.00 sec)
If this user is in the Eastern United States (EST), and the times in this table are UTC, the results are going to come across as October 24th for the first two rows in the table results. Basically, I want to exclude those first two rows if the user is EST.
I have been experimenting with the use of DateTime(), however - I am stuck because this spans DST end (November 4, 2012). When I select the date on October 25th, DST is active, so it shows the users local time as UTC -0400. When the job ends, it is after (UTC -0500).
I am completely stuck on how to make this work.

Get the UTC timestamp for what is the user's midnight (which is 4am UTC in the below example):
$start = new DateTime('2012-10-25 00:00:00', new DateTimeZone('America/New_York'));
$start->setTimeZone(new DateTimeZone('UTC'));
$start = $start->format('Y-m-d H:i:s');
// do the same with $end
SELECT ... WHERE `created` BETWEEN $start AND $end
The final query should select WHERE BETWEEN 2012-10-25 04:00:00 AND 2012-10-26 03:59:59, which is the day of October 25th in America/New York.
This should also work during DST changes. On November 4th, the query would be BETWEEN 2012-11-04 04:00:00 AND 2012-11-05 04:59:59, encompassing one more hour.

Related

MYSQL How to query open time slots between booked events for a set date and time?

I am attempting to create a query to list open time slots for a specific day. I have found many similar questions on here, but none of the answers seem to do the job.
The query will need to only show results between a certain time period. For instance, if I wanted to search open time slots for the a single day I would set a start date of 2017-03-17 08:00:00 (8:00am being opening time) and an end date of 2017-03-17 17:00:00 (5:00pm being the closing time for the day). It would then have to query the day and return open slots and booked appointments from my appointments table. (Note: I do not save open slots in the table or a separate table, these will need to be generated by the query).
A simplified version of my booked appointments table:
Table name: booked_appointments (before output)
+----------------------+---------------------+----------+
| start_time | end_time | duration |
+----------------------+---------------------+----------+
| 2017-03-17 10:45:00 | 2017-03-17 11:30:00 | 45 |
| 2017-03-22 09:30:00 | 2017-03-22 10:30:00 | 60 |
| 2017-03-24 08:45:00 | 2017-03-24 11:30:00 | 165 |
| 2017-03-22 11:30:00 | 2017-03-22 12:30:00 | 60 |
+----------------------+---------------------+----------+
My current MYSQL query: (I pulled this from another answer and altered for my use)
SET #timeMinimum='2017-03-17 08:00:00';
SET #timeMaximum='2017-03-17 17:00:00';
SELECT IF (d.name = 'Free', #timeMinimum, b.start_time) AS free_from,
IF (d.name = 'Free', b.start_time, #timeMinimum := b.start_time + INTERVAL b.duration MINUTE) AS free_until,
d.name AS Free
FROM (SELECT 1 AS place, 'Free' AS NAME UNION SELECT 2 AS place, 'Booked' AS NAME ) AS d
INNER JOIN booked_appointments b
HAVING free_from < free_until
UNION SELECT #timeMinimum AS free_from, #timeMaximum AS free_until, 'Free' AS Free
FROM (SELECT 1) AS d
WHERE DATE(#timeMinimum) < DATE(#timeMaximum)
ORDER BY free_from, free_until;
The query does almost everything correctly except it ignores the timeMinimum and timeMaximum I have set and it instead returns results for the entire table that ranges from 2017-03-17 through 2017-03-24. This might not be an issue once I get query working for the day but it also is ignoring the last test date that I purposefully put out of order in the table to test a real world example of data entered.
The following is output from above query:
+---------------------+---------------------+--------+
| free_from | free_until | Free |
+---------------------+---------------------+--------+
| 2017-03-17 08:00:00 | 2017-03-17 10:45:00 | Free |
| 2017-03-17 10:45:00 | 2017-03-17 11:30:00 | Booked |
| 2017-03-17 11:30:00 | 2017-03-22 09:30:00 | Free |
| 2017-03-22 09:30:00 | 2017-03-22 10:30:00 | Booked |
| 2017-03-22 10:30:00 | 2017-03-24 08:45:00 | Free |
| 2017-03-22 11:30:00 | 2017-03-22 12:30:00 | Booked |
| 2017-03-24 08:45:00 | 2017-03-24 11:30:00 | Booked |
+---------------------+---------------------+--------+
Expected output is:
+---------------------+---------------------+--------+
| free_from | free_until | Free |
+---------------------+---------------------+--------+
| 2017-03-17 08:00:00 | 2017-03-17 10:45:00 | Free |
| 2017-03-17 10:45:00 | 2017-03-17 11:30:00 | Booked |
| 2017-03-17 11:30:00 | 2017-03-17 17:00:00 | Free |
+---------------------+---------------------+--------+

Best way to handle business opening time in a year PHP and MySQL

We have developed a website for pharmacies in which we show the opened pharmacies in a specific city.
I handle this with a field named "timestamps" in wich it is stored all timestamps of opening hour every 15 minutes for a period of about 3 months.
For example if every days the farmacy is open from 8:00 to 19:00 there is a range of timestamps from one time to another, with an interval of 15 minutes.
While in the frontend we have a list of opened pharmacies and I could show opened ones by querying the database with for example:
"WHERE timestamps LIKE('%1449820800%')" where the timestamp is the current time rounded to the nearest quarter hour.
The question is: considering the time ranges are different from week to week, is there a better way to handle this situation? Also because we have 25.000 users and the website is slow if we have large amount of timestamps.
Thank you in advance!
You could just have a database with each day of openning for every store :
-----------------------------------------
| StoreId | Day | HourOpen | HourClose |
=========================================
| 1 | 1 | 8:30 | 21:15 |
-----------------------------------------
| 1 | 2 | 9:00 | 17:00 |
-----------------------------------------
| 2 | 1 | 10:00 | 12:30 |
-----------------------------------------
| 2 | 1 | 14:00 | 19:00 |
=========================================
In this table, the day represent the day of the week (1 for monday, 2 for tuesday for example) and then you just have to parameter the openniong hours for each store only once.
You can then query this table to see if a store is open for a day of the week at the very moment.
If a pharmacy has an exceptionnal closure or openning hours for a day, i suggest an ovveride table like this one
----------------------------------------------------------
| StoreId | Date | isOpen | HourOpen | HourClose |
==========================================================
| 1 | 2015-12-20 | true | 10:00 | 16:00 |
----------------------------------------------------------
| 2 | 2015-12-20 | false | null | null |
==========================================================
This way, you can check first if an pharmacy has any record in this table for the current day (not depending of the time) if it does, you check if there is an opening. If there is not any entry in the override table, you check with the first table.
You also can ahve a hour model table with opening and closing time, a day table, and an associative table that creates relations between stores, hours and days.

MySQL searching through time interval

The system is as such. Tutors provide their availability (Monday - Sunday) and the time frame they are available on that day (0700 - 1400) (ie: 7am - 2pm).
I am trying to figure out the best way to store and search through this information to find available tutors. Searching only needs to be done on a daily system (ie: day of the week - mon, tues, wed, etc).
My planned infrastructure:
//Tutor Availability
---------------------------------------------------------------------------
tutorID | monday | tuesday | wednesday | thursday | friday |
---------------------------------------------------------------------------
27 | 0700-1200 | NULL | 1400-1800 | NULL | NULL |
---------------------------------------------------------------------------
35 | NULL | 1400-1600 | NULL | NULL | 1100-1900 |
//Scheduled tutor sessions
------------------------------------
tutorID | day | time |
------------------------------------
27 | monday | 0700-0900 |
------------------------------------
35 | friday | 1300-1500 |
Query: SELECT tutorid FROM tutoravailability WHERE 'monday'=... is available between 0900-1100 and is not in scheduled tutor session.
I have been searching forever about how I can search through (and store) these time intervals in MySQL. Is this the best way to store the time intervals of a 24 hours day? Will it even be possible to search between these intervals? Am I approaching this from the wrong way? Any insight is appreciated.
Updated Infrastructure
//Tutor Availability
-----------------------------------------------------
tutorID | day | start_time | end_time | PK |
-----------------------------------------------------
27 | mon | 0700 | 1200 | 1 |
-----------------------------------------------------
27 | fri | 1400 | 1800 | 2 |
-----------------------------------------------------
35 | tue | 1100 | 1600 | 3 |
//Scheduled tutor sessions
--------------------------------------------------------
tutorID | day | start_time | end_time | PK |
--------------------------------------------------------
27 | mon | 0800 | 1000 | 1 |
--------------------------------------------------------
27 | fri | 1600 | 1800 | 2 |
So with this system it will be much simpler to search for available times. However I am still at a loss as to how to compare the availability against the scheduled lessons to ensure no overlap.
SELECT tutorID
FROM tutoravailability WHERE day = 'fri'
AND start_time <= '1400'
AND end_time >= '1530'
Now I don't understand how I would compare this query against the Scheduled tutor sessions table to avoid duplicate bookings.
Final Update
To ensure their are no overlapping of the Scheduled Tutors sessions I will use the MySQL BETWEEN clause to search for the start and end time.
If you store the time interval using two columns it will be much easier for you to perform a search using sql query.
i.e. tutorID, day, startTime, endTime
You can use a bit flag to indicate the availability (24 bit) and scheduled time (24 bit). Then you can use 24 bit to represent the available hours and scheduled hours for each day.
In the Tutor Availability table, let's say '1' stands for Available in and '0' stands for unavailable. In the Scheduled table, '0' stands for Scheduled, '1' stands for Unscheduled.
So the available interval 0900-1100 can be stored as POW(2,9) | POW(2,10) | POW(2,11); the scheduled 1000-1200 can be stored as ^(POW(2,10) | POW(2,12))
Then the following query can give your the availability of on tutor - available on Monday between 09 am to 11 am:
SELECT ta.tutorid FROM tutoravailability ta, tutorscheduled ts
WHERE ta.tutorid = ts.tutorid AND ts.day = 'monday'
AND (ta.monday & ts.time & (POW(2,9) | POW(2,10) | POW(2,11))) = (POW(2,9) | POW(2,10) | POW(2,11))

PHP/MySQL: Model repeating events in a database but query for date ranges

I'm working on a (what I was intending to be) simple PHP/MySQL app. As part of it I'd like to be able to model repeating events, however I need to be able to query all the events that happened between two dates (including repeated events). The events only have a date, the time of day doesn't matter.
I've been researching this and have looked in to various approaches including Calendar Recurring/Repeating Events - Best Storage Method and Repeating calendar events and some final maths.
However, any example of a database schema supporting this that I find online, only seems to support querying for events that happened on a certain day. There is no support for events that happened between a range of dates.
As an abstract example
Events table (with some sort of repeat representation):
Event | Start Date | Repeats
-------------------------------------
Meeting | 10/Dec/2012 | Every 7 days
Lunch | 10/Dec/2012 | Every 1 days
Target result of abstract query SELECT Events BETWEEN 09/Dec/2012 AND 20/Dec/2012
Event | Date | Repeats
-------------------------------------
Meeting | 10/Dec/2012 | Every 7 days
Meeting | 17/Dec/2012 | Every 7 days
Lunch | 10/Dec/2012 | Every 1 days
Lunch | 11/Dec/2012 | Every 1 days
Lunch | 12/Dec/2012 | Every 1 days
Lunch | 13/Dec/2012 | Every 1 days
etc...
Lunch | 20/Dec/2012 | Every 1 days
Is there a database schema that will support these kind of queries? How would I go around making a query on that schema for any event (including repeating events) that happened between two days?
Or perhaps a design pattern that is used for repeating events?
I would create a tally table with just one col called id and fill that table with numbers from 0 to 500. Now we easily use that to make selections instead of using a while loop.
Id
-------------------------------------
0
1
2
etc...
Then i'd store the events in a table with Name as varchar, startdate as datetime and repeats as int
Name | StartDate | Repeats
-------------------------------------
Meeting | 2012-12-10 00:00:00 | 7
Lunch | 2012-12-10 00:00:00 | 1
Now we can use the tally table to select all dates between two dates by using:
SELECT DATE_ADD('2012-12-09 00:00:00',INTERVAL Id DAY) as showdate
FROM `tally`
WHERE (DATE_ADD('2012-12-09 00:00:00',INTERVAL Id DAY)<='2012-12-20 00:00:00')
ORDER BY Id ASC
ShowDate
-------------------------------------
2012-12-09 00:00:00
2012-12-10 00:00:00
2012-12-11 00:00:00
2012-12-12 00:00:00
2012-12-13 00:00:00
2012-12-14 00:00:00
2012-12-15 00:00:00
2012-12-16 00:00:00
2012-12-17 00:00:00
2012-12-18 00:00:00
2012-12-19 00:00:00
2012-12-20 00:00:00
Then we join this on the events table to calculate the difference between the startdate and the showdate. We devided the results of this by the repeats column and if the remainder is 0, we have match.
All combined becomes:
SELECT E.Id, E.Name, E.StartDate, E.Repeats, A.ShowDate, DATEDIFF(E.StartDate, A.ShowDate) AS diff
FROM events AS E, (
SELECT DATE_ADD('2012-12-09 00:00:00',INTERVAL Id DAY) as showdate
FROM `tally`
WHERE (DATE_ADD('2012-12-09 00:00:00',INTERVAL Id DAY)<='2012-12-20 00:00:00')
ORDER BY Id ASC
) a
WHERE MOD(DATEDIFF(E.StartDate, A.ShowDate), E.Repeats)=0
AND A.ShowDate>=E.StartDate
Which results in
Id | Name |StartDate | Repeats | ShowDate | diff
---------------------------------------------------------------------------------
1 | Meeting | 2012-12-10 00:00:00 | 7 | 2012-12-10 00:00:00 | 0
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-10 00:00:00 | 0
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-11 00:00:00 | -1
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-12 00:00:00 | -2
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-13 00:00:00 | -3
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-14 00:00:00 | -4
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-15 00:00:00 | -5
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-16 00:00:00 | -6
1 | Meeting | 2012-12-10 00:00:00 | 7 | 2012-12-17 00:00:00 | -7
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-17 00:00:00 | -7
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-18 00:00:00 | -8
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-19 00:00:00 | -9
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-20 00:00:00 | -10
Now you could (and should!) speed things up. For instance by directly storing dates in a table so you can just select all dates directly instead of using a tally table with dateadd. Every thing you can cache and dont have to calculate again is good.
I didn't quite understand your goal... Maybe you were looking for UNIQUE function in SQL?

Approach to sending alerts with CakePHP

This is a rather obscure question. It's more about approach than syntax.
I have a MySQL table filled with 'notifications' (id,user_id,date,etc). I have to send an alert (via email, facebook, twitter, whatever... not the issue) when each of those entries pings as 'true'. Here's the thing, how should I go about pinging them as true in the most efficient way possible when the conditions that determine true/false have to be calculated?
Sending an email of a bday is easy. Just search a date field for today's date. Suppose you have to send en email every 20th day starting from a date entered in the field? I have to calculate each row to see if it's true today.
How should I do that? I've considered the following: 1. a complex MySQL query 2. a PHP page cron job run through each row and mark them as done 1 by 1 every x seconds/min 3. pulling my hair out and running out of the room screaming like a little girl. I'm leaning toward 3 at the moment.
My main concern is that I'm on a shared server and I don't want to do anything too intensive. Thanks for spending your brain on this. I appreciate it.
You should have a look at the strtotime() examples and see if it can accomodate the types of alerts you are sending. This could allow you to represent things like annual reminders (birthdays), alerts every 20 days, monthly alerts (first Monday/last Friday of each month) in a table like so:
| id | user_id | status | send_on | next_occurrence |
|------|---------|---------|---------------------|--------------------|
| 1001 | 123 | pending | 2010-03-04 12:00:00 | Next March 4 noon |
| 1002 | 123 | pending | 2010-02-05 00:00:00 | +20 days midnight |
| 1003 | 123 | pending | 2010-02-01 08:00:00 | First Monday 8am |
You then set up a CRON job (or poor man's CRON on a shared host) that fires every ten minutes or so with some fairly simple code:
# get pending alerts
$alerts = $this->Alert->find('all', array(
'conditions' => array(
'send_on <=' => date('Y-m-d H:i:s'),
'status' => 'pending',
),
));
# send alerts
foreach ($alerts as $alert) {
# send alert and update status
$status = $this->Something->send($alert);
$status = ($status) ? 'sent' : 'failed';
$this->Alert->id = $alert['Alert']['id'];
$this->Alert->saveField('status', $status);
# generate and save next pending occurrence
$this->Alert->create();
$this->Alert->save(array('Alert' => array(
'user_id' => $alert['Alert']['user_id'],
'status' => 'pending',
'send_on' => strtotime($alert['Alert']['next_occurrence']),
'next_occurrence' => $alert['Alert']['next_occurrence'],
)));
}
Fast forward to March 5th this year and that same table now looks like this:
| id | user_id | status | send_on | next_occurrence |
|------|---------|---------|---------------------|--------------------|
| 1001 | 123 | sent | 2010-03-04 12:00:00 | Next March 4 noon |
| 1002 | 123 | sent | 2010-02-05 00:00:00 | +20 days midnight |
| 1003 | 123 | sent | 2010-02-01 08:00:00 | First Monday 8am |
| 1004 | 123 | sent | 2010-03-01 08:00:00 | First Monday 8am |
| 1005 | 123 | sent | 2010-02-25 00:00:00 | +20 days midnight |
| 1006 | 123 | pending | 2010-03-17 00:00:00 | +20 days midnight |
| 1007 | 123 | pending | 2010-04-05 08:00:00 | First Monday 8am |
| 1008 | 123 | pending | 2011-03-04 12:00:00 | Next March 4 noon |
Below is somewhat simplified explanation of how I approached a similar situation where I needed to periodically send sms and emails based on varying complex conditions:
I created 2 new tables:
scenarios (id; name; frequency)
processes (id; scenario_id; process_type; execution_type; process)
with:
scenario.frequency: hourly, daily, weekly or monthly
processes.process_type: filter or action
processes.execution_type: sql or function
Then I set up a cron according to these frequencies to go trough the scenarios table and take the scenarios of the appropriate frequency and collect the associated filter (which can be an sql statement or a php function). If the filter returns any results then perform the associated actions with the results. I also extended this system to perform setup, test and teardown so I could safely test my scenarios before activating them.
Hope this helps - Cheers
You might want to look into a queue service ala beanstalk etc.
I know with some of them you can post actions / events into your queue and set them to be periodic, conditional etc.
Queue servers / services is a big topic, but maybe just having thrown this out there will give you some more options and some alternative trains of thought.

Categories