OOP noob here. I'd like to take advantage of CakePHP 2.3's short date methods in order to get "today" or "yesterday" when appropriate while changing the format of the date when the output is not "today" or "yesterday".
Example view: echo $this->Time->niceShort($user['User']['last_invited_on'];.
I've found the following in lib/Cake/Utility/CakeTime.php (Cake's TimeHelper uses the CakeTime utility.):
class CakeTime {
/**
* The format to use when formatting a time using `CakeTime::nice()`
*
* The format should use the locale strings as defined in the PHP docs under
* `strftime` (http://php.net/manual/en/function.strftime.php)
*
* #var string
* #see CakeTime::format()
*/
public static $niceFormat = '%a, %b %eS %Y, %H:%M';
/**
* The format to use when formatting a time using `CakeTime::timeAgoInWords()`
* and the difference is more than `CakeTime::$wordEnd`
*
* #var string
* #see CakeTime::timeAgoInWords()
*/
public static $wordFormat = 'j/n/y';
/**
* The format to use when formatting a time using `CakeTime::niceShort()`
* and the difference is between 3 and 7 days
*
* #var string
* #see CakeTime::niceShort()
*/
public static $niceShortFormat = '%B %d, %H:%M';
Can I somehow override this class's public properties in order to change the outputted date format whenever Time->niceShort is used in a view? (Is this "monkey patching"?) If so, what would be a good/clean way?
Or should I write a new class that extends CakeTime, and would this mean having to change $this->Time to $this->MyNewSpiffingCustomisedTime in views (which I'd rather not do as other people who are used to using Cake's Time are working on the project)? I wondered if this would be overkill just to change a property.
Old:
No need, you could just compare:
if (date('Y-m-d') != $date && date('Y-m-d', strtotime('-1 day')) != $date) {
//something
echo $this->Time->niceShort($date);
//something
}
Y-m-d is whatever your date format is i.e. Y-m-d H:i:s
New:
The proper way would be to call CakePHP wrapper for date('format', strtotime($date));. The CakePHP wrapper is defined as $this->Time->format('format', $date); and calls the previous php function I listed.
You shouldn't personalize base code if you plan to upgrade at any point. The only way (that I can think of right now) to still call $this->Time and extend the base code would be to implement a plugin called Time and include in your $helper and/or $component member variables Time.mySpiffyTimeThingo where mySpiffyTimeThingo is the name of the component or helper. These could extend/overwrite the current CakePHP CakeTime functions.
Why not extend the helper?
Create a class in app/View/Helper/myTimeHelper.php. In there add the reference to the old class you'll be extended like:
App::uses('TimeHelper', 'View/Helper');
class myTimeHelper extends TimeHelper {
public function niceShort(<whatever input the original is getting>) {
// your new logic for the things you want to change
// and/or a return TimeHelper::niceShort(<whatever>) for the cases that are
//fine as they are. If you don't need that then you can remove App::uses.
}
Finally you can have your helper imported as myTime with
public $helpers = array('myTime');
or even change the default Time to call myTime so you don't need to change anything else in your already written code:
public $helpers = array('Time' => array('className' => 'myTime'));
Related
Here is the original core file
https://github.com/phpbb/phpbb-core/blob/master/user.php
Looks the same as mine...
From symfony
/**
* Sets the Date header.
*
* #return $this
*
* #final since version 3.2
*/
public function setDate(\DateTime $date)
{
$date->setTimezone(new \DateTimeZone('UTC'));
$this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT');
return $this;
}
This was running fine and then all a sudden it crashed.
[16-Sep-2022 15:21:12 UTC] PHP Fatal error: Uncaught ArgumentCountError: DateTime::__construct() expects at most 2 parameters, 3 given in /home/groomlake/public_html/engine/user.php:629
I'm looking but I can't see what is causing it. Can someone point it out? Maybe I have been awake too long and I do not see it...
/**
* Format user date
*
* #param int $gmepoch unix timestamp
* #param string $format date format in date() notation. | used to indicate relative dates, for example |d m Y|, h:i is translated to Today, h:i.
* #param bool $forcedate force non-relative date format.
*
* #return mixed translated date
*/
function format_date($gmepoch, $format = false, $forcedate = false)
{
global $engine_dispatcher;
static $utc;
if (!isset($utc))
{
$utc = new \DateTimeZone('UTC');
}
$format_date_override = false;
$function_arguments = func_get_args();
/**
* Execute code and/or override format_date()
*
* To override the format_date() function generated value
* set $format_date_override to new return value
*
* #event core.user_format_date_override
* #var DateTimeZone utc Is DateTimeZone in UTC
* #var array function_arguments is array comprising a function's argument list
* #var string format_date_override Shall we return custom format (string) or not (false)
* #since 3.2.1-RC1
*/
$vars = array('utc', 'function_arguments', 'format_date_override');
extract($engine_dispatcher->trigger_event('core.user_format_date_override', compact($vars)));
if (!$format_date_override)
{
/*error is on the following line*/
$time = new $this->datetime($this, '#' . (int) $gmepoch, $utc);
$time->setTimezone($this->create_timezone());
return $time->format($format, $forcedate);
}
else
{
return $format_date_override;
}
}
You have a problem with your dynamic namespace system. More than likely you have changed the name of a dynamic global namespace.
Backtrack, go back, and restore your changes before you changed your dynamic namespace name. i.e. (engine)
I looked at phpBB and I can see your namespace is (engine) instead of the PhpBB original dynamic namespace name which was (phpbb). That is where your problem is stemming from.
You also may have changed your directory depth somewhere and the dynamic namespace function will cause the pages to not load.
phpBB is one of the very few that have a dynamic namespace system and it is a bit complicated for the average person to traverse in their mind.
I would like to convert a timestamp and have some other values related to it. My question is how I can introduce my own method like DB::raw() that appends everything to the current select values.
So, for instance, for something like this
$user = DB::table('users')
->select('*', DB::timestamp('timestamp_column', 'convert_timezone', 'called_as'))
->where('id', 1)->first();
Let's assume that I am trying to get the value for created_at column and it's called as converted_created_at and it should return something like below.
{
id: 1,
name:'John Doe',
converted_created_at: {
'utc_time': 'created_at value as that is in utc by default',
'converted_time': 'timestamp converted into user timezone',
'diff': '10 hours ago' // difference between created_at and current timestamp
}
}
So, how do I introduce my own method that does this?
You can take example of any SQL database as you wish.
I know I can do that with Model but I wanted to see how to approach this problem using a facade.
Thank you in advance for your help.
First look here: https://stackoverflow.com/a/40615078/860099 - Try this Extend DB facade:
namespace App\Facades;
use Illuminate\Support\Facades\DB as DBBase;
class DB extends DBBase {...}
and in config/app.php change
'DB' => Illuminate\Support\Facades\DB::class,
to
'DB' => App\Facades\DB::class,`
(i write code from head)
Alternative:
You can easily create helper class eg. DBTools witch static methods and inside that methods you will use DB and construct proper query. And use it like that DBTools::yourMethod(...)
As argument to that method you can give... QUERY here is example of calling this method
DBTools::yourMethod(User::query())->first();
and inside you can easyily manipulate that query and return updated version.
ALTERNATIVE: If your goal is to add some new filed in Model (json) that not exist in db but is generated then you can use $appends (look: mutators and appends)
class User extends Model
{
protected $appends = ['converted_created_at'];
...
public function getConvertedCreatedAtAttribute() {
return ...; // return generated value from other fields/sources
}
Thanks to #kamil for showing me the way.
I am writing an answer in case anyone in the future finds this helpful.
I have come up with my own method that helps to convert timezone easily without writing too much code inside select query for DB facade for PostgreSQL.
I have created a file like this now.
<?php
namespace App\Custom\Facade;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class DBTools extends DB
{
/**
* Convert a timestamp
* #param $timestamp - timestamp to be converted
* #param bool $insideRaw - if this helper method is getting used inside DB::raw() method
* #param null $timezone
* #param null $format - time format
* #param null $calledAs - column to called as
* #return \Illuminate\Database\Query\Expression|string
*/
public static function convertTime($timestamp, $insideRaw = false, $timezone = null, $format = null, $calledAs = null)
{
if (Auth::check()) {
if (!$timezone)
$timezone = Auth::user()->timezone;
if (!$format)
$format = Auth::user()->time_format;
}
$query = "to_char($timestamp at time zone '$timezone', '$format')" . ($calledAs ? " as $calledAs" : '');
if (!$insideRaw) {
return DB::raw($query);
}
return $query;
}
}
Now this can be easily be called inside select for DB facade or inside DB::raw() in case you're handling much more complicated query.
Hope this helps someone.
I'm looking for a DateTime Mutator that change the format of dates, I'm working with Oracle DB and the admitted Format is (DD/MM/YYYY) and the input type "date" stores dates in (YYYY,MM,DD) format.
I found the $date function and a trait by Torzer, but I have to indicate the fields that I want to convert the format.
is there some trait or function that detect all date fields and convert them automatically in a format (DD/MM/YYYY)? this without indicate the field.
nowadays I use protected $date in my model:
protected $dates = [ 'fecha_nac', 'fecha_nac1', 'fecha_nac2', ];
By default laravel uses date formate 'Y-m-d H:i:s' if you want to use a different format you can customize it in your model in the following way.
protected $dateFormat = 'your date formate';
in your case it will be.
protected $dateFormat = 'd-m-Y';
You can override the getDates method on HasAttributes trait.
/**
* Get the attributes that should be converted to dates.
*
* #return array
*/
public function getDates()
{
$defaults = [static::CREATED_AT, static::UPDATED_AT];
return $this->usesTimestamps()
? array_unique(array_merge($this->dates, $defaults))
: $this->dates;
}
On your model:
public function getDates()
{
$dates = parent::getDates();
// add your dynamic logic here
return $dates;
}
I would really go for explicitly defining which fields should be converted as these dynamic operations can be expensive if you are working with the model quite a lot.
I've built an application in Laravel and eloquent returns dates in this format: 2015-04-17 00:00:00. I'm sending one particular query to JSON so I can make a graph with D3, and I think I would like the dates in ISO8601 ('1995-12-17T03:24:00') or some other format that plays nice with the javascript Date() constructor.
Is there a way to change the date format being output to JSON on the Laravel end? I'm not sure using a mutator is the best approach because it would affect the date in other parts of my application.
Or would it be better to leave the JSON output as is, and use some javascript string methods to manipulate the date format before passing it to the Date() constructor? Which approach is more efficient?
Here is my model:
class Issue extends Model {
protected $fillable = [
'client_id',
'do',
'issue_advocate',
'service_number',
'issue_location',
'issue_description',
'level_of_service',
'outcome',
'referral_id',
'file_stale_date',
'date_closed',
'issue_note',
'staff_hours'
];
protected $dates = [
'do',
'date_closed',
'file_stale_date'
];
public function setDoAttribute($value)
{
$this->attributes['do'] = Carbon::createFromFormat('F j, Y', $value)->toDateString();
}
}
Here is my query:
$issues = Issue::with('issuetypes')
->select(['do','level_of_service','outcome','id'])
->whereBetween('do',[$lastyear,$now])
->get()->toJson();
And the JSON I get back:
[{"do":"2014-12-23 00:00:00","level_of_service":1,"outcome":1,"id":18995,"issuetypes":[{"id":9,"issuetype":"Non Liberty","pivot":{"issue_id":18995,"issuetype_id":9}}]}]
I know it's an old question, but there is still no good answer to that.
Changing protected $dateFormat will affect database, instead method serializeDate() must be overriden
class MyModel extends Eloquent {
protected function serializeDate(\DateTimeInterface $date) {
return $date->getTimestamp();
}
}
Or myself I chose to create trait
trait UnixTimestampSerializable
{
protected function serializeDate(\DateTimeInterface $date)
{
return $date->getTimestamp();
}
}
and then add
class SomeClassWithDates extends Model {
use UnixTimestampSerializable;
...
}
Expanding on umbrel's answer a bit I've created a trait that turns the DateTimeInstance into a Carbon instance so that I can easily make use of it's common formats.
In my particular case I wanted to serialize all dates according to ISO-8601.
The trait is as follows...
use DateTimeInterface;
use Carbon\Carbon;
trait Iso8601Serialization
{
/**
* Prepare a date for array / JSON serialization.
*
* #param \DateTimeInterface $date
* #return string
*/
protected function serializeDate(DateTimeInterface $date)
{
return Carbon::instance($date)->toIso8601String();
}
}
and from here I can simply use it on the relevant models...
class ApiObject extends Model
{
use Iso8601Serialization;
}
Obviously you could name the trait more appropriately if you're using a different format but the point is that you can use any of Carbon's common formats simply by replacing toIso8601String() with the format you need.
I strongly suggest you use the Carbon class to handle all your dates and datetimes variables, it already comes with Laravel 5 so you can start using whenever you want.
Check it out on Carbon Repo to see what you can do with it.
As an example, you can format dates from your model like this
Carbon::parse($model->created_at)->format('d-m-Y')
As for a good approach, I would suggest to use the Repository Pattern along with Presenters and Transformers. By using it you can define how you want your json to be displayed/mounted and opt to skip the presenter whenever you want in order to still get you Eloquent model returned when you make your queries.
use this function in any Model
protected function serializeDate(DateTimeInterface $date){
return $date->format('Y-m-d h:i:s');
}
Result
You can easily change the format that used to convert date/time to string when your models are serialized as JSON by setting $dateFormat property of your model to the format you need, e.g.:
class MyModel extends Eloquent {
protected $dateFormat = 'Y-m-d';
}
You can find docs on different placeholders you can use in the format string here: http://php.net/manual/en/datetime.createfromformat.php
If you use usuals techniques as
protected $dateFormat = 'Y-m-d';
or
protected function serializeDate(DateTimeInterface $date) { ... }
or
protected $casts = [ "myDate" => "date:Y-m-d" ];
It'll only works when laravel will serialize itself objects. And you will anyway to put those code inside all models, for all properties.
So my solution, you have to (too) put this code in all models for all date properties by at last, it works in ALL cases :
public function getMyDateAttribute()
{
return substr($this->attributes['my_date'], 0, 10);
}
I'd like to be able to set the time for every instance of DateTime instantiated for the duration of a PHPUnit or Behat Test.
I'm testing business logic relating to time. For example that a method in a class only returns events in the past or future.
Thing's I don't want to do if possible:
Write a wrapper around DateTime and use this instead of DateTime throughout my code. This would involve a bit of a re-write of my current code base.
Dynamically generate a dataset each time the test / suite is run.
So the question is: Is it possible to override DateTimes behaviour to always supply a specific time when requested?
You should stub the DateTime methods you need in your tests to return expected values.
$stub = $this->getMock('DateTime');
$stub->expects($this->any())
->method('theMethodYouNeedToReturnACertainValue')
->will($this->returnValue('your certain value'));
See https://phpunit.de/manual/current/en/test-doubles.html
If you cannot stub the methods because they are hardcoded into your code, have a look at
Stubbing Hard-Coded Dependencies by Sebastian Bergmann
which explains how to invoke a callback whenever new is invoked. You could then replace the DateTime class with a custom DateTime class that has a fixed time. Another option would be to use http://antecedent.github.io/patchwork
You can also use the time traveler lib which uses aop php pecl extention to bring things similar to ruby monkey patching https://github.com/rezzza/TimeTraveler
There's also this php extension, inspired from ruby timecop one:
https://github.com/hnw/php-timecop
As I'm using Symfony's WebTestCase to perform functional testing using the PHPUnit testing bundle, it quickly became impractical to mock all usages of the DateTime class.
I wanted to test the application as it handles requests over time, such as testing cookie or cache expiration, etc.
The best way I've found for doing this is to implement my own DateTime class that extends the default class, and providing some static methods to allow for a default time skew to be added/subtracted to all DateTime objects being created from that point onwards.
This is a really easy feature to implement, and doesn't require installing custom libraries.
caveat emptor: The only drawback to this method is the Symfony framework (or whatever framework you're using) won't use your library, so any behaviour it's expected to handle itself, such as internal cache/cookie expirations, won't be affected by these changes.
namespace My\AppBundle\Util;
/**
* Class DateTime
*
* Allows unit-testing of DateTime dependent functions
*/
class DateTime extends \DateTime
{
/** #var \DateInterval|null */
private static $defaultTimeOffset;
public function __construct($time = 'now', \DateTimeZone $timezone = null)
{
parent::__construct($time, $timezone);
if (self::$defaultTimeOffset && $this->isRelativeTime($time)) {
$this->modify(self::$defaultTimeOffset);
}
}
/**
* Determines whether to apply the default time offset
*
* #param string $time
* #return bool
*/
public function isRelativeTime($time)
{
if($time === 'now') {
//important, otherwise we get infinite recursion
return true;
}
$base = new \DateTime('2000-01-01T01:01:01+00:00');
$base->modify($time);
$test = new \DateTime('2001-01-01T01:01:01+00:00');
$test->modify($time);
return ($base->format('c') !== $test->format('c'));
}
/**
* Apply a time modification to all future calls to create a DateTime instance relative to the current time
* This method does not have any effect on existing DateTime objects already created.
*
* #param string $modify
*/
public static function setDefaultTimeOffset($modify)
{
self::$defaultTimeOffset = $modify ?: null;
}
/**
* #return int the unix timestamp, number of seconds since the Epoch (Jan 1st 1970, 00:00:00)
*/
public static function getUnixTime()
{
return (int)(new self)->format('U');
}
}
Using this is simple:
public class myTestClass() {
public function testMockingDateTimeObject()
{
echo "fixed: ". (new DateTime('18th June 2016'))->format('c') . "\n";
echo "before: ". (new DateTime('tomorrow'))->format('c') . "\n";
echo "before: ". (new DateTime())->format('c') . "\n";
DateTime::setDefaultTimeOffset('+25 hours');
echo "fixed: ". (new DateTime('18th June 2016'))->format('c') . "\n";
echo "after: ". (new DateTime('tomorrow'))->format('c') . "\n";
echo "after: ". (new DateTime())->format('c') . "\n";
// fixed: 2016-06-18T00:00:00+00:00 <-- stayed same
// before: 2016-09-20T00:00:00+00:00
// before: 2016-09-19T11:59:17+00:00
// fixed: 2016-06-18T00:00:00+00:00 <-- stayed same
// after: 2016-09-21T01:00:00+00:00 <-- added 25 hours
// after: 2016-09-20T12:59:17+00:00 <-- added 25 hours
}
}
Adding on to what #Gordon already pointed out there is one, rather hackish, way of testing code that relies upon current time:
My mocking out just one protected method that gets you the "global" value you can get around the issues of need to create a Class yourself that you can ask for things like the current time (which would be cleaner but in php it is arguable/understandable that people don't want to do that).
That would look something like this:
class Calendar {
public function getCurrentTimeAsISO() {
return $this->currentTime()->format('Y-m-d H:i:s');
}
protected function currentTime() {
return new DateTime();
}
}
class CalendarTest extends PHPUnit_Framework_TestCase {
public function testCurrentDate() {
$cal = $this->getMockBuilder('Calendar')
->setMethods(array('currentTime'))
->getMock();
$cal->expects($this->once())
->method('currentTime')
->will($this->returnValue(
new DateTime('2011-01-01 12:00:00')
)
);
$this->assertSame(
'2011-01-01 12:00:00',
$cal->getCurrentTimeAsISO()
);
}
}
You could change your implementation to instantiate DateTime() explicitly with time():
new \DateTime("#".time());
This doesn't change the behaviour of your class. But now you can mock time() by providing a namespaced function:
namespace foo;
function time() {
return 123;
}
You could also use my package php-mock/php-mock-phpunit for doing so:
namespace foo;
use phpmock\phpunit\PHPMock;
class DateTimeTest extends \PHPUnit_Framework_TestCase {
use PHPMock;
public function testDateTime() {
$time = $this->getFunctionMock(__NAMESPACE__, "time");
$time->expects($this->once())->willReturn(123);
$dateTime = new \DateTime("#".time());
$this->assertEquals(123, $dateTime->getTimestamp());
}
}
I'm proposing a different approach here, based on a testing library called
ClockMock.
The idea is to have all date-time related functions, classes and method to be mocked in a transparent way at the engine level (in fact the library uses a php extension) without any hack or code change specifically made to be able to mock time in tests.
Example:
$nowYmd = ClockMock::executeAtFrozenDateTime(new \DateTime('1986-06-05'), function () {
// Code executed in here will use the above date and time as "current"
return date('Y-m-d');
});
$this->assertEquals('1986-06-05', $nowYmd);
You can read this article for more information.
Disclaimer: I'm the author and maintainer.