12

http://ejohn.org/files/pretty.js

// Takes an ISO time and returns a string representing how
// long ago the date represents.
function prettyDate(time){
    var date = new Date((time || "").replace(/-/g,"/").replace(/[TZ]/g," ")),
        diff = (((new Date()).getTime() - date.getTime()) / 1000),
        day_diff = Math.floor(diff / 86400);

    if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )
        return;

    return day_diff == 0 && (
            diff < 60 && "just now" ||
            diff < 120 && "1 minute ago" ||
            diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
            diff < 7200 && "1 hour ago" ||
            diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
        day_diff == 1 && "Yesterday" ||
        day_diff < 7 && day_diff + " days ago" ||
        day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
}

// If jQuery is included in the page, adds a jQuery plugin to handle it as well
if ( typeof jQuery != "undefined" )
    jQuery.fn.prettyDate = function(){
        return this.each(function(){
            var date = prettyDate(this.title);
            if ( date )
                jQuery(this).text( date );
        });
    };

How exactly is the prettyDate() method returning a string? Is this another one of those 'strange' things you can do in JavaScript or am I just missing something?

edit: I didn't ask how he's returning a value, I asked how he's returning a string.

return day_diff == 0 && (....) returns a boolean in any language I've ever used.

Wayne
  • 59,728
  • 15
  • 131
  • 126
NRaf
  • 7,407
  • 13
  • 52
  • 91

10 Answers10

28

In JavaScript:

  • a || b is equivalent to a ? a : b
  • a && b is equivalent to a ? b : a
  • any non-empty string in a boolean expression evaluates to true

With this knowledge, the logic of the return statement becomes fairly straightforward.

Assume, for example, that day_diff = 5

Then taking the statement from above step-by-step:

return day_diff == 0 && (
       diff < 60 && "just now" ||
       diff < 120 && "1 minute ago" ||
       diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
       diff < 7200 && "1 hour ago" ||
       diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
   day_diff == 1 && "Yesterday" ||
   day_diff < 7 && day_diff + " days ago" ||
   day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";

First, day_diff == 0 will evaluate to false and the right-hand side:

(diff < 60 && "just now" ||
 diff < 120 && "1 minute ago" ||
 diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
 diff < 7200 && "1 hour ago" ||
 diff < 86400 && Math.floor( diff / 3600 ) + " hours ago")

...is not evaluated. Both sides of:

day_diff == 1 && "Yesterday"

...evaluate to false. Next is:

day_diff < 7 && day_diff + " days ago"

In this expression day_diff < 7 evaluates to true, so its right-hand side, which is a string, will be evaluated and its result returned.

Further reading:

http://www.ejball.com/EdAtWork/2005/02/19/JavaScriptBooleanOperators.aspx

Wayne
  • 59,728
  • 15
  • 131
  • 126
  • 1
    I think he's more concerned about the concatenation occurring inside the conditional in the return statement than with the meaning of the `+` operator. – Marc W Feb 17 '11 at 04:18
  • Thanks. That was a bit knee-jerk. I'm updating with something that I hope is more useful. – Wayne Feb 17 '11 at 04:27
11

It says right there: return ... and then goes into a long nested list of basically "inline ifs". ;-)

In Javascript, the boolean operators return the value of one of the operands, not just true or false. E.g. 0 || 'foo' returns 'foo'. This characteristic is used in conjunction with operator short-circuiting. false && true will not evaluate the true side and return false immediately, since the whole expression must be false.

deceze
  • 510,633
  • 85
  • 743
  • 889
  • 1
    But more to the point, this poster has probably only had experience with statically typed languages (type being known everywhere at compile time). JavaScript is a dynamically typed language, which means expressions can return different types based on runtime conditions. This allows `"cat" && 5` to return `"cat"` and "`null || 32.1`" to return `32.1`. – jpsimons Feb 17 '11 at 05:01
7

Are you a Java person? Because if so you probably think if(x) needs x to be a boolean, and that "x && y" returns a boolean. It doesn't in JavaScript and in a lot of other languages like Perl. In many weakly-typed languages && is called the guard operator and || is called the default operator. They return one of their two arguments.

KthProg
  • 2,050
  • 1
  • 24
  • 32
jpsimons
  • 27,382
  • 3
  • 35
  • 45
  • 2
    I'm not sure why you call them "more evolved", because they're not - they're just different. – Ken Wayne VanderLinde Feb 17 '11 at 04:30
  • Sorry about the "more evolved" crack, I work with Java every day and still can't believe you have to say `if (myObject != null)` instead of just `if (myObject)`. – jpsimons Feb 17 '11 at 04:40
  • The way logical operations work in JavaScript, Python, Ruby, Perl, etc just wouldn't work in Java because variable types are immutable. So you're right, it is just different. – jpsimons Feb 17 '11 at 04:44
  • @darkporter edited for less opinionated flame-inducing comment and more factual. "Weakly-typed" is what you're looking for. – KthProg Feb 09 '15 at 19:29
2

The return statement is just a complicated if/else cascade that ends up returning a string in all the non-error cases.

E.g.

return day_diff == 0 && (
        diff < 60 && "just now" ||
        diff < 120 && "1 minute ago" || [...]

If day_diff is zero (meaning the date is today), then it falls into the check to see if it's less than 60. If that statement is true, then it will short-circuit evaluating the rest of the whole thing, and return the value of the expression, which will be "just now". If diff isn't less than 60, it will short circuit the sub-expresion, and move on to the diff < 120 check, and so on.

The strings are always "true" in the expressions, and they also become the result of evaluating the expression when that case matches.

This is functional but fairly obfuscated code. Not for teaching purposes. :)

Ben Zotto
  • 70,108
  • 23
  • 141
  • 204
1

Yeah, it's weird Javascript stuff. String concatenation evaluates to true and the return statement at the bottom of prettyDate() takes advantage of this plus short-circuiting in conditionals.

So basically, in the first case, diff < 60 && "just now" evaluates to the string "just now" if diff is indeed less than 60 because all the other top-level items in the conditional are OR'd together so the Javascript evaluator doesn't care about them once this first condition holds true. The same goes on down the line.

Marc W
  • 19,083
  • 4
  • 59
  • 71
1

It is playing the dangerous game of relying on operator precedence to process conditionals:

+ trumps && which trumps ||

See: https://developer.mozilla.org/en/JavaScript/Reference/Operators/Operator_Precedence

This is how I read it:

(day_diff == 0 && (
            (diff < 60 && "just now") ||
            (diff < 120 && "1 minute ago") ||
            (diff < 3600 && Math.floor( diff / 60 ) + " minutes ago") ||
            (diff < 7200 && "1 hour ago") ||
            (diff < 86400 && Math.floor( diff / 3600 ) + " hours ago")
        )) ||
        (day_diff == 1 && "Yesterday") ||
        (day_diff < 7 && day_diff + " days ago") ||
        (day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago");
mrcrowl
  • 1,265
  • 12
  • 14
1

The last statement of the line has the form

return boolExpression && otherBoolExpression

When javascript reads this, the following happens:

  1. if boolExpression if falsey, then it returns boolExpression
  2. otherwise, it returns otherBoolExpression.

This is JavaScript's way of doing short-circuit logic.

Since, in this case, otherBoolExpression uses a string concatenation, the function returns string, as long as dayDiff is not 0.

Ken Wayne VanderLinde
  • 18,915
  • 3
  • 47
  • 72
1

Basically you just have to know that

return day_diff == 0 && "lala" + "lolo"

will return lalalolo if day_diff == 0... because of operator precedence.

So it's just a shorter way to write

if (day_diff == 0) {
   if (diff < 60) {
      return "just now"
   } else (...)
} else {
   if (day_diff == 1) {
     return "..."
   }
}
Robin
  • 21,667
  • 10
  • 62
  • 85
0

There are two variables: diff - difference in seconds, daydiff - difference in days. If the daydiff is zero, the return value is based on diff, else it is number of days.

Read the 'return' as series of 'if' / 'else' clauses deciding the return string.

CMR
  • 986
  • 7
  • 16
0

The return value of the prettyDate() function depends on two variables day_diff and diff used in the following evaluation

return day_diff == 0 && (
       diff < 60 && "just now" ||
       diff < 120 && "1 minute ago" ||
       diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
       diff < 7200 && "1 hour ago" ||
       diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
   day_diff == 1 && "Yesterday" ||
   day_diff < 7 && day_diff + " days ago" ||
   day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";

That evaluation can be written more typically as nested if/else statements, but no string is returned if the time argument is a date that was more that 31 days ago.

if (day_diff == 0) {
  if (diff <60) 
    return "just now";
  else if (diff < 120)  
    return "1 minute ago";
  else if (diff < 3600)  
    return Math.floor( diff / 60 ) + " minutes ago";
  else if (diff < 7200)  
    return "1 hour ago";
  else if (diff < 86400) 
    return Math.floor( diff / 3600 ) + " hours ago";
}
else if (day_diff == 1)
    return "Yesterday";
else if (day_diff < 7)
    return day_diff + " days ago";
else if (day_diff < 31)
  return Math.ceil( day_diff / 7 ) + " weeks ago"

(and slightly updated with template literals which emphasize a string cis being returned)

if (day_diff == 0) {
  if (diff <60)   return "just now";
  else if (diff < 120)  return "1 minute ago";
  else if (diff < 3600)  return `${Math.floor( diff / 60 )} minutes ago`;
  else if (diff < 7200)  return "1 hour ago";
  else if (diff < 86400)  return `${Math.floor( diff / 3600 )} hours ago`;
}
else if (day_diff == 1)  return "Yesterday";
else if (day_diff < 7)  return `${day_diff} days ago`;
else if (day_diff < 31) return `${Math.ceil( day_diff / 7 )} weeks ago`;
Paul Higgs
  • 11
  • 3