【问题标题】:easy way to add 1 month to a time_t in C/C++在 C/C++ 中将 1 个月添加到 time_t 的简单方法
【发布时间】:2010-09-30 06:03:29
【问题描述】:

我有一些代码使用 Oracle 函数 add_months 将日期增加 X 个月。

我现在需要在 C/C++ 函数中重新实现相同的逻辑。由于我不想/不需要进入的原因,我不能简单地向 oracle 发出查询以获取新日期。

有人知道将 X 个月数添加到 time_t 的简单可靠的方法吗? 下面显示了一些计算类型的示例。

30/01/2009 + 1 个月 = 28/02/2009
2009 年 1 月 31 日 + 1 个月 = 2009 年 2 月 28 日
2009 年 2 月 27 日 + 1 个月 = 2009 年 3 月 27 日
2009 年 2 月 28 日 + 1 个月 = 2009 年 3 月 31 日
2009 年 1 月 31 日 + 50 个月 = 2013 年 3 月 31 日

【问题讨论】:

  • 正如 Mehrdad Afshari 在下面指出的那样,28/02/2009 + 1 个月 = 31/03/2009 不可能使用简单类型。你怎么知道 28/02/2009 是“一个月的最后一天”而不是“一个月的第 28 天”。它根本没有在类型中编码。你需要更好的表现形式。
  • 我认为 28/02/2009 + 1 个月 = 31/03/2009 是错误的。它应该是 28/02/2009 + 1 个月 = 28/03/2009。从 .Net 的行为方式来看,唯一不会导致当月同一天的示例是生成的月份的天数少于原始月份的天数。即:2009 年 1 月 31 日 + 1 个月 = 2009 年 2 月 28 日
  • 现在我重新阅读了您的示例,您似乎真正在寻找的是如何在未来一个月的某个时间查找本月的最后一天,这是一个略有不同的问题.
  • 微妙:30/1/2009 + 1 个月 + 1 个月 = 28/3/2009 !
  • @MSalters 更微妙的是:30/1/2009 +1 month - 1 month = 28/1/2009。这似乎是定义月份的不好方法。

标签: c++ c date time-t date-math


【解决方案1】:

您可以为此使用Boost.GregorianDate

更具体地说,通过添加正确的date_duration 来确定月份,然后使用日期算法中的end_of_month_day()

【讨论】:

  • 因为“只有在复杂性的另一面才能找到简单性。” time_t 根本没有足够的信息来说明他在示例中要求的上下文类型。在函数中使用 boost 进行转换,然后转换回 time_t 对我来说似乎是合理的。
  • 这对我来说似乎也很合理。不幸的是,我们不使用 Boost,也不会在不久的将来使用。 :-(
【解决方案2】:

time_t 转换为struct tm,将X 添加到月份,将月份> 12 添加到年,然后转换回来。 tm.tm_mon 是一个 int,加上 32000+ 个月应该不是问题。

[edit] 一旦遇到更困难的情况,例如将 12 个月添加到 29/02/2008,您可能会发现匹配 Oracle 会很棘手。 01/03/2009 和 28/02/2008 都是合理的。

【讨论】:

  • 这与 oracle 函数不太一样。使用上面的 30-01-2009 变成 02-03-2009
【解决方案3】:

真的一个真的老问题的新答案!

使用 this free and open source library 和 C++14 编译器(例如 clang)我现在可以这样写:

#include "date.h"

constexpr
date::year_month_day
add(date::year_month_day ymd, date::months m) noexcept
{
    using namespace date;
    auto was_last = ymd == ymd.year()/ymd.month()/last;
    ymd = ymd + m;
    if (!ymd.ok() || was_last)
        ymd = ymd.year()/ymd.month()/last;
    return ymd;
}

int
main()
{
    using namespace date;
    static_assert(add(30_d/01/2009, months{ 1}) == 28_d/02/2009, "");
    static_assert(add(31_d/01/2009, months{ 1}) == 28_d/02/2009, "");
    static_assert(add(27_d/02/2009, months{ 1}) == 27_d/03/2009, "");
    static_assert(add(28_d/02/2009, months{ 1}) == 31_d/03/2009, "");
    static_assert(add(31_d/01/2009, months{50}) == 31_d/03/2013, "");
}

它会编译。

请注意实际代码与 OP 的伪代码之间的显着相似性:

30/01/2009 + 1 个月 = 28/02/2009
2009 年 1 月 31 日 + 1 个月 = 2009 年 2 月 28 日
2009 年 2 月 27 日 + 1 个月 = 2009 年 3 月 27 日
2009 年 2 月 28 日 + 1 个月 = 2009 年 3 月 31 日
2009 年 1 月 31 日 + 50 个月 = 2013 年 3 月 31 日

还要注意编译时信息in会导致编译时信息out

【讨论】:

  • 您应该在答案中声明它是您的图书馆。 :)
  • @LightnessRacesinOrbit:如果你点击链接看最上面一行,是不是不清楚?我不确定如何听从你的建议而不显得尴尬。但我愿意接受建议。
  • 恐怕帮不了你 :) 我对这个答案没有问题,但我知道一些元警察痴迷于公然披露等
  • @HowardHinnant 使用不太权威的短语可以使它看起来不那么“强加”。但是我认为它没有太大问题,因为它是免费和开源的。
  • @TemplateRex:year,是的,2015_ymonth,是的,但不使用用户定义的文字。 jan, 'feb, etc. weeks, no. I couldn't settle on a good way to spell it. So weeks{2}. But there is sun, mon, tue`等工作日。
【解决方案4】:

方法 AddMonths_OracleStyle 可以满足您的需要。

也许您希望将 IsLeapYear 和 GetDaysInMonth 替换为一些图书管理员方法。

#include <ctime>
#include <assert.h>

bool IsLeapYear(int year) 
{
    if (year % 4 != 0) return false;
    if (year % 400 == 0) return true;
    if (year % 100 == 0) return false;
    return true;
}

int daysInMonths[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

int GetDaysInMonth(int year, int month)
{
    assert(month >= 0);
    assert(month < 12);

    int days = daysInMonths[month];

    if (month == 1 && IsLeapYear(year)) // February of a leap year
        days += 1;

    return days;
}

tm AddMonths_OracleStyle(const tm &d, int months)
{
    bool isLastDayInMonth = d.tm_mday == GetDaysInMonth(d.tm_year, d.tm_mon);

    int year = d.tm_year + months / 12;
    int month = d.tm_mon + months % 12;

    if (month > 11)
    {
        year += 1;
        month -= 12;
    }

    int day;

    if (isLastDayInMonth)
        day = GetDaysInMonth(year, month); // Last day of month maps to last day of result month
    else
        day = std::min(d.tm_mday, GetDaysInMonth(year, month));

    tm result = tm();

    result.tm_year = year;
    result.tm_mon = month;
    result.tm_mday = day;

    result.tm_hour = d.tm_hour;
    result.tm_min = d.tm_min;
    result.tm_sec = d.tm_sec;

    return result;
}

time_t AddMonths_OracleStyle(const time_t &date, int months)
{
    tm d = tm();

    localtime_s(&d, &date);

    tm result = AddMonths_OracleStyle(d, months);

    return mktime(&result);
}

【讨论】:

  • 我想你想改变 GetDaysInMonth 来检查 IsLeapYear(year) 和月份是否是二月
  • 如果添加的额外时间跨越 dst 边界,也不会将 tm 结构中的 isdst 重置为 -1 会导致答案错误。强烈建议窃取已经可以正常工作/处理的代码。
  • hamishmcn,谢谢。这就是微软为 Zune 开发软件的方式:)
  • 这适用于一些修改。 1、用gmtime代替localtime。 2、查询是否为闰年时,年份加1900。 3、需要强制 mktime 使用 GMT 作为时区。这应该注意跨越 DST 边界
  • 爱因斯坦:关于我可以“窃取”一些工作代码的位置有什么建议吗?
猜你喜欢
  • 2012-11-09
  • 1970-01-01
  • 1970-01-01
  • 2023-04-06
  • 2013-09-21
  • 2014-03-28
  • 2013-07-14
  • 1970-01-01
相关资源
最近更新 更多