У меня есть код, который использует функцию 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 + 1 месяц = 28.03.2009. Судя по тому, как ведет себя .Net, единственные примеры, которые не приводят к одному и тому же дню месяца, - это когда в результирующем месяце меньше дней, чем в исходном месяце. то есть: 31.01.2009 + 1 месяц = 28.02.2009
Теперь, когда я перечитал ваши примеры, похоже, что вы действительно ищете, как искать последний день месяца в какой-то момент месяца в будущем, а это немного другой вопрос.
Тонко: 30.01.2009 + 1 месяц + 1 месяц = 28.03.2009!
@MSalters Еще более тонкий: 30/1/2009 +1 month - 1 month = 28/1/2009. Это кажется плохим способом определения месяцев.
@Aaron, дата ускорения add_month работает именно так. Я считаю это странным.





Преобразуйте 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
Вы можете использовать для этого Boost.GregorianDate.
Более конкретно, определите месяц, добавив правильный date_duration, а затем используйте end_of_month_day() из алгоритмов даты
Потому что «простоту можно найти только по другую сторону сложности». time_t просто не имеет достаточно информации для того типа контекста, который он запрашивает в своих примерах. Мне кажется разумным использование boost в функции для преобразования, а затем обратного преобразования в time_t.
Мне это тоже кажется разумным. К сожалению, мы не используем Boost и не будем использовать его в ближайшем будущем. :-(
Метод 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 (год) И если месяц февраль
Также отсутствие сброса isdst в -1 в структуре tm делает ответ неверным, если добавленное дополнительное время пересекает границы dst. Настоятельно рекомендую украсть код, который уже работает / обрабатывает это должным образом.
hamishmcn, спасибо. Так Microsoft разработала программное обеспечение для Zune :)
Это работает с некоторыми модификациями. 1, используйте gmtime вместо localtime. 2, при проверке високосного года прибавьте 1900 к году. 3, необходимо заставить mktime использовать GMT в качестве часового пояса. Это должно позаботиться о пересечении границ летнего времени
Эйнштейн: Есть какие-нибудь предложения относительно мест, где я мог бы «украсть» рабочий код?
Глен, спасибо за обзор. Теперь я понимаю, что в наши дни стандартный C++ слишком сложен для меня. Я бы предложил что-то более высокий уровень абстракции, чем time_t. Что касается места для кражи кода - Boost выглядит наиболее очевидным кандидатом.
Действительно новый ответ на старый вопрос В самом деле!
Теперь, используя эта бесплатная библиотека с открытым исходным кодом и компилятор 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
Также обратите внимание, что информация времени компиляции в приводит к информации времени компиляции вне.
В ответе вы должны отказаться от того, что это ваша библиотека. :)
@LightnessRacesinOrbit: Если щелкнуть ссылку и посмотреть на верхнюю строку, не ясно? Я не знаю, как последовать твоему совету и не выглядеть неловко. Но я открыт для предложений.
Боюсь, что не могу вам помочь :) У меня нет проблем с этим ответом, но я знаю, что некоторые из метаполиции одержимы вопиющим раскрытием информации и т. д.
@HowardHinnant Использование менее авторитетной фразы может сделать его менее "навязанным". Однако я не вижу в этом большой проблемы, поскольку это что-то бесплатное и с открытым исходным кодом.
Предоставляет ли ваша библиотека также определяемые пользователем литералы, такие как wk, mo и yr?
@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` и т. д. Для будних дней.
Как Мехрдад Афшари указывает ниже, 28.02.2009 + 1 месяц = 31.03.2009 невозможно с простым шрифтом. Откуда вы знаете, что 28.02.2009 - это «последний день месяца», а не «28-й день месяца». Это просто не закодировано в типе. Вам нужно лучшее представление.