I’m using LANG=en_US.UTF-8 for reasons on all of my systems. No big deal – but I’m also using ncal, a command line calender. On my Ubuntu box ncal decides – when in doubt – to go with the locale. This gives me wrong week numbers depending on what weekday the first of January falls. Double check with LANG=de_DE.UTF-8 ncal -w and yes – this seems right. So my locale is the problem.

Realizing that there probably will not be that much people using ncal and likely less people using it in Europe/Germany with an US locale, I decided to fix it for me. (Later I found out, there is actually a bug report for Debian)

So I fetched the sources via apt-source and started digging into it. After unpacking I found a Makefile. OK, this looks easy – let’s run make.

Hmm, make complains about a missing header file. Let’s find it – all my apt skills seem not to be enough, gonna ask the interwebz.

Asking in #ubuntu on freenode it turns out that header is a BSD-only header, but this is solved by a patch from Debian. Luckily the nice guy helping (unfortunately I forgot the name, but thanks fellow stranger \o/) gave me the right keywords to proceed: build with debuild or dpkg-buildpackage. debuild just created a package and no binary to run. A bit of searching the web gave the right switches to dpkg-buildpackage to build and keep the binaries – it’s dpkg-buildpackage -rfakeroot -b.

Still not knowing what exactly I’m doing DEB_BUILD_OPTIONS="debug nostrip noopt" dpkg-buildpackage -rfakeroot -b gave me a binary with debug symbols.

Let’s have a look at the sources. find quickly revealed a ncal.c and searching for week quickly revealed some places to look into. So, call :Termdebug in vim, set some breakpoints and dive in.

After some time it was quite clear that these lines are the problem:

263 /*
264  * If more than 3 days of this week are in the preceding year, the
265  * next week is week 1 (and the next sunday/monday is the answer),
266  * otherwise this week is week 1 and the last sunday/monday is the
267  * answer.
268  */
269 /* 3 may or may not be correct, better use what the locale says */
270 if ((wd = weekday(nd) + 1 - weekstart) >= *nl_langinfo(_NL_TIME_WEEK_1STWEEK))
271     return (nd - wd + 7);
272 else
273     return (nd - wd);

Easy target! I’d just add another command line parameter to force the use of ISO week number.

So, add the command line parameter to the case statement checking the options – I chose -W. This basically says force the usage of the correct week number (Remember, the original option for week numbers is -w). Adjust all functions to carry this flag. To not bother with the rest of the code, I also set the original flag for week numbers and add wrapper functions for the original ones:

373 case 'W':
374     if (flag_backward)
375         usage();
376     else
377         no_backward = 1;
378     flag_iso_weeks = 1;
379     flag_weeks = 1;
380     break;

Well, that was easy, let’s save, commit and recompile!

Doh, dpkg-buildpackage complains about

Patch fix-big-1stweek.patch does not remove cleanly (refresh it or enforce with -f)
/usr/share/quilt/quilt.make:23: recipe for target 'unpatch' failed

Apparently changing the code breaks the patching… Still not knowing what I do here…

A quick search on my favorite search engine returned a thread from a Debian mailing list suggesting to RTFM of quilt. As a good developer I’m craving to do this.

As the mail suggests the README is in /usr/share/doc/quilt/ – just use less. Oh, I forgot for some goddamned bloody reason the plain text README is shipped as a .gz file (come on, I’m watching HD streams!). Never mind, I just cd into /tmp unpacked it with gunzip. Oh, I forgot for some goddamned bloody reason gunzip does not just gunzip the file, it will gunzip it in place. This means, no write rights in /usr - computer says no.

OK, copy this very fine README to /tmp, gunzip it and open it in vim. (Excuse my little rant, I really like the old school Unix tools, but this trips me every time)1

The README states in a very clear and easy way how to add a patch.

So, let’s follow the manual step by step – oh, wait! Am I supposed to apply my patch to the unpatched sources files or the patched ones?

I’ll start with unpatched; if this doesn’t work I’ll try the patched ones.

Unfortunately neither works.

After a good night of sleep, I read a little bit more about quilt, the tool used to apply all the patches. Then I realised that the README is a bit confusing when it says

Add new patches:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. create a new patch: quilt new {patchname, e.g., sysctl_fix.patch}
2. add/edit file(s): quilt edit filepath
⋮
7. remove file(s) from patch: quilt remove {filepath}

Though this looks like the last step to create a patch it’s only an option; use this command if you want to remove files from the patch (This is one of the moments where you feel like really really stupid afterwards…). Doing everything again without this step and the patching works. Finally I can compile my code.

My modifications are doing what I want them to do. Digging a bit further in, with my gained knowledge about quilt and patching I saw that the original ncal code doesn’t even have the problem I face.

261 /*
262  * If more than 3 days of this week are in the preceding year, the
263  * next week is week 1 (and the next monday is the answer),
264  * otherwise this week is week 1 and the last monday is the
265  * answer.
266  */
267 if ((wd = weekday(nd)) > 3)
268     return (nd - wd + 7);
269 else
270     return (nd - wd);
271 }

What the comment describes (the comment is unfortunately kept in the patched version) is exactly the definition from ISO 8601 – though in other words.

So let’s use my added switch with the original code:

269 /*
270  * If more than 3 days of this week are in the preceding year, the
271  * next week is week 1 (and the next sunday/monday is the answer),
272  * otherwise this week is week 1 and the last sunday/monday is the
273  * answer.
274  */
275 if(iso){
276     if ((wd = weekday(nd)) > 3)
277         return (nd - wd + 7);
278     else
279         return (nd - wd);
280 }
281 /* 3 may or may not be correct, better use what the locale says */
282 if ((wd = weekday(nd) + 1 - weekstart) >= *nl_langinfo(_NL_TIME_WEEK_1STWEEK))
283     return (nd - wd + 7);
284 else
285     return (nd - wd);
286 }

This does exactly what I need.

Next big step was to change the man page to reflect the changes. Never ever having anything to do with man pages I only heard that it should be an ugly format. A quick web search revealed that it is a simple text file with markup symbols. After opening ncal.1 in vim it became quickly apparent how to write what I need just by example from the existing text.

To preview the changes to the man page use man -l FILE.

Ready, set, use quilt refresh to add the changes to the patch.

Wow, now I have the first patch to an open source tool ever in my life. As I found out in the meanwhile, there is a bug report on bugs.debian.org for my now solved problem. (Technically it is not a bug to not follow ISO 8601 when your locale tells otherwise.)

What should I do now? How to tell the world I made it better? All the FAQs and tutorials for contributing to Debian / Ubuntu tell you a lot of technical details about packaging, maintaining and whatever stuff, but not where to send the code when you actually fixed something.

Two months later…

I found no clear instructions how to submit the patch (also I got distracted (: ). Should I just send it to the maintainer? I could comment on the Debian bug with the patch, but I did only test it on Ubuntu.

After solving the problem the fun part ends and the work starts. To be honest, my motivation to contribute is not big enough to invest more time at this moment.

This is partly driven by the fact, that I found an easy workaround for the whole problem: Create an shell alias with alias ncal='LC_ALL=C ncal'. This sets the default locale before executing ncal.

Problem solved, learned interesting things, let’s move on…


  1. And yeah, I know and understand why one would design it this way and it makes sense