1

I have constructed an function that takes as input a specific date and return this date in std::chrono::milliseconds format.

milliseconds lowerRangeBound = TimeStamp(mm, dd, HH, MM, SS, yyyy);

For example,

milliseconds a = TimeStamp(8/*month*/, 23/*day*/, 14/*hours*/, 46/*minutes*/, 32/*seconds*/, 2017/*year*/);

returns in a converted string format: 2017.08.23-14.46.32

What I want now to do and does not work is given two dates (milliseconds) to take a random date inside the range defined by these two dates. For example, given

milliseconds a = TimeStamp(8/*month*/, 23/*day*/, 13/*hours*/, 46/*minutes*/, 32/*seconds*/, 2017/*year*/);
milliseconds b = TimeStamp(10/*month*/, 23/*day*/, 13/*hours*/, 46/*minutes*/, 32/*seconds*/, 2017/*year*/);

the expected output is a milliseconds c which in string format is a date like this, 2017.09.13-12.56.12. Note that the desired output is a milliseconds, the string format is provided in order to speak in readable format.

What I have tried so far is to convert each milliseconds variable into long number (.count()) and get a random long in the range [a,b]. However,the output date is something irrelevant: 1977.12.06-16.27.02.

Could you please give a hand on this?

Thank you in advance.

EDIT: The code bellow is inspired by this link

milliseconds TimeStamp(int mm, int dd, int HH, int MM, int SS, int yyyy) {

    tm ttm = tm();
    ttm.tm_year = yyyy - 1900; // Year since 1900
    ttm.tm_mon = mm - 1; // Month since January 
    ttm.tm_mday = dd; // Day of the month [1-31]
    ttm.tm_hour = HH; // Hour of the day [00-23]
    ttm.tm_min = MM;
    ttm.tm_sec = SS;

    time_t ttime_t = mktime(&ttm);
    system_clock::time_point time_point_result = std::chrono::system_clock::from_time_t(ttime_t);
    milliseconds now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(time_point_result).time_since_epoch();
    return now_ms;
}

milliseconds getRandomTimestamp(int mm_1, int dd_1, int HH_1, int MM_1, int SS_1, int yyyy_1,
    int mm_2, int dd_2, int HH_2, int MM_2, int SS_2, int yyyy_2, int N) {

    milliseconds lowerRangeBound = fixedTimeStamp(mm_1, dd_1, HH_1, MM_1, SS_1, yyyy_1);
    milliseconds upperRangeBound = fixedTimeStamp(mm_2, dd_2, HH_2, MM_2, SS_2, yyyy_2);

    long lowerRange_ = lowerRangeBound.count();
    long upperRange_ = upperRangeBound.count();

    //long output = rand() % (upperRange_ - lowerRange_ + 1) + lowerRange_;
    // rand() replaced after @Jarod42's suggestion.
    std::default_random_engine generator;
    std::uniform_int_distribution<int> distribution(lowerRange_, upperRange_);
    long output = distribution(generator);        

    std::chrono::duration<long> dur(output);
    return dur;
}
Darkmoor
  • 862
  • 11
  • 29
  • 1
    *What* have you tried? Please show us an [Minimal, Complete, and Verifiable Example](http://stackoverflow.com/help/mcve) of it, instead of just describing it. – Some programmer dude Oct 19 '17 at 08:07
  • 2
    off-topic to your question: `myTimeStamp(8, 23, 13, 46, 32, 2017);` is a horrible, head-ache, error prone design – bolov Oct 19 '17 at 08:09
  • I hope the post now is more attractive. Thanks for the interest by the way! – Darkmoor Oct 19 '17 at 08:18
  • 1
    `rand()` can be replaced by stuff in ``. – Jarod42 Oct 19 '17 at 08:18
  • @Jarod42 thanks for the suggestion, I replaced it. – Darkmoor Oct 19 '17 at 08:22
  • 1
    What happens if the `millisecond` count can't be stored in a `long`? Remember that `long` is not a fixed-size integer type, it can be either 32 or 64 bits long. – Some programmer dude Oct 19 '17 at 08:22
  • @Someprogrammerdude if I understand what you said, I have tried to work with `long long` but I couldn't cast it in `milliseconds`. Are in the same line? – Darkmoor Oct 19 '17 at 08:27
  • 1
    Actually it gets even worse, your `std::uniform_int_distribution` uses `int` which I have never seen as 64 bits. In your `getRandomTimestamp` function, change the `long` types to `auto`, and use e.g. `std::uniform_int_distribution` instead. Also, `return milliseconds(output)` – Some programmer dude Oct 19 '17 at 08:34
  • @Someprogrammerdude I think it is working now! Thank you! – Darkmoor Oct 19 '17 at 11:18
  • 1
    For further "enhancements", you don't need the `output` variable, you can use the `distribution(generator)` expression directly in the construction of the duration. And if you call this `getrAndomTimestamp` multiple times, then consider making the generator `static` so it won't be reseeded every call. – Some programmer dude Oct 19 '17 at 11:21
  • @Someprogrammerdude thanks for the comments interesting details!! – Darkmoor Oct 19 '17 at 11:33

2 Answers2

3

It could be that you're getting confused with the maths because of mixing concerns.

I suggest that you write your "random timestamp" routine in terms of milliseconds.

You can perform the conversions from/to timestamps in a separate step.

I would write the routine something like this:

#include<random>
#include<chrono>
#include<cassert>
#include<iostream>

template<class Eng>
std::chrono::milliseconds getRandomTimestamp(Eng& eng, 
                                             std::chrono::milliseconds low, 
                                             std::chrono::milliseconds high) 
{
    // deduce the actual integer type. 
    // Otherwise we'll have to guess what T is when using
    // uniform_int_distribution<T>
    using count_type = decltype(low.count());

    // from this we can deduce the correct integer distribution
    using dist_type = std::uniform_int_distribution<count_type>;

    // create the distribution generator
    auto dist = dist_type(low.count(), high.count());

    // and apply it to the random generator, returning
    // milliseconds somewhere between low and high
    return std::chrono::milliseconds(dist(eng));    
}


int main()
{
    using namespace std::literals;

    std::random_device dev;
    std::default_random_engine eng(dev());

    auto t1 = 10'000'000ms;
    auto t2 = 11'000'000ms;
    auto t3 = getRandomTimestamp(eng, t1, t2);

    assert(t1 <= t3);
    assert(t3 <= t2);

    std::cout << t1.count() << std::endl;
    std::cout << t2.count() << std::endl;
    std::cout << t3.count() << std::endl;
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
1

You can make this much easier if you take advantage of Howard Hinnant's free, open-source header-only date/time library. The syntax is concise and very readable. For example you can create chrono::time_points of any precision (such as milliseconds) with date::sys_time<std::chrono::milliseconds>.

As your problem deals with time_points as opposed to durationss, that is the better way to go from a type-safety standpoint. Here is one way to write getRandomTimestamp:

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

date::sys_time<std::chrono::milliseconds>
getRandomTimestamp(date::sys_time<std::chrono::milliseconds> lowerBound,
                   date::sys_time<std::chrono::milliseconds> upperBound)
{
    static std::default_random_engine generator;
    using namespace std::chrono;
    std::uniform_int_distribution<milliseconds::rep> distribution{
        lowerBound.time_since_epoch().count(),
        upperBound.time_since_epoch().count()};
    return date::sys_time<milliseconds>{milliseconds{distribution(generator)}};
}

Now your only problem is how to create those time_points! And that turns out to be trivial:

int
main()
{
    using namespace date::literals;
    using namespace std::chrono_literals;
    auto t1 = date::sys_days{2017_y/8/23} + 13h + 46min + 32s;
    auto t2 = date::sys_days{2017_y/10/23} + 13h + 46min + 32s;
    auto t3 = getRandomTimestamp(t1, t2);
    // ...
}

Above date::sys_days is just another chrono::time_point but with a precision of days instead of milliseconds (it could have also been spelled date::sys_time<date::days>). And 2017_y/8/23 is a literal of type date::year_month_day that means 2017.08.23.

I've taken advantage of C++14's chrono literals. If you're in C++11, you'll need to use hours{13} in place of 13h.

Finally, you can format and stream out t3 very easily in a wide variety of formats, for example:

std::cout << date::format("%Y.%m.%d-%T", t3) << '\n';

This just output for me:

2017.09.04-17:14:04.220

If you have run-time integral values instead of literals as input, that is easily handled as well:

using namespace date;
auto t1 = sys_days{year{y}/m/d} + hours{h} + ...

And if you need to get any of the field values out of t3, that is also very easy, for example:

sys_days sd = floor<days>(t3);
time_of_day<milliseconds> tod(t3-sd);
year_month_day ymd = sd;

ymd has getters for year, month and day, and tod has getters for hours, minutes, seconds and milliseconds (the latter being spelled subseconds).

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