1/*
2**********************************************************************
3* Copyright (C) 1998-2012, International Business Machines Corporation
4* and others.  All Rights Reserved.
5**********************************************************************
6*
7* File date.c
8*
9* Modification History:
10*
11*   Date        Name        Description
12*   06/16/99    stephen     Creation.
13*******************************************************************************
14*/
15
16#include <stdio.h>
17#include <string.h>
18#include <stdlib.h>
19
20#include "unicode/uloc.h"
21#include "unicode/udat.h"
22#include "unicode/ucal.h"
23#include "unicode/ustring.h"
24#include "unicode/uclean.h"
25
26#include "uprint.h"
27
28#if UCONFIG_NO_FORMATTING
29
30int main(int argc, char **argv)
31{
32  printf("%s: Sorry, UCONFIG_NO_FORMATTING was turned on (see uconfig.h). No formatting can be done. \n", argv[0]);
33  return 0;
34}
35#else
36
37
38/* Protos */
39static void usage(void);
40
41static void version(void);
42
43static void cal(int32_t month, int32_t year,
44                UBool useLongNames, UErrorCode *status);
45
46static void get_symbols(const UDateFormat *fmt,
47                        UDateFormatSymbolType type,
48                        UChar *array[],
49                        int32_t arrayLength,
50                        int32_t lowestIndex,
51                        int32_t firstIndex,
52                        UErrorCode *status);
53
54static void free_symbols(UChar *array[],
55                         int32_t arrayLength);
56
57static void get_days(const UDateFormat *fmt,
58                     UChar *days [], UBool useLongNames,
59                     int32_t fdow, UErrorCode *status);
60
61static void free_days(UChar *days[]);
62
63static void get_months(const UDateFormat *fmt,
64                       UChar *months [], UBool useLongNames,
65                       UErrorCode *status);
66
67static void free_months(UChar *months[]);
68
69static void indent(int32_t count, FILE *f);
70
71static void print_days(UChar *days [], FILE *f, UErrorCode *status);
72
73static void  print_month(UCalendar *c,
74                         UChar *days [],
75                         UBool useLongNames, int32_t fdow,
76                         UErrorCode *status);
77
78static void  print_year(UCalendar *c,
79                        UChar *days [], UChar *months [],
80                        UBool useLongNames, int32_t fdow,
81                        UErrorCode *status);
82
83/* The version of cal */
84#define CAL_VERSION "1.0"
85
86/* Number of days in a week */
87#define DAY_COUNT 7
88
89/* Number of months in a year (yes, 13) */
90#define MONTH_COUNT 13
91
92/* Separation between months in year view */
93#define MARGIN_WIDTH 4
94
95/* Size of stack buffers */
96#define BUF_SIZE 64
97
98/* Patterm string - "MMM yyyy" */
99static const UChar sShortPat [] = { 0x004D, 0x004D, 0x004D, 0x0020,
1000x0079, 0x0079, 0x0079, 0x0079 };
101/* Pattern string - "MMMM yyyy" */
102static const UChar sLongPat [] = { 0x004D, 0x004D, 0x004D, 0x004D, 0x0020,
1030x0079, 0x0079, 0x0079, 0x0079 };
104
105
106int
107main(int argc,
108     char **argv)
109{
110    int printUsage = 0;
111    int printVersion = 0;
112    UBool useLongNames = 0;
113    int optInd = 1;
114    char *arg;
115    int32_t month = -1, year = -1;
116    UErrorCode status = U_ZERO_ERROR;
117
118
119    /* parse the options */
120    for(optInd = 1; optInd < argc; ++optInd) {
121        arg = argv[optInd];
122
123        /* version info */
124        if(strcmp(arg, "-v") == 0 || strcmp(arg, "--version") == 0) {
125            printVersion = 1;
126        }
127        /* usage info */
128        else if(strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) {
129            printUsage = 1;
130        }
131        /* use long day names */
132        else if(strcmp(arg, "-l") == 0 || strcmp(arg, "--long") == 0) {
133            useLongNames = 1;
134        }
135        /* POSIX.1 says all arguments after -- are not options */
136        else if(strcmp(arg, "--") == 0) {
137            /* skip the -- */
138            ++optInd;
139            break;
140        }
141        /* unrecognized option */
142        else if(strncmp(arg, "-", strlen("-")) == 0) {
143            printf("cal: invalid option -- %s\n", arg+1);
144            printUsage = 1;
145        }
146        /* done with options, display cal */
147        else {
148            break;
149        }
150    }
151
152    /* Get the month and year to display, if specified */
153    if(optInd != argc) {
154
155        /* Month and year specified */
156        if(argc - optInd == 2) {
157            sscanf(argv[optInd], "%d", (int*)&month);
158            sscanf(argv[optInd + 1], "%d", (int*)&year);
159
160            /* Make sure the month value is legal */
161            if(month < 0 || month > 12) {
162                printf("icucal: Bad value for month -- %d\n", (int)month);
163
164                /* Display usage */
165                printUsage = 1;
166            }
167
168            /* Adjust because months are 0-based */
169            --month;
170        }
171        /* Only year specified */
172        else {
173            sscanf(argv[optInd], "%d", (int*)&year);
174        }
175    }
176
177    /* print usage info */
178    if(printUsage) {
179        usage();
180        return 0;
181    }
182
183    /* print version info */
184    if(printVersion) {
185        version();
186        return 0;
187    }
188
189    /* print the cal */
190    cal(month, year, useLongNames, &status);
191
192    /* ICU cleanup.  Deallocate any memory ICU may be holding.  */
193    u_cleanup();
194
195    return (U_FAILURE(status) ? 1 : 0);
196}
197
198/* Usage information */
199static void
200usage()
201{
202    puts("Usage: icucal [OPTIONS] [[MONTH] YEAR]");
203    puts("");
204    puts("Options:");
205    puts("  -h, --help        Print this message and exit.");
206    puts("  -v, --version     Print the version number of cal and exit.");
207    puts("  -l, --long        Use long names.");
208    puts("");
209    puts("Arguments (optional):");
210    puts("  MONTH             An integer (1-12) indicating the month to display");
211    puts("  YEAR              An integer indicating the year to display");
212    puts("");
213    puts("For an interesting calendar, look at October 1582");
214}
215
216/* Version information */
217static void
218version()
219{
220    printf("icucal version %s (ICU version %s), created by Stephen F. Booth.\n",
221        CAL_VERSION, U_ICU_VERSION);
222    puts(U_COPYRIGHT_STRING);
223}
224
225static void
226cal(int32_t month,
227    int32_t year,
228    UBool useLongNames,
229    UErrorCode *status)
230{
231    UCalendar *c;
232    UChar *days [DAY_COUNT];
233    UChar *months [MONTH_COUNT];
234    int32_t fdow;
235
236    if(U_FAILURE(*status)) return;
237
238    /* Create a new calendar */
239    c = ucal_open(0, -1, uloc_getDefault(), UCAL_TRADITIONAL, status);
240
241    /* Determine if we are printing a calendar for one month or for a year */
242
243    /* Print an entire year */
244    if(month == -1 && year != -1) {
245
246        /* Set the year */
247        ucal_set(c, UCAL_YEAR, year);
248
249        /* Determine the first day of the week */
250        fdow = ucal_getAttribute(c, UCAL_FIRST_DAY_OF_WEEK);
251
252        /* Print the calendar for the year */
253        print_year(c, days, months, useLongNames, fdow, status);
254    }
255
256    /* Print only one month */
257    else {
258
259        /* Set the month and the year, if specified */
260        if(month != -1)
261            ucal_set(c, UCAL_MONTH, month);
262        if(year != -1)
263            ucal_set(c, UCAL_YEAR, year);
264
265        /* Determine the first day of the week */
266        fdow = ucal_getAttribute(c, UCAL_FIRST_DAY_OF_WEEK);
267
268        /* Print the calendar for the month */
269        print_month(c, days, useLongNames, fdow, status);
270    }
271
272    /* Clean up */
273    ucal_close(c);
274}
275/*
276 * Get a set of DateFormat symbols of a given type.
277 *
278 * lowestIndex is the index of the first symbol to fetch.
279 * (e.g. it will be one to fetch day names, since Sunday is
280 *  day 1 *not* day 0.)
281 *
282 * firstIndex is the index of the symbol to place first in
283 * the output array. This is used when fetching day names
284 * in locales where the week doesn't start on Sunday.
285 */
286static void get_symbols(const UDateFormat *fmt,
287                        UDateFormatSymbolType type,
288                        UChar *array[],
289                        int32_t arrayLength,
290                        int32_t lowestIndex,
291                        int32_t firstIndex,
292                        UErrorCode *status)
293{
294    int32_t count, i;
295
296    if (U_FAILURE(*status)) {
297        return;
298    }
299
300    count = udat_countSymbols(fmt, type);
301
302    if(count != arrayLength + lowestIndex) {
303        return;
304    }
305
306    for(i = 0; i < arrayLength; i++) {
307        int32_t idx = (i + firstIndex) % arrayLength;
308        int32_t size = 1 + udat_getSymbols(fmt, type, idx + lowestIndex, NULL, 0, status);
309
310        array[idx] = (UChar *) malloc(sizeof(UChar) * size);
311
312        *status = U_ZERO_ERROR;
313        udat_getSymbols(fmt, type, idx + lowestIndex, array[idx], size, status);
314    }
315}
316
317/* Free the symbols allocated by get_symbols(). */
318static void free_symbols(UChar *array[],
319                         int32_t arrayLength)
320{
321    int32_t i;
322
323    for(i = 0; i < arrayLength; i++) {
324        free(array[i]);
325    }
326}
327
328/* Get the day names for the specified locale, in either long or short
329form.  Also, reorder the days so that they are in the proper order
330for the locale (not all locales begin weeks on Sunday; in France,
331weeks start on Monday) */
332static void
333get_days(const UDateFormat *fmt,
334         UChar *days [],
335         UBool useLongNames,
336         int32_t fdow,
337         UErrorCode *status)
338{
339    UDateFormatSymbolType dayType = (useLongNames ? UDAT_WEEKDAYS : UDAT_SHORT_WEEKDAYS);
340
341    if(U_FAILURE(*status))
342        return;
343
344    /* fdow is 1-based */
345    --fdow;
346
347    get_symbols(fmt, dayType, days, DAY_COUNT, 1, fdow, status);
348}
349
350static void free_days(UChar *days[])
351{
352    free_symbols(days, DAY_COUNT);
353}
354
355/* Get the month names for the specified locale, in either long or
356short form. */
357static void
358get_months(const UDateFormat *fmt,
359           UChar *months [],
360           UBool useLongNames,
361           UErrorCode *status)
362{
363    UDateFormatSymbolType monthType = (useLongNames ? UDAT_MONTHS : UDAT_SHORT_MONTHS);
364
365    if(U_FAILURE(*status))
366        return;
367
368    get_symbols(fmt, monthType, months, MONTH_COUNT - 1, 0, 0, status); /* some locales have 13 months, no idea why */
369}
370
371static void free_months(UChar *months[])
372{
373    free_symbols(months, MONTH_COUNT - 1);
374}
375
376/* Indent a certain number of spaces */
377static void
378indent(int32_t count,
379       FILE *f)
380{
381    char c [BUF_SIZE];
382
383    if(count <= 0)
384    {
385        return;
386    }
387
388    if(count < BUF_SIZE) {
389        memset(c, (int)' ', count);
390        fwrite(c, sizeof(char), count, f);
391    }
392    else {
393        int32_t i;
394        for(i = 0; i < count; ++i)
395            putc(' ', f);
396    }
397}
398
399/* Print the days */
400static void
401print_days(UChar *days [],
402           FILE *f,
403           UErrorCode *status)
404{
405    int32_t i;
406
407    if(U_FAILURE(*status)) return;
408
409    /* Print the day names */
410    for(i = 0; i < DAY_COUNT; ++i) {
411        uprint(days[i], f, status);
412        putc(' ', f);
413    }
414}
415
416/* Print out a calendar for c's current month */
417static void
418print_month(UCalendar *c,
419            UChar *days [],
420            UBool useLongNames,
421            int32_t fdow,
422            UErrorCode *status)
423{
424    int32_t width, pad, i, day;
425    int32_t lens [DAY_COUNT];
426    int32_t firstday, current;
427    UNumberFormat *nfmt;
428    UDateFormat *dfmt;
429    UChar s [BUF_SIZE];
430    const UChar *pat = (useLongNames ? sLongPat : sShortPat);
431    int32_t len = (useLongNames ? 9 : 8);
432
433    if(U_FAILURE(*status)) return;
434
435
436    /* ========== Generate the header containing the month and year */
437
438    /* Open a formatter with a month and year only pattern */
439    dfmt = udat_open(UDAT_PATTERN,UDAT_PATTERN,NULL,NULL,0,pat, len,status);
440
441    /* Format the date */
442    udat_format(dfmt, ucal_getMillis(c, status), s, BUF_SIZE, 0, status);
443
444
445    /* ========== Get the day names */
446    get_days(dfmt, days, useLongNames, fdow, status);
447
448    /* ========== Print the header */
449
450    /* Calculate widths for justification */
451    width = 6; /* 6 spaces, 1 between each day name */
452    for(i = 0; i < DAY_COUNT; ++i) {
453        lens[i] = u_strlen(days[i]);
454        width += lens[i];
455    }
456
457    /* Print the header, centered among the day names */
458    pad = width - u_strlen(s);
459    indent(pad / 2, stdout);
460    uprint(s, stdout, status);
461    putc('\n', stdout);
462
463
464    /* ========== Print the day names */
465
466    print_days(days, stdout, status);
467    putc('\n', stdout);
468
469
470    /* ========== Print the calendar */
471
472    /* Get the first of the month */
473    ucal_set(c, UCAL_DATE, 1);
474    firstday = ucal_get(c, UCAL_DAY_OF_WEEK, status);
475
476    /* The day of the week for the first day of the month is based on
477    1-based days of the week, which were also reordered when placed
478    in the days array.  Account for this here by offsetting by the
479    first day of the week for the locale, which is also 1-based. */
480    firstday -= fdow;
481
482    /* Open the formatter */
483    nfmt = unum_open(UNUM_DECIMAL, NULL,0,NULL,NULL, status);
484
485    /* Indent the correct number of spaces for the first week */
486    current = firstday;
487    if(current < 0)
488    {
489       current += 7;
490    }
491    for(i = 0; i < current; ++i)
492        indent(lens[i] + 1, stdout);
493
494    /* Finally, print out the days */
495    day = ucal_get(c, UCAL_DATE, status);
496    do {
497
498        /* Format the current day string */
499        unum_format(nfmt, day, s, BUF_SIZE, 0, status);
500
501        /* Calculate the justification and indent */
502        pad = lens[current] - u_strlen(s);
503        indent(pad, stdout);
504
505        /* Print the day number out, followed by a space */
506        uprint(s, stdout, status);
507        putc(' ', stdout);
508
509        /* Update the current day */
510        ++current;
511        current %= DAY_COUNT;
512
513        /* If we're at day 0 (first day of the week), insert a newline */
514        if(current == 0) {
515            putc('\n', stdout);
516        }
517
518        /* Go to the next day */
519        ucal_add(c, UCAL_DATE, 1, status);
520        day = ucal_get(c, UCAL_DATE, status);
521
522    } while(day != 1);
523
524    /* Output a trailing newline */
525    putc('\n', stdout);
526
527    /* Clean up */
528    free_days(days);
529    unum_close(nfmt);
530    udat_close(dfmt);
531}
532
533/* Print out a calendar for c's current year */
534static void
535print_year(UCalendar *c,
536           UChar *days [],
537           UChar *months [],
538           UBool useLongNames,
539           int32_t fdow,
540           UErrorCode *status)
541{
542    int32_t width, pad, i, j;
543    int32_t lens [DAY_COUNT];
544    UNumberFormat *nfmt;
545    UDateFormat *dfmt;
546    UChar s [BUF_SIZE];
547    const UChar pat [] = { 0x0079, 0x0079, 0x0079, 0x0079 };
548    int32_t len = 4;
549    UCalendar  *left_cal, *right_cal;
550    int32_t left_day, right_day;
551    int32_t left_firstday, right_firstday, left_current, right_current;
552    int32_t left_month, right_month;
553
554    if(U_FAILURE(*status)) return;
555
556    /* Alias */
557    left_cal = c;
558
559    /* ========== Generate the header containing the year (only) */
560
561    /* Open a formatter with a month and year only pattern */
562    dfmt = udat_open(UDAT_PATTERN,UDAT_PATTERN,NULL,NULL,0,pat, len, status);
563
564    /* Format the date */
565    udat_format(dfmt, ucal_getMillis(left_cal, status), s, BUF_SIZE, 0, status);
566
567    /* ========== Get the month and day names */
568    get_days(dfmt, days, useLongNames, fdow, status);
569    get_months(dfmt, months, useLongNames, status);
570
571    /* ========== Print the header, centered */
572
573    /* Calculate widths for justification */
574    width = 6; /* 6 spaces, 1 between each day name */
575    for(i = 0; i < DAY_COUNT; ++i) {
576        lens[i] = u_strlen(days[i]);
577        width += lens[i];
578    }
579
580    /* width is the width for 1 calendar; we are displaying in 2 cols
581    with MARGIN_WIDTH spaces between months */
582
583    /* Print the header, centered among the day names */
584    pad = 2 * width + MARGIN_WIDTH - u_strlen(s);
585    indent(pad / 2, stdout);
586    uprint(s, stdout, status);
587    putc('\n', stdout);
588    putc('\n', stdout);
589
590    /* Generate a copy of the calendar to use */
591    right_cal = ucal_open(0, -1, uloc_getDefault(), UCAL_TRADITIONAL, status);
592    ucal_setMillis(right_cal, ucal_getMillis(left_cal, status), status);
593
594    /* Open the formatter */
595    nfmt = unum_open(UNUM_DECIMAL,NULL, 0,NULL,NULL, status);
596
597    /* ========== Calculate and display the months, two at a time */
598    for(i = 0; i < MONTH_COUNT - 1; i += 2) {
599
600        /* Print the month names for the two current months */
601        pad = width - u_strlen(months[i]);
602        indent(pad / 2, stdout);
603        uprint(months[i], stdout, status);
604        indent(pad / 2 + MARGIN_WIDTH, stdout);
605        pad = width - u_strlen(months[i + 1]);
606        indent(pad / 2, stdout);
607        uprint(months[i + 1], stdout, status);
608        putc('\n', stdout);
609
610        /* Print the day names, twice  */
611        print_days(days, stdout, status);
612        indent(MARGIN_WIDTH, stdout);
613        print_days(days, stdout, status);
614        putc('\n', stdout);
615
616        /* Setup the two calendars */
617        ucal_set(left_cal, UCAL_MONTH, i);
618        ucal_set(left_cal, UCAL_DATE, 1);
619        ucal_set(right_cal, UCAL_MONTH, i + 1);
620        ucal_set(right_cal, UCAL_DATE, 1);
621
622        left_firstday = ucal_get(left_cal, UCAL_DAY_OF_WEEK, status);
623        right_firstday = ucal_get(right_cal, UCAL_DAY_OF_WEEK, status);
624
625        /* The day of the week for the first day of the month is based on
626        1-based days of the week.  However, the days were reordered
627        when placed in the days array.  Account for this here by
628        offsetting by the first day of the week for the locale, which
629        is also 1-based. */
630
631        /* We need to mod by DAY_COUNT since fdow can be > firstday.  IE,
632        if fdow = 2 = Monday (like in France) and the first day of the
633        month is a 1 = Sunday, we want firstday to be 6, not -1 */
634        left_firstday += (DAY_COUNT - fdow);
635        left_firstday %= DAY_COUNT;
636
637        right_firstday += (DAY_COUNT - fdow);
638        right_firstday %= DAY_COUNT;
639
640        left_current = left_firstday;
641        right_current = right_firstday;
642
643        left_day = ucal_get(left_cal, UCAL_DATE, status);
644        right_day = ucal_get(right_cal, UCAL_DATE, status);
645
646        left_month = ucal_get(left_cal, UCAL_MONTH, status);
647        right_month = ucal_get(right_cal, UCAL_MONTH, status);
648
649        /* Finally, print out the days */
650        while(left_month == i || right_month == i + 1) {
651
652        /* If the left month is finished printing, but the right month
653        still has days to be printed, indent the width of the days
654            strings and reset the left calendar's current day to 0 */
655            if(left_month != i && right_month == i + 1) {
656                indent(width + 1, stdout);
657                left_current = 0;
658            }
659
660            while(left_month == i) {
661
662            /* If the day is the first, indent the correct number of
663                spaces for the first week */
664                if(left_day == 1) {
665                    for(j = 0; j < left_current; ++j)
666                        indent(lens[j] + 1, stdout);
667                }
668
669                /* Format the current day string */
670                unum_format(nfmt, left_day, s, BUF_SIZE, 0, status);
671
672                /* Calculate the justification and indent */
673                pad = lens[left_current] - u_strlen(s);
674                indent(pad, stdout);
675
676                /* Print the day number out, followed by a space */
677                uprint(s, stdout, status);
678                putc(' ', stdout);
679
680                /* Update the current day */
681                ++left_current;
682                left_current %= DAY_COUNT;
683
684                /* Go to the next day */
685                ucal_add(left_cal, UCAL_DATE, 1, status);
686                left_day = ucal_get(left_cal, UCAL_DATE, status);
687
688                /* Determine the month */
689                left_month = ucal_get(left_cal, UCAL_MONTH, status);
690
691                /* If we're at day 0 (first day of the week), break and go to
692                the next month */
693                if(left_current == 0) {
694                    break;
695                }
696            };
697
698            /* If the current day isn't 0, indent to make up for missing
699            days at the end of the month */
700            if(left_current != 0) {
701                for(j = left_current; j < DAY_COUNT; ++j)
702                    indent(lens[j] + 1, stdout);
703            }
704
705            /* Indent between the two months */
706            indent(MARGIN_WIDTH, stdout);
707
708            while(right_month == i + 1) {
709
710            /* If the day is the first, indent the correct number of
711                spaces for the first week */
712                if(right_day == 1) {
713                    for(j = 0; j < right_current; ++j)
714                        indent(lens[j] + 1, stdout);
715                }
716
717                /* Format the current day string */
718                unum_format(nfmt, right_day, s, BUF_SIZE, 0, status);
719
720                /* Calculate the justification and indent */
721                pad = lens[right_current] - u_strlen(s);
722                indent(pad, stdout);
723
724                /* Print the day number out, followed by a space */
725                uprint(s, stdout, status);
726                putc(' ', stdout);
727
728                /* Update the current day */
729                ++right_current;
730                right_current %= DAY_COUNT;
731
732                /* Go to the next day */
733                ucal_add(right_cal, UCAL_DATE, 1, status);
734                right_day = ucal_get(right_cal, UCAL_DATE, status);
735
736                /* Determine the month */
737                right_month = ucal_get(right_cal, UCAL_MONTH, status);
738
739                /* If we're at day 0 (first day of the week), break out */
740                if(right_current == 0) {
741                    break;
742                }
743
744            };
745
746            /* Output a newline */
747            putc('\n', stdout);
748        }
749
750        /* Output a trailing newline */
751        putc('\n', stdout);
752  }
753
754  /* Clean up */
755  free_months(months);
756  free_days(days);
757  udat_close(dfmt);
758  unum_close(nfmt);
759  ucal_close(right_cal);
760}
761
762#endif
763