Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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              
share|improve this question
add comment

5 Answers 5

up vote 7 down vote accepted

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  " "$(date -d "$(date +${ref[Y]}-${ref[m]}-01)" +'%V')" # week-number (of year) 
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                
share|improve this answer
    
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
add comment

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...

share|improve this answer
    
+1 good one :-) –  Nikhil Mulley Jan 19 '12 at 11:10
    
Not so great :-/. See my edit. –  angus Jan 19 '12 at 12:01
add comment

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)
share|improve this answer
add comment

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.
share|improve this answer
    
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
add comment

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

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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