RSS
 

Posts Tagged ‘date’

Chronos – Let there be time

12 Aug

In PHP most know of DateTime class to handle date and time.
At least with more modern PHP versions it is now not advised anymore to use the plain old date() and time() functions.

The use cases – especially with a more global world – these days more often include correct time zone handling as well as more robust
delta handling. But using objects also means you have to be more careful about (accidentally) modifying the original date when you are creating a new one from it.

Until now the de-facto standard pretty much was Carbon – as we all know.
It wrapped the DateTime object and applied some necessary bugfixes as well as a lot of useful enhancements like better object oriented access for reading and writing.

We at CakePHP also wanted to start using it, faced quite a few issues though at the time.
One was, that it seemed unmaintained over months of time – often times with critical or at least major bugs not being fixed.
There was also the problem that there was no (and still not is) fork or version for a more modern PHP 5.4+ approach. We actually wanted and needed PHP 5.5+ support due to a lot of necessary enhancements of date time handling in PHP, more to that later.
So even after approaching them multiple times, trying to offer a helping hand here, not much changed.

The solution for us then was the only viable one on the table: We need to create a clone of it, and start maintaining it ourselves.
Chronos as modern and future proof stand-alone library to handle date and (date)time.
As a side-effect we were able to also implement better interfaces around it and could leverage all the new PHP features.

And as of this week, the Chronos library is officially marked as stable 1.0.0 🙂

Main differences and improvements

  • Implements ChronosInterface for proper typehinting, e.g. in methods
  • DateTime and Date (no time) handling separately per use case
  • Immutable by default for cleaner coding and less errors
  • Correct some faulty standards (ISO code violations) and behavior (difference calculation)
  • No external dependencies

Mutable vs Immutable

That topic is present throughout many layers of data handling, but with objects being passed around by reference, this is especially important around
data manipulation inside your business layer.
Using mutable by default means that you could easily modify a DateTime object (or Chronos in this case maybe) by accident.
You could have added a day to check if that following one is still a weekday, but at the same time this modification than accidentally back out of the method and down the chain of method invocations. The next method then uses the altered datetime and so on and so on.

// Bad practice - and doesn't work with immutable objects
$datetime->addDay(1);
$this->doSomething($datetime);
return $datetime;
// Better to never touch the original object - this works like you'd expect
$datetime = $datetime->addDay(1);
$datetime = $this->doSomething($datetime);
return $datetime;

Shimming buggy PHP core behavior

Intuitively, if you add months instead of specific days to a date, you would expect this to be "month-exact", not "day-exact".

$dt = new DateTime('2015-01-31');
$dt->modify("+1 month");
echo $dt->format("Y-m-d H:i:s");   //2015-03-03 00:00:00

Clearly, this overflows in unexpected ways.

So Chronos actually gives you addMonths()/subMonths() that actually work as desired:

$dt = new Chronos('2015-01-31');
$dt = $dt->addMonths(1);
echo $dt->format("Y-m-d H:i:s");   //2015-02-28 00:00:00

To get the former PHP behavior back, you can explicitly use addMonthsWithOverflow()/subMonthsWithOverflow() methods. Not that is is ever useful or recommended 🙂

Testing and fixating time

Everyone knows those one second issues when writing tests and (date)time. Sometimes tests fail because the time for now() jumped to the next second.

When writing unit tests, it is helpful to fixate the current time. Chronos lets you fix the current time for each class. As part of your test suite’s bootstrap process you can include the following:

Chronos::setTestNow(Chronos::now());
MutableDateTime::setTestNow(MutableDateTime::now());
Date::setTestNow(Date::now());
MutableDate::setTestNow(MutableDate::now());

This will fix the current time of all objects to be the point at which the test suite started.

Usage

The Chronos API offers a very fast and intuitive way to work with datetime.

Let’s say you want to find the next Tuesday, if the current one is not already one:

$dt = new Chronos('2015-01-31');
if (!$dt->isTuesday()) {
    $dt = $dt->next(ChronosInterface::TUESDAY);
}

Quite convenient are also the checks to find out whether a date is in the past or the future:

$dt->isPast();
$dt->isFuture();

Of course, you could also use a more verbose way with gt()/lt() and a current "now" datetime.

Check out the official chronos docs for how to use it in general.

Usage in frameworks

Usually, frameworks should be able to switch inside DB layer from DateTime or Carbon to Chronos easily.
In CakePHP for example the type conversation is setup in the bootstrap, and it already uses the immutable Chronos objects by default:

// bootstrap.php
/**
 * Enable immutable time objects in the ORM.
 *
 * You can enable default locale format parsing by adding calls
 * to `useLocaleParser()`. This enables the automatic conversion of
 * locale specific date formats. For details see
 * @link http://book.cakephp.org/3.0/en/core-libraries/internationalization-and-localization.html#parsing-localized-datetime-data
 */
Type::build('time')
    ->useImmutable();
Type::build('date')
    ->useImmutable();
Type::build('datetime')
    ->useImmutable();

The underlying classes here extend Carbon and so in the ORM all ingoing and outgoing datetimes are Chronos objects.

 
No Comments

Posted in PHP

 

All new CakePHP Tips and Tricks

26 Mar

This is supposed to be a list of useful tricks gathered over many months.

First Templates, then Bake, then Adjustments

The usual workflow for a new project should be

This way you speed up your development while having all the advantages of custom templates.
Follow the link to read more about the topic.

Don’t sanitize

Sanitizing is not always bad (see later on). But most of the times we don’t have to sanitize every bit of input.
Its overhead and usually makes more harm then good.
On save to database nothing is needed as Cake properly escapes data itself.
What we DO need is some protection in the view.
Use h($var) in the views to make sure all potential dangerous strings are now harmless. This is called "escaping".
Note: You would have to do this with any string that gets in contact with the user. So if you use database content in flash messages, you would have to escape before you call setFlash() – or escape the content in the session flash element (but if you want to be able to use HTML output, the second option is not going to work). Alternatively you could use BBCode for flash messages. That would allow you to use h() and any HTML markup together.
To sum it up: Try to be less restrictive on the input but still make sure your site is safe. This makes the user happier and keeps the efforts for security as low as possible but as high as necessary.

BAD Example:
Firstname/Lastname input fields validated with regexp or [A-Za-zäöüÄÖÜß]
We can see that all major German signs are accepted. But what if a French guy wants to sign up. His name might be Aimé. He would be quite frustrated with the website and leave!

GOOD Example:
Simply validate the length and escape the name on output. Every possible name is accepted 🙂

echo h($user['User']['first_name']);

So follow these tips and you will be fine!

Careful with the super-automatic methods

Methods like updateAll() and deleteAll() are a little bit different from the normal save() or find() methods.
They accept expressions and therefore don’t automatically escape the content. You should only use them with own "controlled" input or after sanitizing the data thoroughly. Otherwise your SQL queries might break or can even be used in harmful ways against you and your site.

Those methods like updateAll() can be pretty handy or even necessary if you need atomic DB updates.
Example:

$this->Model->updateAll(array('Post.view_count' => 'Post.view_count + 1'), array('Post.id' => $post['Post']['id']));

Even if two users trigger this the exact same moment, it will raise the count twice. If you use find() and saveField() you might end up overriding each other and raising it only once.
But in this example the input is not from the user and can therefore be considered safe.

If you use user-input you should cast (string to int if applicable) or strictly sanitize to assert that sql injections are not possible!

Working with dates

I created some date constants in my bootstrap:

define('DEFAULT_DATE', '0000-00-00');
define('DEFAULT_TIME', '00:00:00');
define('FORMAT_DB_DATE','Y-m-d');
define('FORMAT_DB_TIME','H:i:s');
define('FORMAT_DB_DATETIME', FORMAT_DB_DATE . ' ' . FORMAT_DB_TIME);
// now I can use it everywhere (controller, models, views, ...)
$today = date(FORMAT_DB_DATE);
$yesterday = date(FORMAT_DB_DATE, time()-DAY);
$exactlySevenDaysAgo = date(FORMAT_DB_DATETIME, time()-7*DAY);

A very clean and readable approach.

Always try to use the SECOND, HOUR, DAY .. constants.
Note: Everything above WEEK gets fuzzy (MONTH is always 30 days). So for everything from MONTH up you should use the PHP5 DateTime object to correctly add months/years.

Using debug mode 1

Right now you can usually switch between debug mode 0 (no debug) and 2 (3 is not used anymore). With debug mode 2 you usually display the debug bar or debug plugin etc containing the SQL queries and other stuff.
My idea quite some time ago: Why not using 1 as well? 1 could mean debug output is on but no debug bar is displayed. It simply triggers all debug warnings/errors.
Simply make sure that your debug level is > 1 at the bottom of your layout:

$debug = (int)Configure::read('debug');
if ($debug > 1 && Configure::read('Debug.helper')) {
	// display debug tabs with SQL queries etc
}

For debug 1 it will still display all debug messages. And with debug 0 it is still debug-free.

 
1 Comment

Posted in CakePHP