用C / C ++中的周数计算公历日期

我正在使用格里高利历,我想实施IS0 8601周,但我偶然发现了计算任何周数的日期的问题。 例如,ISO日期2010-W01-1应该返回2010 2009-W01-1 1月4日2009-W01-1应该返回2008年12月29日

 // Get the date for a given year, week and weekday(1-7) time_t *GetDateFromWeekNumber(int year, int week, int dayOfWeek) { // Algorithm here } 

编辑:我还没有找到任何在线工作的算法,尝试了很多,但我现在有点卡住了。

目前接受的答案给出了2017年第1周(以及2017年的每周)的错误答案。 函数GetDayAndMonthFromWeekInYear ,给定输入2017年和1 yearweekInYear year应分别输出1 month和2个输入dayInMonth ,表示2017-W01开始于2017年1 month 1日星期一,但它输出格里高利日期2017- 01-01。

这个免费的开源C ++ 11/14库使用以下语法输出从ISO周到Gregorian的正确日期转换:

 #include "date.h" #include "iso_week.h" #include  int main() { using namespace iso_week::literals; std::cout << date::year_month_day{2017_y/1_w/mon} << '\n'; } 2017-01-02 

由于库是开源的,因此可以轻松检查所用算法的源(“iso_week.h”和“date.h”)。 算法也很有效,没有使用迭代。

一般方法是使用此算法将字段2017_y/1_w/mon转换为自1970-01-01以来的连续天数:

 CONSTCD14 inline year_weeknum_weekday::operator sys_days() const NOEXCEPT { return sys_days{date::year{int{y_}-1}/date::dec/date::thu[date::last]} + (date::mon - date::thu) + weeks{unsigned{wn_}-1} + (wd_ - mon); } 

然后使用此算法将该序列天数转换为year/month/day字段类型:

 CONSTCD14 inline year_month_day year_month_day::from_sys_days(const sys_days& dp) NOEXCEPT { static_assert(std::numeric_limits::digits >= 18, "This algorithm has not been ported to a 16 bit unsigned integer"); static_assert(std::numeric_limits::digits >= 20, "This algorithm has not been ported to a 16 bit signed integer"); auto const z = dp.time_since_epoch().count() + 719468; auto const era = (z >= 0 ? z : z - 146096) / 146097; auto const doe = static_cast(z - era * 146097); // [0, 146096] auto const yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365; // [0, 399] auto const y = static_cast(yoe) + era * 400; auto const doy = doe - (365*yoe + yoe/4 - yoe/100); // [0, 365] auto const mp = (5*doy + 2)/153; // [0, 11] auto const d = doy - (153*mp+2)/5 + 1; // [1, 31] #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4146) // unary minus operator applied to unsigned type, result still unsigned #endif auto const m = mp + (mp < 10 ? 3 : -9u); // [1, 12] #ifdef _MSVC_VER #pragma warning(pop) #endif return year_month_day{date::year{y + (m <= 2)}, date::month(m), date::day(d)}; } 

后一种算法在此处以极其详细的方式记录 。

也许你应该看一下boost :: date_time :: gregorian 。 使用它你可以写一个这样的函数:

 #include  // Get the date for a given year, week and weekday(0-6) time_t *GetDateFromWeekNumber(int year, int week, int dayOfWeek) { using namespace boost::gregorian; date d(year, Jan, 1); int curWeekDay = d.day_of_week(); d += date_duration((week - 1) * 7) + date_duration(dayOfWeek - curWeekDay); tm tmp = to_tm(d); time_t * ret = new time_t(mktime(&tmp)); return ret; } 

不幸的是,他们的日期格式与你的不同 – 他们计算从星期日开始的星期几,即Sunday = 0, Monday = 1, ..., Saturday = 6 。 如果它不能满足您的需求,您可以使用这个稍微改变的function:

 #include  // Get the date for a given year, week and weekday(1-7) time_t *GetDateFromWeekNumber(int year, int week, int dayOfWeek) { using namespace boost::gregorian; date d(year, Jan, 1); if(dayOfWeek == 7) { dayOfWeek = 0; week++; } int curWeekDay = d.day_of_week(); d += date_duration((week - 1) * 7) + date_duration(dayOfWeek - curWeekDay); tm tmp = to_tm(d); time_t * ret = new time_t(mktime(&tmp)); return ret; } 

编辑:

在思考了一点之后,我发现了一种在不使用boost的情况下实现相同function的方法。 这是代码:

警告:以下代码已损坏,请勿使用!

 // Get the date for a given year, week and weekday(1-7) time_t *GetDateFromWeekNumber(int year, int week, int dayOfWeek) { const time_t SEC_PER_DAY = 60*60*24; if(week_day == 7) { week_day = 0; week++; } struct tm timeinfo; memset(&timeinfo, 0, sizeof(tm)); timeinfo.tm_year = year - 1900; timeinfo.tm_mon = 0; timeinfo.tm_mday = 1; time_t * ret = new time_t(mktime(&timeinfo)); // set all the other fields int cur_week_day = timeinfo.tm_wday; *ret += sec_per_day * ((week_day - cur_week_day) + (week - 1) * 7); return ret; } 

EDIT2:

是的, EDIT中的代码完全被破坏了,因为我没有花足够的时间来理解如何分配周数。

通过F#

 open System open System.Globalization //wday: 1-7, 1:Monday let DateFromWeekOfYear yw wday = let dt = new DateTime(y, 1, 4) //first week include 1/4 let dow = if dt.DayOfWeek = DayOfWeek.Sunday then 7 else int dt.DayOfWeek //to 1-7 let dtf = dt.AddDays(float(wday - dow)) GregorianCalendar().AddWeeks(dtf, w - 1)