C 或 C++ 中日历日期的算术(将 N 天添加到给定日期)

Posted

技术标签:

【中文标题】C 或 C++ 中日历日期的算术(将 N 天添加到给定日期)【英文标题】:Arithmetics on calendar dates in C or C++ (add N days to given date) 【发布时间】:2013-03-18 02:41:02 【问题描述】:

我得到了一个日期,我将其作为输入,例如(日、月、年):12, 03, 87

现在我需要找出n 天之后的日期。

我为此编写了代码,但效率不高。你能告诉我任何运行速度更快且复杂性更低的好的逻辑吗?

#include <stdio.h>

static int days_in_month[] =  0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ;
int day, month, year;

unsigned short day_counter;

int is_leap(int y) 
    return ((y % 4 == 0 && y % 100 != 0) || y % 400 == 0);


next_day()

    day += 1; day_counter++;
    if (day > days_in_month[month]) 
        day = 1;
        month += 1;
        if (month > 12) 
            month = 1;
            year += 1;
            if (is_leap(year)) 
                days_in_month[2] = 29;
             else 
                days_in_month[2] = 28;
            
        
    


set_date(int d, int m, int y) 

    m < 1 ? m = 1 : 0;
    m > 12 ? m = 12 : 0;
    d < 1 ? d = 1 : 0;
    d > days_in_month[m] ? d = days_in_month[m] : 0;
    if (is_leap(y))
        days_in_month[2] = 29;
     
    else 
        days_in_month[2] = 28;
    
    day = d;
    month = m;
    year = y;


skip_days(int x)

    int i;
    for (i=0;i<x;i++) next_day();


print_date()

    printf ("day: %d month: %d year: %d\n", day, month, year);


int main(int argc, char **argv)

    int i;

    set_date(5, 2, 1980);
    skip_days(40);
    day_counter = 0;
    /* after this call next_day each day */

    print_date();
    return 0;

【问题讨论】:

C 还是 C++?选择一个。 您已将问题标记为 C++,因此我建议您使用 boost's date/time。这将为您处理所有问题。 对 C 使用 Date and time utilities,对 C++ 使用 chrono library @JeffPaquette 实际上,不,the homework tag is officially deprecated。 高效、记录在案的日历算法:howardhinnant.github.io/date_algorithms.html 无需迭代。 【参考方案1】:

您能否告诉我任何运行速度更快且复杂性更低的好逻辑。

如果这个确切的事情确实是您的应用程序的性能关键部分,那么您很可能做错了什么。为了清楚和正确起见,您应该坚持现有的解决方案。选择最适合您的开发环境的一种。


C 方法:

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

int main()
        
    /* initialize */
    int y=1980, m=2, d=5;    
    struct tm t =  .tm_year=y-1900, .tm_mon=m-1, .tm_mday=d ;
    /* modify */
    t.tm_mday += 40;
    mktime(&t);
    /* show result */
    printf("%s", asctime(&t)); /* prints: Sun Mar 16 00:00:00 1980 */
    return 0;

不使用 Boost 的 C++ 方法:

#include <ctime>
#include <iostream>

int main()
        
    // initialize
    int y=1980, m=2, d=5;
    std::tm t = ;
    t.tm_year = y-1900;
    t.tm_mon  = m-1;
    t.tm_mday = d;
    // modify
    t.tm_mday += 40;
    std::mktime(&t);
    // show result
    std::cout << std::asctime(&t); // prints: Sun Mar 16 00:00:00 1980

Boost.Date_Time 方法:

#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>

int main()

    using namespace boost::gregorian;
    // initialize
    date d(1980,2,5);
    // modify
    d += days(40);
    // show result
    std::cout << d << '\n'; // prints: 1980-Mar-16

【讨论】:

你如何比较不同的时间? (小于、大于、等于等) @0x499602D2 可以使用标准的difftime函数。【参考方案2】:

标准库mktime 函数包含一个技巧,可以轻松地将数月或数天添加到给定日期:您可以给它一个日期,例如“2 月 45 日”或“40 日的第 2 天”月”和mktime 会将其标准化为适当的日期。示例:

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

int main() 
    int y = 1980;
    int m = 2;
    int d = 5;
    int skip = 40;

    // Represent the date as struct tm.                                                           
    // The subtractions are necessary for historical reasons.
    struct tm  t =  0 ;
    t.tm_mday = d;
    t.tm_mon = m-1;
    t.tm_year = y-1900;

    // Add 'skip' days to the date.                                                               
    t.tm_mday += skip;
    mktime(&t);

    // Print the date in ISO-8601 format.                                                         
    char buffer[30];
    strftime(buffer, 30, "%Y-%m-%d", &t);
    puts(buffer);

与使用 time_t 在几秒钟内完成算术相比,这种方法的优点是夏令时转换不会导致任何问题。

【讨论】:

这种方法只有在添加月份或年份时才有优势。如果我们添加天数,则没有区别。无论如何,mktime 消除了夏令时。为什么要关心我们要扔掉的东西?将 n*86400 添加到时间戳更容易和更快(假设您已经将初始时间作为 unix 时间戳)。【参考方案3】:

这是使用由 Howard Hinnant 发布的 here 算法得出的解决方案。

int main() 
    int day_count = days_from_civil(1980, 5, 2);
    auto [year, month, day] = civil_from_days(day_count + 40);
    printf("%04i-%02i-%02-i\n", (int)year, (int)month, (int)day);

新的日期功能已获准包含在 C++20 中,但 API 有所不同。 C++20 解决方案可能类似于:

#include <chrono>

int main() 
    using namespace std::chrono;
    sys_days in_days = year_month_day1980y, may, 2d;
    std::cout << year_month_day(in_days + days(40)) << '\n';

【讨论】:

【参考方案4】:

最简单的技巧是使用time_t 类型和相应的函数。

mktime 会将 tm 结构转换为 time_t。这是一个整数值,从 1970 年 1 月 1 日开始计算秒数。

获得time_t 值后,只需添加所需的秒数(每天 86400)。

要转换回来,请使用gmtimelocaltime

【讨论】:

将 86400 秒计为一天是不正确的做法,因为“一天”不像秒那样是固定的时间单位;有些天是 86401 秒长,有些是 82800 秒,有些是 90000 秒。这些差异可能通过 C 时间函数反映,具体取决于平台和时区数据(例如,时区可能会使用准确的闰秒表编译)。 @bames53 有些日子是半年......说真的,你不明白这里的实际问题吗?你为什么要把事情复杂化? @hkBattousai 我不认为注意到这个解决方案可以提供不正确的答案会让事情变得过于复杂。正确、简单的解决方案是使用正确的日期算法。例如。假设一个合适的library 就这样做:civil_from_days(10 + days_from_civil(2015, 11, 16))【参考方案5】:

只需将您想要的天数添加到 time_t 对象。

#define SECOND              1
#define MINUTE              60 * SECOND
#define HOUR                60 * MINUTE
#define DAY                 24 * HOUR





time_t curTime;
time_t futureTime;

time( & curTime );

futureTime = curTime + (5 * DAY);

struct tm * futureDate = gmtime(&futureTime);
std::cout<<"5 days from now will be: "<<futureDate->tm_mday<<"/"<<futureDate->tm_mon+1<<"/"<<futureDate->tm_year+1900<<std::endl;

【讨论】:

天并不是像秒和小时这样的固定时间单位,所以这种事情是不正确的,尽管在实践中这种不准确性通常不是很明显。但是例如,如果上面的代码是在 2012 年 6 月 29 日 00:00:00 运行的。将 5 天添加到 6 月 29 日应该会导致日期为 7 月 4 日,但是 2012 年 6 月 30 日有闰秒。假设时区包含正确的闰秒数据,则此代码的输出将表明从 6 月 29 日起的 5 天是7 月 3 日。此外,转换为夏令时或从夏令时转换可能会导致类似的问题。 与其使用 24*60*60 代替“一天”,不如使用知道如何根据实际日期工作的日期类型。一个例子是 Howard Hinnant 的 chrono::date 库。 @bames53:同样,time_t 不计算闰秒。【参考方案6】:

可以使用 C++ 运算符并通过将日期表示为类以非常 OOP 的方式实现。

#include <iostream>
#include <string>

using namespace std;

class Date 
public:
    Date(size_t year, size_t month, size_t day):m_year(year), m_month(month), m_day(day) 
    ~Date() 

    // Add specified number of days to date
    Date operator + (size_t days) const;

    size_t Year()   return m_year; 
    size_t Month()  return m_month; 
    size_t Day()    return m_day; 

    string DateStr();
private:
    // Leap year check 
    inline bool LeapYear(int year) const
         return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); 

    // Holds all max days in a general year
    static const int MaxDayInMonth[13];

    // Private members
    size_t m_year;
    size_t m_month;
    size_t m_day;   
;

// Define MaxDayInMonth
const int Date::MaxDayInMonth[13] = 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31;

/// Add specified number of days to date
Date Date::operator + (size_t days) const 
    // Maximum days in the month
    int nMaxDays(MaxDayInMonth[m_month] + (m_month == 2 && LeapYear(m_year) ? 1 : 0));

    // Initialize the Year, Month, Days
    int nYear(m_year);
    int nMonth(m_month);
    int nDays(m_day + days);

    // Iterate till it becomes a valid day of a month
    while (nDays > nMaxDays) 
        // Subtract the max number of days of current month
        nDays -= nMaxDays;

        // Advance to next month
        ++nMonth;

        // Falls on to next year?
        if (nMonth > 12) 
            nMonth = 1; // January
            ++nYear;    // Next year
        

        // Update the max days of the new month
        nMaxDays = MaxDayInMonth[nMonth] + (nMonth == 2 && LeapYear(nYear) ? 1 : 0);
    

    // Construct date
    return Date(nYear, nMonth, nDays);


/// Get the date string in yyyy/mm/dd format
string Date::DateStr() 
    return to_string(m_year) 
        + string("/")
        + string(m_month < 10 ? string("0") + to_string(m_month) : to_string(m_month))
        + string("/")
        + string(m_day < 10 ? string("0") + to_string(m_day) : to_string(m_day)); 



int main() 
    // Add n days to a date
    cout << Date(2017, 6, 25).DateStr() << " + 10 days = "
         << (Date(2017, 6, 25) /* Given Date */ + 10 /* Days to add */).DateStr() << endl;

    return 0;


Output
2017/06/25 + 10 days = 2017/07/05

【讨论】:

【参考方案7】:

使用自纪元以来的秒数而不是直接操作日期字段可能更容易。

例如,这个程序打印从现在起 7 天的日期:

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

main()

    time_t t;
    struct tm *tmp;

    time(&t);

    /* add a week to today */
    t += 7 * 24 * 60 * 60;

    tmp = localtime(&t);
    printf("%02d/%02d/%02d\n", tmp->tm_mon+1, tmp->tm_mday,
        tmp->tm_year % 100);

【讨论】:

以这种方式使用秒不是正确的做法,因为“一天”不像秒那样是固定的时间单位;有些天是 86401 秒长,有些是 82800 秒,有些是 90000 秒。这些差异可能通过 C 时间函数反映,具体取决于平台和时区数据(例如,时区可能会使用准确的闰秒表编译)。 @bames53:不是这样。 time_t 不是 UTC。它的所有日子都是 86400 秒长。闰秒在 POSIX 中被忽略/跳过/转换。 @LightnessRacesinOrbit 这取决于系统的配置方式。例如,考虑以下文档:"The asctime(), ctime(), difftime(), gmtime(), localtime(), and mktime() functions conform to ISO/IEC 9899:1990 ("ISO C89''), and conform to ISO/IEC 9945-1:1996 ("POSIX.1'') provided the selected local timezone does not contain a leap-second table (see zic(8) )." [emp.添加] @bames53:该注释与将 POSIX 时间转换为非 POSIX 时间的方式有关(各种人类可读的时间戳、带有分解的年/月/日/小时等字段的结构,以及等等),因此闰秒很重要。但time_t 本身总是只是自 1970 年 1 月 1 日以来的秒数,不计算闰秒。始终以这种方式定义 UNIX 时间戳。因此,您说“差异可能由 C 时间函数反映”是正确的,但您永远不能通过“以这种方式使用秒数”来触发所述差异。安迪的方法总是会在今天加上一周,期间。 @LightnessRacesinOrbit 否,系统可以配置为 time_t 包括闰秒。这是另一个例子"If the time package is configured with leap-second support enabled [...] time_t values continue to increase over leap events"【参考方案8】:

此代码将打印 10 天后的日期。将用户定义的数字的值更改为 N。

#include< iostream.h>

#include< conio.h>

struct dateint d,m,y;;

void main()

    

    date d1;

    void later (date);

    cout<<"ENTER A VALID DATE (dd/mm/yy)";

    cin>>d1.d>>d1.m>>d1.y;

    later(d1);

    getch();

    

    void later(date d1)

    

    int mdays[12]=31,28,31,30,31,30,31,31,30,31,30,31;

    if(((d1.y%4==0) && (d1.y%100!=0))||(d1.y%400==0))
    mdays[1]=29;
    d1.d=d1.d+10;

    if(d1.d>mdays[d1.m-1])

    

    d1.d=d1.d-mdays[d1.m-1];

    d1.m++;

    if(d1.m>12)

    d1.m=1;

    d1.y++;

    

    
    cout<<"DATE AFTER TEN DAYS IS "<<d1.d<<"\t"<<d1.m<<"\t"<<d1.y;

    getch();

    

【讨论】:

以上是关于C 或 C++ 中日历日期的算术(将 N 天添加到给定日期)的主要内容,如果未能解决你的问题,请参考以下文章

LWUIT 日历问题

swift 3或objective c中的水平日历视图

如何将日期/日历小部件添加到 django 表单?

打卡 C++类与对象定义一个日期类 N天以后

如何将 1 天添加到当前日期?

将事件添加到日历时,NSDateFormatter 未正确格式化日期