A user wrote in with an interesting request. He wanted to take a form field, and as a user entered a date in it, display the "time till" the entered date. So for example, if I were to enter tomorrow, let's say an hour from now, we would get a message stating this was one day, one hour away. To make things even more interesting, he wanted to make it dynamic. So as you sat there and watched, the difference would actually count down. Here's how I solved it - and as always - I'm open to folks rewriting this in a better way! (If you do, please use pastebin, don't post your code in the comment.)
I thought I'd begin by simply creating a form that would:
- Validate a string as a date
- Figure the difference between now and then (assume the date is in the future)
- Display the difference as a string (but not worry about updating for now)
Each of these pieces has some interesting twists to it. For my date validation, I turned to Google and it brought up this interesting article over at Stack Overflow. The suggested the following:
function isValidDate(d) {
if ( Object.prototype.toString.call(d) !== "[object Date]" ) return false;
return !isNaN(d.getTime());
}
This function checks to see if the passed object is a Date object first. It then uses the result of getTime() to validate a that it contains a real date. So in theory, all I have to do is get my string, run new Date() on it, and check it. This worked great. Kinda. I noticed that JavaScript had no problem accepting February 29, 2011 as a date. It simply considered it March 1. It also accepted 2 as a date in the past. Yes, just 2. For my demo here, I was ok with that since my code would end up ignoring past dates anyway. The February 29 thing I decided to just spin as a feature. Yep, that's how I roll.
The difference part then becomes simple math. You can subtract one date from another and get the difference in milliseconds. Once you have that, you do some division and you're good to go. Here's the entire version of my first draft below.
//http://stackoverflow.com/questions/1353684/detecting-an-invalid-date-date-instance-in-javascript
function isValidDate(d) {
if ( Object.prototype.toString.call(d) !== "[object Date]" ) return false;
return !isNaN(d.getTime());
} $("#date").keyup(function() {
var v = $(this).val();
v = new Date(v);
if(isValidDate(v)) {
console.log(v + " is a date");
var now = new Date();
//assume v is a future date
var difference = v-now;
//only care if positive
if(difference > 0) {
var days, hours, minutes, seconds = 0; days = Math.floor(difference / (246060*1000)); if(days > 0) {
difference -= days * (246060*1000);
} if(difference > 0) {
hours = Math.floor(difference / (60601000));
if(hours > 0) {
difference -= hours * (60601000);
}
} if(difference > 0) {
minutes = Math.floor(difference / (601000));
if(minutes > 0) {
difference -= minutes * (601000);
}
} if(difference > 0) {
seconds = Math.floor(difference / 1000);
}
$("#timeleft").html(days + " days, "+ hours + " hours, "+
minutes + " minutes, " + seconds + " seconds."); console.log(days + ", "+hours+", "+minutes + ", "+ seconds);
}
}
});
})
</script> <form id="mainForm">
Date: <input type="text" name="date" id="date"><br/>
</form> <div id="timeleft"></div>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script>
$(document).ready(function() {
You can demo this yourself here. And yes - notice I'm using console.log to help debug the application. Please do not post a comment saying the code isn't working in your browser if you don't have console support.
Ok - so now all I have to do is add in the timer aspect. I rearranged my code a bit and added an interval aspect to it. Here's the entire new template.
var selectedDate;
var hb; //http://stackoverflow.com/questions/1353684/detecting-an-invalid-date-date-instance-in-javascript
function isValidDate(d) {
if ( Object.prototype.toString.call(d) !== "[object Date]" ) return false;
return !isNaN(d.getTime());
} function showDiff() {
var now = new Date();
//assume v is a future date
var difference = selectedDate-now;
//only care if positive
if(difference > 0) {
var days, hours, minutes, seconds = 0; days = Math.floor(difference / (246060*1000)); if(days > 0) {
difference -= days * (246060*1000);
} if(difference > 0) {
hours = Math.floor(difference / (60601000));
if(hours > 0) {
difference -= hours * (60601000);
}
} if(difference > 0) {
minutes = Math.floor(difference / (601000));
if(minutes > 0) {
difference -= minutes * (601000);
}
} if(difference > 0) {
seconds = Math.floor(difference / 1000);
}
$("#timeleft").html(days + " days, "+ hours + " hours, "+
minutes + " minutes, " + seconds + " seconds."); console.log(days + ", "+hours+", "+minutes + ", "+ seconds);
}
} $("#date").keyup(function() {
var v = $(this).val();
v = new Date(v);
if(isValidDate(v)) {
console.log(v + " is a date");
selectedDate = v;
//clear in case it was run
clearInterval(hb);
hb = setInterval(showDiff, 1000);
} else {
clearInterval(hb);
}
});
})
</script> <form id="mainForm">
Date: <input type="text" name="date" id="date"><br/>
</form> <div id="timeleft"></div>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script>
$(document).ready(function() {
Basically I took my display code and made it it's own function while also moving some of my values out into a global scope. This will not correctly handle things if the interval hits 0, but, it's a good example I think though. You can demo this one below.