Summary for Date Package, Part 1

Written by | 01 September 2014 | Published in 2014 September
Unfortunately, summer is ending. Let's take a look how I dealt with a GSoC project for our brand new date package.

This is the end

It was really good summer for me and unfortunately it has ended so quickly. My main goal was to provide immutable DateTime object. Mission accomplished! Good for me and I hope it'll be good for everyone. Let's take a quick look on UML class diagram (created in yuml.me):

A lot going on there, so I owe you some explanation.

Why Joomla\DateTime\DateTime doesn't extend PHP DateTime?

PHP DateTime is mutable and my main goal was to make DateTime class immutable. You can read more about it here. To be more precise my goal was to create DateTime as an immutable Value Object.

Why did I create a new DateInterval?

The same reason as for DateTime object. Don't worry, it's just a wrapper and almost all functionality from the PHP version is provided. Even more - now you can add intervals to each other. Only one functionality is not supported - you can't change the value of DateInterval. But that was what I wanted to achieve by creating a new DateInterval, so it can be ignored.

What if you need a PHP DateTime or DateInterval?

Sometimes you'll need a PHP version of DateTime or DateInterval. You can easily get these objects by calling: getDateTime() or getDateInterval().

There is no inheritance between Date and DateTime

Here I was trying many approaches. I tried inheritance in both ways but didn't like it. Then I found something interesting. What if we compare Date and DateTime to Integer and Float? It seems to be OK, doesn't it? DateTime is more precise than Date, and Float is more precise than Integer. Maybe I'm wrong, but from what I know there is no inheritance between Float and Integer. So why should it be between Date and DateTime? You can say that maybe there doesn't exist inheritance between Float and Integer but you can cast between them. That's right and you can cast between Date and DateTime too:

new Date(DateTime::today());
new DateTime(Date::today());

DateRange & DateTimeRange as collections of dates

Most of the times we need just a single object of DateTime, but sometimes we need more of them with certain distances from each other. To avoid creating some nasty foreach loop for it just use a *Range object for it:

$start = new DateTime('2014-08-01 08:00');
$end  = new DateTime('2014-08-01-20:00');
$range = new DateTimeRange($start, $end, new DateInterval('PT1H'));

foreach($range as $datetime) { echo $datetime->format('Y-m-d H:i:s'); }

The results are:

2014-08-01 08:00:00
2014-08-01 09:00:00
2014-08-01 10:00:00
2014-08-01 11:00:00
2014-08-01 12:00:00
2014-08-01 13:00:00
2014-08-01 14:00:00
2014-08-01 15:00:00
2014-08-01 16:00:00
2014-08-01 17:00:00
2014-08-01 18:00:00
2014-08-01 19:00:00
2014-08-01 20:00:00

You can also create a *Range object using factory methods:

/** For ranges that will hold five DateTime objects */
$start = new DateTime('2014-08-01 08:00');
DateTimeRange::from($start, 5, new DateInterval('PT1H'));
$end = new DateTime('2014-08-01 08:00'); DateTimeRange::to($end, 5, new DateInterval('PT1H'));

Getter & Parser

There are a few properties in the current Joomla\Date. Do you need them all? Really? Maybe you need some more? Maybe you need some parser method too? OK, no problem I can add 3 more properties for you, 2 more parsers for you and 6 more properties for you. And by the end of this week we'll have a class with hundreds of properties and parsers. No one will always use all of them. What I'm trying to say here is this: parsers and getters you need depend strongly on your project. There is no chance that anyone can provide all properties for all cases. That's mission impossible. My solution: move whole properties and parsers functionality into separate classes and give some way to customize it. That's how Getter and Parser interfaces were brought to life.

There is no default parser, so if you need one you have to write it for yourself. However, there is a default getter class with all properties from the current Date class, so let's start from Getter. You can get a property by calling get() method explicitly or by using property syntax:

$date->year == $date->get('year')

Providing custom Getter object

We need two new properties: one for a date and the second for a time. This is what we want to achieve:

$date->get('date');
$date->get('time');

Let's create a Getter class. To provide default functionality we will use Decorator pattern.

class MyGetter implements Getter
{
    /** @var Getter */
    private $getter;
    public function __construct(Getter $getter) 
    {
        $this->getter = $getter;
    }
	
    public function get(DateTime $datetime, $name) 
    {
        switch($name) {
            case 'date':
                $value = $datetime->format('Y-m-d');
                break;
            case 'time':
                $value = $datetime->format('H:i:s');
                break;
            default:
                $value = $this->getter->get($datetime, $name);
        }
        return $value;
    }
}

Now we need to inject our Getter object to DateTime:

DateTime::setGetter(new MyGetter(new DateTimeGetter()));

Providing custom Parser object

To provide a new parser we also need to create a new class. Here we have two possibilities: implement interface Parser or extend class AbstractParser. In the example we will use the second option:

class MyParser extends AbstractParser
{    
    public function fromString($value)
    {
        return new DateTime($value);
    }     public function fromDate(Date $date)
    {
        return new DateTime($date);
    }
    public function fromPHPDateTime(\DateTime $datetime)
    {
        return new DateTime($datetime);
    }
}

Because we're extending AbstractParser we can create any methods we need in our parser. We can also call them as we like. And naming here is important:

DateTime::setParser(new MyParser());

DateTime::parse('fromString', '2014-08-15');
DateTime::parse('fromDate', new Date());
DateTime::parse('fromPHPDateTime', new \DateTime());

I know, it's not a valuable Parser because we can pass these objects into constructor of DateTime and we don't need Parser for it. I just wanted to show you how easily you can extend DateTime to fit to your needs.

Some words about GSoC

Again, for me it was an awesome summer! I've learnt a lot about PHP in general, about SPL, PHPUnit, Composer and more. BTW if you're not using Composer to manage your dependencies you definitely should give it a try. It's much better than PEAR and PEAR is falling anyway.

Before I wrote my proposal for this project I had knowledge of TDD and PHPUnit, but I didn't have any experience with it. Now I can say that TDD is really good. Maybe it's not easy, but the idea is simple and helped me a lot. Maybe it's not working for everyone, but first you need to try it to get your own opinion about it. For me it worked really well and I managed almost 98% of code coverage.

It's definitely not my last article for the JCM (I've promised to write Part 2), but GSoC is now officially ended. I would like to thank my mentors (Herman Peeren and Søren Beck Jensen) for their support. You have been very helpful. Thank you. I hope to see you soon!

Tagged under Google Summer of Code, English