Thread overview
What's a good wat to trunctate a time point
May 05, 2017
Dukc
May 05, 2017
Biotronic
May 05, 2017
Dukc
May 10, 2017
Biotronic
May 05, 2017
I have a time point, be it SysTime or DateTime, whatever. I want to trunctate it to weeks, eg. I want it to become the first point of time during the week it was representing. What's a good way to do that? Only hacks came to my mind.

The solution does not have to be generic, trough I'd prefer it to be if it can be without much extra work.
May 05, 2017
On Friday, 5 May 2017 at 08:02:15 UTC, Dukc wrote:
> I have a time point, be it SysTime or DateTime, whatever. I want to trunctate it to weeks, eg. I want it to become the first point of time during the week it was representing. What's a good way to do that? Only hacks came to my mind.
>
> The solution does not have to be generic, trough I'd prefer it to be if it can be without much extra work.

Here's an implementation that supports start of year, month, week, day, hour, minute and second. Works for DateTime and SysTime. Not heavily tested (all tests included):

import std.datetime;

DateTime startOf(string unit, DayOfWeek start = DayOfWeek.sun)(DateTime dt) {
    static if (unit == "year") {
        return DateTime(dt.year, 1, 1);
    } else static if (unit == "month") {
        return DateTime(dt.year, dt.month, 1);
    } else static if (unit == "week") {
        auto delta = dt.dayOfWeek - start;
        if (delta < 0) delta += 7;
        return DateTime(dt.year, dt.month, dt.day) - dur!"days"(delta);
    } else static if (unit == "day") {
        return DateTime(dt.year, dt.month, dt.day);
    } else static if (unit == "hour") {
        return DateTime(dt.year, dt.month, dt.day, dt.hour);
    } else static if (unit == "minute") {
        return DateTime(dt.year, dt.month, dt.day, dt.hour, dt.minute);
    } else static if (unit == "second") {
        return DateTime(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second);
    }
}

SysTime startOf(string unit)(SysTime st) {
    return SysTime(startOf!unit(cast(DateTime)st), st.timezone);
}

unittest {
    auto now            = DateTime(2017, 5,  5, 10, 39, 17);

    auto expectedYear   = DateTime(2017, 1,  1,  0,  0,  0);
    auto expectedMonth  = DateTime(2017, 5,  1,  0,  0,  0);
    auto expectedWeek   = DateTime(2017, 4, 30,  0,  0,  0);
    auto expectedDay    = DateTime(2017, 5,  5,  0,  0,  0);
    auto expectedHour   = DateTime(2017, 5,  5, 10,  0,  0);
    auto expectedMinute = DateTime(2017, 5,  5, 10, 39,  0);
    auto expectedSecond = DateTime(2017, 5,  5, 10, 39, 17);

    auto startOfYear   = now.startOf!"year";
    auto startOfMonth  = now.startOf!"month";
    auto startOfWeek   = now.startOf!"week";
    auto startOfDay    = now.startOf!"day";
    auto startOfHour   = now.startOf!"hour";
    auto startOfMinute = now.startOf!"minute";
    auto startOfSecond = now.startOf!"second";

    assert(expectedYear   == startOfYear);
    assert(expectedMonth  == startOfMonth);
    assert(expectedWeek   == startOfWeek);
    assert(expectedDay    == startOfDay);
    assert(expectedHour   == startOfHour);
    assert(expectedMinute == startOfMinute);
    assert(expectedSecond == startOfSecond);

    now = DateTime(2017, 4, 30, 10, 39, 17);
    auto expectedWeek2  = DateTime(2017, 4, 24,  0,  0,  0);
    auto startOfWeek2   = now.startOf!("week", DayOfWeek.mon);
    auto expectedWeek3  = DateTime(2017, 4, 29,  0,  0,  0);
    auto startOfWeek3   = now.startOf!("week", DayOfWeek.sat);

    assert(startOfWeek2 == expectedWeek2);
    assert(startOfWeek3 == expectedWeek3);
}
May 05, 2017
On Friday, 5 May 2017 at 09:14:21 UTC, Biotronic wrote:
>
> Here's an implementation that supports start of year, month, week, day, hour, minute and second. Works for DateTime and SysTime. Not heavily tested (all tests included):
>
> [lots of code]

Wow! You might want to create a pr to phobos from that -It is so general, and despite what you say I have seen much worse tested stuff there.

Thanks for the reply.
May 10, 2017
On Friday, 5 May 2017 at 09:14:21 UTC, Biotronic wrote:
> Here's an implementation that supports start of year, month, week, day, hour, minute and second. Works for DateTime and SysTime. Not heavily tested (all tests included):

As the last sentence says, there were holes in the testing, specifically for unsupported units. Updated:

import std.datetime;

DateTime startOf(string unit, DayOfWeek start = DayOfWeek.sun)(DateTime dt) {
    static if (unit == "year") {
        return DateTime(dt.year, 1, 1);
    } else static if (unit == "month") {
        return DateTime(dt.year, dt.month, 1);
    } else static if (unit == "week") {
        auto delta = dt.dayOfWeek - start;
        if (delta < 0) delta += 7;
        return DateTime(dt.year, dt.month, dt.day) - dur!"days"(delta);
    } else static if (unit == "day") {
        return DateTime(dt.year, dt.month, dt.day);
    } else static if (unit == "hour") {
        return DateTime(dt.year, dt.month, dt.day, dt.hour);
    } else static if (unit == "minute") {
        return DateTime(dt.year, dt.month, dt.day, dt.hour, dt.minute);
    } else static if (unit == "second") {
        return DateTime(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second);
    } else {
        static assert(false, "\"" ~ unit ~ "\" is not a valid time unit for startOf().\nPlease use one of \"year\", \"month\", \"week\", \"day\", \"hour\", \"minute\" or \"second\"");
    }
}

SysTime startOf(string unit)(SysTime st) {
    return SysTime(startOf!unit(cast(DateTime)st), st.timezone);
}

unittest {
    auto now            = DateTime(2017, 5,  5, 10, 39, 17);

    auto expectedYear   = DateTime(2017, 1,  1,  0,  0,  0);
    auto expectedMonth  = DateTime(2017, 5,  1,  0,  0,  0);
    auto expectedWeek   = DateTime(2017, 4, 30,  0,  0,  0);
    auto expectedDay    = DateTime(2017, 5,  5,  0,  0,  0);
    auto expectedHour   = DateTime(2017, 5,  5, 10,  0,  0);
    auto expectedMinute = DateTime(2017, 5,  5, 10, 39,  0);
    auto expectedSecond = DateTime(2017, 5,  5, 10, 39, 17);

    auto startOfYear   = now.startOf!"year";
    auto startOfMonth  = now.startOf!"month";
    auto startOfWeek   = now.startOf!"week";
    auto startOfDay    = now.startOf!"day";
    auto startOfHour   = now.startOf!"hour";
    auto startOfMinute = now.startOf!"minute";
    auto startOfSecond = now.startOf!"second";

    assert(expectedYear   == startOfYear);
    assert(expectedMonth  == startOfMonth);
    assert(expectedWeek   == startOfWeek);
    assert(expectedDay    == startOfDay);
    assert(expectedHour   == startOfHour);
    assert(expectedMinute == startOfMinute);
    assert(expectedSecond == startOfSecond);

    now = DateTime(2017, 4, 30, 10, 39, 17);
    auto expectedWeek2  = DateTime(2017, 4, 24,  0,  0,  0);
    auto startOfWeek2   = now.startOf!("week", DayOfWeek.mon);
    auto expectedWeek3  = DateTime(2017, 4, 29,  0,  0,  0);
    auto startOfWeek3   = now.startOf!("week", DayOfWeek.sat);
    auto expectedWeek4  = DateTime(2017, 4, 30,  0,  0,  0);
    auto startOfWeek4   = now.startOf!("week", DayOfWeek.sun);

    assert(startOfWeek2 == expectedWeek2);
    assert(startOfWeek3 == expectedWeek3);
    assert(startOfWeek4 == expectedWeek4);

    assert(!__traits(compiles, now.startOf!"fortnight"));
}