# You Should Use ISO-8601 for Time Durations

## Cache TTL

In writing cache logic, we often need to specify a time duration for the cache to expire. It's called TTL, or "Time To Live." Considering the following code:

```php
Cache::remember(key: 'all-users', ttl: 60 * 60 * 24, function () {
  return User::all();
});
```

From the above code, we can probably guess intuitively that the unit of the TTL parameter is seconds. But we won't always be in this case:

```php
Cache::remember(key: 'all-users', ttl: 60 * 3, function () {
  return User::all();
});
```

Can you still tell if it is 3 hours or 3 minutes?

To avoid confusion, I always pass the [`Carbon`](https://carbon.nesbot.com/) instance into it. [`Carbon`](https://carbon.nesbot.com/) is a famous PHP package that provides a fluent way to manipulate `DateTime` objects:

```php
Cache::remember(key: 'all-users', ttl: Carbon::now()->addSeconds(60 * 3), function () {
  return User::all();
});
```

Now it is very clear that `60 * 3` is in seconds.

To avoid having [magic numbers](https://en.wikipedia.org/wiki/Magic_number_(programming)) in our program, we often extract these literal values into constants:

```php
final class UserRepository
{
    public const ALL_USER_CACHE_KEY = 'all-users';
    public const ALL_USER_CACHE_TTL = 60 * 3;

    public function getAllUsers()
    {
        return Cache::remember(
            key: self::ALL_USER_CACHE_KEY,
            ttl: Carbon::now()->addSeconds(self::ALL_USER_CACHE_TTL),
            function () {
                return User::all();
            },
        );
    }
}
```

Here the problem appears again. We can't tell if it is 3 hours or 3 minutes when we only see the line that defines the const `ALL_USER_CACHE_TTL`. In order to know if it is hours or minutes, we must search for the usage of this constant. Of course, we can add a comment with the constant definition:

```php
/** Cache TTL (Seconds) **/
public const ALL_USER_CACHE_TTL = 60 * 3;
```

An alternative way is to just specify the unit in the constant name:

```php
final class UserRepository
{
    public const ALL_USER_CACHE_KEY = 'all-users';
    public const ALL_USER_CACHE_TTL_SECONDS = 60 * 3;

    public function getAllUsers()
    {
        return Cache::remember(
            key: self::ALL_USER_CACHE_KEY,
            ttl: Carbon::now()->addSeconds(self::ALL_USER_CACHE_TTL_SECONDS),
            function () {
                return User::all();
            },
        );
    }
}
```

These methods are acceptable but ugly. The comment only hints at you when you are reading the constant definition, but not when you are using the constant. And to add the unit to the name of the constant? It is causing the name to become longer and contains too much information.

## The Solution, ISO-8601

Many people know about the existence of ISO-8601. It's a standard format to represent time, and dates. For date time, it's separated by a literal `T`: `2023-01-10T09:32:00Z`.

Besides the format to represent a specific point of time or date, ISO-8601 also specified a way to represent **a duration of time.** For example, `P1M` means 1 month, and `PT42S` means 42 seconds. We can have something like `P1Y2M3DT4H5M6S` to represent 1 year, 2 months, 3 days, 4 hours, 5 minutes, and 6 seconds long.

In PHP, the build-in class [`DateInterval`](http://www.php.net/manual/en/class.dateinterval.php) are already based on this syntax:

```php
$ttl = new DateInterval('PT3M');
```

But I think it is too less known. In [Laravel's documentation on Cache](https://laravel.com/docs/9.x/cache#accessing-multiple-cache-stores), all examples are written like this:

```php
Cache::store('redis')->put('bar', 'baz', 600); // 10 Minutes
```

this:

```php
Cache::put('key', 'value', $seconds = 10);
```

or this:

```php
Cache::put('key', 'value', now()->addMinutes(10));
```

If you checked the source code of `Cache` class, you will see that [it uses `DateInterval` internally](https://github.com/laravel/framework/blob/cb6d25743e57eebe700d672a257fb6e8e09f7d5c/src/Illuminate/Cache/Repository.php#L528-L543):

```php
// https://github.com/laravel/framework/blob/cb6d25743e57eebe700d672a257fb6e8e09f7d5c/src/Illuminate/Cache/Repository.php#L528-L543

/**
 * Calculate the number of seconds for the given TTL.
 *
 * @param  \DateTimeInterface|\DateInterval|int  $ttl
 * @return int
 */
protected function getSeconds($ttl)
{
    $duration = $this->parseDateInterval($ttl);

    if ($duration instanceof DateTimeInterface) {
        $duration = Carbon::now()->diffInRealSeconds($duration, false);
    }

    return (int) ($duration > 0 ? $duration : 0);
}

// https://github.com/laravel/framework/blob/cb6d25743e57eebe700d672a257fb6e8e09f7d5c/src/Illuminate/Support/InteractsWithTime.php#L40-L53

/**
 * If the given value is an interval, convert it to a DateTime instance.
 *
 * @param  \DateTimeInterface|\DateInterval|int  $delay
 * @return \DateTimeInterface|int
 */
protected function parseDateInterval($delay)
{
    if ($delay instanceof DateInterval) {
        $delay = Carbon::now()->add($delay);
    }

    return $delay;
}
```

But the documentation didn't mention `DateInterval` at all! [The `DateInterval` support are added back in 2017.](https://github.com/laravel/framework/pull/20102) In my opinion, using `DateInterval` with ISO-8601's duration syntax is much-much more readable than using just seconds or using Carbon.

## Fixing Our Code

Now with the knowledge of ISO-8601 and `DateInterval`. We can refactor our code into this:

```php
final class UserRepository
{
    public const ALL_USER_CACHE_KEY = 'all-users';
    public const ALL_USER_CACHE_TTL = 'PT3M';

    public function getAllUsers()
    {
        return Cache::remember(
            key: self::ALL_USER_CACHE_KEY,
            ttl: new DateInterval(self::ALL_USER_CACHE_TTL),
            function () {
                return User::all();
            },
        );
    }
}
```

From the constant name, you can tell that it holds a time duration. Looking at the value, `PT3M`, you will easily tell that it means 3 minutes, not 3 hours. Our code looks so much better now!
