Perpetual Calendar - Part 3a

The previous part looked at an ictime ufun / @function that can handle arbitrary dates with correct day of the week. Now to put that in a nice-looking calendar.

The model for ths is the cal program on unix/linux - a month calendar.

The first thing we want is for players to be able to type +cal and get the calendar for the current month, with the header that looks like this:

Mo Tu We Th Fr Sa Su

We need to figure out how many days each month has. We also need to account for leapyears. The &numdays ufun takes the year as %0 and the month as %1, either as a number or as the short English form, and uses the &isleapyear ufun from the dayofweek object in Part 1.
NUM_DAYS [#1764]: switch(%1,1,31,Jan,31,3,31,Mar,31,5,31,May,31,7,31,Jul,31,8,31,Aug,31,10,31,Oct,31,12,31,Dec,31,2,if(u(isleapyear,%0),29,28),Feb,if(u(isleapyear,%0),29,28),30)

We will also probably be doing a lot of switching back and forth between month names and month numbers, so a couple of accessory ufuns will be useful.
TO_MNAME [#1764]: setq(0,switch(%0,1,January,2,February,3,March,4,April,5,May,6,June,7,July,8,August,9,September,10,October,11,November,12,December))[if(%1,left(%q0,3),%q0)]
TO_MNUM [#1764]: switch(%0,Jan*,1,Feb*,2,Mar*,3,Apr*,4,May,5,Jun*,6,Jul*,7,Aug*,8,Sep*,9,Oct*,10,Nov*,11,Dec*,12)

Now, the easy thing to do would be to output the days of the month with iter(NUMBER OF DAYS,BLAH) and then wrap or align it to the correct width, but a couple of problems exist with that.

1) We need to skip a few spaces in the beginning of the calendar, since not every month begins on a Monday (or Sunday, if you prefer).
2) We want to make a special case for the current date, and mark it so it stands out.

For #1, since we can figure out what day of the week the first of the month falls on (see? that day-of-the-week thing really comes in handy!), we can skip an appropriate number of days in the beginning. For #2, we'll use ansi(). So we wlil put together a &cal_month ufun which takes the year as %0 and the month (number, not name) as %1.

Mo Tu We Th Dr Sa Su The header
repeat(.%b,switch(ulocal(dayofweek,ulocal(to_mname,%1,short) 1 %0),Mon,0,Tue,1,Wed,2,Thu,3,Fri,4,Sat,5,Sun,6)) pads the beginning of the month with . equal to the number of spaces needed to make the first of the month fall on the correct day of the week
lnum(1,ulocal(num_days,%0,%1)) Lists the days of the month.

Combining stuff in an iter():
iter(repeat(.%b,switch(ulocal(dayofweek,ulocal(to_mname,%1,short) 1 %0),Mon,0,Tue,1,Wed,2,Thu,3,Fri,4,Sat,5,Sun,6))[lnum(1,ulocal(num_days,%0,%1))],rjust(##,2,.))
We are padding the dates with leading . for spacing issues.

We want to wrap this to the same width as the header. I used a table() with a field width of 2 and line length of 22.
table(iter(repeat(.%b,switch(ulocal(dayofweek,ulocal(to_mname,%1,short) 1 %0),Mon,0,Tue,1,Wed,2,Thu,3,Fri,4,Sat,5,Sun,6))[lnum(1,ulocal(num_days,%0,%1))],rjust(##,2,.)),2,22)

We want to hilight the current date, so we need to capture the current date in a register. We also need to edit out all the periods we used for spacing. Putting it all together, we get

&CAL_MONTH calendar=setq(1,extract(time(),5) [ulocal(to_mnum,extract(time(),2))] [extract(time(),3)])[edit(Mo Tu We Th Fr Sa Su%R[table(iter(repeat(.%b,switch(ulocal(dayofweek,ulocal(to_mname,%1,short) 1 %0),Mon,0,Tue,1,Wed,2,Thu,3,Fri,4,Sat,5,Sun,6))[lnum(1,ulocal(num_days,%0,%1))],switch(%0 %1 [rjust(##,2,0)],%q1,ansi(i,##),##)),2,.)),2,22)]),.,%b)]

We will call this with a $command:
+CAL2 [#1764]: $+cal:@pemit %#=u(cal_month,last(time()),u(to_mnum,first(rest(time()))),1)

The output should looks like this, except with ansi invert instead of the strikeout:

Mo Tu We Th Fr Sa Su
       1  2  3  4  5
 6  7  8  9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31

In part 3b we will look at how to extend the +cal $command to be more flexible, and add more info.