You should be automatically redirected . If not, visit
http://newlisper.wordpress.com
and update your bookmarks.

18/09/2006

My mistake

I wrote a newLISP script last October which has run reliably since then. But in August this year it suddenly started going wrong. I've managed to locate and fix the problem, but I thought the mistake was obscure enough to entertain you for a few moments. See if you can spot it before I tell you what it was.

The script takes an HTML page and converts it to an RSS/XML feed. It runs in the background (triggered by a folder action), so I never see the feedback. One of the tasks is to take a date formatted to ISO 8601:

2005-10-16 12:12:12

and convert it to the format apparently required by RSS:

Sun, 16 Oct 2005 13:12 BST

(It's possible that this is no longer necessary - I haven't looked at the specifications for years!)

This is how I described my solution at the time:


The parse function is great because it allows you to specify more than one delimiter, using a regular expression. So, this ISO format date:

2005-10-16 12:12:12

is easily split into its constituent parts if you use three delimiters, " ", "-", and ":". (The 0 says to use regular expressions.)

(parse "2005-10-16 12:12:12" { |-|:} 0)

which returns ("2005" "10" "16" "12" "12" "12"). These strings are then converted to integers by mapping the int function over the list:

(map int (parse "2005-10-16 12:12:12" { |-|:} 0))

which returns (2005 10 16 12 12 12). These numbers happen to be in the right order for use with date-value, which gives them temporal significance. Since date-value wants six integers, we can use apply to apply the date-value function directly to the six list elements.

(apply date-value (map int (parse "2005-10-16 12:12:12" { |-|:} 0)))

The integer result, 1129464732, is the time in seconds of our ISO date since 1970-1-1 00:00:00. Finally, date is now able to output a suitable string given this number. date uses the standard strftime formatting techniques, so it's easy to find the right mystic runes for RFC822:

(date (apply date-value (map int (parse "2005-10-16 12:12:12" { |-|:} 0))) 0 "%a, %d %b %Y %H:%M %Z")

I thought it was cool.

But why did this script work perfectly for 10 months, then stop working correctly? newLISP has developed in that time, of course, but I don't remember anything changing in the date/time handling functions.

Spotted my mistake yet?

Let's look closer at this line:

(map int (parse "2005-10-16 12:12:12" { |-|:} 0))

I worked on this script on an October afternoon, using times generated by the system. October is the 10th month, and after 12:00 noon the 24 hour clock always results in hours greater than 12. So int appears to work well, but it's not a good solution. If I'd tested this script on December 8th or 9th, or at 8 or 9 o'clock in the morning, I would have noticed the problem immediately.

Watch:

> (int "07")
7
> (int "08")
0
> (int "09")
0
> (int "10")
10
> (int "11")
11

Briefly, int switches to octal, when it sees a string starting with a zero! The resulting date can be nearly 10 months too early. Obviously I never tried to run this script in the morning (before 10), or at 8 or 9 minutes past the hour - if I had, I might have spotted the errors. Perhaps I did run it at 9 minutes past the hour one day, but then ran it again successfully a few moments later, and attributed the one-off failure to stray neutrinos or forgetfulness. But when August came, the script went wrong every time, of course.

The solution is to force int to use base 10:

(int s 1 10)

And the lesson is to read the manual more carefully, and test scripts more rigorously!

1 Comments:

At 20:13, Blogger don Lucio said...

You are not the first getting caught in the octal conversion trap, and it always happens converting date strings. Perhaps we should put some warning in the manual.

 

Post a Comment

Links to this post:

Create a Link

<< Home