Cal

January 1, 2010

Happy New Year! And best wishes for a healthy and prosperous year for all my readers and their families.

On the first day of a new year it seems appropriate to write an exercise based on calendars. Unix provides a cal command to print calendars. There are several forms:

  • cal — prints a calendar for the current month
  • cal year — prints a twelve-month calendar for the specified year; note that year 10 occurred during the time of Christ, so you must specify 2010 for the current year
  • cal month year — prints a calendar for the specified month and year; the month is given as a number from 1 to 12
  • cal -3 — prints a three-month calendar for the prior month, current month and next month

The current date is highlighted wherever it appears.

Your task is to implement cal. When you are finished, you are welcome to read or run a suggested solution, or to post your own solution or discuss the exercise in the comments below.

Advertisement

Pages: 1 2

5 Responses to “Cal”

  1. […] today’s Programming Praxis exercise we have to implement the Unix utility cal, which prints calendars. […]

  2. Remco Niemeijer said

    My Haskell solution (see http://bonsaicode.wordpress.com/2010/01/01/programming-praxis-cal/ for a version with comments):

    import Data.List
    import Data.List.Split
    import qualified Data.Text as T
    import Data.Time
    import System.Environment
    import System.Locale
    
    days :: Integer -> Int -> [Day]
    days y m = map (fromGregorian y m) [1..gregorianMonthLength y m]
    
    fmt :: FormatTime t => String -> t -> String
    fmt = formatTime defaultTimeLocale
    
    monthCal :: Integer -> Int -> String
    monthCal y m = unlines $ (T.unpack . (T.center 20 ' ') . T.pack .
        fmt "%B %Y" $ fromGregorian y m 1) : "Su Mo Tu We Th Fr Sa" :
        (map unwords . take 6 . chunk 7 $
            replicate (read . fmt "%w" . head $ days y m) "  " ++
            map (fmt "%e") (days y m) ++ repeat "  ")
    
    showCal :: [String] -> IO ()
    showCal = putStrLn . unlines . map (unlines . map
                (intercalate "  ") . transpose . map lines) . chunk 3
    
    surround :: UTCTime -> [String]
    surround d = map ((\(y, m, _) -> monthCal y m) . toGregorian .
                   (`addGregorianMonthsRollOver` utctDay d)) [-1..1]
    
    main :: IO ()
    main = do args <- getArgs
              now  <- getCurrentTime
              let (curYear, curMonth) = read $ fmt "(%Y,%m)" now
              case args of
                  [y,m]  -> showCal [monthCal (read y) (read m)]
                  ["-3"] -> showCal $ surround now
                  [y]    -> showCal $ map (monthCal $ read y) [1..12]
                  []     -> showCal [monthCal curYear curMonth]
                  _      -> error "Invlaid parameters"
    
  3. johnwcowan said

    There’s a bug here: compare the output of your cal with the standard one (either BSD or GNU) on September 1752 or any earlier date.

    “Give us back our eleven days!”

  4. adactuslatem said

    C++ style

    #include
    #include
    #include
    using namespace std;

    const int MONTHS_IN_YEAR = 12;
    const int DAYS_IN_WEEK = 7;
    const int MAX_DAYS_IN_MONTH = 31;
    const int MIN_DAYS_IN_MONTH = 28;
    const int LEAP_YEAR_FACTOR = 4;
    const int DAY_WIDTH = 3;

    const int JAN = 1;
    const int FEB = 2;
    const int MAR = 3;
    const int APR = 4;
    const int MAY = 5;
    const int JUN = 6;
    const int JUL = 7;
    const int AUG = 8;
    const int SEP = 9;
    const int OCT = 10;
    const int NOV = 11;
    const int DEC = 12;

    void PrintMonthHeader(int month);
    string MonthString(int month);
    void PrintMonthBody(int year, int month, int& day);
    void PrintMonthDates(int& day, int lastday);
    void PrintSelectedMonths(int year, int start_month,
    int day, int total_months);

    int main()
    {
    int month, totalMonths, startDay, year;

    cout <> year;

    while (year != 0)
    {
    cout <> month;
    cout <> totalMonths;
    cout << "What day of the week does the starting month fall on?\n";
    cout <> startDay;
    PrintSelectedMonths(year, month, startDay, totalMonths);
    cout <> year;
    }
    return 0;
    }

    //———————————————————————–
    // This function uses the integer month to print out that months name.
    // It also prints out the one letter abbreviations for Sunday
    // through Saturday and the underscores under them.
    // Parameters: (in)
    //———————————————————————–
    void PrintMonthHeader(int month)
    {
    cout << "\n" << " " << MonthString(month) << '\n';
    cout << " S M T W T F S\n";
    cout < 0)
    {
    if (day == DAYS_IN_WEEK)
    daySpaces = 0;
    else
    {
    cout << " ";
    daySpaces–;
    }
    }
    while (daysInMonth <= lastday)
    {
    if (day % DAYS_IN_WEEK == 0 && daysInMonth != 1)
    {
    cout << "\n";
    day = 0;
    }

    cout << setw(DAY_WIDTH) << daysInMonth;
    daysInMonth++;
    day++;
    }
    cout << endl;
    return;
    }

    //———————————————————————–
    // This function prints out the calendar for the given number
    // of months (total_months), the given year, using the
    // start_month and starting day (day). The function repeatedly
    // calls PrintMonthHeader and PrintMonthBody.
    // Parameters: (in, in, in, in)
    //———————————————————————–
    void PrintSelectedMonths(int year, int start_month,
    int day, int total_months)
    {
    int monthCount = 0;
    cout << "\n\n" << " " << year << "\n" ;
    while(monthCount < total_months)
    {
    PrintMonthHeader(start_month);
    PrintMonthBody(year, start_month, day);
    if (start_month % 12 == 0 && total_months != 12)
    {
    year++;
    cout << "\n\n\n" << " " << year << "\n" ;
    monthCount = 1;
    start_month = 0;
    }
    monthCount++;
    start_month++;
    }
    return;
    }

  5. Mike said
    import calendar
    import datetime as dt
    import itertools as it
    import re
    
    calendar.setfirstweekday(calendar.SUNDAY)
    
    def onemonth( month=None, year=None ):
        today = dt.date.today()
        month = month or today.month
        year = year or today.year
        
        mstring = re.sub( r"\n", r" \n ",  calendar.month( year, month ) )
    
        if year == today.year and month == today.month:
            mstring = re.sub( r" (%2s) "%today.day, r"[\1]", mstring )
    
        return mstring.splitlines()
    
    
    def threemonth( month=None, year=None ):
        today = dt.date.today()
        month = month or today.month
        year = year or today.year
    
        m1 = onemonth( month-1, year ) if month > 1 else onemonth( 12, year-1 )
        m2 = onemonth( month,   year )
        m3 = onemonth( month+1, year ) if month < 12 else onemonth( 1, year+1 )
    
        fmt = "{0:22}{1:22}{2:22}".format
        return it.starmap( fmt, it.izip_longest( m1, m2, m3, fillvalue='' ) )
    
    
    def twelvemonths(year=None):
        return it.chain( *( threemonth( m, year ) for m in (2, 5, 8, 11) ) )
    
    
    def cal( *args ):
        if len( args ) == 1:
            year = int( args[0] )
            if year == -3: 
                rows = threemonth()
            else:
                rows = twelvemonths( year )
        else:
            rows = onemonth( *map( int, args ) ) 
    
        return '\n'.join( rows )
    
    if __name__ == '__main__':
        from sys import argv
        print cal( *argv[1:] )
    
    

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: