Re: Assigning dates with C++ driver

2,024 views
Skip to first unread message

Jason Rassi

unread,
Oct 31, 2012, 7:12:47 PM10/31/12
to mongod...@googlegroups.com
You're looking for a mongo::Date_t object, which will get serialized with BSON's datetime type.  Its constructor takes an integer representing milliseconds since the epoch. See [1] for its definition.  Once you've initialized one, you can pass it in directly to the BSON() macro in place of where "new Date(...)" was in your snippet.

You also have other options for ways to generate BSON datetime values.  Here's your example using C-style dates (see [2] for definitions of the various BSONObjBuilder helper methods):

struct tm birthdate;
strptime("6 Dec 2001 12:33:45", "%d %b %Y %H:%M:%S", &birthdate);

mongo::BSONObjBuilder builder;
builder.genOID();
builder.append("name", "George");
builder.appendTimeT("birthdate", mktime(&birthdate));

mongo::BSONObj doc = builder.obj();


On Wednesday, October 31, 2012 1:18:12 PM UTC-7, George Thompson wrote:
I want to do this in C++

mongo::BSONObj doc = BSON(mongo::GENOID << "name" << "George" << "birthdate" << new Date(1990, 4, 15) );


which gives me

1>garden.cpp(59): error C2061: syntax error : identifier 'Date'
1>garden.cpp(59): error C2228: left of '.obj' must have class/struct/union
1>garden.cpp(59): error C2143: syntax error : missing ';' before ')'
1>garden.cpp(59): error C2143: syntax error : missing ';' before ')'

Which tells me I have no idea how to assign a date with the C++ driver. For the life of me, I couldn't find anything in the online docs or a google search except for a ticket requesting examples in the documentation.

So, what is the trick?

George Thompson

unread,
Oct 31, 2012, 7:39:46 PM10/31/12
to mongod...@googlegroups.com
So, the trick is not to use the BSON helpers? There isn't a way to assign a date with BSON? Or can I set the date to a BSONElement and then stream it with BSON?

If not, I'll stop using BSON when building my BSONObjs. Doesn't this mean BSON is fairly limited?

Thanks for your tips!

George

Jason Rassi

unread,
Oct 31, 2012, 8:50:56 PM10/31/12
to mongod...@googlegroups.com
> So, the trick is not to use the BSON helpers?

No, that wasn't what I was saying.  I presented two different solutions; the first used the BSON() macro, and the second didn't.  I'll spell out the first one more explicitly:

long long birthDateInMillisSinceEpoch = ...;
mongo::BSONObj doc = BSON(mongo::GENOID << "name" << "George" << "birthdate" << new mongo::Date_t(birthDateInMillisSinceEpoch));

You can generate "birthDateInMillisSinceEpoch" using POSIX functions like the ones I called before, or Boost's Date-Time library, etc.

It's illustrative to see the BSONObjBuilder methods being used explicitly, since the BSON() macro stream operator is implemented with BSONObjBuilder.  To see a list of all of the different objects you can use the stream syntax with, see all of the implementations of append() in the bsonobjbuilder.h file I linked to earlier.

George Thompson

unread,
Oct 31, 2012, 8:53:38 PM10/31/12
to mongod...@googlegroups.com
Yes -- I finally figured that out (slow, but I catch on). Working on Boost (I'm on windows) right now.

Thanks so much!

Therefore

unread,
Nov 1, 2012, 7:31:22 PM11/1/12
to mongod...@googlegroups.com
This did the trick with Boost:
 
    #include "boost/date_time/posix_time/posix_time.hpp"
    using namespace boost::posix_time;

    ptime epoch  = time_from_string("1970-01-01 00:00:00.000");     ptime birthdate = time_from_string("2001-12-06 12:33:45");     time_duration const diff = birthdate - epoch;     long long birthDateInMillisSinceEpoch = diff.total_milliseconds();

    mongo::BSONObj doc = BSON(mongo::GENOID << "name" << "George" << "birthdate" << mongo::Date_t(birthDateInMillisSinceEpoch));

I didn't need the "new".

Does this look like a reasonable Boost solution?

Thanks!

Jason Rassi

unread,
Nov 2, 2012, 1:34:52 PM11/2/12
to mongod...@googlegroups.com
Your solution has the right idea.  See [1] for a similar but more succinct one (the snippet under the "Boost to Mongo" section of the question).  When you're generalizing, make sure you handle dates before the epoch correctly (these are represented by Date_t objects initialized with negative values).

Passing in the Date_t object to the << operator without calling "new" is correct (I left it in as a typo when copy-pasting your original line).  "new" returns a pointer, which C++ will implicitly cast to a bool here, throwing away the date information you're trying to serialize (in addition to creating a memory leak).

Therefore

unread,
Nov 2, 2012, 4:09:35 PM11/2/12
to mongod...@googlegroups.com
This is the generalization I came up with:

   // Create a mongo date (Date_t) from year, month, and day passed as integers
  // (year = with century, month = 1 - 12, day = 1-31)
  mongo::Date_t MongoDate(const int &year, const int &month, const int &day)
  {
      boost::posix_time::ptime epoch(boost::gregorian::date(1970,1,1));
      try
      {
      boost::posix_time::ptime ptime_date(boost::gregorian::date(year,month,day));
      boost::posix_time::time_duration const diff = ptime_date - epoch;
      long long ms = diff.total_milliseconds();
      return mongo::Date_t(ms);
      }
      catch (std::out_of_range& oor)
      {
          std::cerr << year << "-" << month << "-" << day << " is an invalid date: " << oor.what() << endl;
          return mongo::Date_t(0);
      }
  }

(Note: Please ignore the absurdity of returning mongo::Date_t(0) -- I'm either going to use Boost::Optional to return a NULL or stop using BSON and handling the addition of dates with a function using BSONObjBuilder).

This works for the full range of boost dates, 1400 - 10000. I'm confused why it works since Date_t is an unsigned long long.  However, after executing "birthdate" << MongoDate(1960,4,16), the shell's db.personnel.find() displays ISODate("1960-04-16T00:00:00Z") and "cout << doc.toString() << endl" displays "new Date(-306460800000)". misc.h does have a "TODO: make signed". I'm using 2.2.0.

I had tried to solve this before posting here using tm and mktime and never had any luck. Replicating from the Stackoverflow link:

    boost::posix_time::ptime ptime_date(boost::gregorian::date(year,month,day));
    std::tm tm_date = boost::posix_time::to_tm(ptime_date);
    std::time_t ms = mktime(tm_date);
    return mongo::Date_t(ms);

This didn't compile -- I believe it should be mktime(&tm_date). mktime returns seconds whereas I intend to generalize this further by allowing times including milliseconds. mktime returns "-1" with years less than 1970. I'm sure there is a way to finesse this to work properly, but I am not seeing why the first solution isn't somewhat more intuitive. But I lack experience in using "struct tm" and mktime, so I'm sure that is the source of the solution's apparent deficiencies.

Jason Rassi

unread,
Nov 2, 2012, 7:00:02 PM11/2/12
to mongod...@googlegroups.com
> I'm confused why it works since Date_t is an unsigned long long.

Your solution works because Date_t's member "millis" is interpreted as a signed value when read, such as when MongoDB reads it as BSON off the wire (your first example, with .find()), and in bson-inl.h's BSONElement::toString (your second example, with cout).

> mktime returns "-1" with years less than 1970.

That's not strictly the case: mktime() attempts to convert its argument to a time_t (which on 32-bit platforms is generally limited to dates from ~1901 to ~2038), and returns (time_t)-1 if it can't be represented as such.  You're correct in that you can't use this solution if you need sub-second precision.

Your pure-Boost solution is fine; it overcomes a number of POSIX's limitations that you saw in the other approaches.
Reply all
Reply to author
Forward
0 new messages