простой способ добавить 1 месяц к time_t в C/C++

У меня есть код, который использует функцию Oracle add_months для увеличения даты на X месяцев.

Теперь мне нужно повторно реализовать ту же логику в функции C/C++. По причинам, которые я не хочу/нужно вдаваться, я не могу просто отправить запрос к оракулу, чтобы получить новую дату.

Кто-нибудь знает простой и надежный способ добавления X месяцев к time_t? Некоторые примеры типов расчетов показаны ниже.

30.01.2009 + 1 месяц = ​​28.02.2009
31.01.2009 + 1 месяц = ​​28.02.2009
27.02.2009 + 1 месяц = ​​27.03.2009
> 28.02.2009 + 1 месяц = ​​31.03.2009
31.01.2009 + 50 месяцев = 31.03.2013


person Glen    schedule 08.01.2009    source источник
comment
Как указывает ниже Мехрдад Афшари, 28/02/2009 + 1 месяц = ​​31/03/2009 невозможно для простого типа. Откуда вы знаете, что 28.02.2009 - это последний день месяца, а не 28-й день месяца. Это просто не закодировано в типе. Вам нужно лучшее представление.   -  person Aaron    schedule 08.01.2009
comment
Я думаю, что 28.02.2009 + 1 месяц = ​​31.03.2009 неверно. Должно быть 28.02.2009 + 1 месяц = ​​28.03.2009. Судя по тому, как ведет себя .Net, единственные примеры, которые не приводят к одному и тому же дню месяца, - это когда в результирующем месяце меньше дней, чем в исходном месяце. т.е.: 31.01.2009 + 1 месяц = ​​28.02.2009   -  person Kibbee    schedule 08.01.2009
comment
Теперь, когда я перечитал ваши примеры, кажется, что вы действительно ищете, как искать последний день месяца в какой-то момент месяца в будущем, что является немного другим вопросом.   -  person Kibbee    schedule 08.01.2009
comment
Тонкий: 30.01.2009 + 1 месяц + 1 месяц = ​​28.03.2009!   -  person MSalters    schedule 09.01.2009
comment
@MSalters Еще более тонко: 30/1/2009 +1 month - 1 month = 28/1/2009. Это кажется плохим способом определения месяцев.   -  person nwp    schedule 20.07.2015
comment
@Aaron, дата повышения add_month просто работает таким образом. Я думаю, это странно.   -  person Zhang    schedule 14.05.2019


Ответы (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);
}
person Konstantin Spirin    schedule 08.01.2009
comment
Я думаю, вы хотите изменить GetDaysInMonth, чтобы проверить, IsLeapYear (год) И если месяц февраль - person hamishmcn; 08.01.2009
comment
Кроме того, отсутствие сброса isdst в -1 в структуре tm делает ответ неверным, если дополнительное добавленное время пересекает границы dst. Настоятельно рекомендуется украсть код, который уже работает/обрабатывает это должным образом. - person Einstein; 08.01.2009
comment
hamishmcn, спасибо. Вот как Microsoft разработала программное обеспечение для Zune :) - person Konstantin Spirin; 08.01.2009
comment
Это работает с несколькими модификациями. 1, используйте gmtime вместо локального времени. 2, добавьте 1900 к году, когда проверяете, является ли он високосным. 3, необходимо заставить mktime использовать GMT в качестве часового пояса. Это должно позаботиться о пересечении границ DST - person Glen; 08.01.2009
comment
Эйнштейн: Есть какие-нибудь предложения относительно мест, где я мог бы «украсть» рабочий код? - person Glen; 08.01.2009
comment
Глен, спасибо за обзор. Теперь я понимаю, что стандартный C++ слишком сложен для меня в эти дни. Я бы предложил что-то более высокого уровня абстракции, чем time_t. Что касается места для кражи кода, то Boost выглядит наиболее очевидным кандидатом. - person Konstantin Spirin; 08.01.2009

Для этого можно использовать Boost.GregorianDate.

В частности, определите месяц, добавив правильный date_duration, а затем используйте end_of_month_day() из алгоритмов даты

person Pieter    schedule 08.01.2009
comment
Потому что есть простота, которую можно найти только по другую сторону сложности. time_t просто не имеет достаточно информации для того типа контекста, который он запрашивает в своих примерах. Использование boost в функции для преобразования, а затем обратное преобразование в time_t кажется мне разумным. - person Aaron; 08.01.2009
comment
Мне тоже это кажется разумным. К сожалению, мы не используем Boost и не будем использовать его в ближайшем будущем. :-( - person Glen; 08.01.2009

Преобразовать time_t в struct tm, добавить X к месяцу, добавить месяцы> 12 к годам, преобразовать обратно. tm.tm_mon - это целое число, добавление 32000+ месяцев не должно быть проблемой.

[править] Вы можете обнаружить, что сопоставление с Oracle сложно, когда вы доберетесь до более сложных случаев, таких как добавление 12 месяцев к 29/02/2008. И 03.01.2009, и 28.02.2008 разумны.

person MSalters    schedule 08.01.2009
comment
Это работает не совсем так, как функция оракула. При использовании вышеуказанного 30-01-2009 становится 03-02-2009. - person Glen; 08.01.2009

Действительно новый ответ на действительно старый вопрос!

Используя эту бесплатную библиотеку с открытым исходным кодом и компилятор 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
31.01.2009 + 1 месяц = ​​28.02.2009
27.02.2009 + 1 месяц = ​​27.03.2009
> 28.02.2009 + 1 месяц = ​​31.03.2009
31.01.2009 + 50 месяцев = 31.03.2013

Также обратите внимание, что информация времени компиляции in приводит к информации времени компиляции out.

person Howard Hinnant    schedule 20.07.2015
comment
Вы должны отказаться от ответа, что это ваша библиотека. :) - person Lightness Races in Orbit; 20.07.2015
comment
@LightnessRacesinOrbit: Если вы нажмете на ссылку и посмотрите на верхнюю строку, разве это не ясно? Не знаю, как последовать твоему совету и не выглядеть неловко. Но я открыт для предложений. - person Howard Hinnant; 20.07.2015
comment
Боюсь, я не могу вам помочь :) У меня нет проблем с этим ответом, но я знаю, что некоторые из мета-полиции одержимы вопиющим раскрытием информации и т. Д. - person Lightness Races in Orbit; 20.07.2015
comment
@HowardHinnant Использование менее авторитетной фразы может сделать ее менее навязчивой. Однако я не вижу в этом особой проблемы, поскольку это что-то бесплатное и с открытым исходным кодом. - person edmz; 20.07.2015
comment
Предоставляет ли ваша библиотека пользовательские литералы, такие как wk, mo и yr? - person TemplateRex; 20.07.2015
comment
@TemplateRex: year, да, 2015_y. month, да, но без использования пользовательских литералов. 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` и т. д. для рабочих дней. - person Howard Hinnant; 21.07.2015