diff --git a/usr.bin/ncal/ncal.1 b/usr.bin/ncal/ncal.1 index 1dde5aa..329db6d 100644 --- a/usr.bin/ncal/ncal.1 +++ b/usr.bin/ncal/ncal.1 @@ -22,40 +22,54 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/usr.bin/ncal/ncal.1,v 1.8.2.7 2002/10/23 08:10:00 roam Exp $ +.\" $FreeBSD$ .\" -.Dd March 12, 2009 +.Dd March 14, 2009 .Dt CAL 1 .Os .Sh NAME .Nm cal , .Nm ncal -.Nd displays a calendar and the date of easter +.Nd displays a calendar and the date of Easter .Sh SYNOPSIS .Nm -.Op Fl jy -.Op Fl m Ar month +.Op Fl 3hjy +.Op Fl A Ar number +.Op Fl B Ar number .Oo .Op Ar month .Ar year .Oc +.Nm +.Op Fl 3hj +.Op Fl A Ar number +.Op Fl B Ar number +.Fl m Ar month +.Op Ar year .Nm ncal -.Op Fl jJpwy -.Op Fl m Ar month +.Op Fl 3hjJpwy +.Op Fl A Ar number +.Op Fl B Ar number .Op Fl s Ar country_code .Oo .Op Ar month .Ar year .Oc .Nm ncal -.Op Fl Jeo +.Op Fl 3hJeo +.Op Fl A Ar number +.Op Fl B Ar number .Op Ar year +.Nm ncal +.Op Fl CN +.Op Fl H Ar yyyy-mm-dd +.Op Fl d Ar yyyy-mm .Sh DESCRIPTION The .Nm utility displays a simple calendar in traditional format and .Nm ncal -offers an alternative layout, more options and the date of easter. +offers an alternative layout, more options and the date of Easter. The new format is a little cramped but it makes a year fit on a 25x80 terminal. If arguments are not specified, @@ -63,19 +77,29 @@ the current month is displayed. .Pp The options are as follows: .Bl -tag -width indent +.It Fl h +Turns off highlighting of today. .It Fl J Display Julian Calendar, if combined with the .Fl e -option, display date of easter according to the Julian Calendar. +option, display date of Easter according to the Julian Calendar. .It Fl e -Display date of easter (for western churches). +Display date of Easter (for western churches). .It Fl j Display Julian days (days one-based, numbered from January 1). .It Fl m Ar month Display the specified .Ar month . +If +.Ar month +is specified as a decimal number, it may be followed by the letter +.Ql f +or +.Ql p +to indicate the following or preceding month of that number, +respectively. .It Fl o -Display date of orthodox easter (Greek and Russian +Display date of Orthodox Easter (Greek and Russian Orthodox Churches). .It Fl p Print the country codes and switching days from Julian to Gregorian @@ -90,38 +114,68 @@ associated with the If not specified, .Nm ncal tries to guess the switch date from the local environment or -falls back to September 2, 1752. This was when Great +falls back to September 2, 1752. +This was when Great Britain and her colonies switched to the Gregorian Calendar. .It Fl w Print the number of the week below each week column. .It Fl y -Display a calendar for the current year. +Display a calendar for the specified year. +.It Fl 3 +Display the previous, current and next month surrounding today. +.It Fl A Ar number +Display the +.Ar number +of months after the current month. +.It Fl B Ar number +Display the +.Ar number +of months before the current month. +.It Fl C +Switch to +.Nm cal +mode. +.It Fl N +Switch to +.Nm ncal +mode. +.It Fl d Ar yyyy-mm +Use +.Ar yyyy-mm +as the current date (for debugging of date selection). +.It Fl H Ar yyyy-mm-dd +Use +.Ar yyyy-mm-dd +as the current date (for debugging of highlighting). .El .Pp -A single parameter specifies the year (1 - 9999) to be displayed; -note the year must be fully specified. -Two parameters denote the month and year; the -.Ar month -is either a number between -1 and 12, or a full or abbreviated name as specified by the current locale. -A single dot, -.Ql \&. , -for the -.Ar year -parameter refers to the current year. +A single parameter specifies the year (1\(en9999) to be displayed; +note the year must be fully specified: +.Dq Li cal 89 +will +.Em not +display a calendar for 1989. Two parameters denote the month and +year; the month is either a number between 1 and 12, or a full or +abbreviated name as specified by the current locale. Month and +year default to those of the current system clock and time zone (so +.Dq Li cal -m 8 +will display a calendar for the month of August in the current +year). .Pp -A year starts on Jan 1. -.Sh EXIT STATUS -.Ex -std +Not all options can be used together. For example +.Dq Li -3 -A 2 -B 3 -y -m 7 +would mean: +show me the three months around the seventh month, three before +that, two after that and the whole year. +.Nm ncal +will warn about these combinations. +.Pp +A year starts on January 1. +.Pp +Highlighting of dates is disabled if stdout is not a tty. .Sh SEE ALSO .Xr calendar 3 , .Xr strftime 3 -.Sh STANDARDS -The -.Nm -utility conforms to -.St -p1003.1-2004 -and is ahead of the standard with locale-specific Gregorian adoption details. .Sh HISTORY A .Nm @@ -137,5 +191,8 @@ The command and manual were written by .An Wolfgang Helbig Aq Mt helbig@FreeBSD.org . .Sh BUGS -The assignment of Julian - Gregorian switching dates to -country codes is historically naive for many countries. +The assignment of Julian\(enGregorian switching dates to country +codes is historically naive for many countries. +.Pp +Not all options are compatible and using them in different orders +will give varying results. diff --git a/usr.bin/ncal/ncal.c b/usr.bin/ncal/ncal.c index be43a50..be78d74 100644 --- a/usr.bin/ncal/ncal.c +++ b/usr.bin/ncal/ncal.c @@ -22,15 +22,16 @@ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. - * - * $FreeBSD: src/usr.bin/ncal/ncal.c,v 1.11.2.3 2002/10/23 08:10:00 roam Exp $ - * $DragonFly: src/usr.bin/ncal/ncal.c,v 1.2 2003/06/17 04:29:29 dillon Exp $ */ +#include +__FBSDID("$FreeBSD$"); + #include #include #include #include +#include #include #include #include @@ -38,34 +39,37 @@ #include #include #include +#include +#include #include #undef lines /* term.h defines this */ -/* Width of one month with backward compatibility */ +/* Width of one month with backward compatibility and in regular mode*/ #define MONTH_WIDTH_B_J 27 #define MONTH_WIDTH_B 20 -#define MONTH_WIDTH_J 24 -#define MONTH_WIDTH 18 +#define MONTH_WIDTH_R_J 24 +#define MONTH_WIDTH_R 18 #define MAX_WIDTH 64 typedef struct date date; struct monthlines { - char name[MAX_WIDTH + 1]; + wchar_t name[MAX_WIDTH + 1]; char lines[7][MAX_WIDTH + 1]; char weeks[MAX_WIDTH + 1]; + unsigned int extralen[7]; }; struct weekdays { - char names[7][4]; + wchar_t names[7][4]; }; /* The switches from Julian to Gregorian in some countries */ static struct djswitch { - char *cc; /* Country code according to ISO 3166 */ - char *nm; /* Name of country */ + const char *cc; /* Country code according to ISO 3166 */ + const char *nm; /* Name of country */ date dt; /* Last day of Julian calendar */ } switches[] = { {"AL", "Albania", {1912, 11, 30}}, @@ -99,85 +103,88 @@ static struct djswitch { {"RO", "Romania", {1919, 3, 31}}, {"RU", "Russia", {1918, 1, 31}}, {"SI", "Slovenia", {1919, 3, 4}}, - {"SW", "Sweden", {1753, 2, 17}}, + {"SE", "Sweden", {1753, 2, 17}}, {"TR", "Turkey", {1926, 12, 18}}, {"US", "United States", {1752, 9, 2}}, {"YU", "Yugoslavia", {1919, 3, 4}} }; -struct djswitch *dftswitch = +static struct djswitch *dftswitch = switches + sizeof(switches) / sizeof(struct djswitch) - 2; /* default switch (should be "US") */ /* Table used to print day of month and week numbers */ -char daystr[] = " 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" - " 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47" - " 48 49 50 51 52 53"; +static char daystr[] = " 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" + " 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47" + " 48 49 50 51 52 53"; /* Table used to print day of year and week numbers */ -char jdaystr[] = " 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 32 33 34 35 36 37 38 39" - " 40 41 42 43 44 45 46 47 48 49" - " 50 51 52 53 54 55 56 57 58 59" - " 60 61 62 63 64 65 66 67 68 69" - " 70 71 72 73 74 75 76 77 78 79" - " 80 81 82 83 84 85 86 87 88 89" - " 90 91 92 93 94 95 96 97 98 99" - " 100 101 102 103 104 105 106 107 108 109" - " 110 111 112 113 114 115 116 117 118 119" - " 120 121 122 123 124 125 126 127 128 129" - " 130 131 132 133 134 135 136 137 138 139" - " 140 141 142 143 144 145 146 147 148 149" - " 150 151 152 153 154 155 156 157 158 159" - " 160 161 162 163 164 165 166 167 168 169" - " 170 171 172 173 174 175 176 177 178 179" - " 180 181 182 183 184 185 186 187 188 189" - " 190 191 192 193 194 195 196 197 198 199" - " 200 201 202 203 204 205 206 207 208 209" - " 210 211 212 213 214 215 216 217 218 219" - " 220 221 222 223 224 225 226 227 228 229" - " 230 231 232 233 234 235 236 237 238 239" - " 240 241 242 243 244 245 246 247 248 249" - " 250 251 252 253 254 255 256 257 258 259" - " 260 261 262 263 264 265 266 267 268 269" - " 270 271 272 273 274 275 276 277 278 279" - " 280 281 282 283 284 285 286 287 288 289" - " 290 291 292 293 294 295 296 297 298 299" - " 300 301 302 303 304 305 306 307 308 309" - " 310 311 312 313 314 315 316 317 318 319" - " 320 321 322 323 324 325 326 327 328 329" - " 330 331 332 333 334 335 336 337 338 339" - " 340 341 342 343 344 345 346 347 348 349" - " 350 351 352 353 354 355 356 357 358 359" - " 360 361 362 363 364 365 366"; - -int flag_weeks; /* user wants number of week */ -int nswitch; /* user defined switch date */ -int nswitchb; /* switch date for backward compatibility */ -const char *term_so, *term_se; -int today; - -char *center(char *s, char *t, int w); -void mkmonth(int year, int month, int jd_flag, struct monthlines * monthl); -void mkmonthb(int year, int month, int jd_flag, struct monthlines * monthl); -void mkweekdays(struct weekdays * wds); -int parsemonth(const char *s); -void printcc(void); -void printeaster(int year, int julian, int orthodox); -void printmonth(int year, int month, int jd_flag); -void printmonthb(int year, int month, int jd_flag); -void printyear(int year, int jd_flag); -void printyearb(int year, int jd_flag); -int firstday(int y, int m); -date *sdate(int ndays, struct date * d); -date *sdateb(int ndays, struct date * d); -int sndays(struct date * d); -int sndaysb(struct date * d); -static void usage(void); -int weekdayb(int nd); +static char jdaystr[] = " 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 32 33 34 35 36 37 38 39" + " 40 41 42 43 44 45 46 47 48 49" + " 50 51 52 53 54 55 56 57 58 59" + " 60 61 62 63 64 65 66 67 68 69" + " 70 71 72 73 74 75 76 77 78 79" + " 80 81 82 83 84 85 86 87 88 89" + " 90 91 92 93 94 95 96 97 98 99" + " 100 101 102 103 104 105 106 107 108 109" + " 110 111 112 113 114 115 116 117 118 119" + " 120 121 122 123 124 125 126 127 128 129" + " 130 131 132 133 134 135 136 137 138 139" + " 140 141 142 143 144 145 146 147 148 149" + " 150 151 152 153 154 155 156 157 158 159" + " 160 161 162 163 164 165 166 167 168 169" + " 170 171 172 173 174 175 176 177 178 179" + " 180 181 182 183 184 185 186 187 188 189" + " 190 191 192 193 194 195 196 197 198 199" + " 200 201 202 203 204 205 206 207 208 209" + " 210 211 212 213 214 215 216 217 218 219" + " 220 221 222 223 224 225 226 227 228 229" + " 230 231 232 233 234 235 236 237 238 239" + " 240 241 242 243 244 245 246 247 248 249" + " 250 251 252 253 254 255 256 257 258 259" + " 260 261 262 263 264 265 266 267 268 269" + " 270 271 272 273 274 275 276 277 278 279" + " 280 281 282 283 284 285 286 287 288 289" + " 290 291 292 293 294 295 296 297 298 299" + " 300 301 302 303 304 305 306 307 308 309" + " 310 311 312 313 314 315 316 317 318 319" + " 320 321 322 323 324 325 326 327 328 329" + " 330 331 332 333 334 335 336 337 338 339" + " 340 341 342 343 344 345 346 347 348 349" + " 350 351 352 353 354 355 356 357 358 359" + " 360 361 362 363 364 365 366"; + +static int flag_nohighlight; /* user doesn't want a highlighted today */ +static int flag_weeks; /* user wants number of week */ +static int nswitch; /* user defined switch date */ +static int nswitchb; /* switch date for backward compatibility */ +static int highlightdate; + +static char *center(char *s, char *t, int w); +static wchar_t *wcenter(wchar_t *s, wchar_t *t, int w); +static int firstday(int y, int m); +static void highlight(char *dst, char *src, int len, int *extraletters); +static void mkmonthr(int year, int month, int jd_flag, + struct monthlines * monthl); +static void mkmonthb(int year, int month, int jd_flag, + struct monthlines * monthl); +static void mkweekdays(struct weekdays * wds); +static void monthranger(int year, int m, int jd_flag, + int before, int after); +static void monthrangeb(int year, int m, int jd_flag, + int before, int after); +static int parsemonth(const char *s, int *m, int *y); +static void printcc(void); +static void printeaster(int year, int julian, int orthodox); +static date *sdater(int ndays, struct date * d); +static date *sdateb(int ndays, struct date * d); +static int sndaysr(struct date * d); +static int sndaysb(struct date * d); +static void usage(void); int main(int argc, char *argv[]) @@ -185,44 +192,38 @@ main(int argc, char *argv[]) struct djswitch *p, *q; /* to search user defined switch date */ date never = {10000, 1, 1}; /* outside valid range of dates */ date ukswitch = {1752, 9, 2};/* switch date for Great Britain */ - date dt; /* today as date */ + date dt; int ch; /* holds the option character */ int m = 0; /* month */ int y = 0; /* year */ int flag_backward = 0; /* user called cal--backward compat. */ - int flag_hole_year = 0; /* user wants the whole year */ + int flag_wholeyear = 0; /* user wants the whole year */ int flag_julian_cal = 0; /* user wants Julian Calendar */ - int flag_julian_day = 0; /* user wants the Julian day - * numbers */ - int flag_orthodox = 0; /* use wants Orthodox easter */ - int flag_easter = 0; /* use wants easter date */ + int flag_julian_day = 0; /* user wants the Julian day numbers */ + int flag_orthodox = 0; /* user wants Orthodox easter */ + int flag_easter = 0; /* user wants easter date */ + int flag_3months = 0; /* user wants 3 month display (-3) */ + int flag_after = 0; /* user wants to see months after */ + int flag_before = 0; /* user wants to see months before */ + int flag_specifiedmonth = 0;/* user wants to see this month (-m) */ + int flag_givenmonth = 0; /* user has specified month [n] */ + int flag_givenyear = 0; /* user has specified year [n] */ char *cp; /* character pointer */ + char *flag_today = NULL; /* debug: use date as being today */ char *flag_month = NULL; /* requested month as string */ - char *locale; /* locale to get country code */ - char tbuf[1024], cbuf[512], *b; - time_t t; - struct tm *tm1; - - term_se = term_so = NULL; - today = 0; - t = time(NULL); - tm1 = localtime(&t); - y = dt.y = tm1->tm_year + 1900; - m = dt.m = tm1->tm_mon + 1; - dt.d = tm1->tm_mday; - if (isatty(STDOUT_FILENO) && tgetent(tbuf, NULL) == 1) { - b = cbuf; - term_so = tgetstr("so", &b); - term_se = tgetstr("se", &b); - today = sndaysb(&dt); - } + char *flag_highlightdate = NULL; /* debug: date to highlight */ + int before, after; + const char *locale; /* locale to get country code */ + + flag_nohighlight = 0; + flag_weeks = 0; /* * Use locale to determine the country code, * and use the country code to determine the default * switchdate and date format from the switches table. */ - if ((locale = setlocale(LC_ALL, "")) == NULL) + if (setlocale(LC_ALL, "") == NULL) warn("setlocale"); locale = setlocale(LC_TIME, NULL); if (locale == NULL || @@ -247,25 +248,57 @@ main(int argc, char *argv[]) * Get the filename portion of argv[0] and set flag_backward if * this program is called "cal". */ - for (cp = argv[0]; *cp; cp++) - ; - while (cp >= argv[0] && *cp != '/') - cp--; - if (strcmp("cal", ++cp) == 0) + if (strncmp(basename(argv[0]), "cal", strlen("cal")) == 0) flag_backward = 1; /* Set the switch date to United Kingdom if backwards compatible */ if (flag_backward) nswitchb = ndaysj(&ukswitch); - while ((ch = getopt(argc, argv, "Jejm:ops:wy")) != -1) + before = after = -1; + + while ((ch = getopt(argc, argv, "3A:B:Cd:eH:hjJm:Nops:wy")) != -1) switch (ch) { + case '3': + flag_3months = 1; + break; + case 'A': + if (flag_after > 0) + errx(EX_USAGE, "Double -A specified"); + flag_after = strtol(optarg, NULL, 10); + if (flag_after <= 0) + errx(EX_USAGE, + "Argument to -A must be positive"); + break; + case 'B': + if (flag_before > 0) + errx(EX_USAGE, "Double -A specified"); + flag_before = strtol(optarg, NULL, 10); + if (flag_before <= 0) + errx(EX_USAGE, + "Argument to -B must be positive"); + break; case 'J': if (flag_backward) usage(); nswitch = ndaysj(&never); flag_julian_cal = 1; break; + case 'C': + flag_backward = 1; + break; + case 'N': + flag_backward = 0; + break; + case 'd': + flag_today = optarg; + break; + case 'H': + flag_highlightdate = optarg; + break; + case 'h': + flag_nohighlight = 1; + break; case 'e': if (flag_backward) usage(); @@ -275,7 +308,10 @@ main(int argc, char *argv[]) flag_julian_day = 1; break; case 'm': + if (flag_specifiedmonth) + errx(EX_USAGE, "Double -m specified"); flag_month = optarg; + flag_specifiedmonth = 1; break; case 'o': if (flag_backward) @@ -308,7 +344,7 @@ main(int argc, char *argv[]) flag_weeks = 1; break; case 'y': - flag_hole_year = 1; + flag_wholeyear = 1; break; default: usage(); @@ -322,43 +358,144 @@ main(int argc, char *argv[]) if (flag_easter) usage(); flag_month = *argv++; + flag_givenmonth = 1; + m = strtol(flag_month, NULL, 10); /* FALLTHROUGH */ case 1: - if (strcmp(*argv, ".") == 0) - /* year was already set above */ - argv++; - else - y = atoi(*argv++); + y = atoi(*argv); if (y < 1 || y > 9999) - errx(EX_USAGE, "year %d not in range 1..9999", y); + errx(EX_USAGE, "year `%s' not in range 1..9999", *argv); + argv++; + flag_givenyear = 1; break; case 0: + if (flag_today != NULL) { + y = strtol(flag_today, NULL, 10); + m = strtol(flag_today + 5, NULL, 10); + } else { + time_t t; + struct tm *tm; + + t = time(NULL); + tm = localtime(&t); + y = tm->tm_year + 1900; + m = tm->tm_mon + 1; + } break; default: usage(); } if (flag_month != NULL) { - m = parsemonth(flag_month); - if (m < 1 || m > 12) + if (parsemonth(flag_month, &m, &y)) { errx(EX_USAGE, "%s is neither a month number (1..12) nor a name", flag_month); + } } + /* + * What is not supported: + * -3 with -A or -B + * -3 displays 3 months, -A and -B change that behaviour. + * -3 with -y + * -3 displays 3 months, -y says display a whole year. + * -3 with a given year but no given month or without -m + * -3 displays 3 months, no month specified doesn't make clear + * which three months. + * -m with a given month + * conflicting arguments, both specify the same field. + * -y with -m + * -y displays the whole year, -m displays a single month. + * -y with a given month + * -y displays the whole year, the given month displays a single + * month. + * -y with -A or -B + * -y displays the whole year, -A and -B display extra months. + */ + + /* -3 together with -A or -B. */ + if (flag_3months && (flag_after || flag_before)) + errx(EX_USAGE, "-3 together with -A and -B is not supported."); + /* -3 together with -y. */ + if (flag_3months && flag_wholeyear) + errx(EX_USAGE, "-3 together with -y is not supported."); + /* -3 together with givenyear but no givenmonth. */ + if (flag_3months && flag_givenyear && + !(flag_givenmonth || flag_specifiedmonth)) + errx(EX_USAGE, + "-3 together with a given year but no given month is " + "not supported."); + /* -m together with xx xxxx. */ + if (flag_specifiedmonth && flag_givenmonth) + errx(EX_USAGE, + "-m together with a given month is not supported."); + /* -y together with -m. */ + if (flag_wholeyear && flag_specifiedmonth) + errx(EX_USAGE, "-y together with -m is not supported."); + /* -y together with xx xxxx. */ + if (flag_wholeyear && flag_givenmonth) + errx(EX_USAGE, "-y together a given month is not supported."); + /* -y together with -A or -B. */ + if (flag_wholeyear && (flag_before > 0 || flag_after > 0)) + errx(EX_USAGE, "-y together a -A or -B is not supported."); + /* The rest should be fine. */ + + /* Select the period to display, in order of increasing priority .*/ + if (flag_wholeyear || + (flag_givenyear && !(flag_givenmonth || flag_specifiedmonth))) { + m = 1; + before = 0; + after = 11; + } + if (flag_givenyear && flag_givenmonth) { + before = 0; + after = 0; + } + if (flag_specifiedmonth) { + before = 0; + after = 0; + } + if (flag_before) { + before = flag_before; + } + if (flag_after) { + after = flag_after; + } + if (flag_3months) { + before = 1; + after = 1; + } + if (after == -1) + after = 0; + if (before == -1) + before = 0; + + /* Highlight a specified day or today .*/ + if (flag_highlightdate != NULL) { + dt.y = strtol(flag_highlightdate, NULL, 10); + dt.m = strtol(flag_highlightdate + 5, NULL, 10); + dt.d = strtol(flag_highlightdate + 8, NULL, 10); + } else { + time_t t; + struct tm *tm1; + + t = time(NULL); + tm1 = localtime(&t); + dt.y = tm1->tm_year + 1900; + dt.m = tm1->tm_mon + 1; + dt.d = tm1->tm_mday; + } + highlightdate = sndaysb(&dt); + + /* And now we finally start to calculate and output calendars. */ if (flag_easter) printeaster(y, flag_julian_cal, flag_orthodox); - else if (argc == 1 || flag_hole_year) { - if (flag_backward) - printyearb(y, flag_julian_day); - else - printyear(y, flag_julian_day); - } else + else if (flag_backward) - printmonthb(y, m, flag_julian_day); + monthrangeb(y, m, flag_julian_day, before, after); else - printmonth(y, m, flag_julian_day); - + monthranger(y, m, flag_julian_day, before, after); return (0); } @@ -366,15 +503,19 @@ static void usage(void) { - fprintf(stderr, "%s\n%s\n%s\n", - "usage: cal [-jy] [[month] year]", - " ncal [-Jjpwy] [-s country_code] [[month] year]", - " ncal [-Jeo] [year]"); + fputs( +"Usage: cal [general options] [-hjy] [[month] year]\n" +" cal [general options] [-hj] [-m month] [year]\n" +" ncal [general options] [-hJjpwy] [-s country_code] [[month] year]\n" +" ncal [general options] [-hJeo] [year]\n" +"General options: [-NC3] [-A months] [-B months]\n" +"For debug the highlighting: [-H yyyy-mm-dd] [-d yyyy-mm]\n", + stderr); exit(EX_USAGE); } -/* print the assumed switches for all countries */ -void +/* Print the assumed switches for all countries. */ +static void printcc(void) { struct djswitch *p; @@ -394,13 +535,13 @@ printcc(void) printf(FSTR"\n", FSTRARG(p)); } -/* print the date of easter sunday */ -void +/* Print the date of easter sunday. */ +static void printeaster(int y, int julian, int orthodox) { date dt; struct tm tm; - char buf[80]; + char buf[MAX_WIDTH]; static int d_first = -1; if (d_first < 0) @@ -425,176 +566,201 @@ printeaster(int y, int julian, int orthodox) printf("%s\n", buf); } -void -printmonth(int y, int m, int jd_flag) -{ - struct monthlines month; - struct weekdays wds; - int i; - - mkmonth(y, m - 1, jd_flag, &month); - mkweekdays(&wds); - printf(" %s %d\n", month.name, y); - for (i = 0; i != 7; i++) - printf("%.2s%s\n", wds.names[i], month.lines[i]); - if (flag_weeks) - printf(" %s\n", month.weeks); -} - -void -printmonthb(int y, int m, int jd_flag) -{ - struct monthlines month; - struct weekdays wds; - char s[MAX_WIDTH], t[MAX_WIDTH]; - int i; - int mw; - - mkmonthb(y, m - 1, jd_flag, &month); - mkweekdays(&wds); - - mw = jd_flag ? MONTH_WIDTH_B_J : MONTH_WIDTH_B; - - sprintf(s, "%s %d", month.name, y); - printf("%s\n", center(t, s, mw)); - - if (jd_flag) - printf(" %s %s %s %s %s %s %.2s\n", wds.names[6], wds.names[0], - wds.names[1], wds.names[2], wds.names[3], - wds.names[4], wds.names[5]); - else - printf("%s%s%s%s%s%s%.2s\n", wds.names[6], wds.names[0], - wds.names[1], wds.names[2], wds.names[3], - wds.names[4], wds.names[5]); - - for (i = 0; i != 6; i++) - printf("%s\n", month.lines[i]+1); -} +#define MW(mw, me) ((mw) + me) +#define DECREASEMONTH(m, y) \ + if (--m == 0) { \ + m = 12; \ + y--; \ + } +#define INCREASEMONTH(m, y) \ + if (++(m) == 13) { \ + (m) = 1; \ + (y)++; \ + } +#define M2Y(m) ((m) / 12) +#define M2M(m) (1 + (m) % 12) -void -printyear(int y, int jd_flag) +/* Print all months for the period in the range [ before .. y-m .. after ]. */ +static void +monthrangeb(int y, int m, int jd_flag, int before, int after) { struct monthlines year[12]; struct weekdays wds; - char s[80], t[80]; + char s[MAX_WIDTH], t[MAX_WIDTH]; + wchar_t ws[MAX_WIDTH], ws1[MAX_WIDTH]; + const char *wdss; int i, j; int mpl; int mw; + int m1, m2; + int printyearheader; + int prevyear = -1; + + mpl = jd_flag ? 2 : 3; + mw = jd_flag ? MONTH_WIDTH_B_J : MONTH_WIDTH_B; + wdss = (mpl == 2) ? " " : ""; + + while (before != 0) { + DECREASEMONTH(m, y); + before--; + after++; + } + m1 = y * 12 + m - 1; + m2 = m1 + after; - for (i = 0; i != 12; i++) - mkmonth(y, i, jd_flag, year + i); mkweekdays(&wds); - mpl = jd_flag ? 3 : 4; - mw = jd_flag ? MONTH_WIDTH_J : MONTH_WIDTH; - sprintf(s, "%d", y); - printf("%s\n", center(t, s, mpl * mw)); + /* + * The year header is printed when there are more than 'mpl' months + * and if the first month is a multitude of 'mpl'. + * If not, it will print the year behind every month. + */ + printyearheader = (after >= mpl - 1) && (M2M(m1) - 1) % mpl == 0; + + m = m1; + while (m <= m2) { + int count = 0; + for (i = 0; i != mpl && m + i <= m2; i++) { + mkmonthb(M2Y(m + i), M2M(m + i) - 1, jd_flag, year + i); + count++; + } - for (j = 0; j != 12; j += mpl) { - printf(" %-*s%-*s", - mw, year[j].name, - mw, year[j + 1].name); - if (mpl == 3) - printf("%s\n", year[j + 2].name); - else - printf("%-*s%s\n", - mw, year[j + 2].name, - year[j + 3].name); - for (i = 0; i != 7; i++) { - printf("%.2s%s%s", - wds.names[i], - year[j].lines[i], - year[j + 1].lines[i]); - if (mpl == 3) - printf("%s\n", year[j + 2].lines[i]); - else - printf("%s%s\n", - year[j + 2].lines[i], - year[j + 3].lines[i]); + /* Empty line between two rows of months */ + if (m != m1) + printf("\n"); + + /* Year at the top. */ + if (printyearheader && M2Y(m) != prevyear) { + sprintf(s, "%d", M2Y(m)); + printf("%s\n", center(t, s, mpl * mw)); + prevyear = M2Y(m); } - if (flag_weeks) { - if (mpl == 3) - printf(" %s%s%-s\n", - year[j].weeks, - year[j + 1].weeks, - year[j + 2].weeks); - else - printf(" %s%s%s%-s\n", - year[j].weeks, - year[j + 1].weeks, - year[j + 2].weeks, - year[j + 3].weeks); + + /* Month names. */ + for (i = 0; i < count; i++) + if (printyearheader) + wprintf(L"%-*ls ", + mw, wcenter(ws, year[i].name, mw)); + else { + swprintf(ws, sizeof(ws), L"%-ls %d", + year[i].name, M2Y(m + i)); + wprintf(L"%-*ls ", mw, wcenter(ws1, ws, mw)); + } + printf("\n"); + + /* Day of the week names. */ + for (i = 0; i < count; i++) { + wprintf(L"%s%ls%s%ls%s%ls%s%ls%s%ls%s%ls%s%ls ", + wdss, wds.names[6], wdss, wds.names[0], + wdss, wds.names[1], wdss, wds.names[2], + wdss, wds.names[3], wdss, wds.names[4], + wdss, wds.names[5]); + } + printf("\n"); + + /* And the days of the month. */ + for (i = 0; i != 6; i++) { + for (j = 0; j < count; j++) + printf("%-*s ", + MW(mw, year[j].extralen[i]), + year[j].lines[i]+1); + printf("\n"); } + + m += mpl; } } -void -printyearb(int y, int jd_flag) +static void +monthranger(int y, int m, int jd_flag, int before, int after) { struct monthlines year[12]; struct weekdays wds; - char s[80], t[80]; + char s[MAX_WIDTH], t[MAX_WIDTH]; int i, j; int mpl; int mw; + int m1, m2; + int prevyear = -1; + int printyearheader; + + mpl = jd_flag ? 3 : 4; + mw = jd_flag ? MONTH_WIDTH_R_J : MONTH_WIDTH_R; + + while (before != 0) { + DECREASEMONTH(m, y); + before--; + after++; + } + m1 = y * 12 + m - 1; + m2 = m1 + after; - for (i = 0; i != 12; i++) - mkmonthb(y, i, jd_flag, year + i); mkweekdays(&wds); - mpl = jd_flag ? 2 : 3; - mw = jd_flag ? MONTH_WIDTH_B_J : MONTH_WIDTH_B; - sprintf(s, "%d", y); - printf("%s\n\n", center(t, s, mw * mpl + mpl)); + /* + * The year header is printed when there are more than 'mpl' months + * and if the first month is a multitude of 'mpl'. + * If not, it will print the year behind every month. + */ + printyearheader = (after >= mpl - 1) && (M2M(m1) - 1) % mpl == 0; + + m = m1; + while (m <= m2) { + int count = 0; + for (i = 0; i != mpl && m + i <= m2; i++) { + mkmonthr(M2Y(m + i), M2M(m + i) - 1, jd_flag, year + i); + count++; + } - for (j = 0; j != 12; j += mpl) { - printf("%-*s ", mw, center(s, year[j].name, mw)); - if (mpl == 2) - printf("%s\n", center(s, year[j + 1].name, mw)); - else - printf("%-*s %s\n", mw, - center(s, year[j + 1].name, mw), - center(t, year[j + 2].name, mw)); - - if (mpl == 2) - printf(" %s %s %s %s %s %s %s " - " %s %s %s %s %s %s %.2s\n", - wds.names[6], wds.names[0], wds.names[1], - wds.names[2], wds.names[3], wds.names[4], - wds.names[5], - wds.names[6], wds.names[0], wds.names[1], - wds.names[2], wds.names[3], wds.names[4], - wds.names[5]); - else - printf("%s%s%s%s%s%s%s " - "%s%s%s%s%s%s%s " - "%s%s%s%s%s%s%.2s\n", - wds.names[6], wds.names[0], wds.names[1], - wds.names[2], wds.names[3], wds.names[4], - wds.names[5], - wds.names[6], wds.names[0], wds.names[1], - wds.names[2], wds.names[3], wds.names[4], - wds.names[5], - wds.names[6], wds.names[0], wds.names[1], - wds.names[2], wds.names[3], wds.names[4], - wds.names[5]); - for (i = 0; i != 6; i++) { - if (mpl == 2) - printf("%s %s\n", - year[j].lines[i]+1, - year[j + 1].lines[i]+1); + /* Empty line between two rows of months. */ + if (m != m1) + printf("\n"); + + /* Year at the top. */ + if (printyearheader && M2Y(m) != prevyear) { + sprintf(s, "%d", M2Y(m)); + printf("%s\n", center(t, s, mpl * mw)); + prevyear = M2Y(m); + } + + /* Month names. */ + wprintf(L" "); + for (i = 0; i < count; i++) + if (printyearheader) + wprintf(L"%-*ls", mw, year[i].name); else - printf("%s %s %s\n", - year[j].lines[i]+1, - year[j + 1].lines[i]+1, - year[j + 2].lines[i]+1); + wprintf(L"%-ls %-*d", year[i].name, + mw - wcslen(year[i].name) - 1, M2Y(m + i)); + printf("\n"); + /* And the days of the month. */ + for (i = 0; i != 7; i++) { + /* Week day */ + wprintf(L"%.2ls", wds.names[i]); + + /* Full months */ + for (j = 0; j < count; j++) + printf("%-*s", + MW(mw, year[j].extralen[i]), + year[j].lines[i]); + printf("\n"); } + + /* Week numbers. */ + if (flag_weeks) { + printf(" "); + for (i = 0; i < count; i++) + printf("%-*s", mw, year[i].weeks); + printf("\n"); + } + + m += mpl; } + return; } -void -mkmonth(int y, int m, int jd_flag, struct monthlines *mlines) +static void +mkmonthr(int y, int m, int jd_flag, struct monthlines *mlines) { struct tm tm; /* for strftime printing local names of @@ -605,7 +771,6 @@ mkmonth(int y, int m, int jd_flag, struct monthlines *mlines) int firstm; /* first day of first week of month */ int i, j, k, l; /* just indices */ int last; /* the first day of next month */ - int lastm; /* first day of first week of next month */ int jan1 = 0; /* the first day of this year */ char *ds; /* pointer to day strings (daystr or * jdaystr) */ @@ -613,8 +778,9 @@ mkmonth(int y, int m, int jd_flag, struct monthlines *mlines) /* Set name of month. */ memset(&tm, 0, sizeof(tm)); tm.tm_mon = m; - strftime(mlines->name, sizeof(mlines->name), "%OB", &tm); - mlines->name[0] = toupper((unsigned char)mlines->name[0]); + wcsftime(mlines->name, sizeof(mlines->name) / sizeof(mlines->name[0]), + L"%OB", &tm); + mlines->name[0] = towupper(mlines->name[0]); /* * Set first and last to the day number of the first day of this @@ -633,13 +799,10 @@ mkmonth(int y, int m, int jd_flag, struct monthlines *mlines) /* * Set firstm to the day number of monday of the first week of * this month. (This might be in the last month) - * Set lastm to the day number of monday of the first week of - * the next month. Our canvas spans [firstm, lastm). */ firstm = first - weekday(first); - lastm = last + 7 - weekday(last); - /* Set ds (daystring) and dw (daywidth) according to the jd_flag */ + /* Set ds (daystring) and dw (daywidth) according to the jd_flag. */ if (jd_flag) { ds = jdaystr; dw = 4; @@ -655,39 +818,30 @@ mkmonth(int y, int m, int jd_flag, struct monthlines *mlines) */ for (i = 0; i != 7; i++) { l = 0; - for (j = firstm + i, k = 0; j < lastm; j += 7, k += dw) { - if (j >= first && j < last) { + for (j = firstm + i, k = 0; j < last; j += 7, k += dw) { + if (j >= first) { if (jd_flag) dt.d = j - jan1 + 1; else - sdate(j, &dt); - if (j == today && (term_so != NULL && term_se != NULL)) { - l = strlen(term_so); - /* separator */ - mlines->lines[i][k] = ' '; - } - /* the actual text */ - memcpy(mlines->lines[i] + k + l, - ds + dt.d * dw, dw); - if (j == today && (term_so != NULL && term_se != NULL)) { - /* highlight on */ - memcpy(mlines->lines[i] + k + 1, term_so, l); - /* highlight off */ - memcpy(mlines->lines[i] + k + l + dw, term_se, - strlen(term_se)); - l = strlen(term_se) + strlen(term_so); - } + sdater(j, &dt); + if (j == highlightdate && !flag_nohighlight + && isatty(STDOUT_FILENO)) + highlight(mlines->lines[i] + k, + ds + dt.d * dw, dw, &l); + else + memcpy(mlines->lines[i] + k + l, + ds + dt.d * dw, dw); } else memcpy(mlines->lines[i] + k + l, " ", dw); } mlines->lines[i][k + l] = '\0'; - + mlines->extralen[i] = l; } - /* fill the weeknumbers */ + /* fill the weeknumbers. */ if (flag_weeks) { - for (j = firstm, k = 0; j < lastm; k += dw, j += 7) - if (j <= nswitch || j >= last) + for (j = firstm, k = 0; j < last; k += dw, j += 7) + if (j <= nswitch) memset(mlines->weeks + k, ' ', dw); else memcpy(mlines->weeks + k, @@ -696,7 +850,7 @@ mkmonth(int y, int m, int jd_flag, struct monthlines *mlines) } } -void +static void mkmonthb(int y, int m, int jd_flag, struct monthlines *mlines) { @@ -721,11 +875,12 @@ mkmonthb(int y, int m, int jd_flag, struct monthlines *mlines) dw = 3; } - /* Set name of month centered */ + /* Set name of month centered. */ memset(&tm, 0, sizeof(tm)); tm.tm_mon = m; - strftime(mlines->name, sizeof(mlines->name), "%OB", &tm); - mlines->name[0] = toupper((unsigned char)mlines->name[0]); + wcsftime(mlines->name, sizeof(mlines->name) / sizeof(mlines->name[0]), + L"%OB", &tm); + mlines->name[0] = towupper(mlines->name[0]); /* * Set first and last to the day number of the first day of this @@ -767,29 +922,19 @@ mkmonthb(int y, int m, int jd_flag, struct monthlines *mlines) */ for (i = 0; i != 6; i++) { l = 0; - for (j = firsts + 7 * i, k = 0; k != dw * 7; + for (j = firsts + 7 * i, k = 0; j < last && k != dw * 7; j++, k += dw) { - if (j >= first && j < last) { + if (j >= first) { if (jd_flag) dt.d = j - jan1 + 1; else sdateb(j, &dt); - if (j == today && (term_so != NULL && term_se != NULL)) { - l = strlen(term_so); - /* separator */ - mlines->lines[i][k] = ' '; - } - /* the actual text */ - memcpy(mlines->lines[i] + k + l, - ds + dt.d * dw, dw); - if (j == today && (term_so != NULL && term_se != NULL)) { - /* highlight on */ - memcpy(mlines->lines[i] + k + 1, term_so, l); - /* highlight off */ - memcpy(mlines->lines[i] + k + l + dw, term_se, - strlen(term_se)); - l = strlen(term_se) + strlen(term_so); - } + if (j == highlightdate && !flag_nohighlight) + highlight(mlines->lines[i] + k, + ds + dt.d * dw, dw, &l); + else + memcpy(mlines->lines[i] + k + l, + ds + dt.d * dw, dw); } else memcpy(mlines->lines[i] + k + l, " ", dw); } @@ -797,36 +942,40 @@ mkmonthb(int y, int m, int jd_flag, struct monthlines *mlines) mlines->lines[i][1] = '\0'; else mlines->lines[i][k + l] = '\0'; + mlines->extralen[i] = l; } } -/* Put the local names of weekdays into the wds */ -void +/* Put the local names of weekdays into the wds. */ +static void mkweekdays(struct weekdays *wds) { - int i, len; + int i, len, width = 0; struct tm tm; - char buf[20]; + wchar_t buf[20]; memset(&tm, 0, sizeof(tm)); for (i = 0; i != 7; i++) { tm.tm_wday = (i+1) % 7; - strftime(buf, sizeof(buf), "%a", &tm); - len = strlen(buf); - if (len > 2) - len = 2; - strcpy(wds->names[i], " "); - strncpy(wds->names[i] + 2 - len, buf, len); + wcsftime(buf, sizeof(buf), L"%a", &tm); + for (len = 2; len > 0; --len) { + if ((width = wcswidth(buf, len)) <= 2) + break; + } + wmemset(wds->names[i], L'\0', 4); + if (width == 1) + wds->names[i][0] = L' '; + wcsncat(wds->names[i], buf, len); + wcsncat(wds->names[i], L" ", 1); } } /* - * Compute the day number of the first - * existing date after the first day in month. - * (the first day in month and even the month might not exist!) + * Compute the day number of the first existing date after the first day in + * month. (the first day in month and even the month might not exist!) */ -int +static int firstday(int y, int m) { date dt; @@ -835,9 +984,9 @@ firstday(int y, int m) dt.y = y; dt.m = m; dt.d = 1; - nd = sndays(&dt); + nd = sndaysr(&dt); for (;;) { - sdate(nd, &dt); + sdater(nd, &dt); if ((dt.m >= m && dt.y == y) || dt.y > y) return (nd); else @@ -850,8 +999,8 @@ firstday(int y, int m) * Compute the number of days from date, obey the local switch from * Julian to Gregorian if specified by the user. */ -int -sndays(struct date *d) +static int +sndaysr(struct date *d) { if (nswitch != 0) @@ -867,7 +1016,7 @@ sndays(struct date *d) * Compute the number of days from date, obey the switch from * Julian to Gregorian as used by UK and her colonies. */ -int +static int sndaysb(struct date *d) { @@ -877,9 +1026,9 @@ sndaysb(struct date *d) return (ndaysj(d)); } -/* Inverse of sndays */ -struct date * -sdate(int nd, struct date *d) +/* Inverse of sndays. */ +static struct date * +sdater(int nd, struct date *d) { if (nswitch < nd) @@ -888,8 +1037,8 @@ sdate(int nd, struct date *d) return (jdate(nd, d)); } -/* Inverse of sndaysb */ -struct date * +/* Inverse of sndaysb. */ +static struct date * sdateb(int nd, struct date *d) { @@ -899,30 +1048,131 @@ sdateb(int nd, struct date *d) return (jdate(nd, d)); } -/* Center string t in string s of length w by putting enough leading blanks */ -char * +/* Center string t in string s of length w by putting enough leading blanks. */ +static char * center(char *s, char *t, int w) { - char blanks[80]; + char blanks[MAX_WIDTH]; memset(blanks, ' ', sizeof(blanks)); sprintf(s, "%.*s%s", (int)(w - strlen(t)) / 2, blanks, t); return (s); } -int -parsemonth(const char *s) +/* Center string t in string s of length w by putting enough leading blanks. */ +static wchar_t * +wcenter(wchar_t *s, wchar_t *t, int w) +{ + char blanks[MAX_WIDTH]; + + memset(blanks, ' ', sizeof(blanks)); + swprintf(s, MAX_WIDTH, L"%.*s%ls", (int)(w - wcslen(t)) / 2, blanks, t); + return (s); +} + +static int +parsemonth(const char *s, int *m, int *y) { - int v; + int nm, ny; char *cp; struct tm tm; - v = (int)strtol(s, &cp, 10); - if (cp != s) - return (v); - if (strptime(s, "%B", &tm) != NULL) - return (tm.tm_mon + 1); - if (strptime(s, "%b", &tm) != NULL) - return (tm.tm_mon + 1); - return (0); + nm = (int)strtol(s, &cp, 10); + if (cp != s) { + ny = *y; + if (*cp == '\0') { + ; /* no special action */ + } else if (*cp == 'f' || *cp == 'F') { + if (nm <= *m) + ny++; + } else if (*cp == 'p' || *cp == 'P') { + if (nm >= *m) + ny--; + } else + return (1); + if (nm < 1 || nm > 12) + return 1; + *m = nm; + *y = ny; + return (0); + } + if (strptime(s, "%B", &tm) != NULL || strptime(s, "%b", &tm) != NULL) { + *m = tm.tm_mon + 1; + return (0); + } + return (1); } + +static void +highlight(char *dst, char *src, int len, int *extralen) +{ + static int first = 1; + static const char *term_so, *term_se; + + if (first) { + char tbuf[1024], cbuf[512], *b; + + term_se = term_so = NULL; + + /* On how to highlight on this type of terminal (if any). */ + if (isatty(STDOUT_FILENO) && tgetent(tbuf, NULL) == 1) { + b = cbuf; + term_so = tgetstr("so", &b); + term_se = tgetstr("se", &b); + } + + first = 0; + } + + /* + * This check is not necessary, should have been handled before calling + * this function. + */ + if (flag_nohighlight) { + memcpy(dst, src, len); + return; + } + + /* + * If it is a real terminal, use the data from the termcap database. + */ + if (term_so != NULL && term_se != NULL) { + /* separator. */ + dst[0] = ' '; + dst++; + /* highlight on. */ + memcpy(dst, term_so, strlen(term_so)); + dst += strlen(term_so); + /* the actual text. (minus leading space) */ + len--; + src++; + memcpy(dst, src, len); + dst += len; + /* highlight off. */ + memcpy(dst, term_se, strlen(term_se)); + *extralen = strlen(term_so) + strlen(term_se); + return; + } + + /* + * Otherwise, print a _, backspace and the letter. + */ + *extralen = 0; + /* skip leading space. */ + src++; + len--; + /* separator. */ + dst[0] = ' '; + dst++; + while (len > 0) { + /* _ and backspace. */ + memcpy(dst, "_\010", 2); + dst += 2; + *extralen += 2; + /* the character. */ + *dst++ = *src++; + len--; + } + return; +} +