7
\$\begingroup\$

@chux pointed out that my attempted solution contained several problems. Since I screwed that up so badly, I figure that I should put my revised solution up for review.

#include <stdio.h>
#include <time.h>

/**
 * The current year (number of years since 1900)
 */
static int this_year(void) {
    time_t now = time(NULL);
    struct tm thetime;
    localtime_r(&now, &thetime);
    return thetime.tm_year;
}

static struct tm jan1(int yy) {
    struct tm t = { 0 };
    t.tm_year = yy;
    t.tm_mday = 1;
    t.tm_isdst = -1;
    return t;
}

int main(void) {
    int yy = this_year();

    for (struct tm t = jan1(yy); mktime(&t), t.tm_year == yy; t.tm_mday++) {
        char buf[80];
        if (!strftime(buf, sizeof buf, "%m/%d/%Y is a %A", &t)) {
            return 1;   // Unexpected failure
        }
        puts(buf);
    }
}

Concerns include standards compliance, portability, and correctness in all possible time zones. If possible, a more succinct way to obtain the current year and January 1 of the current year would be appreciated.

\$\endgroup\$
  • \$\begingroup\$ @Quill Agree the overall task is more of calendar systems than time zones, but usage of functions time() and then mktime() offer no separation of calendar systems and time zones. Either neither with time() or both with mktime(). \$\endgroup\$ – chux - Reinstate Monica Sep 10 '15 at 14:50
2
\$\begingroup\$

The only thing I have to say is that the use of strftime and %A means that the output will depend on the current locale setting.

This may be a bug or a feature depending on what your original intention was.

Also, the use of char buf[80] seems rather small for today's processors. The only way that strftime is going to fail is if it can't fit all of the output in the buffer. Since memory is cheap, you might as well make it char buf[256] or buf[1024]. This kinda of ties in with with the question of whether you expect to use this with custom locales.

Alternatives:

  • use strftime_l to use a specific locale
  • create your own array of weekday names instead of %A and use the tm_wday field to index into that array
\$\endgroup\$
2
\$\begingroup\$

Functionality

Code needs to set t.tm_isdst = -1; before calling mktime(&t) each day to avoid only advancing only 23 hours.

setenv("TZ", "America/Sao_Paulo", 1);
tzset();
...
    for (struct tm t = jan1(yy); mktime(&t), t.tm_year == yy; t.tm_mday++) {
        char buf[80];
        if (!strftime(buf, sizeof buf, "%m/%d/%Y%z is a %A", &t)) {
            return 1;   // Unexpected failure
        }
        puts(buf);
    }

02/20/2015-0200 is a Friday
02/21/2015-0200 is a Saturday
02/21/2015-0300 is a Saturday
02/22/2015-0300 is a Sunday

Data abstraction

Even though struct tm references the year 1900, a function called this_year() should either use a more neutral epoch or be renamed. Since this is a static function - used only locally, not a strong need for this.

int this_year(void)  // Return 2015 for this year
int this_year_since_1900(void)  // Return 115 for this year

Generality

If this was not a static function, but for general use, tm jan1() should returned a struct tm with all its fields in a consistent state.

static struct tm jan1(int yy) {
    struct tm t = { 0 };
    t.tm_year = yy;
    t.tm_mday = 1;
    t.tm_isdst = -1;
    mktime(&t); // Add to up_date tm_isdst, tm_yday, tm_wday and other optional fields.
    return t;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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