Im currently programming a calendar with php and mysql and im stuck with some functionality while selecting all events i want to display. I implemented the possibilty to repeat an event. Therefore i set a timestamp from which the event starts and a timestamp to determine when the event ends. Furthermore i got some integer values which represent the rythm in which the event is repeated.
Then i fetch the events based on a timestamp which is send with a request.
I now want to enable the user to shift events from the weekend to the Friday before the weekend or the monday after the weekend. For example:
From: 1450306800 (today)
until: 0 (infinite)
rythm : 1 (-> every month)
jump:2 (-> on every 2nd day / month)
weekends : 3 (-> shift to next monday)
-> January 2nd 2016 is a saturday and i want to display that event on the next monday.
currently my select looks something like this: (:day -> timestamp from request, :d -> day of month from :day, :weekday -> day of the week from :day)
SELECT * FROM events
WHERE repeat_from <= :day
AND ((repeat_until >= :day) OR (repeat_until = 0))
AND CASE weekends
WHEN 0 THEN (:weekday BETWEEN 1 AND 7)
WHEN 1 THEN (:weekday < 6)
WHEN 2 THEN ??
WHEN 3 THEN ??
AND CASE rythm
WHEN 1 THEN (:d - DAY(FROM_UNIXTIME(repeat_from))) / (jump + 1) = CEIL ((:d - DAY(FROM_UNIXTIME(repeat_from))) / (jump + 1)) ... [all the other cases]
How do i check if the event would have been displayed on saturday or sunday before/after within the select? The only way i can think of is to more or less repeat the whole select from the "...AND CASE rythm..." part which is quite alot.
Or would the best way be, to fetch the event on every monday/friday anyway if it shifts and then check with a php function if the event would have been displayed on saturday or sunday ?
Why don't you do all the calculations and matching on the PHP back-end? With that much cases and clauses in your MySQL query you'll probably get yourself a really slow execution.
Select all Events by your major criteria. In your case that would probably be:
SELECT * FROM
events
WHERE
repeat_from <= :day AND
(
(repeat_until >= :day) OR
(repeat_until = 0)
)
Then just loop through all fetched rows and apply the built-in date() method - much faster and flexible:
# you can match the week number of different dates
$currentWeekNumber = date('W');
$specificDayWeekNumber = date('W', strtotime($specificDate));
# or match the day number
$currentDayNumber = date('N');
$specificDayNumber = date('N', strtotime($specificDate));
The date() method is super flexible and will allow you to check if certain Event date is within the weekend or not, so you can manipulate it further.
If you provide some further explanations and/or examples, I'll try to be more specific with the solution. Cheers.
Related
I have a query, A user fill records of every day for each month and I want, the user can edit/delete his data till 7th of next month.
I got the diff between dates using date() function, but can't understand how to compare that record fill date is less than 7th of next month.
//$fill_date; get data filled date from database
$filled_date = date("Y-m-d", strtotime($fill_date));
$datestring = $filled_date.' first day of next month';
$dt=date_create($datestring);
$d_mont = $dt->format('Y-m-07'); // give 7th of next month from data insert date
//get next month from fill date
$f_date = date('Y-m-d' , strtotime($fill_date));
if(strtotime($f_date) <= strtotime($d_mont)) {
echo strtotime($f_date)." <= ".strtotime($d_mont);
echo "you can edit";
}
There I share a query which can fetch the data till 7th.May this query help you if found any issue then comment.
this table contains last data till 7th of every month otherwise
no data.
SELECT * from datatable where day(curdate()) <= 7 and cast(datefield as date) >= (curdate() + interval -1 month);
Here is an example code
$curdate = date('Y-m-d', strtotime($fill_date)); // or time() for current date
$month = date('m', strtotime($curdate));
$year = date('Y', strtotime($curdate));
$nextmonth = ($month == 12)? 1: $month+1;
$date2 = date("Y-m-d", mktime(0, 0, 0, $nextmonth, 7, $year));
You can compare the dates with strtotime as you already do
What your asking to do is very confusing, but I understand, because I am cool like that.
The best way to do this to set a flag in the data, have a field in the database that is called locked or such that is a simple boolen value 1=true, 0=false
Then on the 7th of the month at midnight run a cron job that updates all the records before the current month with a 0 and set them to a 1.
It would be pretty trivial to write a cron job to do that maybe 20 lines of code tops.
Brief example (psudo code)
$date = (new DateTime())->modify('first day of this month')->format('Y-m-d');
$Sql = "UPDATE tbl SET locked = 1 WHERE DATE(date_field) < '{$date}' AND locked=0";
$DB->query($Sql);
Cron: if you don't know what it is
https://en.wikipedia.org/wiki/Cron
Use cron to schedule jobs (commands or shell scripts, including PHP )
to run periodically at fixed times, dates, or intervals.
If they can only edit tell the 7th of the next month, then on the 7th of each month all the data from last month is no longer editable.
Then when you pull them to edit, you just do
SELECT * FROM table WHERE locked = 0
That is if you truly what it to stop on the 7th and not a month. AS I said in the comments if I put a record on the 1st that gives me that whole month + 7 days, if I did it on the last day, I would have only 7 days to edit the record.
It's not as trivial to write a date range query for this as it first seems. Because if it's before the 7th, you have to select everything from last month and everything from this month ( tell the current date ). But if it's the 8th, you have to select everything from the beginning of the month tell the current data ( omitting last month ). So the query would change depending on the day it currently is.
To try to filter the data after pulling it out seems like a waste, because you will always pull more records then the user can edit, and then you have to work out the date switch anyway.
An advantage of having a locked field also, it that you can selectively unlock a record for a user so they could edit it again, just by fliping the 1 back to a 0. ( -note- the cron job I outlined above would re-lock it ) The point is it would be possible to allow them to edit specific records without code changes.
IMO, it's the best way to do it.
I have a query which calculates the total hours worked for the current week by task:
SELECT name AS 'Task Name'
, sum(hours) AS `Total Hours`
FROM time_records
WHERE yearweek(record_date, 3) = yearweek(now(), 3)
GROUP BY
weekday(record_date), name
The WHERE clause specifies the current week:
WHERE yearweek(record_date, 3) = yearweek(now(), 3)
This data is displayed as a table using the MySQL query and a PDO statement.
I would now like to add an option for the viewer to see the previous week of data by clicking a link under the table (e.g., "Previous Week"). In order to show that data, I would need to update the WHERE clause to query for the previous week:
WHERE yearweek(record_date, 3) = yearweek(now(), 3) - 1
I'm wondering what the best way to do this is? I would prefer to keep just one version of the overall query. One thought is to use PHP to add the "- 1" to the query, such as:
WHERE yearweek(record_date, 3) = yearweek(now(), 3) <?php if (prev) {echo "- 1" ;} ?>
I think this should work, but how can I determine the prev condition? Should I make the entire query be inside a function so that for the default it can be "Time_hours(0)" and for the previous it can be "Time_hours(1)" - or is there some better way?
I'm concerned about keeping the code manageable and following best practices. Any tips would be appreciated. Thanks!
I suggest you try a query that has a parameter for the number of weeks ago you want to process. YEARWEEK is a bad choice for weekly processing because it handles end-of-year rollovers poorly.
Try this, if your weeks begin on Sunday. This will give the current week.
select r.record_id, r.record_date, w.week weekstart
from record r
join (
select
(FROM_DAYS(TO_DAYS(CURDATE()) -MOD(TO_DAYS(CURDATE()) -1, 7))
- interval 0 week) week
)w
where record_date >= w.week
and record_date < w.week + interval 1 week
Fiddle: http://sqlfiddle.com/#!2/4c487/1/0
If you want, let's say, one week ago, change - interval 0 week to - interval 1 week in your query. See this fiddle. http://sqlfiddle.com/#!2/4c487/2/0
You can put an an arbitrary number of weeks ago, even weeks that span year ends. See here for interval -42 week for example. http://sqlfiddle.com/#!2/4c487/3/0
The expression FROM_DAYS(TO_DAYS(CURDATE()) -MOD(TO_DAYS(CURDATE()) -1, 7)) rounds the current date down to the previous Sunday. If you want to round it to the previous Monday, use FROM_DAYS(TO_DAYS(CURDATE()) -MOD(TO_DAYS(CURDATE()) -2, 7)). For example: http://sqlfiddle.com/#!2/4c487/8/0
I'm trying to figure out how to repeat certain tasks on specific days at certain intervals based on an existing date.
For example, I have this:
Date Task
2011-01-12 MJK-0083
I want to:
Determine the day of the week based on the date provided (in this case, Wednesday)
Determine which Wednesday of the month it is (in this case, the 2nd Wednesday)
Calculate the date of the 2nd Wednesday in June as the next date this task will occur
I've been looking at some examples, but while I can do bits of it, I can't seem to figure out how to do all of it. For example, I'm using this for step 1:
date('l', strtotime('2011-01-12'))
But I am lost when it comes to steps 2 and 3... Can someone help me out?
To find out which Wednesday of the month it is, you can do this using the day of the month
(int)(day_of_month / 7) + (day_of_month % 7 == 0 ? 0 : 1)
Thus, for 2012-01-12 you'll get:
(int)(12 / 7) + (12 % 7 == 0 ? 0 : 1) = 1 + 1 = 2 -> second Wednesday
I am currently trying to write a little program to track time-off requests for employees. I'm fairly new to MYSQL and PHP, so it's a learning project for me as well. I've run into this problem which I do not seem to be able to figure out.
I want to display time off requests for a given week (Mon-Fri). I've got the requests in a table 'requests', with a 'Starttime' and 'Endtime' in separate fields, both Date/Time.
I can currently easily search and retrieve requests that have either (or both) Starttime or Endtime values that fall within the given ISO week I am looking at (WEEKOFYEAR() ).
What I need to be able to do is search for requests that may include days in the ISO week I am displaying, but not have a Starttime or Endtime during that week.
Example:
Employee takes off Tuesday of Week 24 through Friday of Week 24.
Currently, I would correctly display that the employee was off starting Tuesday and show a return on that Friday, but on Wed and Thursday nothing would be entered.
Employee takes off Friday of Week 30 through Monday of Week 32.
Currently, I would show that employee as not being 'off' during Week 31 because the search would not show a Starttime or Endtime during that week even though they are actually off the entire week. Though the Starttime and Endtime would be noted on the correct days.
Right now, what I do to work around this is run 5 additional queries to check if the date for each day Mon-Fri during Week 31 is contained BETWEEN the Starttime and Endtime of each request in the db.
I hate to run a total of 6 queries to get this information. Is there an easier way to get that information?
I just wrote a calendar app for events and ran into this- how bout something like this:
SELECT * FROM Requests WHERE
Start_Date BETWEEN <first_day_of_week> AND <last_day_of_week>
OR
End_Date BETWEEN <first_day_of_week> AND <last_day_of_week>
OR
<day_of_week_monday> BETWEEN Start_Date AND End_Date
OR
<day_of_week_tuesday> BETWEEN Start_Date AND End_Date
OR
<day_of_week_wedenesday> BETWEEN Start_Date AND End_Date
OR
<day_of_week_thursday> BETWEEN Start_Date AND End_Date
OR
<day_of_week_friday> BETWEEN Start_Date AND End_Date
GROUP BY ID ORDER BY Start_Date ASC, Date ASC
While < value_names > are generated with php via the currently viewed week requested. Should cover your bases.
What format are these dates stored in the DB? Assuming they are using MySQL's DATE format, its pretty easy to do. You can just use comparison operators on the fields and MySQL will do the work for you in one query.
$sql = "SELECT * FROM table WHERE startdate <= $someday AND $someday <= enddate";
Sticking to your week of year way of doing it, you can run a check to see if the current week falls within the range of the starting week off and the ending week off.
SELECT * FROM table WHERE WEEKOFYEAR(Starttime) <= N AND WEEKOFYEAR(Endtime) >= N;
(where N is the week you're displaying)
What you'd want to do, once you get the rows, is parse each day in PHP to see if that day falls between the starttime and endtime.
A good method for that is using:
$start_timestamp = strtotime($row['Starttime']);
$end_timestamp = strtotime($row['Endtime']);
You can use a similar method to get a timestamp of the day you are displaying, and see if it falls between $start_timestamp and $end_timestamp to determine if that day is off.
since you mentioned PHP as the display:
$start=30; //Friday of Week 30
$end=32; //Monday of Week 32
foreach (range($start, $end) as $number) {
echo $number;
}
You would expect to get 30,31,32.
You will have to verify that $start is < $end though as well for December to January weeks.
The range for WEEKOFYEAR() is 1-53. In this case, add 53 to the $end and display mod of $end if greater than 53:
$start=52; //Friday of Week 52, 2009
$end=2; //Monday of Week 2, 2010
If($start>$end) $end+=53;
foreach (range($start, $end) as $number) {
if($number>53){
echo $number%53;}
else{
echo $number;
}
}
You would expect to get 52,53,1,2
Probably need the query to read either like the big one with all the ORs or something like this
WHERE
Starttime <= <value for last day/time of the week>
AND
Endtime >= <value for the first day/time of the week>
That should get all the dates you need that could fall in that week, then you parse the records you get with PHP to find the ones for that actual week... though there may be an easier way to do that.
I need to calculate a difference between a starting date/time and an ending date/time. But, I only want to do this for the 5-day work week (exclude Sat/Sun as days). What is the best way to do this? My thought is that from the date, I'll have to get the day of the week and if it is a weekday, then I add to the accumulator. If it's not, then I don't add anything.
I'm sure someone has done this before, but I couldn't seem to find anything searching. Any links or other help would be very useful.
Many thanks,
Bruce
DAYOFWEEK returns 1 for Sunday and 7 for Saturday. I'm not sure how your schema is set up, but this will perform a TIMEDIFF of two dates that are on a Monday - Friday work week.
select TIMEDIFF(date1,date2) from table
where DAYOFWEEK(date1) not in (1,7) and DAYOFWEEK(date2) not in (1,7)
MySQL DATE/TIME functions
EDIT: From Bruce's comment about holidays. If you have a table full of holiday dates, something like this would work to exclude processing those days:
select TIMEDIFF(date1,date2) from table
where date1 not in (select holiday from holiday_table) and
date2 not in (select holiday from holiday_table) and
DAYOFWEEK(date1) not in (1,7) and DAYOFWEEK(date2) not in (1,7)
NETWORKDAYS() "Returns the number of whole working days between start_date and end_date. Working days exclude weekends and any dates identified in holidays. Use NETWORKDAYS to calculate employee benefits that accrue based on the number of days worked during a specific term." according to the Excel 2007 help file.
The "between" description is a bit inaccurate because it includes the start and end dates, i.e. networkdays(21-01-2010. 22-01-2010) = 2. It also takes no account of times.
Here's a function in PHP that will give the same results. It doesn't work properly if the end date is less than the start date, nor does do anything about holidays (see below the function).
function networkdays($startdate, $enddate)
{
$start_array = getdate(strtotime($startdate));
$end_array = getdate(strtotime($enddate));
// Make appropriate Sundays
$start_sunday = mktime(0, 0, 0, $start_array[mon], $start_array[mday]+(7-$start_array[wday]),$start_array[year]);
$end_sunday = mktime(0, 0, 0, $end_array[mon], $end_array[mday]- $end_array[wday],$end_array[year]);
// Calculate days in the whole weeks
$week_diff = $end_sunday - $start_sunday;
$number_of_weeks = round($week_diff /604800); // 60 seconds * 60 minutes * 24 hours * 7 days = 1 week in seconds
$days_in_whole_weeks = $number_of_weeks * 5;
//Calculate extra days at start and end
//[wday] is 0 (Sunday) to 7 (Saturday)
$days_at_start = 6 - $start_array[wday];
$days_at_end = $end_array[wday];
$total_days = $days_in_whole_weeks + $days_at_start + $days_at_end;
return $total_days;
}
To take holidays into account, you'd have to work out the number of days using this function, then use a query like
Select count (holiday_date) from holidays
where holiday_date between start_date and end_date
and DAYOFWEEK(holiday_date) not in (1,7)
Be careful that there isn't a problem with the end_date being treated as 00:00 (i.e. first thing in the morning) - you may have to condition it to be 23:59:59 so that it works properly. It all depends on how your holidays are stored.
To return the holidays in the same time period and subtract that from the number you first thought of.