I have an iOS app where users can reserve a car either daily or hourly and if a user reserves one car other user can't reserve it till the first reservation is over. I'm using Laravel as the API to save data to MySQL database.
The format of time and date are like this 27 Dec 2016 15:21. I'm saving the data
public function requested(Request $request)
{
$time = new Reservation();
$time->from_date = $request->from_date;
$time->to_date = $request->to_date;
$time->from_time = $request->from_time;
$time->to_time = $request->to_time;
}
but this won't prevent time overlapping for sure so I tried this
$carCount = Reservation::where(
function ($query) use ($startTime, $endTime) {
$query->where('from_time', '<', $endTime)
->where('to_time', '>', $startTime);
}
)->count();
if ($carCount > 0) {
return response()->json(['request' => 'no']); // this response just to check
} else {
$carRoom->save();
response()->json(['request' => 'yes']);
}
then I thought this isn't working because of date/time format. But I wanted to make sure what format I should convert the date in laravel?
this is my migration for Reservation:
$table->string('from_date')->nullable();
$table->string('to_date')->nullable();
$table->string('from_time')->nullable();
$table->string('to_time')->nullable();
I made the string because I wasn't sure if Time or anything is the right one
My main question is how can I avoid time and date overlapping in my app's database?
Store the dates in the database as timestamps
then your query should work as intended then convert the incoming dates:
27 Dec 2016 15:21
to timestamps using strtotime() or with datetime objects
both are part of PHP check the PHP documentation to see more information.
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();
}
Im using cyrildewit/eloquent-viewable package to get page views. When I check out the documentation, I don't see a method where I can only get views for a single date. It uses Period and Carbon to get dates in a particular period so the (since and upTo) functions.
Any fix or alternative package to get page views.
I am trying to create a analytics page.
public function pastDateViewsChart(){
$days = [];
$view_count = [];
foreach($this->views as $v){
//get views for the past 28 days
if($v->viewed_at >= Carbon::create("28 days ago")){
if(!in_array(Carbon::parse($v->viewed_at)->format('Y-m-d'),$days,true))
array_push($days, Carbon::parse($v->viewed_at)->format('Y-m-d'));
}
}
//get views for each day
$count = views($this)
->period(Period::upto(Carbon::parse($days[0])))
->count();
array_push($view_count,$count);
return $view_count;
}
You're not limited to since and upto, Period has (as per what I can see there https://github.com/cyrildewit/eloquent-viewable/blob/master/src/Support/Period.php) a create method that can take any date as start and end:
->period(Period::create(
Carbon::parse($days[0])->startOfDay(),
Carbon::parse($days[0])->endOfDay()
))
I want to update my date to next day date. How Can I do it?
Now I do it using this.
$calendar = Calendar::find($id);
$calendar->update(['started_at' => $calendar->started_at->addDay(1)));
or I can do it
$calendar->started_at->addDay(1);
$calendar->save();
But this solutions is bad for me because there are 2 request in database. I wont do it using only one request.
Is there a way to dynamically update date to next day date?
For example
Calendar::where('id', $id)->updateToNextDay('started_at');
I find also sql equivalent
UPDATE `calendar` SET `started_at` = `started_at` - INTERVAL 1 DAY;
Thanks for attention.
Calendar::where('id', $id)->update() is just syntactical sugar. This proxies you to the Query Builder and is the same as running DB::table('calendar')->where('id', $id)->update();
The power of a model in an ORM is obtaining the data from the database, mapping it to properties in an object, and then manipulating that object. The overhead of a single select for an update is pretty small and if you're worried about that overhead in the development phase, you're probably overoptimizing.
If you wish to forego the select, you can use the Query Builder with a raw SQL expression. Either will call the Query Builder and run the same exact query:
Calendar::where('id', $id)
->update(['started_at' => DB::raw("started_at + INTERVAL 1 DAY")]);
or
DB::table('calendars')->where('id', $id)
->update(['started_at' => DB::raw("started_at + INTERVAL 1 DAY")]);
This should work
Calendar::where('id', $id)->update([
'started_at' => DB::raw("DATE_ADD(started_at, INTERVAL 1 DAY)")
]);
Let me know :)
$startDate = date_timestamp_get($calendar->started_at);
$date = date('Y-m-d H:i:s', strtotime('+1 day', $startDate));
$calendar->update(['started_at' => $date]);
You can write your own method in Calendar model like,
public function updateToNextDay(string $column)
{
$this->update([
$column => \Db::raw("$column + INTERVAL DAY 1");
]);
}
Not tested, but it should work.
I running a Magento store in CentOS on top of a LEMP stack and I am trying to import orders from the website into our CRM using created_at date time of the order (so we can carry out delta updates).
The server's timezone is Europe/London and my php-fpm pool config (for my site) is said to be the same also:
php_admin_value[date.timezone] = Europe/London
I created an order and it correctly shows when the order was created at, for example:
However, if I looked at this order in the database, the created_at is set to one hour earlier (leads me to believe the BST timezone setting isn't in effect):
Does this mean Magento doesn't support BST? or is our magento setup incorrectly? or do I need a workaround (i.e. detect if daylight saving is on, then add/remove hour etc...)?
Update
This is how I have implemented give me all the orders since my last sync functionality, where $API_Data in the code below refers to last sync from server:
private function GetNewOrderIncrementIds($API_Data = '')
{
// Init
$now = new DateTime();
// Set Timezone to Europe/London (From Config)
$now->setTimezone(new DateTimeZone(Mage::getStoreConfig('mymodule_config/system_config/default_timezone')));
// Generate Date & Time
$fromDate = $API_Data;
$toDate = $now->format('Y-m-d H:i:s');
// Load Straight Order Sync Config
$sos_enabled = ((int)Mage::getStoreConfig('mymodule_config/order_sync/straight_order_sync_enabled'));
$sos_storeid = (int)Mage::getStoreConfig('mymodule_config/order_sync/straight_order_sync_storeid');
$sos_shipping = Mage::getStoreConfig('mymodule_config/order_sync/straight_order_sync_shippingmethod');
// Load Order Collection
$order_collection = Mage::getModel('sales/order')
->getCollection()
->addAttributeToFilter('created_at', array(
'from' => $fromDate,
'to' => $toDate
));
// Build Order Increment Id List
$new_orders = array();
foreach ($order_collection as $order)
{
// Check If This Order Is Straight Order Sync
$doSOS = ($sos_enabled &&
(int)$order->getStoreId() == $sos_storeid &&
$order->getShippingMethod() == $sos_shipping);
// Append Order
$new_orders[] = array(
'OrderNumber' => $order->getIncrementId(),
'DoSOS' => $doSOS
);
}
$order_collection = null;
// Finished
$this->API_Response(false, '', json_encode(array(
'LastSync' => $toDate,
'NewOrders' => $new_orders
)));
}
Magento sets script’s time relative to server time, converted to UTC. So each Magento store (database-wise) is synced to UTC. Read more # Guide through Magento’s timezones
To save created_at date use
Mage::getSingleton('core/date')->gmtDate()
To retrieve created_at store date use
Mage::helper('core')->formatDate($this->getCreatedAtStoreDate(), $format, true);
see /app/code/core/Mage/Core/Block/Abstract.php
To get order date
foreach ($order_collection as $order)
{
...
$created_at = Mage::helper('core')->formatDate($order->getCreatedAt(), 'medium', true);
I'm using Yii to construct a web application. One of my input forms has a CJuiDatePicker for the date. I have two drop down lists, one for the hours, and one for the minutes.
My problem is in the data model, where I'm trying to convert the date, hour, and minute from the form into a MySQL datetime string. I have to produce a datetime string that looks like this - 2011-02-27 20:11:56, so Yii can convert the string into a MySQL datetime and insert the value into the row.
In the model, I have a rule that looks like this:
array('event_datetime_from', 'createDatetime',
'date'=>'event_date_from', 'hour'=>'event_hour_from',
'minute'=>'event_minute_from'),
The createDateTime validator function looks like this:
public function createDatetime($attribute, $params) {
if (!$this->hasErrors()) {
$date = $this->$params['date'];
$hour = $this->$params['hour'];
$minute = $this->$params['minute'];
if (trim($date) === '') {
$this->$attribute = null;
} else {
$parse = CDateTimeParser::parse(
$date.' '.$hour.':'.$minute,
'MM/dd/yyyy hh:mm');
$this->$attribute = date('Y-m-d H:i:s', $parse);
}
}
}
Now, I'm not a PHP developer. However, it appears to me that $params['date'] is returning the string value event_date_from, rather than the value of event_date_from.
My PHP question is, how do I get the value of event_date_from inside the createDateTime validator function?
My apologies if I overlooked the answer somewhere in the Yii documentation. I couldn't find many examples of validator functions. The Yii validator classes have a different parameter signature than validator functions.
Edited based on thaddeusmt's answer:
I tried extending CActiveRecord and coded an afterValidate method, but I couldn't find a place to define my working date, hour, and minute variables. I defined them in the extended method, and the afterValidate method couldn't see them. I got a PHP undefined variable error in the afterValidate method.
In the controller, I coded the following function:
protected function createDateTime($dateString, $hour, $minute) {
if (trim($dateString) == '') {
return null;
} else {
$timeString = $dateString.' '.$hour.':'.$minute;
return date('Y-m-d H:i:s', strtotime($timeString));
}
}
It should be a cinch to call a function in PHP, right?
I tried both of these calls in the actionCreate() function:
$model->event_datetime_from =
createDateTime($_POST['event_date_from'],
$_POST['event_hour_from'],
$_POST['event_minute_from']
);
and:
$model->event_datetime_from =
createDateTime($model->event_date_from,
$model->event_hour_from,
$model->event_minute_from
);
My controller code dies with either of these calls, and I get a blank (no HTML) response page.
I thought what I wanted to do was pretty simple. I want to take a date, hour, and minute string, and convert the concatenation to a datetime string. What am I missing?
What I do is, in the POST action in the Controller (where the POST vars are assigned), I convert the posted date and time values into a MySQL datetime with the date() and mktime() function, then validate/save. So, here is an example of the post action:
public function actionUpdate() {
$model=$this->loadModel();
if(isset($_POST['Model'])) {
$model->attributes = $_POST['Model']; // assign the rest of the POST vars here
$model->event_datetime_from = date(
'Y-m-d H:i:s', // convert the timestamp to the mySQL format
mktime( // create the timestamp from the posted date and time vars
$_POST['my-hour-var'], // set the hour
$_POST['my-minute-var'], // set the min
$_POST['my-second-var'], // set the sec
date("m"), // set the month
date("d"), // set the day
date("Y") // set the year
)
); // create a MySQL Y-m-d H:i:s format date from the POST vars
$model->save(); // this run the validation rules, naturally
}
}
(This assumes a model called "Model", POSTed hour, minute and second variables called my-hour-var, my-minute-var and my-second-var respectively, and that you are setting the DATE part to today.)
And here is an example of validation rule in the Model model using the CTypeValidator:
public function rules() {
return array(
array('event_datetime_from', 'type', 'type'=>'datetime', 'datetimeFormat'=>'yyyy-MM-dd hh:mm:ss', 'message' => '{attribute} is not a date and time!'),
}
I hope this helps!
I'd highly recommend checking out this extension:
http://www.yiiframework.com/extension/i18n-datetime-behavior/
It does some of this behavior automatically. You may need to update it a bit depending on how you expect your incoming dates to look. One way is to always run the property through strtotime() (built in php date parsing function) instead of the specific date parser in the extension.
I finally got my date, hour, and minute strings to convert to a datetime string.
Here's the code that I put in the actionCreate method and the actionUpdate method of the CalendarEventController:
$model->attributes=$_POST['CalendarEvent'];
// new code
$timeString = $_POST['CalendarEvent']['event_date_from'].' '.
$_POST['CalendarEvent']['event_hour_from'].':'.
$_POST['CalendarEvent']['event_minute_from'];
$model->event_datetime_from =
date('Y-m-d H:i:s', strtotime($timeString));
if (trim($_POST['CalendarEvent']['event_date_to']) === '') {
$model->event_datetime_to = null;
} else {
$timeString = $_POST['CalendarEvent']['event_date_to'].' '.
$_POST['CalendarEvent']['event_hour_to'].':'.
$_POST['CalendarEvent']['event_minute_to'];
$model->event_datetime_to =
date('Y-m-d H:i:s', strtotime($timeString));
}
I had two datetime fields in this model, with the second field optional. This code didn't work when I put it in a function. It only worked when I put the code inline in the two action methods.
I guess I don't understand PHP function calls. But, in case anyone else comes across this question, here's the answer that worked.