Detecting DateTime timespan overlap in Ruby

The Scenario
Let’s say you have an instance of DateTime that represents a starting time, and you have a duration that, when added to the starting time, represents an ending time.

Let’s say you have two such things and you want to know if they overlap (or intersect, or collide). For instance:

  1. March 29, 2009 @ 10:00am; 120 minutes
  2. March 29, 2009 @ 11:00am; 90 minutes

In this example there’s one hour of overlap in which #1 stretches from 10:00am – 12:00pm and #2 stretches from 11:00am – 12:30pm.

How to detect this? I’m sure there’s many clever ways, this way is mine.

We’re Gonna Need More Monkeys
First, we need to monkey-patch some core classes. Monkey-patching is fun, it makes us feel 31337.

As an aside: it’s patently absurd that DateTime doesn’t have a built-in function for converting to a Time instance (which is layered atop the patently absurd need for Ruby to have three unique Date/Time classes… but that’s another rant)….

Anyhow, let’s monkey-patch DateTime so it can return itself as an instance of Time:

class DateTime
  def to_time
    return Time.mktime( year, month, day, hour, min, sec )
  end
end

Now we need to alter Range to detect intersections with other ranges the way Array does with its & operator. We do this with Bill Siggelkow’s clever intersection() method:

class Range
  def intersection(range)
    res = self.to_a & range.to_a
    res.empty? ? nil : (res.first..res.last)
  end
  alias_method :&, :intersection
end

Ta-da; monkey-patching complete.

DateTime is the VB.Net of Date/Time Classes
You’ve likely inferred from the code above that we won’t be using DateTime for this jaunty action, but rather instances of Time. You’d be correct. DateTime is remarkably unsuited for this sort of thing. But since DateTime is so popular and prevalent that’s where we’ll start.

Our first moment of comparison will start right now to 60 minutes in the future:

a = DateTime.now
a_start = a.to_time
a_end = a_start + ( 60 * 60 )

Our second moment in time will start 30 minutes from now to 120 minutes in the future:

b = DateTime.now
b_start = b.to_time
b_start = b_start + ( 30 * 60 )
b_end = b_start + ( 120 * 60 )

Free-range Comparison
Now that we have our times, let’s convert them into ranges:

# Turn these into ranges
a_range = (a_start.to_i..a_end.to_i)
b_range = (b_start.to_i..b_end.to_i)

and compare them:

puts "Result A-B: #{a_range.intersection( b_range )}"

As of this writing the output is:

Result A-B: 1238340860..1238342660

which indicates an overlap/intersection/collision between the two timespans, displayed as seconds. Had there been no collision, nil would have been returned instead.

2,262 views

By chris on March 29th 2009 in /dev/random

Where’d the Comments Go?

After much consideration I’ve decided to close comments on all posts on this blog. I do this for a number of reasons:

  1. Once again dealing with the comment spam has become far too annoying. Akismet has been great but enough spam was slipping through that it had become a chore to manage each morning. Under different circumstances I might continue to put up with it but…
  2. The nature of this blog is such that for the most part the posts don’t generate comments-based discussion. I use it place primarily as a storage device for things I think might be useful later and often, via Google, other people find these posts and find them useful as well. Thus, most of the comments are of the “thank you” nature, which is immensely gratifying and I thoroughly enjoy them but massaging my own ego is not a good enough reason to wade through the spam each morning!
  3. And on the few posts that do warrant discussion or criticism I’m very much on board with reading your responses on your blogs (and thus I’ve left open trackback and pingback). It seems that people do tend to write more thoughtfully when writing from their own pulpit so if you’re so inclined then link back to me and let’s run discussions that way. It’ll be fun!

All that said, whether or not you’ve ever left a comment, thanks for reading!

884 views

By chris on March 2nd 2009 in /dev/random

mysql2sqlite.rb

Introducing mysql2sqlite.rb, a Ruby script for converting MySQL databases into Sqlite databases.

Based on my googling of the web, I figure I’m one of perhaps five people in the world who’ve ever wanted to do this conversion but in case you’re number six, here you go.

Usage is pretty straight-forward. It can either be configured via command-line parameters or via a specified YAML file:

./mysql2sqlite.rb database_name database_user database_password
./mysql2sqlite.rb config.yaml

and it will spit out two files: one of the raw, converted SQL and one that is the Sqlite database. For instance, if your MySQL database was called ‘clients’ you’d end up with clients.sql and clients.sqlite.

For more details and source code see the mysql2sqlite.rb project on github.

1,213 views

By chris on March 2nd 2009 in /dev/ruby

Pagination in Merb

Pagination is one of those things that almost every site needs and for which there is absolutely no glory. Boring stuff, pagination, the Edmonton of software development. Which might explain why, when I needed to figure out how to paginate in Merb a little while ago, it was so difficult to find clear instructions on how to do so. When you find yourself in pagination you do what you have to do and then move on, with nary a glance back (apologies to both my readers in Edmonton, but you know I’m right.)

Oh sure, lots of posts on the web say “use Lori Holden’s dm-is-paginated with merb-pagination” but none say quite exactly how. And while I’m fully willing to concede I may be a little thick sometimes I found the examples on her pages somewhat under-enlightening. Hints of light bulbs but no switches, as it were.

So, embarking under the assumption that both dm-is-paginated and merb-pagination are installed on your system, here’s how I got pagination working with Merb. In this example I’ll use my DbAdminLog class as an example.

First, make sure you have the dependencies declared in dependencies.rb. At the time of this writing, these were:

dependency "merb-pagination", "0.0.1"
dependency "dm-is-paginated", "0.0.1"

In the DbAdminLog model, I added this line underneath my property definitions and validations:

is_paginated

This provides the pagination methods to the model necessary elsewhere. If you don’t add this line, no pagination for you, which is funny because it isn’t mentioned anywhere on the example pages linked above ( “…on public display in the local planning department for months, locked in a filing cabinet in the darkened cellar behind a sign that says, ‘Beware of the Leopard’”.)

In the DbAdminLogs controller I added the following to the index() method:

def index
  @current_page = ( params[:page] && ( params[:page].to_i > 0 ) ) ? params[:page].to_i : 1
  @page_count, @db_admin_logs = DbAdminLog.paginated( :o rder => [ :created_at.desc ], :conditions => cond, :page => @current_page, :per_page => 20 )
  display @db_admin_logs
end

The first line figures out which page of results we’re displaying, based on the GET parameter supplied (or not supplied, in the case of the default “1″.) The second line is the pagination magic in which I define the order to be displayed (they’re log files, so LIFO), any filtering conditions via the cond variable (if you want every record unfiltered you can leave :conditions out or replace cond with ["1"]), the page number and the number of entries to be displayed per page.

(For extra credit, swap out the && with andand and be 1337.)

That’s the controller.

The view is pleasantly straightforward. In index.html.haml I added the following at the bottom below the display code:

= paginate( @current_page, @page_count, :inner_window => 5 )

And there you have it: merb pagination.

979 views

By chris on February 27th 2009 in /dev/random

Brutal But Honest: A Developer’s Code?

I wish I knew the original source for this quote. Anyone?

Duplicate code is the root of all evil in software design. When a system is littered with many snippets of indentical, or nearly identical code, it is indicative of sloppiness, carelessness, and sheer unprofessionalism. It is the guilt-edged responsibility of all software developers to root out and eliminate duplication whenever they find it.

From An Accidental Doppelgänger in Ruby.

867 views

By chris on February 20th 2009 in /dev/random

DataMapper on Red Hat EL5

Last month I started rewriting the admin reporting section of one of my sites in Merb rather than refactor the existing PHP version, for a number of reasons. The pace of development with Ruby and Merb was tremendous and within about a week and a half I had a fully-functional, extensible reporting site developed, replete with graphs and statistics. DataMapper made tying into the legacy database, with it’s rather novel table and column naming schemes, trivial. It was all tremendously satisfying and, dare I say it for a project so unglamorous, fun.

And then I tried to get it to run on our production server, which is running Red Hat Enterprise Linux 5, and the fun went away.

I first tried installing Phusion Passenger (a product I’m absolutely enthralled by) but no such luck; it had a hell of a time with the default Apache install and lack of development headers and various file locations. I wasn’t willing to muck about with the production server in a vain attempt to force it to work at the risk of the rest of the site.

Instead I figured it ought to run just fine on Rack. And every dependent gem installed with nary a hitch save for DataMapper, specifically do_mysql, which would fail to build the native extension with the following error:

In file included from /usr/include/mysql/my_global.h:83, from do_mysql_ext.c:6:
/usr/include/mysql/my_config.h:15:28: error: my_config_i386.h: No such file or directory

The solution was ultimately provided by Dan Kubb:

We probably should make it so that all the DO driver specs can be run on installed gems, but in the meantime, checkout the source from git using the following commands:

git clone git://github.com/datamapper/do.git
cd do/data_objects
sudo rake install
cd ../do_mysql
... remove the references to my_config.h in do_mysql ...
rake compile spec
sudo rake install

This will first install the edge version of DataObjects, and then will compile and run the specs for do_mysql, and then install it. You will want to remove all references to my_config.h from the do_mysql C libs just prior to running the specs of course.

The really important bit here: “remove the references to my_config.h in do_mysql”. That’s the magic, and with that everything was good and right and Merb was run, and the users were happy.

(For the very curious, the entire DataMapper Google Groups thread.)

922 views

By chris on February 20th 2009 in /dev/random, /dev/ruby

MacSpeech Dictate

this is an attempt to write a blog using nothing but voice recognition. The software in question is called MacSpeech Dictate which I’ve been using on and off for about the last two or three months. So far it’s extremely impressive; this post as you see it is exactly as MacSpeech Dictate has translated it without any manual corrections made by me.

So far the hardest part of using MacSpeech Dictate has been cognitive; I still find it very difficult to compose speak and read at the same time, for the process of dictation is very different in the process of type. That last sentence should read: “… very different than the process of typing.”

In a nutshell, while it’s taken me probably four or five times longer to compose this post by speaking rather than typing it is pretty cool not to touch the keyboard at all. And for someone who’s recovering from an RSI MacSpeech Dictate just might be the ticket.

(I’m very impressed that recognized RSI)

633 views

By chris on February 19th 2009 in /dev/random

Smarty Gotcha: Spaces

One of the projects I’m working on uses Smarty as its templating engine. Frankly in a post-Rails/Merb/CakePHP/Django world Smarty is pretty painful to work with and its age and implementation restrictions become readily apparent… but that’s another post for another time.

This post is about how spaces in Smarty templates might trip you up. Witness the following code from a Smarty template (yes, this is kind’a like PHP but less functional and less readable):

<img src="{$deletion_item->default_image(false)}" />
<img src="{$deletion_item->default_image( false )}" />

One would tend to think, or at least I tend to think, that those two lines of HTML/Smarty code would be functionally equivalent. They look the same, save for the second developer’s preference for some extra whitespace.

However example one works with Smarty, example two throws the following error:

Fatal error: Smarty error: [in /Users/chris/Sites/dreambank/templates/en/delete_item.tpl line 42]:
syntax error: unrecognized tag: $deletion_item->default_image( false ) (Smarty_Compiler.class.php, line 446) in /Users/chris/Sites/dreambank/includes/smarty/Smarty.class.php on line 1095

So, there you have it. There’s not much more to this than to say: when working with Smarty be very careful with your whitespace.

724 views

By chris on February 16th 2009 in /dev/random

Mephisto 0.8.2

Last week Eric Kidd announced the latest version of Mephisto, the Rails-based blogging system, has been released:

Mephisto 0.8.2 is now available on the download page!

Mephisto’s JavaScript is in much better shape, and most of the remaining “tainted string” errors should now be fixed. The default article and comment filter is now Textile (instead of raw HTML), and our gem management has been cleaned up.

I mention this here, vainly, because I’m cited in the contributor notes, having fixed a couple of very minor bugs.

Mephisto still has a long, long way to go before it can lay claim to rivaling the likes of WordPress but it appears to be coming along slowly but surely ( “Don’t call me Shirley”). At the very least I can say without hesitation that the Mephisto source code is much nicer to work with that that of any other blogging platform I’ve had to dig into; in fact, compared to hacking WP, Mephisto is pure pleasure.

To check out the blog I’m running on Mephisto, take a look at Beers ‘n Booze, a blog dedicated to drinking quality beer in Vancouver.

622 views

By chris on February 16th 2009 in /dev/random

Bundling Gems With Merb

As part of the deployment of my first Merb app I wanted to make sure it contained everything it would need. This is pretty easy in Merb:

thor merb:gem:install

Once that runs through and bundles all the gems in your dependencies.rb file I knew that I should have been able to simply run:

./bin/merb

and use the bundled install of Merb. But this was failing mightily with dramatic FATAL errors. Using:

./bin/merb --verbose

shed light onto the issue. It turns out that because I was doing all my development by just running merb Merb was loading necessary dependencies that I hadn’t specified explicitly in my dependencies.rb file. Dependencies like “rmagick” and “mongrel”.

Putting all the dependencies into the file and re-running thor merb:gem:install did the trick.

Incidentally, if you attempt to do something like:

thor merb:gem:install mongrel

without having mongrel specified in dependencies.rb the install will fail. This, in hindsight, makes perfect sense to me – I like that file being the authority – however the error message generated is a bit obtuse ( in this case: "Configuration could not be confirmed: Could not find RubyGem mongrel (>= 0)").

It also appears (though I’m surprised by this so I suspect my understanding of this process is suspect) that you’ll need to declare dependencies that dependent gems rely on. Again in my case to get gruff bundled I also had to define rmagick:

dependency "rmagick", "2.9.0"
dependency "gruff", "0.3.4"

So if you’re bundling gems make sure they’re all explicitly defined and use –verbose when running Merb to double-check. And read Getting Started with Merb – Bundling Merb with your Application. It was immensely helpful.

1,305 views

By chris on January 26th 2009 in /dev/ruby