DEV Community

Cover image for Why OOP is Your Old New Best Friend: The Date Distance Problem
Yonel Ceruto
Yonel Ceruto

Posted on

Why OOP is Your Old New Best Friend: The Date Distance Problem

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:

function date_distance(string $origin, string $destination): int 
{
    return strtotime($destination) - strtotime($origin);
}
Enter fullscreen mode Exit fullscreen mode

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:

function date_distance(string $origin, string $destination): int 
Enter fullscreen mode Exit fullscreen mode

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

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:

function date_distance(DateTime $origin, DateTime $destination): Seconds
Enter fullscreen mode Exit fullscreen mode

So, instead of any datetime string, we ask for a DateTime object representing a valid datetime. And instead of returning just any integer, we return the number of Seconds.

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);
}
Enter fullscreen mode Exit fullscreen mode

The implementation looks a lot like the first version, with small changes. We're using objects and methods instead of scalar 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

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

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

    return new Seconds($distance);
}
Enter fullscreen mode Exit fullscreen mode

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

$seconds = date_distance($dateA, $dateB);
Enter fullscreen mode Exit fullscreen mode

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."

"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:

$seconds = $dateA->distanceTo($dateB);
Enter fullscreen mode Exit fullscreen mode

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

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

        return new Seconds($distance);
    }
}
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (2)

Collapse
 
xwero profile image
david duymelinck

I understand you want to demonstrate the power of OOP.

I think the function is fine if you change the name to date_strings_to_seconds.I know it is better to make a function name more generic for future use, but sometimes more context is better.

I love return type objects, but naming an object Seconds just to increase the readabilty of the code feels like a step to far for me. I would use the DateInterval class.

Another thing with your type hinted example is the prep work the code needs to do before calling the function after the change.

// from 
function date_distance('+ 1 day', 'now');
// to
function date_distance(new  DateTime('+1 day'), new DateTime());
Enter fullscreen mode Exit fullscreen mode

If the function is used many times, this is going to be a painful refactor.

While OOP is a good tool, don't use it for everything.

Collapse
 
slowwie profile image
Michael

Thank you. Very valuable and well written.
I often see a lot of code in the second version in projects. It's great that you share something like this with us. keep up the good work.