计算从纪元开始经过的天数 - 基于公式

Calculate number of days elapsed from epoch - formula based

我正在尝试计算从给定 GMT 时间开始经过的天数。

好吧,我可以用迭代计算方法让它工作(找到正常年份的数量 和闰年)

函数 get_number_of_leap_years_from_base_year 遍历从 1970 年到给定日期的所有年份,并每年检查是否有飞跃,最后添加所有日期。

有没有其他方法(公式)可以计算平年和闰年。

/* so-prg-2: Calculating number normal & leap years passed */

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

#define BASE_YEAR 1970

void print_time_readable_format(struct tm tm);
int convert_gmt_date_time_to_tm_format(char* gmt_time_fmt);
int get_number_of_leap_years_from_base_year(int start_year, int end_year);
int calculate_days_elapsed_from_epoch(struct tm tm);

int main()
{
    int days = 0;
    char gmt_time_fmt[] = "Dec 28 18:40:01 2020 GMT";
    //char gmt_time_fmt[] = "Jan 20 19:00:01 2019 GMT";
    //char gmt_time_fmt[] = "Dec 27 14:52:30 2020 GMT";
    //char gmt_time_fmt[] = "Jan 01 00:00:01 1970 GMT";
    days = convert_gmt_date_time_to_tm_format(gmt_time_fmt);

    printf("GMT = %s and days are %d\n", gmt_time_fmt, days);
    return 0;
}

int convert_gmt_date_time_to_tm_format(char* gmt_time_fmt)
{
    struct tm tm;
    char tm_time_fmt[255];

    //set tm struture to 0
    memset(&tm, 0, sizeof(struct tm));
    // convert gmt_time_fmt to format required by 'tm' structure
    strptime(gmt_time_fmt, "%B %d %H:%M:%S %Y GMT", &tm);

    strftime(tm_time_fmt, sizeof(tm_time_fmt), "%s", &tm);
    printf("tm_time_fmt = %s\n", tm_time_fmt);

    print_time_readable_format(tm);
    return calculate_days_elapsed_from_epoch(tm);
}

int calculate_days_elapsed_from_epoch(struct tm tm)
{
    int days_by_month [2][12] = {
        /* normal years */
        { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
        /* leap years */
        { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}
    };

    int current_year = tm.tm_year+1900;
    int total_years_passed = current_year - BASE_YEAR;
    /* -1, to skip the current year */
    int nleap_years_passed = get_number_of_leap_years_from_base_year(BASE_YEAR, current_year-1); 
    int normal_years = total_years_passed - nleap_years_passed;
    int total_days_passed = (normal_years*365 + nleap_years_passed*366 );
    
    printf(" **Years total_days_passed =%d\n", total_days_passed);
    
    total_days_passed += days_by_month[(current_year%4 == 0) - (current_year%100 == 0) + (current_year%400 == 0)][tm.tm_mon];
    total_days_passed += tm.tm_mday - 1; /* to skip the current day */
    
    printf(" **total_days_passed =%d\n", total_days_passed);
    return total_days_passed;
}

int get_number_of_leap_years_from_base_year(int start_year, int end_year)
{
    int leap_year_count = 0;
    int year = start_year;
    
    while( year <= end_year)
    {
        if( (year%4 == 0) - (year%100 == 0) + (year%400 == 0) )
            leap_year_count++;
        year++;
    }
    printf("leap_year_count = %d\n", leap_year_count);
    return leap_year_count;
}

void print_time_readable_format(struct tm tm)
{
    printf("tm.tm_year = %d ", tm.tm_year);
    printf("tm.tm_mon = %d ", tm.tm_mon);
    printf("tm.tm_mday = %d ",tm.tm_mday);
    printf("tm.tm_hour = %d ", tm.tm_hour); 
    printf("tm.tm_min = %d ", tm.tm_min );
    printf("tm.tm_sec = %d\n", tm.tm_sec );
}

闰年的条件总结为:

  • 闰年如果在 400 之前完全可见
  • 如果 100 可见但不能被 400 整除,则不是闰年
  • 如果不能被 100 整除但能被 4 整除则为闰年
  • 其他年份都不是闰年

所以逻辑可以表示为:

if (((year%4 == 0) && (year%100 != 0)) || (year%400 == 0))
{
    leap_year_count++;
}
year++;

但不确定重构现有逻辑是否会增加任何速度优势。

使用mktime()

因为您的代码允许同时使用标准 C strftime() and POSIX strptime(), there's no reason not to use Standard C mktime()

它给你一个 time_t 值,这是自大纪元以来的秒数。

int calculate_days_elapsed_from_epoch(struct tm tm)
{
    time_t t = mktime(&tm);
    return t / 86400;   // 24 * 60 * 60 = 86400
}

但如果目标是计算自大纪元以来的秒数,您可以立即从 mktime() 中得到答案。

请注意,mktime() 传递了一个 struct tm 指针,它接受 'out of range' 的值并对结果进行规范化。另请参阅 'Demonstrating mktime()'.

部分中的示例代码

计算闰日

我有一个函数 jl_dmy_conversion() 潜伏在我的库中,它将年、月、日的组合转换为自 1899-12-31 以来的天数(所以在这个系统中,第 1 天是 1900- 01-01).但它包括对闰日数的计算。此代码是包的内部代码,其中参数已在日期范围 0001-01-01 .. 9999-12-31 内被验证为有效,因此它并不能保护自己免受无效数据的影响。还有另一个函数调用它来进行数据验证。此处显示的一些信息来自 header,大部分来自包含实现的源文件。

typedef int Date;

enum { DATE_NULL = -2147483648 };   /* Informix NULL DATE */

#define LEAPYEAR(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))

#define PRId_Date "d"

/*
** In 400 years, there are 97 leap years (because the three years
** divisible by 100 but not by 400 are not leap years).  This also
** happens to be exactly 20871 weeks.
*/
#define DAYS_IN_400_YEARS   (400*365+97)
#define DAYS_IN_2000_YEARS  (5*DAYS_IN_400_YEARS)

enum
{
    DAYS_IN_JANUARY   = 31,
    DAYS_IN_FEBRUARY  = 28,
    DAYS_IN_MARCH     = 31,
    DAYS_IN_APRIL     = 30,
    DAYS_IN_MAY       = 31,
    DAYS_IN_JUNE      = 30,
    DAYS_IN_JULY      = 31,
    DAYS_IN_AUGUST    = 31,
    DAYS_IN_SEPTEMBER = 30,
    DAYS_IN_OCTOBER   = 31,
    DAYS_IN_NOVEMBER  = 30,
    DAYS_IN_DECEMBER  = 31
};

static const int days_in_month[][2] =
{
    { 0,                 0                  },
    { DAYS_IN_JANUARY,   DAYS_IN_JANUARY    },
    { DAYS_IN_FEBRUARY,  DAYS_IN_FEBRUARY+1 },
    { DAYS_IN_MARCH,     DAYS_IN_MARCH      },
    { DAYS_IN_APRIL,     DAYS_IN_APRIL      },
    { DAYS_IN_MAY,       DAYS_IN_MAY        },
    { DAYS_IN_JUNE,      DAYS_IN_JUNE       },
    { DAYS_IN_JULY,      DAYS_IN_JULY       },
    { DAYS_IN_AUGUST,    DAYS_IN_AUGUST     },
    { DAYS_IN_SEPTEMBER, DAYS_IN_SEPTEMBER  },
    { DAYS_IN_OCTOBER,   DAYS_IN_OCTOBER    },
    { DAYS_IN_NOVEMBER,  DAYS_IN_NOVEMBER   },
    { DAYS_IN_DECEMBER,  DAYS_IN_DECEMBER   }
};

/* Return date as number of days since 31st December 1899 - no range check */
static Date jl_dmy_conversion(int d, int m, int y)
{
    int             leap;
    int             i;
    Date            daynum;

    /* No need to assert here - calling functions have checked basics */
    DB_TRACE(1, "[[-- jl_dmy_conversion (d = %2d, m = %2d, y = %4d) ", d, m, y);

    leap = LEAPYEAR(y);
    if (d > days_in_month[m][leap])
    {
        DB_TRACE(1, "<<-- NULL (invalid day of month)\n");
        return(DATE_NULL);
    }

    /* Number of days so far this month */
    daynum = d;

    /* Days so far this year prior to this month */
    for (i = 1; i < m; i++)
        daynum += days_in_month[i][leap];
    DB_TRACE(4, "YDAY = %3ld ", daynum);

    /*
    ** Now compute number of days to 1st of January of this year.  Add
    ** 2000 years (5 periods of 400 years) to ensure that numbers
    ** resulting from subtraction are positive, even when dates back to
    ** 0001-01-01 are allowed, and then remove the number of days found
    ** in 2000 years.  This assumes int is 32-bit or longer.
    **
    ** NB: Things begin to go haywire when (y - 1901) yields -4, which
    ** is when y == 1897.  Things get worse before 1601.  The result is
    ** usually, but not always, off by one.  Adding 2000 years and then
    ** subtracting the appropriate number of days sidesteps the
    ** problems.
    */
    y += 2000;
    daynum += 365 * (y - 1900); /* Ignoring leap years */
    DB_TRACE(4, "INC1 = %7d ", 365 * (y - 1900));
    daynum += (y - 1901) / 4;   /* Allowing for leap years */
    DB_TRACE(4, "INC2 = %4d ", (y - 1901) / 4);
    daynum -= (y - 1901) / 100; /* Allowing for non-leap years */
    DB_TRACE(4, "INC3 = %3d ", -(y - 1901) / 100);
    daynum += (y - 1601) / 400; /* Allowing for leap years */
    DB_TRACE(4, "INC4 = %2d ", (y - 1601) / 400);
    daynum -= DAYS_IN_2000_YEARS;
    DB_TRACE(1, " (r = %7" PRId_Date ") --]]\n", daynum);

    return(daynum);
}

DB_TRACE 宏是从 #define a macro for debug printing in C?. The DB_TRACE macro is available in my SOQ (Stack Overflow Questions) repository on GitHub as files debug.c and debug.h in the src/libsoq sub-directory 中显示的代码派生而来的。 格式给出了一行显示计算步骤。

上面的代码编译时包含 debug.h header 和 <stdio.h>,以及最小的 main(),加上来自 debug.c 的代码链接:


int main(void)
{
    int dd = 28;
    int mm = 12;
    int yyyy = 2020;
    Date dt = jl_dmy_conversion(dd, mm, yyyy);
    printf("%.4d-%.2d-%.2d = %d\n", yyyy, mm, dd, dt);
    return 0;
}

示范[​​=15=]

如上所述,mktime() 被传递了一个 struct tm 指针,据记载它接受 'out of range' 的值并对结果进行规范化——修改它传递的结构.它还设置 tm_wdaytm_yday 字段——它忽略它们作为输入。

如果您有 2020-12-28 的 struct tm 值 08:20:26 并且您想知道 6 天 18 小时 43 分 32 秒的 time_t 值稍后,您可以使用这样的代码:

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

static void print_time(time_t t, const struct tm *tm)
{
    char buffer[32];
    strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S %A", tm);
    printf("%lld: %s (%d)\n", (long long)t, buffer, tm->tm_yday);
}

int main(void)
{
   struct tm tm = { .tm_year = 2020 - 1900, .tm_mon = 12 - 1, .tm_mday = 28,
                    .tm_hour = 8, .tm_min = 20, .tm_sec = 26 };
   time_t t0 = mktime(&tm);
   print_time(t0, &tm);
   tm.tm_mday += 6;
   tm.tm_hour += 18;
   tm.tm_min += 43;
   tm.tm_sec += 32;
   time_t t1 = mktime(&tm);
   print_time(t1, &tm);
   return 0;
}

当运行(在US/Mountain标准时区——UTC-7)时,它产生:

1609168826: 2020-12-28 08:20:26 Monday (362)
1609754638: 2021-01-04 03:03:58 Monday (3)