Working with Fuzzy Dates and Times

You've probably seen those relative or "fuzzy" date displays on various sites across the internet, especially on sites like Twitter and Facebook where they'll say "Just Now" or "13 minutes ago" rather than a traditional date-formatted string. It makes sense, after all that's how we speak to each other. For example, if you're meeting someone, if you're asked how long you'd been there, you don't say "I got here at 11:05:46 am". Instead, you say "I got here about 5 minutes ago."

Wanting similar functionality in a project I was working on, I looked around the web and sites like CFLib to see if I could find something similar. I couldn't, so I decided to write my own. The code for the function is below.  Although it could be a lot smarter and more international-friendly, it gets the basic job done.

<cffunction name="fuzzyDateDiff" access="public" output="false" returntype="string">
	<cfargument name="date1" type="date" required="true" />
	<cfargument name="date2" type="date" required="true" />
	<cfset var intDiffUnits = 0 />
	<!--- Seconds --->
	<cfif dateDiff("s",arguments.date1,arguments.date2) lt 60>
		<cfreturn "Just now" />
	</cfif>
	<!--- Minutes --->
	<cfset intDiffUnits = dateDiff("n",arguments.date1,arguments.date2) />
	<cfif intDiffUnits lt 60>
		<cfreturn "#intDiffUnits# minute#iif(intDiffUnits gt 1,de("s"),"")# ago" />
	</cfif>
	<!--- Hours --->
	<cfset intDiffUnits = dateDiff("h",arguments.date1,arguments.date2) />
	<cfif intDiffUnits lt 24>
		<cfreturn "#intDiffUnits# hour#iif(intDiffUnits gt 1,de("s"),"")# ago" />
	</cfif>
	<!--- Days --->
	<cfset intDiffUnits = dateDiff("d",arguments.date1,arguments.date2) />
	<cfif intDiffUnits lt 7>
		<cfreturn dateFormat(arguments.date1,"dddd") />
	</cfif>
	<!--- Weeks --->
	<cfset intDiffUnits = dateDiff("ww",arguments.date1,arguments.date2) />
	<cfif intDiffUnits is 1>
		<cfreturn "Last week" />
	<cfelseif intDiffUnits lt 4>
		<cfreturn "#intDiffUnits# weeks ago" />
	</cfif>
	<!--- Months/Years --->
	<cfset intDiffUnits = dateDiff("yyyy",arguments.date1,arguments.date2) />
	<cfif intDiffUnits lt 1>
		<cfreturn dateFormat(arguments.date1,"mmmm d") />
	<cfelse>
		<cfreturn dateFormat(arguments.date1,"mmmm d, yyyy") />
	</cfif>
</cffunction>

Basic usage fuzzyDateDiff (date1, date2)

If you want the result to be realitive to the current date and time, simply use now() for date2. Happy coding!

No Comments

Backing Up a Database to Amazon S3

In a project I've been working on, I wanted a way to remotely backup a mySQL database on to Amazon S3. Because the site was hosted on a shared server, I also didn't have access to the command line.

There are probably dozens of different ways to go about doing this, but the solution I arrived at is incredibly simple and easily lends itself to a quick restore script.

First, we need to get a list of tables in the database:

<!--- Get data  --->
<cfquery name="qryTables" result="stcTables">
    SHOW TABLES
</cfquery>

We will then get all the data from each of those tables and create a WDDX packet out of each (which is a XML representation of ColdFusion query object). Each of these packets is written in to a text file.

<!--- Pull down data and export each table's data into WDDX file--->
<cfset variables.strTempDirectory = "#expandPath("./")#temp\" />
<cfloop query="qryTables">
    <cfset variables.strTableName = qryTables[listFirst(stcTables.ColumnList)] />
    <cfquery name="qryData">
        SELECT * FROM #variables.strTableName#
    </cfquery>
    <cfwddx
        action="cfml2wddx"
        input="#qryData#"
        output="variables.strData" />
    <cffile
        action="write"
        file="#variables.strTempDirectory##variables.strTableName#.txt"
        output="#variables.strData#"
        charset="utf-8" />
</cfloop>

Finally, we'll take all those text files and zip them into a single file, which will be sent to Amazon S3 (thanks to the CF 9.01 enhancement).

<!--- Package WDDX files into a ZIP file --->
<cfset variables.strBackupZip = "database-backup-#dateFormat(now(),"yyyy-mm-dd")#.zip" />
<cfzip
    action="zip"
    file="#variables.strTempDirectory##variables.strBackupZip#"
    source="#variables.strTempDirectory#"
    filter="*.txt" />

<!--- Send to Amazon S3 --->
<cffile
    action="copy"
    source="#variables.strTempDirectory##variables.strBackupZip#"
    destination="s3://your-bucket-name/database/#variables.strBackupZip#"/>

No Comments

Getting It Right

I've been using Hostek for a couple of years now, and recently inquired about using the latest version of CF for one of my sites. This was the response I got from their support team:

"We currently have all of our new servers running ColdFusion 9.0.1 if you would like we can move the domain to one of the servers running CF 9.0.1, copy the files/DSNs and the have you test it before pointing domain to new server IP... Please let us know the domain name that needs to be moved and if you would like us to proceed. There will be no downtime during control panel migration or site move."

You hear a lot of hosting horror stories out there, but don't always hear when companies get it right.

No Comments

Revisiting Table Drag and Drop with jQuery and ColdFusion

Last April, I posted an article on how I implemented table row drag and drop functionality using jQuery and ColdFusion. While it was relatively simple, since the time of my original article, I ran into a number of issues with my solution and rethought the whole thing.

The Problems

  1. My solution was based on a jQuery plug-in called TableDnD, which seems to have lost any further development. I found the source up on Google Code, but there's been absolutely no activity since the project was created in June 2008.
  2. In the original solution, after each drop of a table row, an ajax call was made to update the database. While this worked great for sorting a few things, if you wanted sort a bunch rows, the numerous ajax calls just got plain annoying, and (truthfully) probably didn't scale very well.
  3. Later implementations of my project included dynamically adding rows to the table. This pretty much broke the ability of TableDnD to do its thing most of the time. At best, you couldn't move new elements above existing rows.
  4. Finally, it was hard to visualize what you were dragging – it always stayed confined to the table. Additionally, because it was a table, it wasn't always obvious you could drag and drop.

The Solution

As it turned out, the best solution for what I wanted to do didn't involve tables at all, but rather an unordered list and the jQuery UI library. Granted, this really isn't table drag and drop (per se), but just bear with me and I think you'll see what we've done is much cooler than the previous solution. The jQuery UI library includes some basic drag and drop functionality though the use of sortable lists. A simple example of how this works can be found on the jQuery UI website.

What we can do then is create a sortable list, each list item containing a hidden field with the necessary information we need to sort everything. When the form is submitted, it is submitted along with all those hidden fields. The key lies in form.FieldNames, which as most CFers know is a comma-delimited list of all the form fields submitted. It just so happens they are submitted in the order in which they appear on the form.

<form action="index.cfm" method="post">
	<cfoutput>
		<!--- This UL will contain the items we want to sort --->
		<ul id="SortItems" class="sortable">
			<!--- Counter to ensure each hidden field has a unique name --->
			<cfset intCounter = 0 />
			<!--- Loop over list of items to sort -- in real life this is probably a query --->
			<cfloop list="#lstSortItems#" index="strIndex">
				<cfset intCounter++ />
				<li style="cursor: move;">#strIndex#<input type="hidden" name="SortItem_#intCounter#" value="#strIndex#" /></li>
			</cfloop>
		</ul>
	</cfoutput>
	<input type="submit" value="Submit" />
</form>
<!--- This JS enables jQuery UI sorting (see included JS above, they're required) --->
<script type="text/javascript">
	$(function() {
		$("#SortItems").sortable({
			revert: true
		});
	});
</script>

Finally, we loop over the contents of form.FieldNames, looking for our hidden fields using each to update the order of our list. I've attached a very simple example of how this works. Presently, the value of the hidden field is a simple text label, but in real-life would probably be a primary key of whatever it is you're sorting.

The best part these lists can be styled however you like. For example a bit from application I've been working on. See how it looks like a table?

An even better part is because we're using unordered lists and jQuery, it's super easy to add and remove elements on-the-fly. Check out .append() and .remove().

Download attachment

No Comments

Two Types of Developers

There are two types of developers:
[1] those who think arrays should be one-based
[1] those who think arrays should be zero-based

No Comments