2008-10-26

Calendars and Dates

Here’s a new gadget that might prove useful to those people determined to create a replacement for DSOrganise:

Yep, it’s a calendar/date picker. It raises EVENT_ACTION events each time a new day is clicked. The left and right arrows page through the months and years.

Working out the layout of a calendar is actually very simple. It’s much simpler than I was expecting it to be. First of all, we make a few assumptions:

  • We will only show one month at a time (though we will show days that border the start and end of the current month)
  • We will always have 7 columns (7 days in the week)
  • We will always have 6 rows (this caters for the worst-case scenario, or largest number of visible days, and sticking to this reduces the complexity of the algorithms we need to use)

Once we’ve got that set up, we need to know what day of the week the current month starts on. The magic for this is handled inside a new Date class that can manipulate the days, months and years of a given date, the logic for which came from here (note that the rest of that site seems to be totally bonkers). If we know what day of the week the month starts on, we can generate all of the preceding day buttons, then the buttons for the current month, and finally any buttons from the next month.

Using the Calendar gadget is simple. It automatically grows and shrinks to fit within the dimensions specified in its constructor, it raises an EVENT_ACTION event when a day is clicked (as noted previously), and it has methods to set and get the date. It doesn’t inherit from the Window class, so it can be added to screens or an existing window alongside other gadges. There’s an example in the “examples” folder.

Two caveats with the Calendar and Date classes at the moment. I’m not sure that Date::addDays() works correctly for negative days (need to write a test, but as it’s not used by the Calendar it’s not a problem right now) and the Calendar doesn’t have a resize() function.

Comments

Jeff on 2008-10-27 at 03:03 said:

Here’s another way of computing dayofweek (dow) from d/m/y - I picked it up from the Internet a long time back, from a site that since disappeared. We haven’t had any problems with it and it has a few less ‘modulus’ operations than yours though perhaps a few more divides


static unsigned long dtodow(long d,long m,long y)
{
    /* see http://www.pip.dknet.dk/~c-t/cal/node3.html, sec 2.5 */
    long aa = (14 - m)/12;
    long yy = y - aa;
    long mm = m + 12*aa - 2;

    return (    d
        +       yy
        +       (int)(yy/4)
        -       (int)(yy/100)
        +       (int)(yy/400)
        +       (int)((31*mm)/12)
        ) % 7;
}

We have equally gnarly functions (from the same source) for converting to/fro julian dates, which seem pointless but make it way easier to do things like ‘compute date - 1000’ which have to take leap years into account, etc.

Actually, now that I look at it, I think your ‘dayoffset=day%7’ is probably redundant since you just add it in to an expression and then do the %7 again. ie, (a+(b%7))%7==(a+b)%7

(I hate modulo-arithmetic…)

None of the values you are kicking around will be big enough to overflow a long, which is why most people do that stuff.

Since I’m not sure how aggressive devkitpro is at common sub-expression-recognition, it would be a good idea to factor out year/100

Jeff on 2008-10-27 at 03:05 said:

For the benefit of readers who don’t know, the key phrase you should be googling on this is “Zellers Congruence”

ant on 2008-10-27 at 09:17 said:

Hey, that’s a much tidier solution. I’ll replace my current function with that one. Thanks!

ant on 2008-10-27 at 09:39 said:

Here’s the version I’ll use - it returns the day in ISO format* (Monday = 1, Sunday = 7). It’s just your code with a minor change to the return value and more descriptive names for things. I’ll see if I can get the calendar to show “MTWTFSS” instead of “SMTWTFS”.


u8 getDayOfWeek(u8 day, u8 month, u16 year) {

    u8 aa = (14 - month) / 12;
    u16 yy = year - aa;
    u16 mm = (month + (12 * aa)) - 2;

    u8 dayOfWeek = (day + yy + (yy / 4) - (yy / 100) + (yy / 400) + ((31 * mm) / 12)) % 7;

    // Get day of week in ISO format
    return ((dayOfWeek + 6) % 7) + 1;
}

*The alternative to returning in ISO format is, of course, to declare “Sunday = 0” to be a new standard, produce some crap documentation and bung it in the post to the ISO along with a cheque for a fiver. They’ll fast-track it through the standards process soon enough.