Bucketizing time (e.g., 4 min => 0-5 min)
Here are some thoughts for you. I'm guessing other folks will (hopefully) tackle things from a variety of perspectives.

print_reading_time

How about changing this function to reading_time and returning nothing but the estimated time.  Perhaps change the declaration to:

reading_time($content, $wpm=180)


This function can be adjusted based on the reader and does not really need to know anything about the time buckets or how the output will be generated. You are mixing in additional concepts and that is likely to make it harder to reuse or extend the functionality.

minutes_to_time_label

Based on your descriptive language I'd probably go with something like:

minutes_to_time_bucket($minutes, $initial_bucket_size)


From here I think I'd immediately jump into a determination of whether or not the estimate is greater than 1 hour.

if ( $minutes &gt; 60 )
{
  $low = floor($minutes/60);
  $high = $low + 1;

  // return time label here
}


If we are below 60 minutes we can go into the particulars of how to scale the buckets (and do all of our work in minutes).

$bounds = array(0,5,10,15,30,60);
foreach ($bounds as $key =&gt; $val)
{
  if ( $minutes &gt;= $val &amp;&amp; $minutes &lt;= $bounds[$key+1] )
  {
    // generate label using $bounds[$key] and $bounds[$key+1]
  }
}

// If we get here there is an error, quit with error message


If you want to support bounds more dynamically, you could generate the bounds array based on the initial_bucket_size passed in until you end up with a value of 60 or greater. 

You could use an array of multipliers to do this. The multipliers using a bucket size of 5 would be 0, 1, 2, 3, 6, 12. If you optionally passed in the multipliers as an array you'd be able to change boundary definitions on the fly.

Note

I've assumed the options array and use of two functions related to label output was additional complexity to handle units. If you wanted that flexibility back you could add a parameter or two to the minutes_to_time_bucket function.

As an extension, you could perhaps support multiple languages by passing in the text to display for minutes and hours to override English defaults.

In short, my feedback would be to consider ways to keep your functions focused on a single concept. In this case we have a calculation of reading time and a method of generating a text time range based on a reading time. Some controlling code will mash those concepts together (not the functions themselves).

Beyond that, consider how to generalize those functions to support a wider variety of use within that focus. For example, an ability to use the readers own reading speed or output language doesn't change the focus of the function but will allow for use in a wider set of scenarios.