I am returning some results in my laravel app where I want to add a friendly time column to the returned results rather than showing the timestamp.
What I wanting to do is if the timestamp is today to show the time it was saved i.e. 09:45, if the created yesterday based on todays date, then I want to show "Yesterday" and if it is old than yesterday then I want to show a date "12/6/2020". I just cannot work out the logic using Carbon, here is my logic so far,
protected $appends = ['friendly_date'];
public function getFriendlyDateAttribute()
{
if($this->created_at < Carbon::now()->subDays("1") {
return "Yesterday";
}
}
Carbon offers a utility function to do this:
protected $appends = ['friendly_date'];
public function getFriendlyDateAttribute()
{
if ($this->created_at->isToday()) {
return $this->created_at->format('H:i');
}
if($this->created_at->isYesterday()) {
return "Yesterday";
}
return $this->created_at->format('d/m/y'); // Assuming this is what 12/6/2020 means
}
Related
I want to get last record updated time in a proper format. Right now updated_at field is default laravel field. The code below return through API as json data: updated_at: "2020-08-01T09:10:01.000000Z"
This is not readable. I tried different conversion method, No one worked.
Code in controller:
public function index()
{
$updatedon = Corona::all('updated_at')->last();
return $updatedon;
}
There are two ways you can do
in your Corona model you can add
public function getUpdatedAtAttribute($date)
{
return Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $date)->format('Y-m-d');
}
you can change the format as you wish, you can read more about Accessor
while getting the last record you can do
collect(Corona::all('updated_at')->last())->map(function ($updated_at) {
return Carbon\Carbon::createFromTimeString($updated_at)->format('Y-m-d');
}))
or simply you can do this
$last_record = Corona::all('updated_at')->last();
return \Carbon\Carbon::createFromTimeString($last_record->updated_at)->format('Y-m-d');
By default, Eloquent converts the timestamps columns to instances of Carbon
$updateon->diffForHumans();
https://carbon.nesbot.com/docs/#api-humandiff
This will give you human readable format like 1 day ago, etc
OR
to any custom format
$updateon->format('Y-m-d');
I have the following problem in Laravel 5.4:
There´s a user table and a membership table, where a user can have many memberships, and a membership belongs to a user. The memberships are of annual duration, but a member will get an additional free day for each friend they recommend that registers on my site, thus the number of free days is constantly changing; this in turn changes the membership´s expiration date.
So the question is: How to scope the active memberships for a given user, if the expiration date is a variable?
I will need to do something like this:
First the expiration date in Membership.php:
This calculates the total days for each membership:
Note that the friendsDays are calculated per user in User.php
public function getTotalDaysAttribute() {
$days = $this->paidDays + $this->user->friendsDaysRemaining;
return $days;
}
This calculates the expiration date for each membership:
public function getExpirationDateAttribute() {
$date = $this->startDay->addDays($this->TotalDays);
return $date;
}
So far so good... Now, this is where I´m stuck (pseudo code):
public function scopeActive($query, $dateToCheck = Null) {
$query->where($dateToCheck >= $this->expirationDate);
}
How to code this properly to get:
dump($user->membership()->active()->get());
Thanks in advance.
You have two problems:
You are trying to use model values in your scope. Scopes happen before the model values have been set. This is because in order to get the values in the model to be set the query which fetches the data must first be executed.
Unfortunately due to your database design you won't be able to create a scope to get the answer you want. This is because you are using values on a different table to calculate your TotalDays value.
I suggest you change expirationDate value in the database and call it when a friend is invited.
Something like:
function addDaysToExpiration(User $user) {
$user->expirationDate = date('Y-m-d h:m:s', strtotime('2008-10-05' . '+1 day'));
$user->save();
}
You can pass variable to scope, so for example you can define scope like this:
public function scopeActive($query, \Carbon\Carbon $dateToCheck = Null)
{
$query->where('some_date_field', '>=' ($expirationDate ?? now())->toDateTimeString());
}
and then you can do:
$dateToCheck = now()->addDays(30);
dump($user->membership()->active($dateToCheck)->get());
You can also pass only number of days to scope instead of Carbon instance if it's more convienient to use in your case.
With the API you defined for yourself:
$user->membership()->active()->get();
Your method scopeActive won't be able to see related User and friendsDaysRemaining variable that you need for calculating the expiration date. You can try it for yourself:
public function scopeActive($query) {
var_dump($this->id); // null
var_dump($this->user); // null, this part will try to do the query: select * from users where id = null
}
In your position, I would probably go with a persisted expiration_date column on the memberships table and update it whenever needed. This would then allow you to do smth like:
public function scopeActive($query) {
return $query->where('expiration_date', '>', Carbon::now());
}
Thank you guys for your prompt answers. I figured it out using a different approach, based on your ideas. Since I cannot use calculated fields in the query, I went back to a field that do exists in the DB, this is the renewalDueDate, that´s a year from the payment date; both are known and fixed dates. Then, in the query I pass the $user and the $dateToCheck as parameters, substract the remaining friends days and compare to that value, like this:
public function scopeActive($query, $user, $dateToCheck = Null) {
// If no date is passed, use today()
$dateToCheck = is_null($dateToCheck) ? Carbon::today() : Carbon::parse($dateToCheck);
//Substract the friendsDaysRemaining from the dateToCheck
$AdjustedEndDate = $DateToCheck->copy()->subDays($user->friendsDaysRemaining);
//Build the query
$query ->where('paid', 1) //its been paid for
->where('startDay', '<=', $DateToCheck) //It has started
->where('renewalDueDate', '>=', $AdjustedEndDate); //It has not expired
return $query;
}
Although is cumbersome to have to pass the user to get the remaining friends days, this is now working fine:
$dateToCheck= '2018-09-01';
dump($user->membership()->active($user, $dateToCheck)->pluck('id'));
Result:
Collection {#299 ▼ #items: array:2 [▼
0 => 83
1 => 6 ] }
Of course you could also pass the $friendsDaysRemaining instead of the $user, but is also far from elegant.
Thanks again.
I believe that there are more developers who need to have a locale date format (shown in app front end) than the ones who use the default format in browsers which is 12/22/2016.
So i have made a small trait in my Laravel project for standard dates like created_at, updated_at and deleted_at:
<?php
namespace App\Traits;
use Carbon\Carbon;
trait FormatDates
{
protected $localFormat = 'd.m.Y H:i';
// save the date in UTC format in DB table
public function setCreatedAtAttribute($date)
{
$this->attributes['created_at'] = Carbon::parse($date);
}
// convert the UTC format to local format
public function getCreatedAtAttribute($date)
{
return Carbon::parse($date)->format($this->localFormat);
}
// get diffForHumans for this attribute
public function getCreatedAtHumanAttribute()
{
return Carbon::parse($this->attributes['created_at'])->diffForHumans();
}
// save the date in UTC format in DB table
public function setUpdatedAtAttribute($date)
{
$this->attributes['updated_at'] = Carbon::parse($date);
}
// convert the UTC format to local format
public function getUpdatedAtAttribute($date)
{
return Carbon::parse($date)->format($this->localFormat);
}
// get diffForHumans for this attribute
public function getUpdatedAtHumanAttribute()
{
return Carbon::parse($this->attributes['updated_at'])->diffForHumans();
}
// save the date in UTC format in DB table
public function setPublishedAtAttribute($date)
{
$this->attributes['published_at'] = Carbon::parse($date);
}
// convert the UTC format to local format
public function getPublishedAtAttribute($date)
{
return Carbon::parse($date)->format($this->localFormat);
}
// get diffForHumans for this attribute
public function getPublishedAtHumanAttribute()
{
return Carbon::parse($this->attributes['published_at'])->diffForHumans();
}
// save the date in UTC format in DB table
public function setDeletedAtAttribute($date)
{
$this->attributes['deleted_at'] = Carbon::parse($date);
}
// convert the UTC format to local format
public function getDeletedAtAttribute($date)
{
return Carbon::parse($date)->format($this->localFormat);
}
// get diffForHumans for this attribute
public function getDeletedAtHumanAttribute()
{
return Carbon::parse($this->attributes['deleted_at'])->diffForHumans();
}
}
There are actually only 3 functions for those dates and those functions are:
set the date so it can be saved with date time picker
get the date in locale format (22.12.2016 14:39)
get the date in human readable format
So my question is how to make this trait to have only 3 functions instead of repeating it all the time for every single variable? Is this doable?
You could set it up similar to Custom setters and getters in Laravel.
The __get() / __set() methods in your trait will be called prior to the getXAttribute() / setXAttribute() method in the eloquent model.
You can just fetch the dates per model with $this->getDates() and create a helper method to define which method you should call on which date field.
Though less code is required for this solution, personally I don't see a whole lot wrong with having specific accessors & mutators in the FormatDates trait, looking at readability.
I'm storing time in a Mysql database, and by default it stores in the format 00:00:00
I'm using this to retrieve it:
public function events(){
return $this->hasMany('App\Event');
}
$events = $user->events()->orderBy('time')->get();
Now the problem is that I want to return it without the seconds, just in 00:00 format. Is there a laravel way to convert this easily without having to create a loop and convert each record manually?
i think u just need to define an accessor for this field
http://laravel.com/docs/5.1/eloquent-mutators#accessors-and-mutators
like this (in your user model)
public function getTimeAttribute($value)
{
$time = Carbon::createFromFormat('H:i:s', $value);
return $time->format('H:i');
}
I was wondering if this was possible, so let's say I have a model like so:
MyModel
SomeDate - Carbon
Now, I also have a timezone for the current user like so:
User
MyTimezone
the timezones stored in the database are always stored in UTC format (to ensure everything is consistent), and the outputted dates should always be formatted to a specific Timezone (but timezone differs per user), for example America/Chicago for User1 and America/Denver for User2.
Is there a way to automatically format the timezones per Carbon instance to a given one before outputting, or will I have to loop through the collection and set each one accordingly?
Setting app.timezone doesn't work because it also causes Carbon instances to be saved to the database in the app.timezone timezone, whereas all dates in the database should be in UTC, therefore I lose consistency.
I currently have app.timezone set to UTC in the App config but I'm also forced to convert all Carbon instances to the correct timezone before outputting. Is there a better way, maybe by trapping execution before Carbon gets turned into a string and doing it there?
EDIT:
Things i've tried:
Override setAttribute & getAttribute:
public function setAttribute($property, $value) {
if ($value instanceof Carbon) {
$value->timezone = 'UTC';
}
parent::setAttribute($property, $value);
}
public function getAttribute($key) {
$stuff = parent::getAttribute($key);
if ($stuff instanceof Carbon) {
$stuff->timezone = Helper::fetchUserTimezone();
}
return $stuff;
}
overriding asDateTime:
protected function asDateTime($value)
{
// If this value is an integer, we will assume it is a UNIX timestamp's value
// and format a Carbon object from this timestamp. This allows flexibility
// when defining your date fields as they might be UNIX timestamps here.
$timezone = Helper::fetchUserTimezone();
if (is_numeric($value))
{
return Carbon::createFromTimestamp($value, $timezone);
}
// If the value is in simply year, month, day format, we will instantiate the
// Carbon instances from that format. Again, this provides for simple date
// fields on the database, while still supporting Carbonized conversion.
elseif (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $value))
{
return Carbon::createFromFormat('Y-m-d', $value, $timezone)->startOfDay();
}
// Finally, we will just assume this date is in the format used by default on
// the database connection and use that format to create the Carbon object
// that is returned back out to the developers after we convert it here.
elseif ( ! $value instanceof DateTime)
{
$format = $this->getDateFormat();
return Carbon::createFromFormat($format, $value, $timezone);
}
return Carbon::instance($value);
}
Running into the same issue for my application where remote websites would store dates in UTC and I'd have to show the actual dates based on the logged in user, I came up with overriding the Laravel Eloquent Model.
Just extend the Illuminate\Database\Eloquent\Model, like so:
<?php namespace Vendor\Package;
use Illuminate\Database\Eloquent\Model as EloquentModel;
class Model extends EloquentModel
{
/**
* Return a timestamp as a localized DateTime object.
*
* #param mixed $value
* #return \Carbon\Carbon
*/
protected function asDateTime($value)
{
$carbon = parent::asDateTime($value);
// only make localized if timezone is known
if(Auth::check() && Auth::user()->timezone)
{
$timezone = new DateTimeZone(Auth::user()->timezone);
// mutates the carbon object immediately
$carbon->setTimezone($timezone);
}
return $carbon;
}
/**
* Convert a localized DateTime to a normalized storable string.
*
* #param \DateTime|int $value
* #return string
*/
public function fromDateTime($value)
{
$save = parent::fromDateTime($value);
// only make localized if timezone is known
if(Auth::check() && Auth::user()->timezone)
{
// the format the value is saved to
$format = $this->getDateFormat();
// user timezone
$timezone = new DateTimeZone(Auth::user()->timezone);
$carbon = Carbon::createFromFormat($format, $value, $timezone);
// mutates the carbon object immediately
$carbon->setTimezone(Config::get('app.timezone'));
// now save to format
$save = $carbon->format($format);
}
return $save;
}
}
Perhaps this is useful for others stumbling upon this question.
As a reference
laravel 5 (2015-03-18): Illuminate\Database\Eloquent\Model:2809-2889
laravel 4.2 (2015-03-18): Illuminate\Database\Eloquent\Model:2583-2662
If I understand correctly, what you are trying to achieve is to convert timezone from A format to B format and send it to the user, where A format is stored in database and B format is converted to after retrieving records from database.
Here is a neat way to do that.
In the models such as User and MyModel where conversion is needed, add a function in model:
public function getConversionAttribute()
{
$conversion = Convert($this->SomeDate);
//Convert is the customized function to convert data format
//SomeDate is the original column name of dates stored in your database
return $conversion;
}
Now if you query User model or MyModel using $user = User::find(1), you can now get the converted date by accessing the conversion attribute using $user->conversion.
Cheers!
However, attribute added this way will not included in converted array. You need to add another function in your model.
public function toArray()
{
$array = parent::toArray();
//if you want to override the original attribute
$array['SomeDate'] = $this->conversion;
//if you want to keep both the original format and the current format
//use this: $array['Conversion'] = $this->conversion;
return $array;
}
General Version:
public function toArray() {
$array = parent::toArray();
//if you want to override the original attribute
$dates = $this->getDates();
foreach ($dates as $date) {
$local = $this->{$date}->copy();
$local->timezone = ...
$array[$date] = (string)$local;
}
//if you want to keep both the original format and the current format
//use this: $array['Conversion'] = $this->conversion;
return $array;
}