Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
123 views
in Technique[技术] by (71.8m points)

c - Given a day of the year, how would I convert it into date/month format?

So basically I'm reading information from a file and storing it into multiple arrays, one of the questions being asked is to take a specific day of a year and change that into date time format. So if it's the 30th day of the year, I should output ".... 30th day of the month January", or anything that specifies the day and month.

I've been looking into strftime but I'm not too familiar with structures as of yet as I'm still learning it a bit and this is what my attempt looks like so far:

char time_supHi[100];
struct tm *timeHi;
time_t sup;

timeHi = localtime(&mytime);

sup = time(day[high_pos_sup]);
strftime(time_supHi, 100, "%B", mytime);

I have %B as a placeholder for now just to see if the right month is outputted. Any help would be appreciated.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Option 1: Stress-test mktime()

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

static void yd_to_ymd(int year, int day);

int main(void)
{
    yd_to_ymd(1996, 300);
    putchar('
');
    yd_to_ymd(1997, 300);
    return 0;
}

static char *fmt_ordinal(char *buffer, size_t buflen, unsigned n);
static void dump_tm(const char *tag, const struct tm *t);

static void yd_to_ymd(int year, int day)
{
    struct tm t0 =
    {
        .tm_year = year - 1900, .tm_mday = day, .tm_mon = 0,
        .tm_hour = 0, .tm_min = 0, .tm_sec = 0,
        .tm_isdst = -1,
    };

    dump_tm("t0", &t0);

    time_t uts = mktime(&t0);

    printf("%ju
", (intmax_t)uts);
    dump_tm("t1", &t0);

    char ordinal[8];
    fmt_ordinal(ordinal, sizeof(ordinal), t0.tm_mday);
    char month[16];
    strftime(month, sizeof(month), "%B", &t0);
    char weekday[10];
    strftime(weekday, sizeof(weekday), "%A", &t0);
    printf("%s the %s day of the month of %s
", weekday, ordinal, month);
}

static void dump_tm(const char *tag, const struct tm *t)
{
    printf("%s: %.4d-%.2d-%.2d %.2d:%.2d:%.2d (W %d; Y %3d; DST %d)
",
           tag, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
           t->tm_hour, t->tm_min, t->tm_sec,
           t->tm_wday, t->tm_yday + 1, t->tm_isdst);
}

static const char *const suffixes[4] = { "th", "st", "nd", "rd" };

static inline unsigned suffix_index(unsigned n)
{
    unsigned x;

    x = n % 100;
    if (x == 11 || x == 12 || x == 13)
        x = 0;
    else if ((x = x % 10) > 3)
        x = 0;
    return x;
}

static char *fmt_ordinal(char *buffer, size_t buflen, unsigned n)
{
    unsigned x = suffix_index(n);
    int len = snprintf(buffer, buflen, "%d%s", n, suffixes[x]);
    if (len <= 0 || (size_t)len >= buflen)
        return 0;
    return(buffer);
}

The mktime() function normalizes the date/time information in the tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec fields, using tm_isdst to guide choices related to Winter vs Summer time — or Daylight Saving vs Standard time. It also updates the tm_yday and tm_wday fields to reflect what's deduced. So, by setting the structure up to describe the day of the year (e.g. 300) as the 300th day of January, mktime() will convert that to the correct date — 26th October in a leap year such as 1996, 27th October in a non-leap year such as 1997.

The code above initializes a struct tm value with the correct encoding for the 300th day of January in the given year (1996 in this example). It dumps that information via the dump_tm() function. Note that this 'undoes' the encodings in the structure — it reports the value tm_year + 1900; it reports the value tm_mon + 1 (so months are numbered 1..12 instead of 0..11), and it reports the value tm_yday + 1 so 1st January is day 1 of the year rather than day 0. It then calls the mktime() routine to get the Unix timestamp (seconds since the Unix Epoch — which is 1970-01-01T00:00:00Z), and calls dump_tm() to show the adjusted values.

Example output:

t0: 1996-01-300 00:00:00 (W 0; Y   1; DST -1)
846309600
t1: 1996-10-26 00:00:00 (W 6; Y 300; DST 1)
Saturday the 26th day of the month of October

t0: 1997-01-300 00:00:00 (W 0; Y   1; DST -1)
877935600
t1: 1997-10-27 00:00:00 (W 1; Y 300; DST 0)
Monday the 27th day of the month of October

If you look at the data from Option 2, you'll see that 1996-300 was 1996-10-26, and 1997-300 was 1997-10-27.

Given the timestamp 846309600, that corresponds to Sat Oct 26 00:00:00 1996 in the US/Mountain time zone, and 877935600 corresponds to Mon Oct 27 00:00:00 1997 (note that in 1996, the time zone was in daylight saving time, and in 1997 the time zone was in standard time — back then, the time zone did its 'fall back' on the last Sunday in October):

$ timestamp -l 846309600 877935600
846309600 = Sat Oct 26 00:00:00 1996
877935600 = Mon Oct 27 00:00:00 1997
$ timestamp -u 846309600 877935600
846309600 = Sat Oct 26 06:00:00 1996
877935600 = Mon Oct 27 07:00:00 1997
$ timestamp -Z -l 846309600 877935600
846309600 = Sat Oct 26 00:00:00 1996 -06:00
877935600 = Mon Oct 27 00:00:00 1997 -07:00
$ timestamp -Z -u 846309600 877935600
846309600 = Sat Oct 26 06:00:00 1996 +00:00
877935600 = Mon Oct 27 07:00:00 1997 +00:00
$ timestamp -I -Z -l 846309600 877935600
846309600 = 1996-10-26 00:00:00 -06:00
877935600 = 1997-10-27 00:00:00 -07:00
$ timestamp -I -Z -u 846309600 877935600
846309600 = 1996-10-26 06:00:00 +00:00
877935600 = 1997-10-27 07:00:00 +00:00
$

Or, if you prefer to use GNU date, then:

$  date -d @846309600 --iso-8601=seconds
1996-10-26T00:00:00-0600
$ date -u -d @846309600 --iso-8601=seconds
1996-10-26T06:00:00+0000
$

Spelling out the year or day of the month (e.g. "one thousand, nine hundred and ninety-six") is harder; the code to do that is much larger. Making that an ordinal spelling ("… ninety-sixth") requires care too.

Option 2: DIY

I don't think the use of time() or localtime() is warranted. Using strftime() and maybe mktime() would be more sensible, but convert 'day of year' to 'month and day' is not standardized AFAIK.

If you want to convert "year (1..9999) and day of year (1..366)" to "year (1..9999), month (1..12), and day (1..31)", you probably need a leap year function, and a table of the number of days in each month, and then use a brute force the calculation.

Header yd2ymd.c

#ifndef JLSS_ID_YD2YMD_H
#define JLSS_ID_YD2YMD_H

extern int cvt_yd_to_ymd(int year, int yday, int *mon, int *day);
extern int cvt_ymd_to_yd(int year, int mon, int day, int *yday);

#endif

Source yd2ymd.c

#include "yd2ymd.h"
#include <assert.h>
#include <stdbool.h>

static const int days_in_month[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

static inline bool is_leap_year(int year)
{
    if (year % 4 != 0)
        return false;
    if (year % 100 != 0)
        return true;
    if (year % 400 == 0)
        return true;
    return false;
    /* return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); */
}

#define PRECOND(x) do { if (!(x)) return -1; } while (0);

int cvt_yd_to_ymd(int year, int yday, int *mon, int *day)
{
    PRECOND(year > 0 && year <= 9999);
    PRECOND(yday > 0 && (yday < 366 || (yday == 366 && is_leap_year(year))));
    assert(mon != 0 && day != 0);
    if (is_leap_year(year))
    {
        if (yday == 31 + 29)
        {
            *mon = 2;
            *day = 29;
            return 0;
        }
        if (yday > 31 + 29)
            yday--;
    }
    *mon = 1;
    for (int i = 1; i <= 12; i++)
    {
        if (yday > days_in_month[i])
        {
            yday -= days_in_month[i];
            (*mon)++;
        }
        else
        {
            *day = yday;
            return 0;
        }
    }
    assert(0 || "can't happen");
    return -1;
}

int cvt_ymd_to_yd(int year, int mon, int day, int *yday)
{
    PRECOND(year > 0 && year <= 9999);
    PRECOND(mon >= 1 && mon <= 12);
    PRECOND(day >= 1 && (day <= days_in_month[mon] ||
                        (day == 29 && mon == 2 && is_leap_year(year))));
    assert(yday != 0);
    int day_num = day;
    bool leap = is_leap_year(year);
    if (day == 29 && mon == 2 && leap)
    {
            day_num += 31;
    }
    else
    {
        if (leap && mon > 2)
            day_num++;
        for (int i = 1; i < mon; i++)
            day_num += days_in_month[i];
    }
    *yday = day_num;
    return 0;
}

#ifdef TEST

#include <stdio.h>

int main(void)
{
    /* Check leap years */
    int length = 0;
    const char *pad = "";
    for (int i = 1895; i < 2105; i++)
    {
        if (is_leap_year(i))
        {
            int extra = printf("%s%d", pad, i);
            length += extra;
            if (length > 70)
            {
                putchar('
');
                length = 0;
                pad = "";
            }
            else
                pad = ", ";
        }
    }
    if (length > 0)
        putchar('
');

    /* Check conversions from YYYY-DDD to YYYY-MM-DD and back */
    const int years[] = { 1996, 1997 };
    enum { NUM_YEARS = sizeof(years) / sizeof(years[0]) };
    for (int i = 0; i < NUM_YEARS; i++)
    {
        for (int j = 1; j <= 366; j++)
        {
            int day;
            int mon;
            if (cvt_yd_to_ymd(years[i], j, &mon, &day) != 0)
                fprintf(stderr, "Failed to convert year %d, day %d to YYYY-MM-DD
",
                        years[i], j);
            else
            {
                printf("Y = %4d, D = %3d --> M = %2d, D = %2d; ",
                       years[i], j, mon, day);
                int yday;
                if (cvt_ymd_to_yd(years[i], mon, day, &yday) != 0)
                    fprintf(stderr, "Failed to convert %4d-%.2d-%.2d to YYYY-DDD
",
                            years[i], mon, day);
                else if (yday != j)
                    fprintf(stderr, "Incorrect day of year (%d wanted vs %d actual)
",
                            j, yday);
                else
                    printf("Y = %4d, M = %.2d, D = %.2d --> D = %3d",
                           years[i], mon, day, yday);
                putchar('
');
            }
        }
    }

    return 0;
}

#endif

Partial output

1896, 1904, 1908, 1912, 1916, 1920, 1924, 1928, 1932, 1936, 1940, 1944, 1948
1952, 1956, 1960, 1964, 1968, 1972, 1976, 1980, 1984, 1988, 1992, 1996, 2000
2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036, 2040, 2044, 2048, 2052
2056, 2060, 2064, 2068, 2072, 2076, 2080, 2084, 2088, 2092, 2096, 2104
Y = 1996, D =   1 --> M =  1, D =  1; Y = 1996, M = 01, D = 01 --> D =   1
Y = 1996, D =   2 --> M =  1, D =  2; Y = 1996, M = 01, D = 02 --> D =   2
Y = 1996, D =   3 --> M =  1, D =  3; Y = 1996, M = 01, D = 03 --> D =   3
...
Y = 1996, D =  27 --> M =  1, D = 27; Y = 1996, M = 01, D = 27 --> D =  27
Y = 1996, D =  28 --> M =  1, D = 28; Y = 1996, M = 01, D = 28 --> D =  28
Y = 1996, D =  29 --> M =  1, D = 29; Y = 1996, M = 01, D = 29 --> D =  29
Y = 1996, D =  30 --> M =  1, D = 30; Y = 1996, M = 01, D = 30 --> D =  30
Y = 1996, D =  31 --> M =  1, D = 31; Y = 1996, M = 01, D = 31 --> D =  31
Y = 1996, D =  32 --> M =  2, D =  1; Y = 1996, M = 02, D = 01 --> D =  32
Y = 1996, D =  33 --> M =  2, D =  2; Y = 1996, M = 02, D = 02 --> D =  33
Y = 1996, D =  34 --> M =  2, D =  3; Y = 1996, M = 02, D = 03 --> D =  34
...
Y = 1996, D =  58 --> M =  2, D = 27; Y = 1996, M = 02, D = 27 --> D =  58
Y = 1996, D =  59 --> M =  2, D = 28; Y = 1996, M = 02, D = 28 --> D =  59
Y = 1996, D =  60 --> M =  2, D = 29; Y = 1996, M = 02, D = 29 --> D =  60
Y = 1996, D =  61 --> M =  3, D =  1; Y = 1996, M = 03, D = 01 --> D =  61
Y = 1996, D =  62 --> M =  3, D =  2; Y = 1996, M = 03, D = 02 --> D =  62
Y = 1996, D =  63 --> M =  3, D =  3; Y = 199

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...