busybox/coreutils/cal.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * Calendar implementation for busybox
   4 *
   5 * See original copyright at the end of this file
   6 *
   7 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
   8 */
   9
  10/* BB_AUDIT SUSv3 compliant with -j and -y extensions (from util-linux). */
  11/* BB_AUDIT BUG: The output of 'cal -j 1752' is incorrect.  The upstream
  12 * BB_AUDIT BUG: version in util-linux seems to be broken as well. */
  13/* http://www.opengroup.org/onlinepubs/007904975/utilities/cal.html */
  14
  15/* Mar 16, 2003      Manuel Novoa III   (mjn3@codepoet.org)
  16 *
  17 * Major size reduction... over 50% (>1.5k) on i386.
  18 */
  19
  20#include "libbb.h"
  21
  22/* We often use "unsigned" intead of "int", it's easier to div on most CPUs */
  23
  24#define THURSDAY                4               /* for reformation */
  25#define SATURDAY                6               /* 1 Jan 1 was a Saturday */
  26
  27#define FIRST_MISSING_DAY       639787          /* 3 Sep 1752 */
  28#define NUMBER_MISSING_DAYS     11              /* 11 day correction */
  29
  30#define MAXDAYS                 42              /* max slots in a month array */
  31#define SPACE                   -1              /* used in day array */
  32
  33static const unsigned char days_in_month[] ALIGN1 = {
  34        0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
  35};
  36
  37static const unsigned char sep1752[] ALIGN1 = {
  38                 1,     2,      14,     15,     16,
  39        17,     18,     19,     20,     21,     22,     23,
  40        24,     25,     26,     27,     28,     29,     30
  41};
  42
  43/* Set to 0 or 1 in main */
  44#define julian ((unsigned)option_mask32)
  45
  46/* leap year -- account for Gregorian reformation in 1752 */
  47static int leap_year(unsigned yr)
  48{
  49        if (yr <= 1752)
  50                return !(yr % 4);
  51        return (!(yr % 4) && (yr % 100)) || !(yr % 400);
  52}
  53
  54/* number of centuries since 1700, not inclusive */
  55#define centuries_since_1700(yr) \
  56        ((yr) > 1700 ? (yr) / 100 - 17 : 0)
  57
  58/* number of centuries since 1700 whose modulo of 400 is 0 */
  59#define quad_centuries_since_1700(yr) \
  60        ((yr) > 1600 ? ((yr) - 1600) / 400 : 0)
  61
  62/* number of leap years between year 1 and this year, not inclusive */
  63#define leap_years_since_year_1(yr) \
  64        ((yr) / 4 - centuries_since_1700(yr) + quad_centuries_since_1700(yr))
  65
  66static void center(char *, unsigned, unsigned);
  67static void day_array(unsigned, unsigned, unsigned *);
  68static void trim_trailing_spaces_and_print(char *);
  69
  70static void blank_string(char *buf, size_t buflen);
  71static char *build_row(char *p, unsigned *dp);
  72
  73#define DAY_LEN         3               /* 3 spaces per day */
  74#define J_DAY_LEN       (DAY_LEN + 1)
  75#define WEEK_LEN        20              /* 7 * 3 - one space at the end */
  76#define J_WEEK_LEN      (WEEK_LEN + 7)
  77#define HEAD_SEP        2               /* spaces between day headings */
  78
  79int cal_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  80int cal_main(int argc, char **argv)
  81{
  82        struct tm *local_time;
  83        struct tm zero_tm;
  84        time_t now;
  85        unsigned month, year, flags, i;
  86        char *month_names[12];
  87        char day_headings[28];  /* 28 for julian, 21 for nonjulian */
  88        char buf[40];
  89
  90        flags = getopt32(argv, "jy");
  91        /* This sets julian = flags & 1: */
  92        option_mask32 &= 1;
  93        month = 0;
  94        argv += optind;
  95        argc -= optind;
  96
  97        if (argc > 2) {
  98                bb_show_usage();
  99        }
 100
 101        if (!argc) {
 102                time(&now);
 103                local_time = localtime(&now);
 104                year = local_time->tm_year + 1900;
 105                if (!(flags & 2)) { /* no -y */
 106                        month = local_time->tm_mon + 1;
 107                }
 108        } else {
 109                if (argc == 2) {
 110                        month = xatou_range(*argv++, 1, 12);
 111                }
 112                year = xatou_range(*argv, 1, 9999);
 113        }
 114
 115        blank_string(day_headings, sizeof(day_headings) - 7 + 7*julian);
 116
 117        i = 0;
 118        do {
 119                zero_tm.tm_mon = i;
 120                strftime(buf, sizeof(buf), "%B", &zero_tm);
 121                month_names[i] = xstrdup(buf);
 122
 123                if (i < 7) {
 124                        zero_tm.tm_wday = i;
 125                        strftime(buf, sizeof(buf), "%a", &zero_tm);
 126                        strncpy(day_headings + i * (3+julian) + julian, buf, 2);
 127                }
 128        } while (++i < 12);
 129
 130        if (month) {
 131                unsigned row, len, days[MAXDAYS];
 132                unsigned *dp = days;
 133                char lineout[30];
 134
 135                day_array(month, year, dp);
 136                len = sprintf(lineout, "%s %d", month_names[month - 1], year);
 137                printf("%*s%s\n%s\n",
 138                           ((7*julian + WEEK_LEN) - len) / 2, "",
 139                           lineout, day_headings);
 140                for (row = 0; row < 6; row++) {
 141                        build_row(lineout, dp)[0] = '\0';
 142                        dp += 7;
 143                        trim_trailing_spaces_and_print(lineout);
 144                }
 145        } else {
 146                unsigned row, which_cal, week_len, days[12][MAXDAYS];
 147                unsigned *dp;
 148                char lineout[80];
 149
 150                sprintf(lineout, "%d", year);
 151                center(lineout,
 152                           (WEEK_LEN * 3 + HEAD_SEP * 2)
 153                           + julian * (J_WEEK_LEN * 2 + HEAD_SEP
 154                                                   - (WEEK_LEN * 3 + HEAD_SEP * 2)),
 155                           0);
 156                puts("\n");             /* two \n's */
 157                for (i = 0; i < 12; i++) {
 158                        day_array(i + 1, year, days[i]);
 159                }
 160                blank_string(lineout, sizeof(lineout));
 161                week_len = WEEK_LEN + julian * (J_WEEK_LEN - WEEK_LEN);
 162                for (month = 0; month < 12; month += 3-julian) {
 163                        center(month_names[month], week_len, HEAD_SEP);
 164                        if (!julian) {
 165                                center(month_names[month + 1], week_len, HEAD_SEP);
 166                        }
 167                        center(month_names[month + 2 - julian], week_len, 0);
 168                        printf("\n%s%*s%s", day_headings, HEAD_SEP, "", day_headings);
 169                        if (!julian) {
 170                                printf("%*s%s", HEAD_SEP, "", day_headings);
 171                        }
 172                        bb_putchar('\n');
 173                        for (row = 0; row < (6*7); row += 7) {
 174                                for (which_cal = 0; which_cal < 3-julian; which_cal++) {
 175                                        dp = days[month + which_cal] + row;
 176                                        build_row(lineout + which_cal * (week_len + 2), dp);
 177                                }
 178                                /* blank_string took care of nul termination. */
 179                                trim_trailing_spaces_and_print(lineout);
 180                        }
 181                }
 182        }
 183
 184        fflush_stdout_and_exit(EXIT_SUCCESS);
 185}
 186
 187/*
 188 * day_array --
 189 *      Fill in an array of 42 integers with a calendar.  Assume for a moment
 190 *      that you took the (maximum) 6 rows in a calendar and stretched them
 191 *      out end to end.  You would have 42 numbers or spaces.  This routine
 192 *      builds that array for any month from Jan. 1 through Dec. 9999.
 193 */
 194static void day_array(unsigned month, unsigned year, unsigned *days)
 195{
 196        unsigned long temp;
 197        unsigned i;
 198        unsigned day, dw, dm;
 199
 200        memset(days, SPACE, MAXDAYS * sizeof(int));
 201
 202        if ((month == 9) && (year == 1752)) {
 203                /* Assumes the Gregorian reformation eliminates
 204                 * 3 Sep. 1752 through 13 Sep. 1752.
 205                 */
 206                unsigned j_offset = julian * 244;
 207                size_t oday = 0;
 208
 209                do {
 210                        days[oday+2] = sep1752[oday] + j_offset;
 211                } while (++oday < sizeof(sep1752));
 212
 213                return;
 214        }
 215
 216        /* day_in_year
 217         * return the 1 based day number within the year
 218         */
 219        day = 1;
 220        if ((month > 2) && leap_year(year)) {
 221                ++day;
 222        }
 223
 224        i = month;
 225        while (i) {
 226                day += days_in_month[--i];
 227        }
 228
 229        /* day_in_week
 230         * return the 0 based day number for any date from 1 Jan. 1 to
 231         * 31 Dec. 9999.  Assumes the Gregorian reformation eliminates
 232         * 3 Sep. 1752 through 13 Sep. 1752.  Returns Thursday for all
 233         * missing days.
 234         */
 235        temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1) + day;
 236        if (temp < FIRST_MISSING_DAY) {
 237                dw = ((temp - 1 + SATURDAY) % 7);
 238        } else {
 239                dw = (((temp - 1 + SATURDAY) - NUMBER_MISSING_DAYS) % 7);
 240        }
 241
 242        if (!julian) {
 243                day = 1;
 244        }
 245
 246        dm = days_in_month[month];
 247        if ((month == 2) && leap_year(year)) {
 248                ++dm;
 249        }
 250
 251        do {
 252                days[dw++] = day++;
 253        } while (--dm);
 254}
 255
 256static void trim_trailing_spaces_and_print(char *s)
 257{
 258        char *p = s;
 259
 260        while (*p) {
 261                ++p;
 262        }
 263        while (p != s) {
 264                --p;
 265                if (!(isspace)(*p)) {   /* We want the function... not the inline. */
 266                        p[1] = '\0';
 267                        break;
 268                }
 269        }
 270
 271        puts(s);
 272}
 273
 274static void center(char *str, unsigned len, unsigned separate)
 275{
 276        unsigned n = strlen(str);
 277        len -= n;
 278        printf("%*s%*s", (len/2) + n, str, (len/2) + (len % 2) + separate, "");
 279}
 280
 281static void blank_string(char *buf, size_t buflen)
 282{
 283        memset(buf, ' ', buflen);
 284        buf[buflen-1] = '\0';
 285}
 286
 287static char *build_row(char *p, unsigned *dp)
 288{
 289        unsigned col, val, day;
 290
 291        memset(p, ' ', (julian + DAY_LEN) * 7);
 292
 293        col = 0;
 294        do {
 295                day = *dp++;
 296                if (day != SPACE) {
 297                        if (julian) {
 298                                ++p;
 299                                if (day >= 100) {
 300                                        *p = '0';
 301                                        p[-1] = (day / 100) + '0';
 302                                        day %= 100;
 303                                }
 304                        }
 305                        val = day / 10;
 306                        if (val > 0) {
 307                                *p = val + '0';
 308                        }
 309                        *++p = day % 10 + '0';
 310                        p += 2;
 311                } else {
 312                        p += DAY_LEN + julian;
 313                }
 314        } while (++col < 7);
 315
 316        return p;
 317}
 318
 319/*
 320 * Copyright (c) 1989, 1993, 1994
 321 *      The Regents of the University of California.  All rights reserved.
 322 *
 323 * This code is derived from software contributed to Berkeley by
 324 * Kim Letkeman.
 325 *
 326 * Redistribution and use in source and binary forms, with or without
 327 * modification, are permitted provided that the following conditions
 328 * are met:
 329 * 1. Redistributions of source code must retain the above copyright
 330 *    notice, this list of conditions and the following disclaimer.
 331 * 2. Redistributions in binary form must reproduce the above copyright
 332 *    notice, this list of conditions and the following disclaimer in the
 333 *    documentation and/or other materials provided with the distribution.
 334 * 3. Neither the name of the University nor the names of its contributors
 335 *    may be used to endorse or promote products derived from this software
 336 *    without specific prior written permission.
 337 *
 338 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 339 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 340 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 341 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 342 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 343 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 344 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 345 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 346 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 347 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 348 * SUCH DAMAGE.
 349 */
 350