14

Don't you just love it when two commands each do one thing you want but neither do both?

This is what cal does. Nice formatting. Lacks week numbers though:

$ cal
    January 2012      
Su Mo Tu We Th Fr Sa  
 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              

This is what ncal does. Weird formatting, but with week numbers:

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

The kind of output I want, actually a crossbreed between cal and ncal -w:

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

If neither of these commands suit your needs you can use gcal to do what you want instead.

Example

$ gcal -K

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

Prints the week number in the last column to the right.

References

  • gcal --starting-day=Monday --with-week-number fits my needs more yet this tool is great. – k0pernikus Dec 21 '16 at 10:48
0

I don't really want to install gcal, and I could not get it to work properly on my system. So without gcal, funollet's answer is indeed the best solution. I just changed it a tiny bit so that it also preserves formatting such as day highlight, colors etc. using the script command. I also removed redundant characters, making it suitable as an alias, as such:

alias today="paste -d' ' <((echo ' '{,};ncal -w|tail -1)|fold -w3) <(script /dev/null -qc cal)"

Or replace cal with a new function. However, with this you cannot do things like cal -3. You get the correct weeks but they are not aligned properly. A simple but not complete solution is to check if cal is called without arguments, like so:

function cal {
    if [[ $# -eq 0 ]]; then
        paste -d' ' <((echo ' '{,};ncal -w $@|tail -1)|fold -w3) <(script /dev/null -qc "cal $@")
    else
        /usr/bin/cal "$@"
    fi
}
1

Without enough rep to comment on the answer by slm, gcal can be installed on Mac OS using brew install gcal.

(I found the answer here first, then this answer on ask different, but the unix answer lacked all the information I needed to install on the mac.)

0

I did this script to generate on-duty calendar for teams. It generates calendar with week numbers and assigning names to them (simply round-robin). After output is printed you just need to copy it to excel and do "text to column" by ; and once again perform "text to column" on first column by using "fixed" option. The benefit of this calendar (comparing with horizontal orientation) is that you can use auto-filter (Alt+d f f) and find only your weeks.

#!/usr/bin/ksh
y=2017
set -A team1 Petars Justas Richard Jukka Jesper
set -A team2 Modestas Timo Mats Andreas

i1=0
i2=0
wn=1
own=1

echo "WN     Month        Mo Tu We Th Fr Sa Su ;Team1 ;Team2"
ycal=`for m in 1 2 3 4 5 6 7 8 9 10 11 12
do
IFS=
        cal -m $m $y|egrep -v "Mo|^$"|while read line
        do
                echo $line|grep -q $y && mname=$line || print $mname $line|sed 's/'$y'//'
        done
done`
IFS=
echo "$ycal"|while read line2
do
IFS=
        echo "$line2"|grep -v "1  2  3  4  5  6  7"|egrep -q "* 1 |* 1$" || let wn++
        [ $own -ne $wn ] && { \
                [ $i1 -eq ${#team1[@]}-1 ] && i1=0 || let i1++;\
                [ $i2 -eq ${#team2[@]}-1 ] && i2=0 || let i2++; }
        printf '%2s %s ;%s ;%s\n' $wn $line2 ${team1[$i1]} ${team2[$i2]}
        own=$wn
done

Sorry, code a bit nasty... and no comments... but it works well ;)

0

Here is my solution which is shorter in code and works fine for other dates than the current month. Sorry for the US people, this uses ISO format, i.e. 1st day of week is monday. This is done with the -m option for cal and the %V option for date

#!/bin/bash
if [ "$1" = "" ]
then
  when="now"
else
  when="$1"
fi
y=$(date --date "$when" +%Y )
if [ $? -ne 0 ]
then
  exit
fi
m=$(date --date "$when" +%m )
w=$(date --date $(printf %4d%02d01 $y $m) +%V)
cal -m $m $y |
  awk -v w=$w '
    NR>2{ww=w++}
    $0!=""{printf "%2s %s\n", ww , $0}
  '
9

This highlights today's date, and can display any month via $1 in the form: YYYY-mm-dd ... It defaults to today's date

It is set up to show ISO week numbers, and the first weekday being Monday.

#!/bin/bash
# Input reference date is expected in  'YYYY-mm-dd' format
#
today=($(date '+%Y %m %d')); Y=0; m=1; d=2                # establish today's date
[[ -z $1 ]] && ref=(${today[@]}) || ref=(${1//-/ })       # get input date
dNbA=$(date --date="$(date +%Y-%m-01)" +'%u')             # day-number of 1st day of reference month
today[m]=$((10#${today[m]})); ref[m]=$((10#${ref[m]}))    # remove leading zero (octal clash)
today[d]=$((10#${today[d]})); ref[d]=$((10#${ref[d]}))    # remove leading zero (octal clash)
nxtm=$(( ref[m]==12 ?1       :ref[m]+1 ))                 # month-number of next month
nxtY=$(( ref[m]==12 ?ref[Y]+1:ref[Y]   ))                 # year-number of next month
nxtA="$nxtY-$nxtm-1"                                      # date of 1st day of next month
refZ=$(date --date "$(date +$nxtA) yesterday" +%Y-%m-%d)  # date of last day of reference  month
days=$(date --date="$refZ" '+%d')                         # days in reference month

h1="$(date --date="${ref[Y]}-${ref[m]}-${ref[d]}" '+%B %Y')" # header 1 
h2="Mo Tu We Th Fr Sa Su"                                    # header 2 
printf "    %$(((${#h2}-${#h1}-1)/2))s%s\n" " " "$h1"
printf "    %s\n" "$h2"
# print week rows   
printf "%2d  " "$((10#$(date -d "$(date +${ref[Y]}-${ref[m]}-01)" +'%V')))" # week-number (of year) with suppressed leading 0
printf "%$(((dNbA-1)*3))s"  # lead spaces (before start of month)
dNbW=$dNbA  # day-number of week
dNbM=1      # day-number of month
while ((dNbM <= days)) ;do
    if (( today[Y]==ref[Y] &&  
          today[m]==ref[m] && 
          today[d]==dNbM )) ;then
        printf "\x1b[7m%2d\x1b[0m " "$dNbM" # highlight today's date 
    else
        printf "%2d " "$dNbM"
    fi
    ((dNbM++))
    if ((dNbW  >=7)) ;then
        cdate=$((10#$(date -d "$(date +${ref[Y]}-${ref[m]}-$dNbM)" +'%V'))) # remove leading zero (octal clash)
        printf "\n%2d  " "$cdate" # week-number of year
        dNbW=0
    fi
    ((dNbW++))
done
printf "%$(((8-dNbW)*3))s\n" # trailing spaces (after end of month)

Here is this month's display (with 20 highlighted)

       January 2012
    Mo Tu We Th Fr Sa Su
52                     1 
 1   2  3  4  5  6  7  8 
 2   9 10 11 12 13 14 15 
 3  16 17 18 19 20 21 22 
 4  23 24 25 26 27 28 29 
 5  30 31                
  • I didn't even notice the iso-issue. That will do :) – k0pernikus Jan 20 '12 at 11:18
  • 1
    @k0pernikus: I've just modified the script.. (about 35 minutes after you posted the above comment).. It was getting caught up on 08 and 09 being interpreted as octal instead of decimal .. It should be fine now... – Peter.O Jan 20 '12 at 12:05
4

One way using Perl (my output language of the cal command is Spanish, but I hope result doesn't vary from English):

$ cal | perl -pe 'if ( m/\A\s*\d/ ) { s/\A/++$i . qq[ ] x 2/e } else { s/\A/qq[ ] x 3/e }'

Output:

       enero de 2012   
   lu ma mi ju vi sá do
1                     1
2   2  3  4  5  6  7  8
3   9 10 11 12 13 14 15
4  16 17 18 19 20 21 22
5  23 24 25 26 27 28 29
6  30 31

Explanation:

-pe                     # For every input line from previous pipe, execute  next
                        # instructions and print to output.
if ( m/\A\s*\d/ )       # If line begins with a digit omitting spaces...
s/\A/++$i . qq[ ] x 2/e # Insert at the beginning of the line a counter plus two spaces.
else                    # else...
s/\A/qq[ ] x 3/e        # Insert three spaces at the beginning of the line.
  • how will this work for subsequent months. February weeks do not start from again 1, they start from either 5/6 similarly March will be from 9/10. – Nikhil Mulley Jan 19 '12 at 10:41
  • cal 02 2012, will it have this failed?? – Nikhil Mulley Jan 19 '12 at 10:42
  • @Nikhil: I don't understand what you mean. Can you try to explain it more in depth? Does the script fail with cal 02 2012? It seemed to work in my test. – Birei Jan 19 '12 at 10:52
  • @Nikhil: Ah, ok. I missunderstood the question. It means absolute week numbers and not relative to each month. I will delete my wrong answer in a while. – Birei Jan 19 '12 at 10:55
  • 1
    cool..do not delete the answer, please keep it for reference. – Nikhil Mulley Jan 19 '12 at 11:09
7

Generate the weeks sequence with ncal and use paste for having both outputs side by side.

$ paste <(echo; echo; ncal -w | tail -1 | xargs -n1 printf '%2d\n') <(cal)

If you don't like having tabs as delimiters just add something like sed 's/\t/ /'


Edit: way simpler, no need to care about tabs:

$ paste -d' ' <((echo -n '   '; ncal -w | tail -1 )| fold -w 3) <(cal)
  • Elegant and embodies many of ESR's 17 Unix Rules. Should be the accepted answer as it truly combines the output of cal and ncal (other approaches replicate behavior of either cal or ncal). – bishop Nov 30 '16 at 14:53
7

You can use nl to number the lines (that's the program purpose :). But you need to extract the first week in the month from somewhere. It can be done from ncal itself:

$ ncal -w 2 2012 | tail -1 | awk '{print $1}'
5

We insert this as a parameter to nl's option -v (starting line number), and tell it to only number lines with numbers or spaces.

$ cal 2 2012 | nl -bp'^[0-9 ]\+$' -w2 -s'  ' -v$(ncal -w 2 2012 | tail -1 | awk '{print $1}')
       February 2012
    Su Mo Tu We Th Fr Sa
 5            1  2  3  4
 6   5  6  7  8  9 10 11
 7  12 13 14 15 16 17 18
 8  19 20 21 22 23 24 25
 9  26 27 28 29

This is all awfully fragile though. Anyway, if you aren't going to need cal's more advanced options, it will work. You can put it in a file and replace "$@" where I put 2 2012.


EDIT: But this is WRONG! I just noticed that the first week in January can have number 52 or 53! So we just either have to make an exception for January, or just extract all the week numbers from ncal and apply them to the output of cal.

This is the solution I thought originally, but I thought (erroneously) I would simplify it using nl. It uses paste, which merges files side-by-side. Since there isn't any file, we have to use the bashism <(...); that's what I was trying to avoid.

Our first "file" will be a list of the week numbers, with two empty lines at the beginning:

$ printf '   \n   \n' && printf '%2d \n' $(ncal -w 1 2011 | tail -1)


52
 1
 2
 3
 4
 5

The second one, just the output of cal. All together, as parameters to paste:

$ paste -d' ' <(printf '   \n   \n' && printf '%2d \n' $(ncal -w 1 2011 | tail -1)) <(cal 1 2011)
        January 2011
    Su Mo Tu We Th Fr Sa
52                     1
 1   2  3  4  5  6  7  8
 2   9 10 11 12 13 14 15
 3  16 17 18 19 20 21 22
 4  23 24 25 26 27 28 29
 5  30 31

Much messier and incompatible that the other one. En fin...

  • Not so great :-/. See my edit. – angus Jan 19 '12 at 12:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.