Easier Timezone support in Rails
August 29th, 2007
As I was coding Too Many Secrets (your one-stop shop for anonymous confessions), I decided I wanted to show the times that secrets were added. No real reason, other than I was sick of having to write down the ID of the last secret I read on sites like Cave Canum. Post-it notes, while cheap, are a pain in the ass to keep successfully organized. It’s much easier to remember “Well, the last time I looked at it was around 2AM on Sunday.”
Seemed easy enough to just write out the value of created_at, and let it go at that. Unfortunately, that displays the time on the server, not the client. Since DreamHost is located in California, and I’m in Texas, it’s a 2 hour difference, and I had to find a way to display it in the client’s local time.
Err The Blog had a good article back in March called A Zoned Defense, that covered just this problem. Of course, if JavaScript or cookies were turned off, it wouldn’t work, but it’s difficult to have a fancy Web-2.0-type site without JS or cookies enabled, so let’s assume they are.
The problem with Err’s solution is that I’d like to cache the shit out of everything. If I’m taking a cookie from the user and outputting the time just for them, there’s no way I can cache it. Well, I could if I wanted to cache the fragment for every possible timezone offset. Turns out there’s a much easier way, and it requires no plugins or cookies, but still requires JavaScript.
To make things simple, I’m using MooTools, though it should be doable with Prototype, or just plain old handcrafted JS.
The secret is to use Time::to_i, which returns the value of the given time as an integer number of seconds since epoch. JavaScript’s Date class has a method that allows you to set the time using the number of milliseconds since epoch. All you have to do is multiply your timestamp as an integer by 1000.
Since most people can’t read epoch times, we’ll put it in an invisible span, like so:
<span class="hide dt"> <%= secret.created_at.to_i * 1000 %><br/> </span>
The CSS classes just hide the span from view using display: none;. What we’ll do is grab every span on the page that has the “dt” (datetime) class, read it’s text (which should be the milliseconds since epoch), use that to set the time of a JS Date object, and replace the span’s text with the new date, then drop it’s “hide” class. Add the following to your application.js in Rails.
var Timezone = {
replaceEpochWithLocalDate : function() {
var date = new Date();
var dateSpans = $$(’span.dt’)
dateSpans.each(function(dt){
var msFromEpoch = dt.getText();
date.setTime(msFromEpoch);
dt.empty().setHTML(date.toLocaleString() + ‘<br/>’);
dt.removeClass(‘hide’);
dt.removeClass(‘dt’);
});
}
};
window.addEvent(‘domready’, Timezone.replaceEpochWithLocalDate);
The “domready” event is only available in MooTools, I believe, but either way, you just need to call replaceEpochWithLocalDate() when the page loads. The “DOM Ready” event just does it as soon as the DOM structure is available, before all of the images have even finished loading, so you don’t have the somewhat jarring effect of all of the dates popping up after the page loads completely. There are probably ways to do it in Prototype or plain JS, but it’s done for me in MooTools, so I could give a crap.
The real magic in this method comes from JavaScript’s Date::toLocaleString, which prints out the date using whatever locale your browser it set to, in your local time. No more screwing around behind the scenes trying to figure out how they want it printed. The work is already done for you by their browser, and they’re either using the default, or they have it configured how they want it. Either way, they see the time they prefer. All you have to do is save and display your timestamps like normal, and stop worrying about timezones.
The only downfall to this method is that you have to have JavaScript enabled, same as the three methods provided in Err’s article. If not, you just won’t see the dates, unless you’re using a browser like Lynx, which is text-only, and no CSS or JavaScript. In that case, you’ll just see the epoch time, plain as day. But if you’re cool enough to use Lynx, you’re cool enough to decode epoch times, so it’s a win-win for everyone involved.
Ruby is my timewaster
August 27th, 2007
A few weeks ago, someone on a message board I frequent asked for some good timewasters while their boss was out of town. Someone came up with Make-A-Word, a sort of half-assed Boggle. The only goal seems to be to use the given letters to make as many words as possible in two minutes, with more points given for longer words. Unlike Boggle, the letters don’t have to be next to each other to make a valid word, which I didn’t figure out until I’d gotten horrible scores a few dozen times.
Once I figured it out, my score jumped up a bit, from the 5th percentile to the 30th. Not good enough. Like any nerd worth his salt, I had to figure out a way to beat the system. I turned to my real favorite timewaster: coding. Having fallen in love with Ruby a year or so ago, it seemed like a natural choice.
- The basic rules for the game:
- Words must be at least 2 and no more than 10 letters in length
- Words can only consist of the 9 letters given, but don’t have to include all 9 letters (duh).
- Words cannot be proper nouns (no names) or have punctuation.
- You can’t type the words in. You must use their JavaScript keypad and click with the mouse (usability nightmare).
First things first, I needed a dictionary. Being on Windows (shut up), I couldn’t use the trusty /usr/share/dict/words. However, I found a version online that was essentially the same file stripped of proper nouns and punctuation. That easily took care of one of the rules. Now that I had a dictionary, I needed a plan, and I went with my first:
- Read each line from the dictionary file (one word per line)
- If the word was too short or too long, trash it and go to the next word
- If the word consisted of only the required letters, add it to a list
All of them were fairly easy to accomplish, and the only one that might add complexity to the script would be comparing the word to verify it only contained the required letters. I decided to go with a regular expression after a few shamefully naive implementations. After that, it was just 10 quick lines away. Here’s the final implementation:
def get_possible_words(dictionary, required_letters, min_length = 1, max_length = -1)
required_letters = required_letters.split(//).sort.uniq.join # no duplicate letters
re_required_letters = Regexp.new(“^[#{required_letters}]*$“) # ensure only required letters are used
possible_words = []
File.readlines(dictionary).each { |line|
len = line.strip!.length
# only add the word if length fits and it matches the regex
possible_words << line if (len >= min_length and line =~ re_required_letters) unless (max_length >= min_length and len > max_length)
}
possible_words
end
So, am I done? I’ve got a way to get the words, but I have no idea how fast it’ll run, and I’ve got to click all those buttons. Better get the longest words first to maximize my score. Simple enough:
def get_possible_words_longest_first(dictionary, required_letters, min_length, max_length)
get_possible_words(dictionary, required_letters, min_length, max_length).sort{|x,y| y.length <=> x.length}
end
def list_possible_words(dictionary, required_letters, min_length, max_length)
get_possible_words_longest_first(dictionary, required_letters, min_length, max_length).each{ |word| puts word }
end
required_letters = ‘qwertasdfgzxcvb‘
dictionary = “words“ # /usr/share/dict/words stripped of proper nouns and punctuation
list_possible_words(dictionary, required_letters, 9, 40)
It should be obvious that required_letters is where you enter the letters from the game. I could have made this take a command-line argument, but I just run it from inside SciTE, and I’m lazy. You should also have noticed that the string I used (qwertasdfzxcvb) is longer than the 9 letters you’ll get in the game.
Remember learning that “stewardesses” is the longest English word you can type using only your left hand? That’s wrong, for three reasons:
- I can type one-handed.
- “Sweaterdresses” comes in at 14 letters, two more than “stewardesses”, which only has 12.
- The following words have the same length as “stewardesses”:
- reaggregated
- decerebrated
- decerebrates
- desegregated
- desegregates
- extravagated
- extravagates
- extravasated
- extravasates
- aftereffects
- reaggregates
- resegregated
- resegregates
- reverberated
- reverberates
- sweaterdress
- abracadabras
- watercresses
The longest right-handed words?
- hypolimnion
- homophony
There you go, 19 words you can type using only your left hand that are as long or longer than “stewardesses”, and the two longest right-handed words in the English language. You can at least say you learned something today. Back to the nerdery.
While I’ve certainly played a lot of Starcraft, there’s no way in hell I’m going to click those buttons fast enough to get more than 20 or so words in, which means I’ll be stuck in the mid 1000’s score-wise (i.e. near the top, but not the best). What’s the point of cheating if you can’t decimate the competition?
Since Make-A-Word uses buttons, and doesn’t submit, they must be using Javascript. If you have Firebug for Firefox, you may have noticed that the console allows you to enter your own Javascript, within the scope of the page. Make-A-Word’s JS simply calls a function named “addletter” on each button-click, and another named “addit” to add the word. All we have to do now is loop through the words, and for each one, loop through the letters, outputting the call to “addletter”, and ending with the call to “addit”. Here’s the code for that:
def cheat_like_hell_on_nerdtests(dictionary, required_letters, min_length, max_length)
get_possible_words_longest_first(dictionary, required_letters, min_length, max_length).each{ |word|
word.split(//).each { |letter| puts “addletter(’#{letter}‘);“ }
puts “addit();“
}
end
Simply run the script, copy the output, and run it in Firebug’s console. The cutting and pasting turned out to be the longest part, and I had to find something to occupy the remaining minute and 50 seconds. Admittedly, I could’ve had it just output a Javascript array and do the iteration in the Firebug console, but this only took me 10 minutes from start to finish, and I have no current need to make it run any faster, as you can see:

As with most games, once you know how to cheat, it ceases to be fun, and ruins it for everyone else, so I only recorded my score for one game. Hell, I had more fun writing the script than the game could ever have given me.

