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.
Archived Comments
There's a jQuery plugin Pretty Date: http://ejohn.org/blog/javas...
Working with dates can be difficult sometimes. I started using the Datejs library with jQuery and it makes it extremely easy to add date-based logic. I've created JS/CFM scripts to perform date/time validation, auto-date/time population, time clock functions, count downs, etc using it. It even allows the user to enter values like "now" and "next saturday" and automatically converts them to valid dates.
http://www.datejs.com/
For a sample of how easy it is to write a date-based rule using Datejs, check out this:
http://www.ssmedia.com/util...
If only the date/time decremented as soon as it was displayed ;-)
@Simon: Err, what?
Only 54 days until Christmas! Raymond, are you taking requests? I'd like to see a proof-of-concept where two people are dragging a jQueryUI themed div around on the screen together. Think "playing cards". Preferably with node.js, but you mentioned BlazeDS before when I mentioned node.
Node.js is still on my list of things to learn. Now that conference season is past I'll try to prioritize it. But BlazeDS would make it easy too. You have to use the Flash shim though. It's not a 100% JS solution.
Ray, Simon was pointing out that (at least for him and I) it doesn't seem to DO anything once you put the date in and press enter. Clicking the back button on the blank screen that it goes to seems to trigger it to display correctly at that point. (FF 7.0.2 with Firebug installed, not sure if that makes a difference).
Oh sorry - so if you don't hit enter, it should work. You were expect to just type in and not hit enter. Obviously this would be part of a larger form with more fields and stuff.
Thanks, just noticed it after posting - I'm a web 1.0 guy obviously!
Actually, it worked well for me. I was kind of just kidding saying that I wanted the date/time to do a count down.
No worries. :)
@ray Node.js does look really interesting. However, with Zeus socket connections will be CFeasy. I think that will simplify the process of pushing data tremendously and CFers can do what they do best...build solutions. Is BlazeDS still going to be the underlying socket server?
Sorry - I don't think I can comment on that yet.