Index: wp-includes/formatting.php
===================================================================
--- wp-includes/formatting.php	(revision 19711)
+++ wp-includes/formatting.php	(working copy)
@@ -1836,45 +1836,191 @@
 }
 
 /**
- * Determines the difference between two timestamps.
+ * Determines the difference between two timestamps in a human readable format.
  *
- * The difference is returned in a human readable format such as "1 hour",
- * "5 mins", "2 days".
+ * Example return values are things like "1 hour", "5 mins", "2 days",
+ * or "5 days, 2 hours, 45 minutes" (with limit=3).
  *
+ * When passing an array of arguments, the arguments are as follows:
+ *
+ * The "from" argument is the starting Unix timestamp. It is the only required argument.
+ *
+ * The "to" argument is the ending Unix timestamp. It defaults to the value of time().
+ *
+ * The "limit" argument controls the maximum number of units to output, i.e. the accuracy.
+ * A limit of 1 could output something like "2 years" while a limit of 3 could output
+ * something like "2 years, 3 months, 10 days". The default value is 1.
+ *
+ * The "seconds" argument is a boolean argument that controls whether to output the
+ * number of seconds, assuming that the limit value is not reached. Defaults to false.
+ *
+ * The "labels" argument allows the unit labels to be customized. An associative array
+ * should be passed with the keys being any of the following: "years", "months", "weeks",
+ * "days", "hours", "minutes", "seconds". The array values should themselves be an array,
+ * the first value being the singular unit name and the second value being the multiple
+ * unit name. Any missing keys will be set to the default. For example:
+ * <code>
+ * $args['labels'] = array(
+ * 'minutes' => array( 'minute', 'minutes' ),
+ * 'seconds' => array( 'second', 'seconds' ),
+ * );
+ * </code>
+ *
+ * The "separator" argument controls the string that goes between the returned units.
+ * It defaults to ", " (note the space).
+ *
+ * The "context" argument is entirely for translation purposes. Unlike in the English
+ * language, unit names like "days" can vary depending on the context used. For details,
+ * @see http://core.trac.wordpress.org/ticket/18495
+ *
  * @since 1.5.0
  *
- * @param int $from Unix timestamp from which the difference begins.
- * @param int $to Optional. Unix timestamp to end the time difference. Default becomes time() if not set.
+ * @param array|int $args Either an array of arguments or the Unix timestamp from which the difference begins.
+ * @param int $to Optional. Unix timestamp to end the time difference. Default becomes time() if not set. Ignored if first argument is an array.
  * @return string Human readable time difference.
  */
-function human_time_diff( $from, $to = '' ) {
-	if ( empty($to) )
-		$to = time();
-	$diff = (int) abs($to - $from);
-	if ($diff <= 3600) {
-		$mins = round($diff / 60);
-		if ($mins <= 1) {
-			$mins = 1;
-		}
-		/* translators: min=minute */
-		$since = sprintf(_n('%s min', '%s mins', $mins), $mins);
-	} else if (($diff <= 86400) && ($diff > 3600)) {
-		$hours = round($diff / 3600);
-		if ($hours <= 1) {
-			$hours = 1;
-		}
-		$since = sprintf(_n('%s hour', '%s hours', $hours), $hours);
-	} elseif ($diff >= 86400) {
-		$days = round($diff / 86400);
-		if ($days <= 1) {
-			$days = 1;
-		}
-		$since = sprintf(_n('%s day', '%s days', $days), $days);
+function human_time_diff( $args ) {
+
+	// Legacy argument format ( $from, $to )
+	if ( ! is_array( $args ) ) {
+		$args = array();
+
+		if ( ! $arg_count = func_num_args() )
+			return false;
+
+		$args['from'] = func_get_arg( 0 );
+		if ( $arg_count >= 2 )
+			$args['to'] = func_get_arg( 1 );
 	}
-	return $since;
+
+	// Required argument
+	if ( ! isset( $args['from'] ) )
+		return false;
+
+	$defaults = array(
+		'to'        => time(),
+		'limit'     => 1,
+		'seconds'   => false,
+		'labels'    => array(),
+		// translators: The seperator for human_time_diff() which seperates the years, months, etc.
+		'separator' => _x( ', ', 'human_time_diff' ),
+		'context'   => 'duration',
+	);
+
+	$args = wp_parse_args( $args, $defaults );
+
+	switch ( $args['context'] ) {
+		case 'age':
+			$default_labels = array(
+				'years'   => array( _x( '%d year',  'age' ), _x( '%d years',  'age' ) ),
+				'months'  => array( _x( '%d month', 'age' ), _x( '%d months', 'age' ) ),
+				'weeks'   => array( _x( '%d week',  'age' ), _x( '%d weeks',  'age' ) ),
+				'days'    => array( _x( '%d day',   'age' ), _x( '%d days',   'age' ) ),
+				'hours'   => array( _x( '%d hour',  'age' ), _x( '%d hours',  'age' ) ),
+				'minutes' => array( _x( '%d min',   'age' ), _x( '%d mins',   'age' ) ),
+				'seconds' => array( _x( '%d sec',   'age' ), _x( '%d secs', '  age' ) ),
+			);
+			break;
+
+		case 'duration':
+		default;
+			$default_labels = array(
+				'years'   => array( _x( '%d year',  'duration' ), _x( '%d years',  'duration' ) ),
+				'months'  => array( _x( '%d month', 'duration' ), _x( '%d months', 'duration' ) ),
+				'weeks'   => array( _x( '%d week',  'duration' ), _x( '%d weeks',  'duration' ) ),
+				'days'    => array( _x( '%d day',   'duration' ), _x( '%d days',   'duration' ) ),
+				'hours'   => array( _x( '%d hour',  'duration' ), _x( '%d hours',  'duration' ) ),
+				'minutes' => array( _x( '%d min',   'duration' ), _x( '%d mins',   'duration' ) ),
+				'seconds' => array( _x( '%d sec',   'duration' ), _x( '%d secs', '  duration' ) ),
+			);
+	}
+	$args['labels'] = wp_parse_args( $args['labels'], $default_labels );
+
+	// Since all months/years aren't the same, these values are what Google's calculator says
+	// Make sure to keep this sorted from largest unit to smallest unit
+	$units = array(
+		'years'   => 31556926,
+		'months'  => 2629744,
+		'weeks'   => 604800,
+		'days'    => 86400,
+		'hours'   => 3600,
+		'minutes' => 60,
+	);
+	if ( $args['seconds'] )
+		$units['seconds'] = 1;
+
+	$args['from'] = (int) $args['from'];
+	$args['to']   = (int) $args['to'];
+
+	if ( empty( $args['to'] ) )
+		$args['to'] = time();
+
+	$diff = abs( $args['to'] - $args['from'] );
+
+	$output = array();
+
+	foreach ( $units as $unit_type => $unit_duration ) {
+		if ( count( $output ) >= $args['limit'] )
+			break;
+
+		if ( $diff < $unit_duration )
+			continue;
+
+		$number_of_this_units = floor( $diff / $unit_duration );
+		$diff = $diff - ( $number_of_this_units * $unit_duration );
+
+		if ( $number_of_this_units > 0 )
+			$output[] = sprintf( _n( $args['labels'][$unit_type][0], $args['labels'][$unit_type][1], $number_of_this_units ), $number_of_this_units );
+	}
+
+	if ( ! empty( $output ) ) {
+		if ( is_rtl() )
+			$output = array_reverse( $output );
+
+		return implode( $args['separator'], $output );
+	} else {
+		// Return "1 {unit}" with the unit being the smallest unit
+		end( $units );
+		$smallest = key( $units );
+		return sprintf( $args['labels'][$smallest][0], 1 );
+	}
 }
 
 /**
+ * Determines the difference between two timestamps in a human readable format in the context of an age.
+ *
+ * @see human_time_diff()
+ * @see http://core.trac.wordpress.org/ticket/18495
+ *
+ * @since 3.4.0
+ *
+ * @param array|int $args Either an array of arguments or the Unix timestamp from which the difference begins.
+ * @param int $to Optional. Unix timestamp to end the time difference. Default becomes time() if not set. Ignored if first argument is an array.
+ * @return string Human readable time difference.
+ */
+function human_time_diff_age( $args ) {
+	$args['context'] = 'age';
+	return human_time_diff( $args );
+}
+
+/**
+ * Determines the difference between two timestamps in a human readable format in the context of a duration.
+ *
+ * @see human_time_diff()
+ * @see http://core.trac.wordpress.org/ticket/18495
+ *
+ * @since 3.4.0
+ *
+ * @param array|int $args Either an array of arguments or the Unix timestamp from which the difference begins.
+ * @param int $to Optional. Unix timestamp to end the time difference. Default becomes time() if not set. Ignored if first argument is an array.
+ * @return string Human readable time difference.
+ */
+function human_time_diff_duration( $args ) {
+	$args['context'] = 'duration';
+	return human_time_diff( $args );
+}
+
+/**
  * Generates an excerpt from the content, if needed.
  *
  * The excerpt word amount will be 55 words and if the amount is greater than
