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

У меня есть код, который использует функцию Oracle add_months для увеличения Date на 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

Как Мехрдад Афшари указывает ниже, 28.02.2009 + 1 месяц = ​​31.03.2009 невозможно с простым шрифтом. Откуда вы знаете, что 28.02.2009 - это «последний день месяца», а не «28-й день месяца». Это просто не закодировано в типе. Вам нужно лучшее представление.

Aaron 08.01.2009 15:22

Я считаю, что 28.02.2009 + 1 месяц = ​​31.03.2009 неправильно. Это должно быть 28.02.2009 + 1 месяц = ​​28.03.2009. Судя по тому, как ведет себя .Net, единственные примеры, которые не приводят к одному и тому же дню месяца, - это когда в результирующем месяце меньше дней, чем в исходном месяце. то есть: 31.01.2009 + 1 месяц = ​​28.02.2009

Kibbee 08.01.2009 17:16

Теперь, когда я перечитал ваши примеры, похоже, что вы действительно ищете, как искать последний день месяца в какой-то момент месяца в будущем, а это немного другой вопрос.

Kibbee 08.01.2009 17:18

Тонко: 30.01.2009 + 1 месяц + 1 месяц = ​​28.03.2009!

MSalters 09.01.2009 16:04

@MSalters Еще более тонкий: 30/1/2009 +1 month - 1 month = 28/1/2009. Это кажется плохим способом определения месяцев.

nwp 20.07.2015 16:29

@Aaron, дата ускорения add_month работает именно так. Я считаю это странным.

Zhang 14.05.2019 07:29
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
6
17 446
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

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

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

Это работает не так, как функция оракула. Использование вышеуказанного 30-01-2009 становится 02-03-2009

Glen 08.01.2009 16:02

Вы можете использовать для этого Boost.GregorianDate.

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

Потому что «простоту можно найти только по другую сторону сложности». time_t просто не имеет достаточно информации для того типа контекста, который он запрашивает в своих примерах. Мне кажется разумным использование boost в функции для преобразования, а затем обратного преобразования в time_t.

Aaron 08.01.2009 15:31

Мне это тоже кажется разумным. К сожалению, мы не используем Boost и не будем использовать его в ближайшем будущем. :-(

Glen 08.01.2009 16:03
Ответ принят как подходящий

Метод 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 (год) И если месяц февраль

hamishmcn 08.01.2009 16:07

Также отсутствие сброса isdst в -1 в структуре tm делает ответ неверным, если добавленное дополнительное время пересекает границы dst. Настоятельно рекомендую украсть код, который уже работает / обрабатывает это должным образом.

Einstein 08.01.2009 16:33

hamishmcn, спасибо. Так Microsoft разработала программное обеспечение для Zune :)

Konstantin Spirin 08.01.2009 17:11

Это работает с некоторыми модификациями. 1, используйте gmtime вместо localtime. 2, при проверке високосного года прибавьте 1900 к году. 3, необходимо заставить mktime использовать GMT в качестве часового пояса. Это должно позаботиться о пересечении границ летнего времени

Glen 08.01.2009 17:22

Эйнштейн: Есть какие-нибудь предложения относительно мест, где я мог бы «украсть» рабочий код?

Glen 08.01.2009 17:23

Глен, спасибо за обзор. Теперь я понимаю, что в наши дни стандартный C++ слишком сложен для меня. Я бы предложил что-то более высокий уровень абстракции, чем time_t. Что касается места для кражи кода - Boost выглядит наиболее очевидным кандидатом.

Konstantin Spirin 08.01.2009 17:37

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

Теперь, используя эта бесплатная библиотека с открытым исходным кодом и компилятор 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 month = 28/02/2009
31/01/2009 + 1 month = 28/02/2009
27/02/2009 + 1 month = 27/03/2009
28/02/2009 + 1 month = 31/03/2009
31/01/2009 + 50 months = 31/03/2013

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

В ответе вы должны отказаться от того, что это ваша библиотека. :)

Lightness Races in Orbit 20.07.2015 06:43

@LightnessRacesinOrbit: Если щелкнуть ссылку и посмотреть на верхнюю строку, не ясно? Я не знаю, как последовать твоему совету и не выглядеть неловко. Но я открыт для предложений.

Howard Hinnant 20.07.2015 07:16

Боюсь, что не могу вам помочь :) У меня нет проблем с этим ответом, но я знаю, что некоторые из метаполиции одержимы вопиющим раскрытием информации и т. д.

Lightness Races in Orbit 20.07.2015 12:33

@HowardHinnant Использование менее авторитетной фразы может сделать его менее "навязанным". Однако я не вижу в этом большой проблемы, поскольку это что-то бесплатное и с открытым исходным кодом.

edmz 20.07.2015 16:07

Предоставляет ли ваша библиотека также определяемые пользователем литералы, такие как wk, mo и yr?

TemplateRex 20.07.2015 22:52

@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` и т. д. Для будних дней.

Howard Hinnant 21.07.2015 02:28

Другие вопросы по теме