从日期中添加或减去天数的算法?

Posted

技术标签:

【中文标题】从日期中添加或减去天数的算法?【英文标题】:Algorithm to add or subtract days from a date? 【发布时间】:2011-01-21 14:34:51 【问题描述】:

我正在尝试编写一个 Date 类以尝试学习 C++。

我正在尝试找到一种算法来为日期添加或减去天数,其中 Day 从 1 开始,Month 从 1 开始。事实证明它非常复杂,而且 google 出现的次数不多,

有谁知道这样的算法吗?

【问题讨论】:

我很惊讶这个问题的存在没有随附的“使用提升”答案和文档链接。 【参考方案1】:

最简单的方法是实际编写两个函数,一个将日期转换为从给定开始日期开始的天数,另一个将转换回日期。将日期表示为天数后,对其进行加减运算就很简单了。

您可以在此处找到算法:http://alcor.concordia.ca/~gpkatch/gdate-algorithm.html

【讨论】:

谢谢,这正是我要找的,由于某种原因,我在网上搜索时找不到算法! 不幸的是,这些函数不是很精确......或者至少当我将我的结果与 wolfram alpha 进行比较时,我差了一天左右。 这里:home.roadrunner.com/~hinnant/date_algorithms.html 是精确的算法。它们的有效性已经过使用 32 位算术在 +/- 580 万年范围内的预测公历上进行了准确测试。他们计算 1970-01-01 之前或之后的天数。 @HowardHinnant,这看起来是一个很好的资源,谢谢。您可以进行的一种简化是从doy 中删除-1,使其范围为[1, 366],然后在末尾减去719469 而不是719468 以进行补偿。 自从我写了上面的评论,我不得不移动我的个人网站。我的日期算法现在在这里:howardhinnant.github.io/date_algorithms.html 我还注意到来自alcor.concordia.ca/~gpkatch/gdate-algorithm.html 的d(g) 函数似乎没有返回g(y,m,d) 的倒数。也许我只是把它编错了,但我还没有发现我的错误。【参考方案2】:

您并不真的需要这样的算法(至少不是名副其实的东西),标准库可以完成大部分繁重的工作;日历计算是出了名的棘手。只要您不需要早于 1900 年的日期,那么:

#include <ctime>

// Adjust date by a number of days +/-
void DatePlusDays( struct tm* date, int days )

    const time_t ONE_DAY = 24 * 60 * 60 ;

    // Seconds since start of epoch
    time_t date_seconds = mktime( date ) + (days * ONE_DAY) ;

    // Update caller's date
    // Use localtime because mktime converts to UTC so may change date
    *date = *localtime( &date_seconds ) ; ;

使用示例:

#include <iostream>

int main()

    struct tm date =  0, 0, 12  ;  // nominal time midday (arbitrary).
    int year = 2010 ;
    int month = 2 ;  // February
    int day = 26 ;   // 26th

    // Set up the date structure
    date.tm_year = year - 1900 ;
    date.tm_mon = month - 1 ;  // note: zero indexed
    date.tm_mday = day ;       // note: not zero indexed

    // Date, less 100 days
    DatePlusDays( &date, -100 ) ; 

    // Show time/date using default formatting
    std::cout << asctime( &date ) << std::endl ;

【讨论】:

感谢您发布此信息。非常有用。 闰秒会打乱这个计算吗? @vargonian:一个好问题; UNIX 时间纪元从 1970 年 1 月 1 日开始,不计算闰秒。然而,将一天中的标称时间设置为正午将避免数万年的任何潜在问题。【参考方案3】:

我假设这是为了某种练习,否则您将使用已经提供给您的时间课程。

您可以将时间存储为自某个日期以来的毫秒数。然后您可以添加适当的值并在调用您的类的访问器时将其转换为日期。

【讨论】:

为什么是毫秒?他似乎只想要日期,而不是时间,当然也不是毫秒精度。这甚至暗示要考虑闰秒。【参考方案4】:

这是一个非常简单的方法的草图。为了简单起见,我假设要添加的天数 d 是正数。很容易将下面的情况扩展到d 为负数的情况。

d 小于 365 或 d 大于或等于 365。

如果d 小于 365:

m = 1;
while(d > numberOfDaysInMonth(m, y)) 
    d -= numberOfDaysInMonth(m, y);
    m++;

return date with year = y, month = m, day = d;

如果d 大于 365:

while(d >= 365) 
    d -= 365;
    if(isLeapYear(y)) 
        d -= 1;
    
    y++;

// now use the case where d is less than 365

或者,您可以用 Julian form 表示日期,然后只需添加到 Julian 格式并转换为 ymd 格式。

【讨论】:

【参考方案5】:

一种方法是将日期映射到日期的儒略数,进行整数运算,然后转换回来。

您会发现大量有关 julian 函数的资源。

【讨论】:

【参考方案6】:

试试这个功能。它正确计算加法或减法。 dateTime 参数必须是 UTC 格式。

tm* dateTimeAdd(const tm* const dateTime, const int& days, const int& hours, const int& mins, const int& secs) 
    tm* newTime = new tm;
    memcpy(newTime, dateTime, sizeof(tm));

    newTime->tm_mday += days;
    newTime->tm_hour += hours;
    newTime->tm_min += mins;
    newTime->tm_sec += secs;        

    time_t nt_seconds = mktime(newTime) - timezone;
    delete newTime;

    return gmtime(&nt_seconds);

还有使用的例子:

time_t t = time(NULL);
tm* utc = gmtime(&t);
tm* newUtc = dateTimeAdd(utc, -5, 0, 0, 0); //subtract 5 days

【讨论】:

【参考方案7】:

我知道这是一个非常古老的问题,但在处理日期和时间时,它是一个有趣且常见的问题。所以我想分享一些代码来计算新日期,而不使用 C++ 中的任何内置时间功能。

#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;

    // Subtract specified number of days from 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);


//===========================================================================================
/// Subtract specified number of days from date
Date Date::operator - (size_t days) const 
    // Falls within the same month?
    if (0 < (m_day - days)) 
        return Date(m_year, m_month, m_day - days);
    

    // Start from this year
    int nYear(m_year);

    // Start from specified days and go back to first day of this month
    int nDays(days);
    nDays -= m_day;

    // Start from previous month and check if it falls on to previous year
    int nMonth(m_month - 1);
    if (nMonth < 1) 
        nMonth = 12; // December
        --nYear;     // Previous year
    

    // Maximum days in the current month
    int nDaysInMonth = MaxDayInMonth[nMonth] + (nMonth == 2 && LeapYear(nYear) ? 1 : 0);

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

        // Falls on to previous month?
        if (nDays > 0) 
            // Go to previous month
            --nMonth;

            // Falls on to previous year?
            if (nMonth < 1) 
                nMonth = 12; // December
                --nYear;     // Previous year
            
        

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

    // Construct date
    return Date(nYear, nMonth, (0 < nDays ? nDays : -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;

    // Subtract n days from a date
    cout << Date(2017, 6, 25).DateStr() << " - 10 days = "
         << (Date(2017, 6, 25) /* Given Date */ - 10 /* Days to subract */).DateStr() << endl;

    return 0;


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

【讨论】:

您的 + 和 - 运算符对于较大的日期非常慢。【参考方案8】:

我建议首先编写一个例程,将年月日转换为自固定日期以来的天数,例如,自 1.01.01 以来。还有一个对称的例程可以将其转换回来。

别忘了正确处理闰年!

拥有这两个,您的任务将是微不足道的。

【讨论】:

【参考方案9】:

我知道这是近十年前提出的一个老问题。但是几天前我遇到了同样的任务,这是here中的答案

// C++ program to find date after adding 
// given number of days. 
#include<bits/stdc++.h> 
using namespace std; 

// Return if year is leap year or not. 
bool isLeap(int y) 
 
    if (y%100 != 0 && y%4 == 0 || y %400 == 0) 
        return true; 

    return false; 
 

// Given a date, returns number of days elapsed 
// from the beginning of the current year (1st 
// jan). 
int offsetDays(int d, int m, int y) 
 
    int offset = d; 

    switch (m - 1) 
     
    case 11: 
        offset += 30; 
    case 10: 
        offset += 31; 
    case 9: 
        offset += 30; 
    case 8: 
        offset += 31; 
    case 7: 
        offset += 31; 
    case 6: 
        offset += 30; 
    case 5: 
        offset += 31; 
    case 4: 
        offset += 30; 
    case 3: 
        offset += 31; 
    case 2: 
        offset += 28; 
    case 1: 
        offset += 31; 
     

    if (isLeap(y) && m > 2) 
        offset += 1; 

    return offset; 
 

// Given a year and days elapsed in it, finds 
// date by storing results in d and m. 
void revoffsetDays(int offset, int y, int *d, int *m) 
 
    int month[13] =  0, 31, 28, 31, 30, 31, 30, 
                    31, 31, 30, 31, 30, 31 ; 

    if (isLeap(y)) 
        month[2] = 29; 

    int i; 
    for (i = 1; i <= 12; i++) 
     
        if (offset <= month[i]) 
            break; 
        offset = offset - month[i]; 
     

    *d = offset; 
    *m = i; 
 

// Add x days to the given date. 
void addDays(int d1, int m1, int y1, int x) 
 
    int offset1 = offsetDays(d1, m1, y1); 
    int remDays = isLeap(y1)?(366-offset1):(365-offset1); 

    // y2 is going to store result year and 
    // offset2 is going to store offset days 
    // in result year. 
    int y2, offset2; 
    if (x <= remDays) 
     
        y2 = y1; 
        offset2 = offset1 + x; 
     

    else
     
        // x may store thousands of days. 
        // We find correct year and offset 
        // in the year. 
        x -= remDays; 
        y2 = y1 + 1; 
        int y2days = isLeap(y2)?366:365; 
        while (x >= y2days) 
         
            x -= y2days; 
            y2++; 
            y2days = isLeap(y2)?366:365; 
         
        offset2 = x; 
     

    // Find values of day and month from 
    // offset of result year. 
    int m2, d2; 
    revoffsetDays(offset2, y2, &d2, &m2); 

    cout << "d2 = " << d2 << ", m2 = " << m2 
        << ", y2 = " << y2; 
 

// Driven Program 
int main() 
 
    int d = 14, m = 3, y = 2015; 
    int x = 366; 

    addDays(d, m, y, x); 

    return 0; 
 

【讨论】:

【参考方案10】:

不知道这是否有帮助。我正在研究一个调度系统,该系统(在第一个简单草案中)将开始日期计算为截止日期 - 提前期天数。我处理了经过的秒数(自纪元以来),以便在未来的代码草稿中实现更高的精度。

#include <iostream>
#include <ctime>

int main() 
  // lead time in days
  int lead_time = 20;
  // assign a due_date of (midnight on) 3rd November 2020
  tm tm_due_date =  0, 0, 0, 3, 11, 2020-1900;
  // convert due_date to seconds elapsed (since epoch)
  time_t tt_due_date = mktime(&tm_due_date);
  // subtract lead time seconds
  tt_due_date -= 60 * 60 * 24 * lead_time;
  // convert back (to local time)
  tm *start_date = localtime(&tt_due_date);
  // otput the result as something we can readily read
  std::cout << asctime(start_date) << "\n";

【讨论】:

以上是关于从日期中添加或减去天数的算法?的主要内容,如果未能解决你的问题,请参考以下文章

PHP 从日期添加或减去天数

如何从/到日期减去/添加天数?

如何使用 postgresql/netezza 从日期时间中减去天数或月数

从日期加上或减去天数

使用 SORT 从日期中减去天数

如何在 TypeScript 中从日期中减去天数