【问题标题】:Calculate Gregorian date from week number in C/C++从 C/C++ 中的周数计算公历日期
【发布时间】:2013-03-28 20:31:05
【问题描述】:

我正在使用公历并且我想实施 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
}

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

【问题讨论】:

标签: c++ c algorithm


【解决方案1】:

当前接受的答案给出了 2017 年第 1 周(以及 2017 年每周)的错误答案。函数GetDayAndMonthFromWeekInYear,分别给yearweekInYear的输入2017和1应该在monthdayInMonth输出2,表示2017-W01从2017-01-02星期一开始,但它改为输出公历日期 2017-01-01。

free, open source, C++11/14 library 使用以下语法输出从 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);
}

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

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

后一种算法是documented in excruciating detail here

【讨论】:

    【解决方案2】:

    也许你应该看看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;
    }
    

    不幸的是,他们的日期格式与您的不同 - 他们从星期日开始计算星期几,即Sunday = 0, Monday = 1, ..., Saturday = 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;
    }
    

    EDIT2:

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

    【讨论】:

    • 谢谢,但我们没有使用 Boost。
    • 当涉及到您的非 Boost 示例时,它会返回 2005 年 12 月 18 日,当年份为 2005、周为 52、工作日为 1 时,正确答案应为2005 年 12 月 26 日,请参阅 epochconverter.com/date-and-time/…。我现在已经修复了我自己的算法(请参阅我的答案),但我认为您想知道您的代码已损坏。
    【解决方案3】:

    通过 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)
    

    【讨论】:

    • 谢谢,但这只是一个 C/C++ 问题。
    • 看算法标签的帖子
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-11
    • 1970-01-01
    • 2022-12-06
    • 2011-04-06
    相关资源
    最近更新 更多