# JavaScript Human-Readable Time Diff (“5 Hours Ago”, “Will Be In A Day”,…)

Some time ago, I’ve wrote an article iCompile – PHP Human-Readable Time Diff (“5 Hours Ago”, “Will Be In A Day”,…),
running the “main action” procedure in a recursive-loop, I could improve the spectrum of the result, so it will include also complex time-periods (“5 hours, 100 milliseconds”, “3 years, 12 days”, “3 days, 14 seconds”, etc…)
among other code improvements :)

[also available in my github’s gist: https://gist.github.com/eladkarako/65b3b395c5c9d5233f104eceeb7cede4]

```function human_time_diff(from, to){ /*accept unix-datetime integer, Date object or date string, fallback is current date*/
"use strict";

var now, diff;

/*---*/
function difference(diff){
var
MINUTE_IN_SECONDS = 60
, HOUR_IN_SECONDS   = 60  * MINUTE_IN_SECONDS
, DAY_IN_SECONDS    = 24  * HOUR_IN_SECONDS
, MONTH_IN_SECONDS  = 30  * DAY_IN_SECONDS
, YEAR_IN_SECONDS   = 365 * DAY_IN_SECONDS
, since
, milliseconds, seconds, minutes, hours, days, months, years
;

if(0 === diff){
since = "0 seconds";
}
else if(diff > 0 && diff < 1){
milliseconds = Math.trunc(diff * 1000);
since = milliseconds + " " + (1 === milliseconds ? "millisecond" : "milliseconds");
}
else if(diff >= 1 && diff < MINUTE_IN_SECONDS){
seconds = Math.trunc(diff);
seconds = Math.max(diff, 1);
since = seconds + " " + (1 === seconds ? "second" : "seconds");

diff = diff - (seconds);
if(diff > 0)
since = since + ", " + difference(diff);                             /* calculate leftover time, recursively */
}
else if(diff >= MINUTE_IN_SECONDS && diff < HOUR_IN_SECONDS){
minutes = Math.trunc(diff / MINUTE_IN_SECONDS);
minutes = Math.max(minutes, 1);
since = minutes + " " + (1 === minutes ? "minute" : "minutes");

diff  = diff - (minutes * MINUTE_IN_SECONDS);
if(diff > 0)
since = since + ", " + difference(diff);                             /* calculate leftover time, recursively */
}
else if(diff >= HOUR_IN_SECONDS && diff < DAY_IN_SECONDS){
hours = Math.trunc(diff / HOUR_IN_SECONDS);
hours = Math.max(hours, 1);
since = hours + " " + (1 === hours ? "hour" : "hours");

diff  = diff - (hours * HOUR_IN_SECONDS);
if(diff > 0)
since = since + ", " + difference(diff);                             /* calculate leftover time, recursively */
}
else if(diff >= DAY_IN_SECONDS && diff < MONTH_IN_SECONDS){
days = Math.trunc(diff / DAY_IN_SECONDS);
days = Math.max(days, 1);
since = days + " " + (1 === days ? "day" : "days");

diff  = diff - (days * DAY_IN_SECONDS);
if(diff > 0)
since = since + ", " + difference(diff);                             /* calculate leftover time, recursively */
}
else if(diff >= MONTH_IN_SECONDS && diff < YEAR_IN_SECONDS){
months = Math.trunc(diff / MONTH_IN_SECONDS);
months = Math.max(months, 1);
since = months + " " + (1 === months ? "month" : "months");

diff  = diff - (months * MONTH_IN_SECONDS);
if(diff > 0)
since = since + ", " + difference(diff);                             /* calculate leftover time, recursively */
}
else if (diff >= YEAR_IN_SECONDS){
years = Math.trunc(diff / YEAR_IN_SECONDS);
years = Math.max(diff, 1);
since = years + " " + (1 === years ? "year" : "years");

diff  = diff - (years * YEAR_IN_SECONDS);
if(diff > 0)
since = since + ", " + difference(diff);                             /* calculate leftover time, recursively */
}

return since;
}
/*---*/

now  = new Date();

from = ("number" === typeof from)                                                   ? Math.max(from, 0)      :
("string" === typeof from)                                                   ? Number(new Date(from)) :
("object" === typeof from && "date" === from.constructor.name.toLowerCase()) ? Number(from)           : Number(now)
;

to   = ("number" === typeof to)                                                     ? Math.max(to, 0)        :
("string" === typeof to)                                                     ? Number(new Date(to))   :
("object" === typeof to && "date" === to.constructor.name.toLowerCase())     ? Number(to)             : Number(now)

if("nan" === String(from).toLowerCase())  throw new Error("Error While Converting Date (first argument)" );
if("nan" === String(to).toLowerCase())    throw new Error("Error While Converting Date (second argument)");

diff = Math.abs(from - to);
console.log(from,to,diff);
return difference(diff);
}

/*
human_time_diff();                                // 0 seconds
human_time_diff("22:15:40");                      // ERROR (invalid date format)
human_time_diff(  Number(new Date()) - 2000  );   // 33 minutes, 20 seconds
human_time_diff( Number(new Date()) - 300 );      // 5 minutes
human_time_diff( Number(new Date()) - 300.123 );  // 5 minutes, 123 milliseconds
*/
```

# Another Take On Label-Design Pure CSS3 and HTML5 Markers

```<style>
[data-role="note"]{
position: relative;
font-style: italic;
margin-left: 2em;
}
[data-role="note"]::before {
content: attr(data-label)" "attr(data-icon);
text-transform: capitalize;

position: absolute;
top: -0.2em;
left: -1.5em;

letter-spacing: 1px;
transform: rotate(-8deg);

border: 1px solid rgba(0,128,0,.8);
box-shadow: 1px  1px 3px rgba(0,0,0,.8)  ,
-1px -1px 1px rgba(0,0,0,.4)  ;
}

[data-role="note"][data-color="green"]        { color: rgba(0,128,0,.9);      background: rgba(221,255,221,.8);   }
[data-role="note"][data-color="green"]::before{ color: rgba(255,255,255,.9);  background: rgba(0,128,0,.8);       }

</style>

<p data-role="note" data-color="green" data-icon="?" data-label="FIY">
The <a href="#mime-type-portion">MIME type portion</a> of a <a href="#parsable-mime-type">parsable MIME type</a>
excludes any and all <a href="#parameters">parameters</a>.
</p>
```

taking “icons” (Unicode supported characters) from http://icompile.eladkarako.com/useful-char-map/ :)

# All MTK Modems + PDAnet, Samsung’s, Google’s Official – ADB USB Drivers

A collection of MTK-devices (mostly Chinese) and older compatible w/ Samsung, HTC, HTM vendors (x86 and x64).
[23.8MB]

# JSON Of DOM-Events From W3Schools

```{
"headers": ["Event", "Category", "Description", "DOM Level"]
, "table":   [
"Mouse",         "onclick",             "The event occurs when the user clicks on an element", 2
, "Mouse",         "oncontextmenu",       "The event occurs when the user right-clicks on an element to open a context menu", 3
, "Mouse",         "ondblclick",          "The event occurs when the user double-clicks on an element", 2
, "Mouse",         "onmousedown",         "The event occurs when the user presses a mouse button over an element", 2
, "Mouse",         "onmouseenter",        "The event occurs when the pointer is moved onto an element", 2
, "Mouse",         "onmouseleave",        "The event occurs when the pointer is moved out of an element", 2
, "Mouse",         "onmousemove",         "The event occurs when the pointer is moving while it is over an element", 2
, "Mouse",         "onmouseover",         "The event occurs when the pointer is moved onto an element, or onto one of its children", 2
, "Mouse",         "onmouseout",          "The event occurs when a user moves the mouse pointer out of an element, or out of one of its children", 2
, "Mouse",         "onmouseup",           "The event occurs when a user releases a mouse button over an element", 2
, "Keyboard",      "onkeydown",           "The event occurs when the user is pressing a key", 2
, "Keyboard",      "onkeypress",          "The event occurs when the user presses a key", 2
, "Keyboard",      "onkeyup",             "The event occurs when the user releases a key", 2
, "Frame/Object",  "onabort",             "The event occurs when the loading of a resource has been aborted", 2
, "Frame/Object",  "onerror",             "The event occurs when an error occurs while loading an external file", 2
, "Frame/Object",  "onhashchange",        "The event occurs when there has been changes to the anchor part of a URL", 3
, "Frame/Object",  "onload",              "The event occurs when an object has loaded", 2
, "Frame/Object",  "onpageshow",          "The event occurs when the user navigates to a webpage", 3
, "Frame/Object",  "onpagehide",          "The event occurs when the user navigates away from a webpage", 3
, "Frame/Object",  "onresize",            "The event occurs when the document view is resized", 2
, "Frame/Object",  "onscroll",            "The event occurs when an element's scrollbar is being scrolled", 2
, "Frame/Object",  "onunload",            "The event occurs once a page has unloaded (for <body>)", 2
, "Form",          "onblur",              "The event occurs when an element loses focus", 2
, "Form",          "onchange",            "The event occurs when the content of a form element, the selection, or the checked state have changed (for <input />, <keygen>, <select>, and <textarea>)", 2
, "Form",          "onfocus",             "The event occurs when an element gets focus", 2
, "Form",          "onfocusin",           "The event occurs when an element is about to get focus", 2
, "Form",          "onfocusout",          "The event occurs when an element is about to lose focus", 2
, "Form",          "oninput",             "The event occurs when an element gets user input", 3
, "Form",          "oninvalid",           "The event occurs when an element is invalid", 3
, "Form",          "onreset",             "The event occurs when a form is reset", 2
, "Form",          "onsearch",            "The event occurs when the user writes something in a search field (for <input =\"search\"/>)", 3
, "Form",          "onselect",            "The event occurs after the user selects some text (for <input /> and </textarea><textarea>)", 2
, "Form",          "onsubmit",            "The event occurs when a form is submitted", 2
, "Drag&Drop",     "ondrag",              "The event occurs when an element is being dragged", 3
, "Drag&Drop",     "ondragend",           "The event occurs when the user has finished dragging an element", 3
, "Drag&Drop",     "ondragenter",         "The event occurs when the dragged element enters the drop target", 3
, "Drag&Drop",     "ondragleave",         "The event occurs when the dragged element leaves the drop target", 3
, "Drag&Drop",     "ondragover",          "The event occurs when the dragged element is over the drop target", 3
, "Drag&Drop",     "ondragstart",         "The event occurs when the user starts to drag an element", 3
, "Drag&Drop",     "ondrop",              "The event occurs when the dragged element is dropped on the drop target", 3
, "Clipboard",     "oncopy",              "The event occurs when the user copies the content of an element", null
, "Clipboard",     "oncut",               "The event occurs when the user cuts the content of an element", null
, "Clipboard",     "onpaste",             "The event occurs when the user pastes some content in an element", null
, "Print",         "onafterprint",        "The event occurs when a page has started printing, or if the print dialogue box has been closed", 3
, "Print",         "onbeforeprint",       "The event occurs when a page is about to be printed", 3
, "Media",         "onabort",             "The event occurs when the loading of a media is aborted", 3
, "Media",         "oncanplay",           "The event occurs when the browser can start playing the media (when it has buffered enough to begin)", 3
, "Media",         "oncanplaythrough",    "The event occurs when the browser can play through the media without stopping for buffering", 3
, "Media",         "ondurationchange",    "The event occurs when the duration of the media is changed", 3
, "Media",         "onemptied",           "The event occurs when something bad happens and the media file is suddenly unavailable (like unexpectedly disconnects)", 3
, "Media",         "onended",             "The event occurs when the media has reach the end (useful for messages like \"thanks for listening\")", 3
, "Media",         "onerror",             "The event occurs when an error occurred during the loading of a media file", 3
, "Media",         "onloadeddata",        "The event occurs when media data is loaded", 3
, "Media",         "onloadstart",         "The event occurs when the browser starts looking for the specified media", 3
, "Media",         "onpause",             "The event occurs when the media is paused either by the user or programmatically", 3
, "Media",         "onplay",              "The event occurs when the media has been started or is no longer paused", 3
, "Media",         "onplaying",           "The event occurs when the media is playing after having been paused or stopped for buffering", 3
, "Media",         "onprogress",          "The event occurs when the browser is in the process of getting the media data (downloading the media)", 3
, "Media",         "onratechange",        "The event occurs when the playing speed of the media is changed", 3
, "Media",         "onseeked",            "The event occurs when the user is finished moving/skipping to a new position in the media", 3
, "Media",         "onseeking",           "The event occurs when the user starts moving/skipping to a new position in the media", 3
, "Media",         "onstalled",           "The event occurs when the browser is trying to get media data, but data is not available", 3
, "Media",         "onsuspend",           "The event occurs when the browser is intentionally not getting media data", 3
, "Media",         "ontimeupdate",        "The event occurs when the playing position has changed (like when the user fast forwards to a different point in the media)", 3
, "Media",         "onvolumechange",      "The event occurs when the volume of the media has changed (includes setting the volume to \"mute\")", 3
, "Media",         "onwaiting",           "The event occurs when the media has paused but is expected to resume (like when the media pauses to buffer more data)", 3
, "Animation",     "animationend",        "The event occurs when a CSS animation has completed", 3
, "Animation",     "animationiteration",  "The event occurs when a CSS animation is repeated", 3
, "Animation",     "animationstart",      "The event occurs when a CSS animation has started", 3
, "Transition",    "transitionend",       "The event occurs when a CSS transition has completed", 3
, "Server-Sent",   "onerror",             "The event occurs when an error occurs with the event source", null
, "Server-Sent",   "onmessage",           "The event occurs when a message is received through the event source", null
, "Server-Sent",   "onopen",              "The event occurs when a connection with the event source is opened", null
, "Misc",          "onmessage",           "The event occurs when a message is received through or from an object (WebSocket, Web Worker, Event Source or a child frame or a parent window)", 3
, "Misc",          "onmousewheel",        "Deprecated. Use the onwheel event instead", null
, "Misc",          "ononline",            "The event occurs when the browser starts to work online", 3
, "Misc",          "onoffline",           "The event occurs when the browser starts to work offline", 3
, "Misc",          "onpopstate",          "The event occurs when the window's history changes", 3
, "Misc",          "onshow",              "The event occurs when a <menu> element is shown as a context menu", 3
, "Misc",          "onstorage",           "The event occurs when a Web Storage area is updated", 3
, "Misc",          "ontoggle",            "The event occurs when the user opens or closes the <details> element", 3
, "Misc",          "onwheel",             "The event occurs when the mouse wheel rolls up or down over an element", 3
, "Touch",         "ontouchcancel",       "The event occurs when the touch is interrupted", null
, "Touch",         "ontouchend",          "The event occurs when a finger is removed from a touch screen", null
, "Touch",         "ontouchmove",         "The event occurs when a finger is dragged across the screen", null
, "Touch",         "ontouchstart",        "The event occurs when a finger is placed on a touch screen", null
]
}
```

# Efficient JSON-Table Data Representation

First NameLast NamePoints
JillSmith50
EveJackson94
JohnDoe80
JackLeay

```<table id="example_table_for_efficient_json_table_representation">
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Points</th>
</tr>
<tbody>
<tr>
<td>Jill</td>
<td>Smith</td>
<td>50</td>
</tr>
<tr>
<td>Eve</td>
<td>Jackson</td>
<td>94</td>
</tr>
<tr>
<td>John</td>
<td>Doe</td>
<td>80</td>
</tr>
<tr>
<td>Johnson</td>
<td>67</td>
</tr>
<tr>
<td>Jack</td>
<td>Leay</td>
<td></td>
</tr>
</tbody>
</table>
```

```First Name	Last Name	Points
Jill	Smith	50
Eve	Jackson	94
John	Doe	80
Jack	Leay	[missing?]

{
headers: [   "First Name",  "Last Name", "Points"]
, table:   [   "Jill",        "Smith",           50
, "Eve",         "Jackson,          94
, "John",        "Doe",             80
, "Jack",        "Leay",            null  ]
}
```

access the “row/col” by modulo the length of headers (requires post processing)

instead of classic wasteful JSON representation (no post processing required) of

```[
{ "First Name":....
,"Last Name":...
,"Points":...
}
,{ "First Name":....
.....
}
,...
]
```

considering the fact there is little to do with JSON without.. doing something with the data, if you’re going to process the data anyway, you might add a little/minimal o(n/k) operation to “expand the data”…

extracting the data from a table is quite easy (assuming specific classic XHTML format for code clarity, naturally infinite complexity can be introduced to support formats variations…)

```(function(table){
"use strict";

var
return item.innerText
});

console.log(
);

}(
document.querySelector('#example_table_for_efficient_json_table_representation')
));
```

(will render `["First Name", "Last Name", "Points"]`)

extracting the table’s body is quite similar:

```(function(table){
"use strict";

NodeList.prototype.map = Array.prototype.map;

var
return item.innerText
})

, body    = table.querySelector('tbody').querySelectorAll('tr').map(function(item){
/*just a row as string: "Jill	Smith	50" */
//return item.innerText;

/*row CORRECT representation (but a bit overly complex) as array of arrays: [ ["Jill","Smith","50"],["Eve","Ja...],...] */
return item.querySelectorAll('td').map(function(item){
return item.innerText
});
})
;

console.log(
, body
);

}(
document.querySelector('#example_table_for_efficient_json_table_representation')
));
```

this will render the following result:

in-order to flatten the array-of-array structure *efficiently*, will use a bit of JavaScript “join magic” and some Discrete mathematics (formal language theory) trick – by using a phrase that is not in our “ABC” language we can join – and later split without any data-compromise!

```(function(table){
"use strict";

NodeList.prototype.map = Array.prototype.map;

var
SAP = "||I_WILL_NEVER_BE_AN_ACTUAL_VALUE_IN_TABLE||"

return item.innerText
})

, body    = table.querySelector('tbody').querySelectorAll('tr').map(function(item){
/*just a row as string: "Jill	Smith	50" */
//return item.innerText;

/*row CORRECT representation (but a bit overly complex) as array of arrays: [ ["Jill","Smith","50"],["Eve","Ja...],...] */
return item.querySelectorAll('td').map(function(item){
return item.innerText
}).join(SAP)
})
;

console.log(
, body
);

}(
document.querySelector('#example_table_for_efficient_json_table_representation')
));
```

easy enough, using join with the same phrase on last array structure, following by split will result a one-level array, in which the values can be accessed by `index % headers.length`

```(function(table){
"use strict";

NodeList.prototype.map = Array.prototype.map;

var
SAP = "||I_WILL_NEVER_BE_AN_ACTUAL_VALUE_IN_TABLE||"

return item.innerText
})

, body    = table.querySelector('tbody').querySelectorAll('tr').map(function(item){
/*just a row as string: "Jill	Smith	50" */
//return item.innerText;

/*row CORRECT representation (but a bit overly complex) as array of arrays: [ ["Jill","Smith","50"],["Eve","Ja...],...] */
return item.querySelectorAll('td').map(function(item){
return item.innerText
}).join(SAP)
}).join(SAP).split(SAP)
;

console.log(
, body
);

}(
document.querySelector('#example_table_for_efficient_json_table_representation')
));
```

result…

```["Jill", "Smith", "50", "Eve", "Jackson", "94", "John", "Doe", "80", "Adam", "Johnson", "67", "Jack", "Leay", ""]
```

cleaning up the code, trimming and using a *less ridiculous* `SAP`:

```(function(table){
"use strict";

NodeList.prototype.map = Array.prototype.map;

var  SAP = "||||"
, body    = table.querySelector('tbody').querySelectorAll('tr').map(function(item){ return item.querySelectorAll('td').map(function(item){ return item.innerText }).join(SAP) }).join(SAP).split(SAP)
;

, "table": body        }
}(
document.querySelector('#example_table_for_efficient_json_table_representation')
));
```

will render a nice little result JSON,
just remember how to read it (yes! modulus! BECAUSE MATH IS FUN!! *sign* geeks-of-the-world–unite!)

# JavaScript Ninja Techniques – JavaScript Based Obfuscation 101 Using Conversion Matrix Unify With A Prime Number

JavScript code obfuscation:
– is used for: “reasons”…
– provides some low-level protecting against: straight-forward debugging, hardening listening or program-flow.
– required to: execute fast, limit ‘eval’ execution, DOM friendly

in plain terms, JavaScript code obfuscation is a translatable matrix-conversion, that stills allows the D.O.M to “understand” the code, but makes debugging/watching too exhausting for a human.

best practices also covers minimal D.O.M evaluations: since obfuscated code usually executes few other methods for the same plain input, a good obfuscation algorithm adds fewest evaluated phrases as possible, and called ‘eval’ method only once.

here is a simple example, that uses JavaScript to obfuscated a plain JavaScript code (can be anything really…)

```function fromE(n){
var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890';
var char;

if(0 === n % 311) //311 is is a prime-number, it is not normally a multiplication of any ASCII char (maybe long UNICODE.. TODO: choose larger primer)
char = String.fromCharCode(n / 311);
else
char = chars.substr(n,1);

return char;
}

var phrase = 'console.log("hello")';
var obfuscated_phrase = "[" + phrase.replace(/./g,toE).replace(/\,\$/,'') + "]";
var translated_plain_phrase = eval(obfuscated_phrase).join('');

console.log("From: \n" + phrase + "\n\n" + "To: \n" + obfuscated_phrase + "\n\n" + "Back To: \n" + translated_plain_phrase + "\n");
```

it result with the following output in the Console:

```From:
console.log("hello")

To:
[fromE(2),fromE(14),fromE(13),fromE(18),fromE(14),fromE(11),fromE(4),fromE(14306),fromE(11),fromE(14),fromE(6),fromE(12440),fromE(10574),fromE(7),fromE(4),fromE(11),fromE(11),fromE(14),fromE(10574),fromE(12751)]

Back To:
console.log("hello")
```

piping the result from this simple obfuscation matrix into Closure Compiler Service or UglifyJS may be interesting to witness, normally both c.compiler and uglifyJS try to “understand” the code by braking it to trees, then parse it using tree-logic permitted-operations resulting smaller trees, then re-parse the trees back to plain code,
so… it will either increase or decrease the complexity of the code using more or fewer transitions, rule-of-thumb is that you should obfuscate your code using three or more chained calls, this will result with a very deep and narrow tree, and UglifyJS or Google Closure-Compiler, will then “work for you” minifying and obfuscating the end-result even more, with minimal, or no-human intervention..

# PHP Human-Readable Time Diff (“5 Hours Ago”, “Will Be In A Day”,…)

Code Ripped From WordPress Trunk And Modified To Not Requiring Translator Objects (Use Plain en_US Default).

```
// Constants for expressing human-readable intervals
// in their respective number of seconds.
define('MINUTE_IN_SECONDS', 60);
define('HOUR_IN_SECONDS', 60 * MINUTE_IN_SECONDS);
define('DAY_IN_SECONDS', 24 * HOUR_IN_SECONDS);
define('WEEK_IN_SECONDS', 7 * DAY_IN_SECONDS);
define('YEAR_IN_SECONDS', 365 * DAY_IN_SECONDS);

/**
* Retrieve the plural or single form based on the supplied amount.
*
* @param string \$single The text that will be used if \$number is 1.
* @param string \$plural The text that will be used if \$number is not 1.
* @param int    \$number The number to compare against to use either \$single or \$plural.
*
* @return string Either \$single or \$plural translated text.
*/
function _n(\$single, \$plural, \$number) {
return 1 === \$number ? \$single : \$plural;
}

/**
* Determines the difference between two timestamps.
*
* The difference is returned in a human readable format such as "1 hour",
* "5 mins", "2 days".
*
* @param int|string \$from Unix timestamp from which the difference begins.
* @param int|string \$to   Optional. Unix timestamp to end the time difference. Default becomes time() if not set.
*
* @return string Human readable time difference.
*/
function human_time_diff(\$from, \$to = '') {
if (empty(\$to)) {
\$to = time();
}

\$diff = (int)abs(\$to - \$from);

if (\$diff < HOUR_IN_SECONDS) {
\$mins = round(\$diff / MINUTE_IN_SECONDS);
if (\$mins <= 1)
\$mins = 1;
/* translators: min=minute */
\$since = sprintf(_n('%s min', '%s mins', \$mins), \$mins);
}
elseif (\$diff < DAY_IN_SECONDS && \$diff >= HOUR_IN_SECONDS) {
\$hours = round(\$diff / HOUR_IN_SECONDS);
if (\$hours <= 1)
\$hours = 1;
\$since = sprintf(_n('%s hour', '%s hours', \$hours), \$hours);
}
elseif (\$diff < WEEK_IN_SECONDS && \$diff >= DAY_IN_SECONDS) {
\$days = round(\$diff / DAY_IN_SECONDS);
if (\$days <= 1)
\$days = 1;
\$since = sprintf(_n('%s day', '%s days', \$days), \$days);
}
elseif (\$diff < 30 * DAY_IN_SECONDS && \$diff >= WEEK_IN_SECONDS) {
\$weeks = round(\$diff / WEEK_IN_SECONDS);
if (\$weeks <= 1)
\$weeks = 1;
\$since = sprintf(_n('%s week', '%s weeks', \$weeks), \$weeks);
}
elseif (\$diff < YEAR_IN_SECONDS && \$diff >= 30 * DAY_IN_SECONDS) {
\$months = round(\$diff / (30 * DAY_IN_SECONDS));
if (\$months <= 1)
\$months = 1;
\$since = sprintf(_n('%s month', '%s months', \$months), \$months);
}
elseif (\$diff >= YEAR_IN_SECONDS) {
\$years = round(\$diff / YEAR_IN_SECONDS);
if (\$years <= 1)
\$years = 1;
\$since = sprintf(_n('%s year', '%s years', \$years), \$years);
}

if (\$to - \$from > 0) {
\$since = 'was ' . \$since . ' ago';
}
elseif (\$to - \$from < 0) {
\$since = 'will be in ' . \$since;
}

return \$since;

}
```

Try it using the output of `time()-9000` for "was 3 hours ago", or `time()+9000` for "will be in 3 hours".