/*
* Javascript Humane Dates
* Copyright (c) 2008 Dean Landolt (deanlandolt.com)
* Re-write by Zach Leatherman (zachleat.com)
*
* Adopted from the John Resig's pretty.js
* at http://ejohn.org/blog/javascript-pretty-date
* and henrah's proposed modification
* at http://ejohn.org/blog/javascript-pretty-date/#comment-297458
*
* Licensed under the MIT license.
*/
/**
* @param {String or Date} date_str either an ISO8601 date string or a Date object.
* Note: ISO8601 dates are always formatted using UTC/GMT timezone.
* Example: 2009-06-03T20:06:44Z
*/
function humane_date(date_str){
var time_formats = [
[1, '1 second'],
[60, 'seconds', 1],
[90, '1 Minute'], // 60*1.5
[3600, 'Minutes', 60], // 60*60, 60
[5400, '1 Hour'], // 60*60*1.5
[86400, 'Hours', 3600], // 60*60*24, 60*60
[129600, '1 Day'], // 60*60*24*1.5
[604800, 'Days', 86400], // 60*60*24*7, 60*60*24
[907200, '1 Week'], // 60*60*24*7*1.5
[2628000, 'Weeks', 604800], // 60*60*24*(365/12), 60*60*24*7
[3942000, '1 Month'], // 60*60*24*(365/12)*1.5
[31536000, 'Months', 2628000], // 60*60*24*365, 60*60*24*(365/12)
[47304000, '1 Year'], // 60*60*24*365*1.5
[3153600000, 'Years', 31536000], // 60*60*24*365*100, 60*60*24*365
[4730400000, '1 Century'], // 60*60*24*365*100*1.5
];
var time = ('' + date_str).replace(/-/g,"/").replace(/[TZ]/g," "),
dt = new Date,
seconds = (date_str instanceof Date ? (dt - date_str)
: (dt - new Date(time) + (dt.getTimezoneOffset() * 60000))) / 1000,
token = ' ago',
i = 0,
format;
if (seconds < 0) {
seconds = Math.abs(seconds);
token = ' from now';
}
while (format = time_formats[i++]) {
if (seconds < format[0]) {
if (format.length == 2) {
return format[1] + token
} else {
return Math.round(seconds / format[2]) + ' ' + format[1] + token;
}
}
}
// overflow for centuries
if(seconds > 4730400000)
return Math.round(seconds / 4730400000) + ' Centuries' + token;
return date_str;
};
if(typeof jQuery != 'undefined') {
jQuery.fn.humane_dates = function(){
return this.each(function(){
var date = humane_date(this.title);
if(date && jQuery(this).text() != date) // don't modify the dom if we don't have to
jQuery(this).text(date);
});
};
}
Monday, July 06, 2009
Tweaks to Zach's humane Date script
Friday, April 17, 2009
JSON Annotations
Suppose you want a generic parser that will turn JSON into a strongly-typed Java object graph. For certain kinds of object graphs you either have to parse into generic objects and then convert, or hint the parser at every step -- a tedious process. What if those type hints were embedded within the JSON itself?
Option 1. Processing Instructions
This approach specifies a reserved prefix for meta-attributes, of which @type is one. Borrowing an idea from sed's variable syntax for find-and-replace separators, you can wrap the entire payload in one outer object that specifies what the metadata prefix is. That way if a given payload would clash with the default metadata prefix, it can be changed easily.
{ "meta": "@"
"data": {
"@type": "java.util.HashMap",
"Joe" : {
"@type": "jeoftp.Person"
"firstName": "Joseph",
"lastName": "Robinson",
"age": 45,
"birthday": new Date(23493822934)
},
"Sue" : {
"@type": "jeoftp.Person"
...
}
}
}
Option 2. Wrap Typed Objects with Annotations
Another way to annotate objects with their types is to wrap each object that needs specific type information within a meta-object that tells us its type. The parser would need to know how to recognize a meta-object vice a regular one.
{
"@class" : "java.util.HashMap",
"@data" : {
"Joe" : {
"@class" : "jeoftp.Person",
"@data" : {
"firstName": "Joseph",
"lastName": "Robinson",
"age": 45,
"birthday": new Date(23493822934)
}
},
"Sue" : {
"@class" : "jeoftp.Person",
"@data" : { ... }
}
}
}
Option 3. Embed Annotations in Comments
You could also embed type information in comments. This approach borrows from Javadoc, Python docstrings, etc. This would certainly break some JSON parsers, but the advantage is that it strictly demarcates metadata from data.
{
/* @type java.util.HashMap */
"Joe" : {
/* @type jeoftp.Person */
"firstName": "Joseph",
"lastName": "Robinson",
"age": 45,
"birthday": new Date(23493822934)
},
"Sue" : {
/* @type jeoftp.Person */
...
}
}
If your type parser was smart enough, perhaps you could even handle generics:
{
/* @type java.util.HashMap */
"Joe" : {
"firstName": "Joseph",
"lastName": "Robinson",
"age": 45,
"birthday": new Date(23493822934)
},
"Sue" : {
...
}
}
I haven't settled on one approach yet, but after reading this I'm leaning toward option 1 -- specifying a metadata prefix -- because it is valid in curent JSON syntax, easy to parse, and relatively compact.
Monday, March 23, 2009
Determine the Java TimeZone of your web visitors using client-side javascript
You want your Java web application to know what TimeZone your visitors are in. You can use client-side javascript code like the following to produce a timezone string that Java can understand:
function getTimezone() {
var tzo = new Date().getTimezoneOffset(); //returns timezone offset in minutes
function pad(num, digits) {
num = String(num); while (num.length < digits) { num="0"+num; }; return num;
}
return "GMT" + (tzo > 0 ? "-" : "+") + pad(Math.floor(tzo/60), 2) + ":" + pad(tzo%60, 2);
}
This will produce strings like GMT-04:00 and GMT-07:00 which you can then pass up to the server and into TimeZone.getTimeZone() to produce a TimeZone object.
Thursday, March 19, 2009
Timing Oracle SQL queries with Glassbox
Glassbox, the slick AOP tool for automatically diagnosing your Java webapp's performance bottlenecks, first caught my eye nearly a year ago. In consulting activities, where off-the-shelf products and custom solution code mix, it can be very difficult to determine whose code is at fault when web pages load slowly.
Glassbox comes to the rescue by tracking each web request through every layer of the J2EE stack. It identifies slow requests and groups them by URL, then identifies the controllers, methods, database queries, network operations, etc. that are causing the operation to be slow. It comes packaged in a war file: just drop it into the app server, hit the webapp root from a browser, and let it autoconfigure itself.
The hitch I ran into was that Glassbox had trouble instrumenting our Oracle JDBC driver, so it couldn't identify the precise SQL queries that were causing slowness. I went to the forums initially, then forgot about it for a while when an answer wasn't readily available.
I became interested again over the past couple days, so I contacted Ron Bodkin directly: he suggested the AspectJ load-time weaving is probably at issue. He gave me some quick pointers on how to use the ajc tool to create an instrumented version of the Oracle JDBC driver so that it wouldn't need to be instrumented on the fly at load-time. I gave it a shot and it worked!
In testing, I purposely crafted a slow query by using a ridiculous number of pointless joins, to verify whether Glassbox would call me out on it.
The cookbook
First, grab Glassbox 2.0 and AspectJ 1.5.3. (To do offline weaving you'll need aspectjtools.jar, which doesn't ship with Glassbox).
Create a project directory and drop in the jar file you want to weave. Then create a lib/ subdirectory. Add aspectjtools.jar from the AspectJ download, and then explode glassbox.war into it. Find ./lib/glassbox.war/install/glassboxMonitor.jar and extract the file META-INF/Xlint.properties from it; place it in the root directory of the project.
Finally, run the following ant script:
Note: Adjust your path to AspectJ as needed.
Things to study when I get a minute
- AspectJ, AJDT, Glassbox - AOP supertools
- Jaxer, Axiom Stack, Helma, AppJet - web platforms that leverage the symmetry of using javascript on both the server and client side
- Lucene, Berkeley DB - faster than a database, given the right use case
- Terracotta - enforces java memory model across a grid of servers
Tuesday, March 17, 2009
Reason #82734 why I hate Oracle JDBC drivers
Are you wondering why the date/time values you read from Oracle 10g's jdbc driver are off by several hours? It's because the 9i/10g drivers don't properly account for the jdbc client's time zone when writing the date value.
So what's the fix?
Suppose you know your server date/time values are stored in GMT. You can call getTimestamp() with a GMT calendar, and then you'll get the right values.
public class OracleJdbcDriverSucks {
public static Calendar GMT_CALENDAR = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
public static Date getGMTDate(ResultSet rs, String column) throws SQLException {
return new Date(rs.getTimestamp(column, GMT_CALENDAR).getTime());
}
}
Thursday, February 12, 2009
jquery rocks! now back to GWT
I just finished a project that includes a rich, complex web interface built on jquery, and now it's time to report the good, the bad, and the ugly.
The Good
Certain things can be expressed with incredible succinctness in jquery. In a finished project there are snippets of code sprinkled throughout that really evoke a smile. Certainly other projects have a lot to learn from the expressive power of jquery.
The Bad
One core principle behind jquery is the idea of progressive enhancement; that is, you start with HTML markup that would render reasonably well in a browser even with no javascript enabled. Then you add in some well placed jquery "enhancements" via selectors, plugins, etc. and the page literally comes alive.
The problem is, projects have deadlines. When a business person funds a project, they don't really care if the site runs without javascript enabled. So, in the push to launch the project, the ideals of progressive enhancement quickly fall by the wayside.
The Ugly
In reaching for the ideal of progressive enhancement, jquery performs weakly in one important feature of traditional software engineering: encapsulation. Certainly the code within plugins is encapsulated, but the markup on which those plugins operate is completely exposed. The very power of selectors and plugins for progressively enhancing existing markup are also their downfall: when the markup needs to change for some reason, you must carefully review/debug/bang-your-head-over all the selectors and plugins and the assumptions they made about the markup that are no longer true.
Back to GWT
I'm starting a new project now, and I'll be doing this one on GWT. Is it because GWT is better than jquery? No, that is certainly not true in the general case. To breathe new life into an existing web site, there is perhaps nothing better than jquery. No, I'll be doing this project on GWT because progressive enhancement is not relevant. The ability to encapsulate DOM structures and behavior together, and then refactor wildly (with no fear of breaking carefully crafted selectors and plugins) will be essential to this project. And so it's back to GWT.
Fear not, jquery, my friend. We have built great things together, and there will be other times.
