2

I have:

  • A year number (can be any year)
  • A month number (from Jan to Dec)
  • A week number (1st, 2nd, 3rd, 4th, last)
  • A week day (Sun, Mon, Tue, Wed, Thu, Fri, Sat)

I need to get a day number [from 1 to ~31] - "YYYY-MM-DD" (ISO8601).

Is there any way to calculate this using boost posix time or by using some other C++ libraries?

I.e.

  • 2014, First sunday of March - That would be 2014-03-02
  • 2014, 4th Wednesday of December - That would be 2014-12-24

Thanks.

Gediminas
  • 1,830
  • 3
  • 27
  • 47
  • So, to be clear, you want "First Sunday of March", or "4th Wednesday of December", and turn that to a numeric date of YYYY-MM-DD? – Mats Petersson Apr 15 '14 at 12:16
  • I.e. 2014, First sunday of March - That would be 2014-03-02 2014, 4th Wednesday of December - That would be 2014-12-17 (I've edited my question) – Gediminas Apr 15 '14 at 12:23
  • Yes, so you want to come up with that from some code? What have you tried so far? I'm not sure there are any libraries that do exactly this, but it wouldn't be terribly hard to make something that does it, based on some simple math. – Mats Petersson Apr 15 '14 at 12:25
  • boost is not a C/C++ library. – Sebastian Mach Apr 15 '14 at 12:33
  • Thanks phresnel for noticing, I just wanted to specify that I'm open for any type of C or C++ library, not necessary boost. Anyway, I have a device that provides my with that kind of information (I need to translate it's date to ISO8601) I haven't found anything that I could use so far. Was looking through boost's posix time documentation, but can't find anything for now. Would like to avoid calculating this by myself as this would involve a lot of calculations and possibly a gap for errors... – Gediminas Apr 15 '14 at 12:42
  • 1
    Not sure, but looks like a duplicate of http://stackoverflow.com/questions/15691579/ – kebs Apr 15 '14 at 12:46
  • Thanks kebs! That's what I was looking for. And Mikhail Melnik's boost variant really hellped me out. – Gediminas Apr 15 '14 at 13:33

3 Answers3

1

I have written a lightweight C library that can do what you want, the interesting part is here, as you can see the algorithm is trivial.

#include <stdio.h>
#include <stdint.h>
#include "dt_dow.h"
#include "dt_accessor.h"

const struct test {
    int      year;
    int      month; /* Month of the year [1=Jan, 12=Dec] */
    int      nth;   /* Occurrence within month           */
    dt_dow_t dow;   /* Day of the week [1=Mon, 7=Sun]    */
    int      dom;   /* Expected day of the month [1, 31] */
} tests[] =  {
    { 2014,  3,  1, DT_SUNDAY,     2 },
    { 2014,  4, -1, DT_TUESDAY,   29 },
    { 2014,  4, -2, DT_MONDAY,    21 },
    { 2014,  4, -5, DT_TUESDAY,    1 },
    { 2014,  4,  1, DT_TUESDAY,    1 },
    { 2014, 12,  4, DT_WEDNESDAY, 24 },
};

int 
main() {
    int i, ntests;

    ntests = sizeof(tests) / sizeof(*tests);
    for (i = 0; i < ntests; i++) {
        const struct test t = tests[i];

        {
            int dom = dt_dom(dt_from_nth_dow_in_month(t.year, t.month, t.nth, t.dow));

            if (t.dom != dom) {
                printf("dt_dom(dt_from_nth_dow_in_month(%d, %d, %d, %d))\n", 
                  t.year, t.month, t.nth, t.dow);
                printf("  got: %d\n", dom);
                printf("  exp: %d\n", t.dom);
            }
        }
    }
    return 0;
}

And here is a re-implementation if you don't want to use the above library/code.

#include <stdio.h>
#include <assert.h>
#include <stdint.h>
#include <stdbool.h>

bool
leap_year(int y) {
    return ((y % 4) == 0 && (y % 100 != 0 || y % 400 == 0));
}

int
days_in_month(int y, int m) {
    static const int T[2][13] = {
        { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
        { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
    };
    assert(m >=  1);
    assert(m <= 12);
    return T[leap_year(y)][m];
}

/* Computes the day of the week [1=Mon, 7=Sun] from the given year, month, day. */
int
ymd_to_dow(int y, int m, int d) {
    static const int T[13] = { 0, 6, 2, 1, 4, 6, 2, 4, 0, 3, 5, 1, 3 };

    assert(y >=  1);
    assert(m >=  1);
    assert(m <= 12);
    assert(d >=  1);

    y -= m < 3;
    return 1 + (y + y/4 - y/100 + y/400 + T[m] + d) % 7;
}

int
dom_from_nth_dow_in_month(int y, int m, int nth, int dow) {
    int dim, dom;

    assert(y   >=  1);
    assert(m   >=  1);
    assert(m   <= 12);
    assert(dow >=  1);
    assert(dow <=  7);

    dim = days_in_month(y, m);
    if (nth > 0) {
        dom = 1;
        dom += (dow - ymd_to_dow(y, m, dom) + 7) % 7;
        dom += --nth * 7;
        if (dom <= dim)
            return dom;
    }
    else if (nth < 0) {
        dom = dim;
        dom -= (ymd_to_dow(y, m, dom) - dow + 7) % 7;
        dom -= ++nth * -7;
        if (dom >= 1)
            return dom;
    }
    return -1;
}

const struct test {
    int year;
    int month; /* Month of the year [1=Jan, 12=Dec] */
    int nth;   /* Occurrence within month           */
    int dow;   /* Day of the week [1=Mon, 7=Sun]    */
    int dom;   /* Expected day of the month [1, 31] */
} tests[] =  {
    { 2014,  3,  1, 7,  2 },
    { 2014,  4, -1, 2, 29 },
    { 2014,  4, -2, 1, 21 },
    { 2014,  4, -5, 2,  1 },
    { 2014,  4,  1, 2,  1 },
    { 2014, 12,  4, 3, 24 },
};

int 
main() {
    int i, ntests;

    ntests = sizeof(tests) / sizeof(*tests);
    for (i = 0; i < ntests; i++) {
        const struct test t = tests[i];

        {
            int dom = dom_from_nth_dow_in_month(t.year, t.month, t.nth, t.dow);

            if (t.dom != dom) {
                printf("dom_from_nth_dow_in_month(%d, %d, %d, %d))\n", 
                  t.year, t.month, t.nth, t.dow);
                printf("  got: %d\n", dom);
                printf("  exp: %d\n", t.dom);
            }
        }
    }
    return 0;
}
chansen
  • 2,446
  • 15
  • 20
1

New answer for old question.

Using this free, open-source date library:

http://howardhinnant.github.io/date_v2.html

2014, First sunday of March - That would be 2014-03-02

#include "date.h"
#include <iostream>

int
main()
{
    using namespace date;
    std::cout << year_month_day{sun[1]/mar/2014} << '\n';
}

which outputs:

2014-03-02

The type date::year_month_day has getters on it named year, month and day. It is pretty easy to work with, and has full documentation at the link above.

2014, 4th Wednesday of December - That would be 2014-12-24

using namespace date;
std::cout << year_month_day{wed[4]/dec/2014} << '\n';

2014-12-24

But this is not the last Wednesday of December 2014, this is:

std::cout << year_month_day{wed[last]/dec/2014} << '\n';

2014-12-31

Here is a video presentation of the library.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
0

Nice job chansen! This might be really useful. But because I'm free to use boost - here's what I've done so far:

// DateFromWeekNumAndDay
// year     - Year YYYY.
// weekNum  - From 0 to 4.
// weekDay  - Week day starts from Sunday - 0 to Saturday - 6.
boost::gregorian::date DateFromWeekNumAndDay(boost::gregorian::date::year_type year, unsigned short weekNum, boost::date_time::weekdays weekDay)
{
   boost::gregorian::date date(year, boost::gregorian::Apr, 1);
   boost::gregorian::date::day_of_week_type d = date.day_of_week();

   date += boost::gregorian::date_duration(weekNum * 7) + boost::gregorian::date_duration(weekDay - d);

   return date;
}

As far as I've testesd - everything seem to work just fine. I've took this method from this post. So thanks user kebs for the link, and Big thanks to user Mikhail Melnik for his "GetDateFromWeekNumber" function sample.

Community
  • 1
  • 1
Gediminas
  • 1,830
  • 3
  • 27
  • 47