在C/C++中计算公历日期的方法,基于周数

4

我使用公历,想要实现IS0 8601的周数,但是在计算任何一周的日期时遇到了一些问题。例如,ISO日期2010-W01-1应该返回2010年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
}

编辑: 我尝试了很多在线算法,但都没有成功,现在有些困难。


我还没有找到任何在线可用的算法。 - user152949
你可能会喜欢维基百科上关于儒略日的文章。 - pmg
@pmg 我不确定这对我有什么帮助,因为我使用的是公历。你是说我应该找到一个使用儒略日计算的算法,然后将结果转换为公历吗? - user152949
@JonathanLeffler 这与我想要的相反,我想要从周数获取日期,而不是从日期获取周数。 - user152949
1
这个stackoverflow回答:http://stackoverflow.com/a/15146434/576911 包含了一个公历日期类的引用并展示了代码(“week_to_date”),该代码将ISO周-日期转换为公历日期。 - Howard Hinnant
显示剩余5条评论
3个回答

4
目前接受的答案错误地给出了2017年第1周(以及2017年的每一周)的错误答案。函数GetDayAndMonthFromWeekInYear应该输出month中的1和dayInMonth中的2,表示2017-W01从星期一开始,即2017-01-02,而不是输出公历日期2017-01-01。 这个免费,开源的C++11 / 14库使用以下语法正确地将ISO周转换为公历日期。
#include "date/date.h"
#include "date/iso_week.h"
#include <iostream>

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);
}

然后,使用以下算法将连续的天数转换为 年/月/日 字段类型:

CONSTCD14
inline
year_month_day
year_month_day::from_sys_days(const sys_days& dp) NOEXCEPT
{
    static_assert(std::numeric_limits<unsigned>::digits >= 18,
             "This algorithm has not been ported to a 16 bit unsigned integer");
    static_assert(std::numeric_limits<int>::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<unsigned>(z - era * 146097);          // [0, 146096]
    auto const yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365;  // [0, 399]
    auto const y = static_cast<sys_days::rep>(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)};
}

后一种算法在此处已经被详细地记录


1
也许你应该看一下boost::date_time::gregorian。使用它,你可以编写这样一个函数:
#include <boost/date_time/gregorian/gregorian.hpp>

// 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;
}

很遗憾,他们的日期格式与您的不同 - 他们从星期日开始编号,即 星期日 = 0,星期一 = 1,...,星期六 = 6。如果这不能满足您的需求,您可以使用这个稍微改变的函数:
#include <boost/date_time/gregorian/gregorian.hpp>

// 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也可以实现相同功能的方法。以下是代码:

警告:下面的代码有问题,请勿使用!

// 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;
}

编辑2:

是的,编辑中的代码完全失效,因为我没有花足够的时间了解如何分配周数。


谢谢,但我们不使用 Boost。 - user152949
1
当涉及到您的非Boost示例时,当年份为2005年,周数为52周,工作日为1日时,它返回2005年12月18日,而正确答案应该是2005年12月26日,请参见http://www.epochconverter.com/date-and-time/weeknumbers-by-year.php?year=2005。我现在已经修复了自己的算法(请参见我的答案),但我想让您知道您的代码有问题。 - user152949

-1

使用 F#

open System
open System.Globalization

//wday: 1-7, 1:Monday
let DateFromWeekOfYear y w 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)

1
谢谢,但这只是一个 C/C++ 的问题。 - user152949
观察算法标签的帖子 - BLUEPIXY

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接