Why OOP is Your Old New Best Friend: The Date Distance Problem
Tags: programming, oop, beginners, php
Published: 2024-07-13 17:46:41

Calculating the distance between two dates is relatively straightforward. There are multiple ways. Think about it for a moment... If you like functional programming, your first thought might look like this:

```php
function date_distance(string $origin, string $destination): int 
{
    return strtotime($destination) - strtotime($origin);
}
```

That's it! Definition and implementation. It gets the job done! Simple, right? But hold on... _simple isn't always right!_

Trying to reuse this function feels like using someone else's toothbrush: it might seem okay in a hurry, but you're not really sure what issues it might cause.

Developers usually read just the first few lines:

```php
function date_distance(string $origin, string $destination): int 
```

If that definition is self-explanatory, nobody cares about the implementation. But is it? Is the return value in seconds, milliseconds, or something else? What about the date arguments, `$origin` and `$destination`? They seem to be dates, but it's not entirely clear. Confusing, right?

Ensuring clarity on what every function does, what inputs it needs, and what it returns isn't easy. Even for you, because trust me, you'll eventually forget the details of your own code.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4xdlfxghlj549v9qix82.png)

Wouldn't it be better to have a specific return type called `Seconds`? Absolutely! It's not just any random number, and _that distinction matters_. _We want to clearly indicate that the distance will be in `Seconds`._ Let's update the function to reflect this:

```php
function date_distance(DateTime $origin, DateTime $destination): Seconds
```

So, instead of any datetime string, we ask for a [`DateTime`](https://www.php.net/manual/en/class.datetime) object representing a valid datetime. And instead of returning just any integer, we return the number of `Seconds`.

```php
class Seconds
{
    public function __construct(private int $distance) {}

    // ...
}

function date_distance(DateTime $origin, DateTime $destination): Seconds
{
    $distance = $destination->getTimestamp() - $origin->getTimestamp();

    return new Seconds($distance);
}
```

The implementation looks a lot like the first version, with small changes. We're using objects and methods instead of [scalar](https://www.php.net/manual/en/function.is-scalar.php) values and functions! _The responsibility of parsing the datetime is handled by requiring a `DateTime` instance._ Nice! And I'm completely sure about the return type, no doubts, it's in seconds.

The `Seconds` object isn't just a data container; it includes behavior through methods. You can add methods for time unit conversions like `toMinutes()` or `toDays()`, and maybe an `asAbsolute()` method to always return a positive distance. Super!

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/41pmlkilb3s8vdtszrqm.png)

However, there's another way to think about this solution. Read it again:

```php
function date_distance(DateTime $origin, DateTime $destination): Seconds
{
    $distance = $destination->getTimestamp() - $origin->getTimestamp();

    return new Seconds($distance);
}
```

Any other improvements? Let's look at the usage snippet:

```php
$seconds = date_distance($dateA, $dateB);
```

In our heads, it reads as "Compute the date distance between date A and date B in seconds," which is correct, but it could simply be "Compute the date A distance to date B in seconds." Still pondering? There's a principle that has always helped me reason about object-oriented solutions: ["_Tell, Don't Ask._"](https://martinfowler.com/bliki/TellDontAsk.html)

"Tell, Don't Ask" principle states that Object-orientation combines data with behavior (methods). Instead of requesting data from an object, we should tell the object what to do.

In this sense, we should never create a global function to solve a problem unless there is no relevant Object concept around it. So, this is what we want to achieve now:

```php
$seconds = $dateA->distanceTo($dateB);
```

The `distanceTo()` method doesn't exist in the `DateTime` class, but we can extend `DateTime` and create our own `DatePoint` class:

```php
class DatePoint extends DateTime
{
    public function distanceTo(self $destination): Seconds
    {
        $distance = $destination->getTimestamp() - $this->getTimestamp();

        return new Seconds($distance);
    }
}
```

Note the `self $destination` argument and the `$this->getTimestamp()` call. This approach keeps the function relevant to the `DatePoint` object, ensuring that the calculation of the distance between two dates is neatly encapsulated within the object-oriented design.

This way, we leverage the full power of object-oriented programming by bundling data and behavior together.

As a challenge, I invite you to develop a complete solution that covers all the issues explained here, including validation constraints around the origin and destination dates.

If you enjoyed this content, please leave a like—it motivates me to write more. Also, share your feedback if you have a different perspective or want to elaborate on this topic.

Ciao!