I've searched SO and Google and can't quite find what I need.
I'm building a simple Event Calendar with CodeIgniter 2. As per the CodeIgniter documentation, this is the basic code structure and it's working...
// $data[2] contains the data for day #2, etc.
$data[2] = 'event title 1';
$data[8] = 'event title 2<br/>event title 3';
$data[13] = 'event title';
$data[24] = 'event title';
// {content} in template is where $data is inserted for each day
$prefs = array (
// my calendar options
'template' => '
{cal_cell_content}{day}<br/>{content}{/cal_cell_content}
{cal_cell_content_today}<div class="highlight">{day}<br/>{content}</div>{/cal_cell_content_today}'
);
$this->load->library('calendar', $prefs);
$year = ($year === FALSE ? date('Y') : $year);
$month = ($month === FALSE ? date('m') : $month);
echo $this->calendar->generate($year, $month, $data);
Now comes how I've set up my database table...
Each Event has a title field and that creates the $slug. (~/events/view/title-slug)
Each Event has a date field and the data format is mm/dd/yyyy
Now, I'm thinking about how I'd query the database for a particular month/year and extract the data to insert into each $data[] variable.
It seems like I'll need to do the following:
create new columns in my database table for month, day, and year.
take my date input data, after it's validated, and save it into date column.
split apart my date input data, and save each piece into month, day, and year columns.
Then I would simply query my database for year & month, then loop through these results to construct my $data[] array for each day.
Is this the correct way I should be approaching this problem? It seems very redundant to have a date column as well as month, day, and year columns. Can it be done with only the date (mm/dd/yyyy) column? Too simple? Too complex? I'd like to avoid giving the user more than one field for entering a date, and ultimately I'll have a jQuery date-picker to help ensure the proper data format.
I know this may seem like a simple problem, but I've failed to locate simple code examples online. Most of the ones I've found are out of date (CI instead of CI2), too complex for what I'm doing, or use daily content items which have URI segments that already contain the date (~/events/view/yyyy/mm/dd).
EDIT:
This is how my Model is presently setup:
return $this->db->get_where('events', array('yyyy' => $year, 'mm' => $month))->result_array();
You can leave everything as-is and just structure your query to return the month and year as separate columns:
in SQL:
SELECT id,
title,
description,
MONTH(date_field) as event_month,
YEAR(date_field) as event_year
FROM my_table
... etc ...
and in Active Record (taken from here):
$this->db->select('id');
$this->db->select('title');
$this->db->select('description');
$this->db->select("MONTH(date_field) AS event_month");
$this->db->select("YEAR(date_field) AS event_year");
$query = $this->db->get('my_table');
$results = $query->result();
Firstly, I changed my date column into the MySQL "date" format, yyyy-mm-dd, in order to take advantage of the built-in MySQL date functions.
My original $query is as follows, where the 'yyyy' and 'mm' are my redundant "year" and "month" columns:
$this->db->get_where('events', array('yyyy' => $year, 'mm' => $month))->result_array();
I simply changed it into the following using the built-in MySQL date functions:
$this->db->get_where('events', array('YEAR(date)' => $year, 'MONTH(date)' => $month))->result_array();
This solved the question. Now I no longer need to maintain the extra columns for "year" and "month".
Additionally, I can assign the "day" portion of the date column to a virtual column called dd by using the MySQL "alias" function:
$this->db->select('DAY(date) AS dd');
However, this would fail since it over-rides CI's default select('*'). So you'll have to select() all the relevant columns first or it will give an error:
$this->db->select('*');
$this->db->select('DAY(date) AS dd');
And putting it all together in the Model:
// select all relevant columns
$this->db->select('*');
/* use MySQL date functions to creates virtual column called 'dd' containing
"day" portion of the real 'date' column. */
$this->db->select('DAY(date) AS dd');
/* use MySQL date functions to match $year and $month to "year" and "month"
portions of real 'date' column. */
return $this->db->get_where('events', array('YEAR(date)' => $year, 'MONTH(date)' => $month))->result_array();
Related
The background
I am building a Laravel application and I have an upsert method on a Booking Controller for updating/inserting bookings.
On upsert.blade.php I want to display a <select> element with a list of days into which a booking can be moved (or inserted).
There is a 'holidays' table with only one column: 'day' (of type datetime, precision 6). Each entry on this table means the system will be on holidays for that day, so bookings cannot be made or transfered into days that appear on this table.
Now, I want the <option>s in the above mentioned <select> to be disabled when they correspond to a holiday.
What I tried:
The view (upsert.blade.php)
<select>
<option value="" disabled selected>Select</option>
#foreach($days as $day)
<option value="{{ $day['value'] }}" #disabled($day['disabled'])>
{{ $day['display'] }}
</option>
#endforeach
</select>
The controller action:
public function upsert()
{
$now = Carbon::now();
$last = Carbon::now()->addDays(30);
$holidays = DB::table('holidays');
$days = [];
// Populate $days with dates from $now until $last
while($now->lte($last))
{
array_push($days, [
'value' => $now->toDateString(),
'display' => $now->format('l j F Y'),
/*
* Mark day as disabled if holidays matching current
* day is greater than 1
* DOESN'T WORK
*/
'disabled' => $holidays->whereDate('day', $now)->count()
]);
$now->addDay();
}
return view('upsert', [
'days' => $days,
]);
}
The problem
The line labelled 'DOESN'T WORK' doesn't work as expected (I expect the query to return 1 if there is a holiday for the current day in the loop, thus marking the day as disabled). It only matches the first day of the loop if it's a holliday, but it won't match any other days.
Note: I have cast the 'day' property of the Holiday model to 'datetime' so Laravel casts the value to a Carbon object when accessing it.
Attempts to solve it
I tried replacing
$holidays = DB::table('holidays');
with
$holidays = Holiday::all();
but that throws the following exception
Method Illuminate\Database\Eloquent\Collection::whereDate does not exist.
So I tried rewriting the query to (note whereDate was replaced by where):
'disabled' => $holidays->where('day', $now->toDateString().' 00:00:00.000000')->count()
But this would never match
The solution
After around 6 hours of fiddling about with this line, reading Laravel documentation and talking to ChatGPT, I couldn't come up with an answert to why this is happening so I replaced the problematic line with
'disabled' => Holiday::whereDate('day', $now)->count()
Which does the job but I think is terrible for performance due to so many (in my opinion unecessary) round trips to the database.
The question
Could anyone shed some light on this?
Although I've found a solution, I don't think it would scale and I also didn't learn a thing from the experience, I still have no idea why the first query is only matching the first day and no other days. Or why the second one using where() doesn't match any days at all when it is comparing strings and I am using the exact format the strings are stored in on the database.
Or maybe the problem is not on the query, but on the Carbon object?
If you want to reproduce it, follow steps on this gist:
https://gist.github.com/alvarezrrj/50cd3669914f52ce8a6188771fdeafcd
DB::table('holidays') instantiates an Illuminate\Database\Query\Builder object. The where method modifies that object in place.
So if you're looping from January 1st-3rd and are adding a new where condition on each loop, that's going to fail because now you are basically querying this. Obviously the day column cannot match 3 different dates.
SELECT * FROM holidays
WHERE DATE(day) = '2022-01-01'
AND DATE(day) = '2022-01-02'
AND DATE(day) = '2022-01-03'
That's also why it only worked on the first loop for you, because at that point there is only 1 where condition.
You would need to move the instantiation inside the while loop so that it gets reset on each loop. Which is basically what you did in your solution.
Re: performance, what you were trying to do would not have saved you any DB cycles anyway. Each time you call count() you are hitting the database, regardless of whether it's a new $holidays object or not.
If you're concerned about performance, one thing you could do is fetch all of the holidays between the start & end date in a single query.
// May need to call toDateString() on $now and $last
$holidays = Holiday::whereBetween('day', [$now, $last])
->get()
->pluck('id', 'day'); // Assuming day is a DATE column not DATETIME or TIMESTAMP
// This will give you a collection with an underlying array like this:
// ['2022-07-04' => 1, '2022-12-25' => 2]
while($now->lte($last))
{
array_push($days, [
// Now you can instantly look it up in the array by the date
'disabled' => isset($holidays[$now->toDateString()]),
]);
$now->addDay();
}
EDIT:
I want to thanks #jimmix for giving me some idea to get started on my last post, But unfortunately, my post was put on hold. Due to the lack of details.
But here are the real scenario, I'm sorry if I didn't explain well my question.
From my CSV file, I have a raw data, then I will upload using my upload() function in into my phpmyadmin database with the table name "tbldumpbio",
See the table structure below:(tbldumpbio)
From my table tbldumpbio data, I have a function called processTimesheet()
Here's the code:
public function processTimesheet(){
$this->load->model('dbquery');
$query = $this->db->query("SELECT * FROM tbldumpbio");
foreach ($query->result() as $row){
$dateTimeExplArr = explode(' ', $row->datetimex);
$dateStr = $dateTimeExplArr[0];
$timeStr = $dateTimeExplArr[1];
if($row->status='C/Out' and !isset($timeStr) || empty($timeStr) ){
$timeStrOut ='';
} else {
$timeStrOut = $dateTimeExplArr[1];
}
if($row->status='C/In' and !isset($timeStr) || empty($timeStr) ){
$timeStrIn ='';
} else {
$timeStrIn = $dateTimeExplArr[1];
}
$data = array(
'ID' => '',
'companyAccessID' => '',
'name' => $row->name,
'empCompID' => $row->empid,
'date' => $dateStr,
'timeIn' => $timeStrIn,
'timeOut' => $timeStrOut,
'status' => '',
'inputType' => ''
);
$this->dbquery->modInsertval('tblempbioupload',$data);
}
}
This function will add another data into another table called "tblempbioupload". But here are the results that I'm getting with:
Please see the below data:(tblempbioupload)
The problem is:
the date should not be duplicated
Time In data should be added if the status is 'C/In'
Time Out data should be added if the status is 'C/Out'
The expected result should be something like this:
The first problem I see is that you have a time expressed as 15:xx:yy PM, which is an ambiguous format, as one can write 15:xx:yy AM and that would not be a valid time.
That said, if what you want is that every time the date changes a row should be written, you should do just that: store the previous date in a variable, then when you move to the next record in the source table, you compare the date with the previous one and if they differ, then you insert the row, otherwise you simply progress reading the next bit of data.
Remember that this approach works only if you're certain that the input rows are in exact order, which means ordered by EmpCompId first and then by date and then by time; if they aren't this procedure doesn't work properly.
I would probably try another approach: if (but this is not clear from your question) only one row per empcompid and date should be present, i would do a grouping query on the source table, finding the minimum entrance time, another one to find the maximum exit date, and use both of them as a source for the insert query.
I am attempting to fetch a count of users who have registered each month, separated by month in the results - so, January - 22, February - 36, March- 56, so on and so forth limited to this month and the last five.
However, I can't seem to get the query correct as it relates to CakePHP 3's query builder. This is what I have so far:
$query = $this->find('all');
$query->select(['count' => $query->func()->count('id'), 'day' => 'DAY(dateRegistered)']);
$query->where(['YEAR(dateRegistered)' => date('Y')]);
$query->group(['MONTH(dateRegistered)']);
$query->enableHydration(false);
return $query->all();
This seems to return the user count for the month, but that's all, and not in an easily graph-able format. I have seen some queries in raw SQL so it should be possible, but I would like to use the native query controls in Cake. Could someone help fix this?
Try this:
$query = $this->find();
$res = $query
->select([
'month'=>'monthname(created)',
'count'=>$query->func()->count('*'),
])
->where('year(created) = year(curdate())')
->group('month')
->enableHydration(false);
pr($res->toArray());
Below I have stripped down my code to a simplified version. I am storing SQL SELECT results for:
last name (dlname)
category (category)
date this data was added to database (date_added)
clients name (client)
I have appended an additional field outside the SQL SELECT called 'days_on_list'. This field shows the number of days since the data was added to the database, making the table output 5 columns of user data. ALL 5 COLUMNS ARE TO BE SORTABLE.
I am using server-side JSON and have successfully been able to display this to the table and perform sorting on 4 of the 5 columns. The problem is that I am unable to sort the 'days_on_list' field as the PHP file containing the SQL code only allows me to sort the 4 fields from the select query. Is there a way I can make 'days_on_list' column be sortable in the table? I know I can add this field to the sql table, but I would have to run a scheduled event on the server to update this daily (which I am not comfortable with).
Is there another way to allow for this kind of flexible table sorting?
Sorry about the question title (may be confusing), I was having trouble putting this into a question.
/*SQL CODE ABOVE HERE STORES SELECT RETURNS IN $result*/
$cart = array();
$i = 0; //index the entries
// get variables from sql result.
if ($num_rows > 0) { //if table is populated...
while ($row = mysqli_fetch_assoc($result)) {
//calculate days on list by getting the number of days from
//the 'date_added' to today
$date1 = date_create($row['date_added']);
$today = date_create(date("d-m-Y"));
$interval = date_diff($date1, $today);
$doty = $interval - > format("%a");
$cart[$i] = array(
"dlname" => htmlspecialchars($row['dlname']),
"category" => htmlspecialchars($row['category']),
"date_added" => htmlspecialchars($row['date_added']),
"client" => htmlspecialchars($row['client']),
"days_on_list" => $doty, //date_added to now
);
$i = $i + 1; //add next row
}
//encoding the PHP array
$json_server_pagination_data = array(
"total" => intval($num_rows),
"rows" => $cart, //array data
);
}
echo json_encode($json_server_pagination_data);
Because days_on_list is calculated by simply comparing date_added to the current date, sorting by days_on_list should have exactly the reverse effect as sorting by date_added.
In other words, you don't actually need to sort by days_on_list. If the user selects days_on_list as the sort column, just use ORDER BY date_added (in the opposite direction ASC/DESC).
I have a model 'listing' with a field 'created' which is in a datetime format.
I need to list in a view all listings that were created over 2 weeks ago.
An extra thing if possible is to somehow mark them as expired.
This is in cakePhp 1.27
Hi I think you can use a simple script to do that in cake.
function meScript(){
// first load your model if necessary
$listingModel = ClassRegistry::init('Listing');
// Then set your date margin to , two weeks back
$date_margin = date("Y-m-d H:i:s", strtotime('-2 week')) ;
// now retrieve all records that were created over 2 weeks ago
$listings = $listingModel ->find('all', array(
'conditions' => array('created <' => $date_margin),
)
);
}
That's pretty much it. Since the margin date is in "Y-m-d H:i:s" format, the " 'created <' => $date_margin" condition will retrieve all records that were created before that date.
As for the next step of marking them as expired:
Simply loop through the results and use their ids to set your 'expired' field (or whatever it is called in your database table) to 'true'.