I have a db-table that holds holidays i.e. when Cristmas, Easter different fasts UN-days and also celebrations from the Hijri (Muslim), Hebrew (Jewish) and Chineese calendars.
All holidays are saved on the date(s) they occur in their own calendar. But not all days are celebrated on a particular date. An example is the four Advent sundays which for the fourth one is between the dates of 18:th and 24:th of december and on a sunday.
There are also other examles as in the Netherlands where some days of celebration are moved when on a Sunday.
When it comes to fasts they can hav varied length and at least in the Swedish protestant church there are also Sunday masses wich are excluded if a more important one occurs.
So my table is set up as follows:
The PHP for the actual SQL, note that it itterates over all calendars.
foreach ($kal as $kalender => $arr_date) {
$go_Y = (int)$arr_date['go']['Y'];
$go_m = (int)$arr_date['go']['m'];
$go_d = (int)$arr_date['go']['d'];
$end_Y = (int)$arr_date['end']['Y'];
$end_m = (int)$arr_date['end']['m'];
$end_d = (int)$arr_date['end']['d'];
$sql = "SELECT
id,
name,
IF (picture_link = '', type_image, picture_link) AS logo,
start_year,
start_month,
start_day,
end_year,
end_month,
end_day,
year_interval,
start_weekday,
shift,
period,
ends_with_holiday,
has_php,
is_type,
in_calendar,
begins_year,
first_year,
bank_holiday,
has_note,
source_link,
COUNT(flagday_id) AS flagdays,
flagday_for_contry, /* if for only one contry, othervise an extra SQL is needed */
flagday_type,
flagday_information,
flagday_source
FROM holiday
LEFT JOIN type ON (type.id = holiday.is_type)
LEFT JOIN flagday ON (holiday.id = flagday_for_holiday)
WHERE is_calendar = $kalender
AND (start_year IS NULL OR start_year <= $end_Y)
AND (end_year IS NULL OR end_year >= $go_Y)
AND IF($end_m < $go_m OR ($end_m = $go_m AND $end_d < $go_d), /* If over new years */
(start_month < $end_m OR start_month = $end_m AND start_day <= $end_d)
OR (end_month > $go_m OR end_month = $go_m AND end_day >= $go_d)
, /* ELSE, if not over new years */
(start_month < $end_m OR start_month = $end_m AND start_day <= $end_d)
AND (end_month > $go_m OR end_month = $go_m AND end_day >= $go_d)
)
GROUP BY id
ORDER BY start_weekday IS NULL DESC,
start_weekday DESC";
$result = mysqli_query($con, $sql) or die(mysqli_error($con));
while ($result && mysqli_num_rows($result) && $row = mysqli_fetch_array($result)) {
if($kalender == 8) { // om den cyclic jewish calendar, one more argument is needed
$out_arr = get_holiday ($ut_arr, $row, $kalender, $go_Y, $go_m, $go_d, $end_Y, $end_m, $end_d, $con, $country, array($kal[4]['go']['Y']));
} else {
$out_arr = get_holiday ($ut_arr, $row, $kalender, $go_Y, $go_m, $go_d, $end_Y, $end_m, $end_d, $con, $country);
}
}
The get_holday() function does the PHP part if 'has_php' = 1 and also adds roman numerals to the end if a holiday is more than one day. But in essence it just puts the row in an multidimensional array: The first part is the gregorian date when it occurs (there are two helperfunctions to convert dates), the second is just an irreation on that Gregorian date and the third is the different columns from the db.
$out_arr['Y-m-d'][$i][$column] = $column_value;
So now the question
This as you can see is cuite time consuming an for now it's only been for personal use so it hasn't been a real problem. If you want an examle (in Swedish) the top left part with the picture icons on my links page
I was instead thinking of outputting all this into an other db table that just holds the id and (Gregorian) date (for the next 10 years) and each year or when a new holiday is added just update the new table. Is this a good idea or does it just create the same data in multiple places and a risk that they don't match in the end?
Use foreign key constraints to make sure your database stays consistent:
Have one table with all days in it,
one for the various kinds of holiday days,
one for the various countries' calendars,
and one mapping table that says which days are what kind of day in what regional calendar.
And then pre-calculate everything. This method is flexible and allows for unlimited calendars/countries/holidays, and you can expand with more metadata about each kind of holiday and so on either by adding it into separate tables, or adjusting only the affected table. This is easier to maintain.
Related
I'm struggling with this question for so long.
There are n persons who want same room in a hotel. Each person wants to stay in the hotel for his own convenient time but only one person can stay at a time. Assume that room is available from 5AM to 11PM. Hotel manager takes 500 rupees from each person who is staying in that room. It does not matter how long a person stays in that room. We have to maximize the profit of the manager. Let us say that n =4 i.e. four persons want same room. Let us say that 1st person wants the room from 6AM to 8AM and 2nd person wants room from 7AM to 8AM, 3rd person wants room from 8AM to 12PM and 4th person wants room from 11AM to 1PM.
By observing above figure, we can easily see that manager can only allow maximum of two persons to stay (1st and 3rd or 1st and 4th or 2nd and 3rd or 2nd and 4th). So maximum profit he can get is 500+500 = 1000 rupees. So we have to implement an algorithm which can find maximum profit value. Assume that the persons want the room only b/w 5AM to 11PM and each person wants room in multiple of hours.
Input description:
{<1st person starting time>#<1st person ending time>,<2nd person starting time>#<2nd person ending time>,…………, # }
Output description:
Output should be maximum profit value.
For the example considered in the question, output is 2000.
Example:
Input: {6AM#8AM,11AM#1PM,7AM#3PM,7AM#10AM,10AM#12PM,2PM#4PM,1PM#4PM,8AM#9AM}
Output:
2000
This is Interval Scheduling Problem.
It can be solved by sorting the intervals by end time, and choosing greedily the earliest deadline first, remove all overlapping intervals and repeat.
Looks like a variant of the Activity Selection Problem.
Read this TopCoder Tutorial for an excellent explanation.
Below is the exact solution for your question:
<?php
// Timezone set
date_default_timezone_set("Asia/Kolkata");
// User - i/p
$input = "{6AM#8AM,11AM#1PM,7AM#3PM,7AM#10AM,10AM#12PM,2PM#4PM,1PM#4PM,8AM#9AM}";
// Processing i/p string to ARRAY
$input_array = []; // Array given as i/p to "calculateprofit"-function
$processed_array = (explode(',', substr($input, 1, -1)));
foreach ($processed_array as $key => $value)
$input_array[] = explode("#", $value);
// Function call and display o/p
$maximim_profit = calculateprofit($input_array);
echo "<strong>Input</strong> = ".$input;
echo "<br/><br/><strong>Maximum Profit</strong> = ".$maximim_profit;
// Function to calculate - Maximum Profit
function calculateprofit($input){
$room_charges = 500;
$members_covered = [$input[0]];
$last_member = 0;
$finishing_time = array();
foreach ($input as $key => $row)
$finishing_time[$key] = date("H:i", strtotime($row[1]));
array_multisort($finishing_time, SORT_ASC, $input);
for($i=1; $i<sizeof($input); $i++){
$current_coustmer = $input[$i];
if(date("H:i", strtotime($current_coustmer[0])) >= date("H:i", strtotime($input[$last_member][1])) ){
$members_covered[] = $input[$i];
$last_member = $i;
}
}
// print_r($members_covered);
return sizeof($members_covered)*$room_charges;
}
?>
I have a piece of code which will select x number of days into the future and print it out.
I'm then trying to select data from those timestamps, and print out accordingly:
Below I am selecting the number of days in the future it should loop ($max) and how many rows/data there is ($data["cnt"])
$stmt=$dbh->prepare("select round((expire - unix_timestamp()) / 86400) as days, count(*) as cnt from xeon_users_rented WHERE user_by=:username group by days;");
$stmt->bindParam(":username",$userdata['username']);
$stmt->execute();
$data=$stmt->fetchAll();
$max = max(array_map(function($d){
return $d['days'];
}, $data));
$expireData = array();
I then loop through x number of days in the future and print it out: (Let's say that $x is = 10)
for($x = 0; $x <= $max; $x++){
if ($data[$x]["cnt"] > 0){
$expireData[] = $data[$x]["cnt"];
}else{
$expireData[] = 0;
}
$stamp = strtotime('+'.$x.' day', time());
$expireDays[] = date("Y/m/d", $stamp);
}
I then print out the days data and the data:
<?php echo implode("', '", $expireDays); ?>
<?php echo implode("', '", $expireData); ?>
Which gives me:
'2014/11/05', '2014/11/06', '2014/11/07', '2014/11/08', '2014/11/09', '2014/11/10', '2014/11/11', '2014/11/12', '2014/11/13', '2014/11/14', '2014/11/15'
and (where each digit represent a value for that specific date)
2,8,0,0,0,0,0,0,0,0
So far so good. The only problem here is, that the data (2,8,0,0etc.) is not correct. It should for example be:
0,0,2,0,0,0,8,0,0,0
My question is: How can I print out the data, where it matches the timestamp (xeon_users_rented.expire)?
To simplify my answer from before and to answer your question directly "How can I print out the data, where it matches the timestamp"?
You need to first put the unix timestamp of "strtotime('+'.$x.' day', time());" into an array from your original loop. Remove the expiredays[] stuff from that loop.
Then loop through that array and then use array_search for finding any matching indexes in the $data array.
if (found in $data array)
$expireDays[] = $data[array_search position]['cnt'];
else
$expireDays[] = 0;
From what I have gathered in what you are trying to establish, the sql query (for example) returns an array such as:
$data = array(
array("days"=>232975857, "cnt"=> 4),
array("days"=>232975867, "cnt"=> 10),
array("days"=>232976689, "cnt"=> 0),
array("days"=>232976688, "cnt"=> 2)
);
The max in your case is 10. However, please note that your code (below):
$max = max(array_map(function($d){
return $d['days'];
}, $data));
could return a lot of PHP E_NOTICE errors and be slow because you are working out a maximum from the unix_timestamp at that stage which is for example 232975867 (far too many loops I suspect that you need). The max should be worked out in the following way I suspect:
$max = count($data);
In my case (from my data example example) this will return something like 4 for which your for loop code will need to reference "<" not "<=". To optimise this I would actually put straight into the for loop "...; $x < count($data); ...;" unless of course you need the $max later.
Here is a big issue for me. I don't see where currently you have any correlation between the $stamp variable and the "days" column from your sql statement. Perhaps I have not seen enough information from you to fully understand or I am interpreting your question incorrectly but your sql for one will not necessarily return the same dates as the stamp variable will calculate and will not certainly return a cnt of 0 for any dates that do not exist in that table. This is why:
if ($data[$x]["cnt"] > 0){
part of the section is unnecessary and possibly incorrect.
To answer your question why do you get "2,8,0,0,0,0...." instead of the order you expected is because the sql does not return 0 cnt values as quite simply the date does not exist in it's table and your implode still returns '0's appended as you forcibly added the 0's afterwords with the line:
$expireData[] = 0;
First of all, you need to fill your data (or re-write your data to contain cnt of '0's in the correct array places). You can achieve this from the sql level with a subquery that ensures missing dates are contained and then a cnt value of 0 is enforced. I'll leave this to you to work out however another way (via PHP) is to do the following (pseudo code):
place each 'stamps' (in unix format) in array from previous loop
forloop $i through $stamps
array_search $stamps against $data['days']
if (found)
$expireDays[] = $data[array_search position]['cnt'];
else
$expireDays[] = 0;
This means that you remove the $expireDays from your first loop.
Finally, perhaps I have misunderstood and that your 'days' column shouldn't match your $stamp dates as those dates are in the future and your 'days' columns are in the past. In this case, your only option is to adjust your sql statement to forcibly include dates that are missing (between a certain date range)
Good luck.
I am usng sqlite to log data every 5 minutes to a column that is time stamped with and integer in Unix time. The user interface uses php code to extract data in various user selectable time frames which is then plotted using javascript. Charts typically have 12 data/time points and I need to extract data for plotting over different periods of say 1Hr/12Hr/24Hr/12days/month/year. So only need to extract 12 data rows per search. So for a 24Hr plot I need to only extract data at houly intervals (when minutes = 0) similarly for 12day plots at daily intervals (when mins=0 && hours=0) etc.
My php code for 1Hr works fine since the data is logged every 5min giving me 12 rows of data between search start time and end time. What is an efficient way of extracting data for the longer periods when number of rows between start time and end time is greater than 12? I need to further filter the search to efficiently extract only the data I need?
any suggestions - most appreciated - frank
$db = new MyDB(); // open database
$t=time(); // get current time
$q1 = "SELECT TimeStamp,myData FROM mdata WHERE ";
$q2 = " AND TimeStamp <=".$t; // end time
$q3 = " AND TimeStamp >=".($t-3600); // start time 1 hour earlier
$qer = $q1.$q2.$q3; // my search query form above parts
$result = $db->query($qer);
$json = array();
while ($data = $result->fetchArray(SQLITE_NUM)) {
$json[] = $data;
}
echo json_encode($json); // data is returned as json array
$db->close(); // close database connection
I think you should use WHERE date BETWEEN in your search query?
This kind of search could take up a lot of time once data builds up?
Since you already know the exact times you're interested in, you should probably just build an array of times and use SQL's IN operator:
$db = new MyDB(); // open database
$timeStep = 300; // Time step to use, 5 minutes here - this would be 3600 for hourly data
$t = time(); // get current time
$t -= $t % $timeStep; // round to the proper interval
$query = "SELECT TimeStamp,myData FROM mdata ";
$query .= "WHERE TimeStamp IN "
$query .= "(" . implode(",", range($t, $t + $timeStep * 12, $timeStep)) . ")";
$result = $db->query($query);
$json = array();
while ($data = $result->fetchArray(SQLITE_NUM)) {
$json[] = $data;
}
You'll need to do some different math for monthly data - try constructing 12 times with PHP's mktime() function.
Here are the references for the PHP implode() and range() functions I used.
The question was difficult to phrase. Hopefully this will make sense.
I have a table of items in my INVENTORY.
Let's call the items Apple, Orange, Pear, Potato. I want to pick a basket of FRUIT (1 x Apple,1 x Orange, 1 x Pear).
Each item in the INVENTORY has a different date for availability. So that...
Apple JANUARY
Apple FEBRUARY
Apple MARCH
Orange APRIL
Apple APRIL
Pear MAY
I don't want to pick the items in the order they appear in the inventory. Instead I want to pick them according to the minimum date range in which all items can be picked. ie Orange & Apple in APRIL and the pear in MAY.
I'm not sure if this is a problem for MYSQL or for some PHP arrays. I'm stumped. Thanks in advance.
If array of fruits isn't already sorted by date, let's sort it.
Now, the simple O(n^2) solution would be to check all possible ranges. Pseudo-code in no particular language:
for (int i = 0; i < inventory.length; ++i)
hash basket = {}
for (int j = i; j < inventory.length; ++j) {
basket.add(inventory[j]);
if (basket.size == 3) { // or whatever's the number of fruits
// found all fruits
// compare range [i, j] with the best range
// update best range, if necessary
break;
}
}
end
You may find it's good enough.
Or you could write a bit more complicated O(n) solution. It's just a sliding window [first, last]. On each step, we move either left border (excluding one fruit from the basket) or right (adding one fruit to the basket).
int first = 0;
int last = 0;
hash count = {};
count[inventory[0]] = 1;
while (true) {
if (count[inventory[first]] > 0) {
--count[inventory[first]];
++first;
} else if (last < inventory.length) {
++last;
++count[inventory[last]];
} else {
break;
}
if (date[last] - date[first] < min_range
&& count.number_of_nonzero_elements == 3) {
// found new best answer
min_range = date[last] - date[first]
}
}
Given you table inventory is structured:
fruit, availability
apple, 3 // apples in march
//user picks the availability month maybe?
$this_month = 5 ;
//or generate it for today
$this_month = date('n') ;
// sql
"select distinct fruit from inventory where availability = $this_month";
Sound quite complicated. The way that I would approach the problem is to group each fruit into its availability month group and see how many are in each group.
JANUARY (1)
FEBRUARY (1)
MARCH (1)
APRIL (2)
MAY (1)
To see that the most fruits fall within APRIL. So APRIL is therefore our preferred month.
I would then remove the items from months with duplicates (Apples in your example), which would remove MARCH as an option. This step could either be done now, or after the next step depending on your data and the results you get.
I would then look at the next most popular month and calculate how far away that month is (eg. JAN is 3 away from APRIL, MARCH is 1 etc). If you then had a tie then it shouldn't matter which you choose. In this example though you would end up choosing the 2 fruits from APRIL and 1 fruit from MAY as you requested.
This approach may not work if the most popular month doesn't actually result in the "best" selection.
Hey guys - The problem stems from a poorly designed database used to store real estate information. I set up a template for my client to select a weekend and to display the open houses for that weekend. Open house times (ohtime1, ohtime2, ohtime3) are stored as tinytext, with no way of knowing AM or PM. "12:00 - 2:00" and "01:00 - 03:00" are common entries that we humans discern as noon-2pm and 1pm-3pm, however when I query the database and ORDER BY ohtime1, it obviously puts 01:00 before 12:00. I am having difficulty sorting using SQL and using the different php sort methods. The initial listings array with all the open house information is set up like something as follows:
$listings[0][displayaddress] = empire state building
$listings[0][baths] = too many to count
$listings[0][ohtime1] = 12:00 - 02:00
$listings[1][displayaddress] = madison square garden
$listings[1][baths] = 2
$listings[1][ohtime1] = 01:00 - 03:00
etc...
I iterate through $listings with foreach($listings as $listing) to process for the smarty templates we use, as well as to separate into the different days, and then again for manhattan and brooklyn listings. This results in 4 new arrays. My theory was if I convert all the times before 09:00am to 24 hour time, then sort them, then assign to the different day/borough it would work. Here is the converting code:
$p = explode("-",$listing[ohtime1]); //01:00 - 03:00
$time1 = trim($p[0]); //01:00
$time2 = trim($p[1]); //03:00
$hour1 = substr($time1,0,2); //01
$hour2 = substr($time2,0,2); //03
$min1 = explode(":",$time1);
$min2 = explode(":",$time2);
$min1 = $min1[1]; //00
$min2 = $min2[1]; //00
//convert all times to 24 hour
if($hour1 < 9) $hour1 = $hour1+12; //13
if($hour2 < 9) $hour2 = $hour2+12; //15
$listing[ohtime1] = $hour1.":".$min1." - ".$hour2.":".$min2; //13:00 - 15:00
$listing[hour1] = $hour1;
$listing[hour2] = $hour2;
Converting wasn't difficult, but am at a loss as to how to sort them. I am not versed in advanced SQL theory as to implement the conversion to 24hrs I did in php into mysql. I was also thinking I could implement a sorting feature when I create the new arrays but I am again at a loss. Here is the code for separating into the new arrays:
foreach($openhouse_date_fields as $oh){ //3 possible open house dates
if(substr($listing[$oh], 0,10) == $date) { //if any of the listings's open houses match the first search date
if($listing[sect] == "Brooklyn") {
$listingsb[$listing[displayaddress]] = $listing;
}
else
$listingsm[$listing[displayaddress]] = $listing;
}
elseif(substr($listing[$oh], 0,10) == $date2) { //if any of the listings's open houses match the second search date
if($listing[sect] == "Brooklyn")
$listingsb2[$listing[displayaddress]] = $listing;
else
$listingsm2[$listing[displayaddress]] = $listing;
}
}
I hope that is enough information. Thanks for taking the time to read and for any feedback!
Here's an example of converting one of the TINYTEXT columns to a pair of columns of type TIME:
SELECT
MAKETIME(start_hour + IF(start_hour<9, 12, 0), start_minute, 0) AS start_time,
MAKETIME(finish_hour + IF(start_hour<9, 12, 0), finish_minute, 0) AS finish_time
FROM (
SELECT
SUBSTRING_INDEX(SUBSTRING_INDEX(ohtime1, ' - ', 1), ':', 1) AS start_hour,
SUBSTRING_INDEX(SUBSTRING_INDEX(ohtime1, ' - ', 1), ':', -1) AS start_minute,
SUBSTRING_INDEX(SUBSTRING_INDEX(ohtime1, ' - ', -1), ':', 1) AS finish_hour,
SUBSTRING_INDEX(SUBSTRING_INDEX(ohtime1, ' - ', -1), ':', -1) AS finish_minute
FROM MyOpenHouseTable) t
ORDER BY start_time;