summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShaun Verch <shaun.verch@10gen.com>2013-10-11 18:10:35 -0400
committerShaun Verch <shaun.verch@10gen.com>2013-10-11 21:27:17 -0400
commit0b19be208f91e70c683d60c22011cad14a9fa78c (patch)
treeddfef3b5deadd9dc95157227172d2adda52b754d
parent34b88976c91fdeec5ff0b8816d35deef72f3767e (diff)
downloadmongo-0b19be208f91e70c683d60c22011cad14a9fa78c.tar.gz
SERVER-6058 Factor out date to UTC handling code and use in mongoexport
-rw-r--r--jstests/tool/csvexport_dates.js65
-rw-r--r--src/mongo/db/pipeline/expression.cpp4
-rw-r--r--src/mongo/db/pipeline/value.cpp17
-rw-r--r--src/mongo/util/time_support.cpp29
-rw-r--r--src/mongo/util/time_support.h12
-rw-r--r--src/mongo/util/time_support_test.cpp46
6 files changed, 152 insertions, 21 deletions
diff --git a/jstests/tool/csvexport_dates.js b/jstests/tool/csvexport_dates.js
new file mode 100644
index 00000000000..7d1c776baa5
--- /dev/null
+++ b/jstests/tool/csvexport_dates.js
@@ -0,0 +1,65 @@
+// Test that we can handle various edge cases of a date type in a csv export
+
+t = new ToolTest("csvexport_date_before_epoch")
+
+c = t.startDB("foo");
+
+function test(date) {
+ print("testing " + date);
+
+ c.drop();
+
+ assert.eq(0, c.count(), "initial collection not empty");
+
+ c.insert({ _id : 1, date : date });
+
+ assert.eq(1, c.count(), "failed to insert document into collection");
+
+ t.runTool("export", "--out", t.extFile, "-d", t.baseName, "-c", "foo", "--csv", "-f",
+ "_id,date")
+
+ c.drop()
+
+ assert.eq(0, c.count(), "failed to drop collection")
+
+ t.runTool("import", "--file", t.extFile, "-d", t.baseName, "-c", "foo", "--type", "csv",
+ "--headerline");
+
+ assert.soon(1 + " == c.count()", "after import");
+
+ // Note: Exporting and Importing to/from CSV is not designed to be round-trippable
+ var expected = { "date" : date.toISOString() };
+
+ var actual = c.findOne();
+
+ delete actual._id
+ assert.eq(expected, actual, "imported doc did not match expected");
+}
+
+// Basic test
+test(ISODate('1960-01-02 03:04:05.006Z'));
+
+// Testing special rounding rules for seconds
+test(ISODate('1960-01-02 03:04:04.999Z')); // second = 4
+test(ISODate('1960-01-02 03:04:05.000Z')); // second = 5
+test(ISODate('1960-01-02 03:04:05.001Z')); // second = 5
+test(ISODate('1960-01-02 03:04:05.999Z')); // second = 5
+
+// Test date before 1900 (negative tm_year values from gmtime)
+test(ISODate('1860-01-02 03:04:05.006Z'));
+
+// Test with time_t == -1 and 0
+test(new Date(-1000));
+test(new Date(0));
+
+// Testing dates between 1970 and 2000
+test(ISODate('1970-01-01 00:00:00.000Z'));
+test(ISODate('1970-01-01 00:00:00.999Z'));
+test(ISODate('1980-05-20 12:53:64.834Z'));
+test(ISODate('1999-12-31 00:00:00.000Z'));
+test(ISODate('1999-12-31 23:59:59.999Z'));
+
+// Test date > 2000 for completeness (using now)
+test(new Date());
+
+t.stop();
diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp
index 8fb62a388a8..cf9eaca53c3 100644
--- a/src/mongo/db/pipeline/expression.cpp
+++ b/src/mongo/db/pipeline/expression.cpp
@@ -1437,9 +1437,7 @@ namespace {
Value ExpressionMillisecond::evaluateInternal(const Variables& vars) const {
Value date(vpOperand[0]->evaluateInternal(vars));
- const int ms = date.coerceToDate() % 1000LL;
- // adding 1000 since dates before 1970 would have negative ms
- return Value(ms >= 0 ? ms : 1000 + ms);
+ return Value(extractMillisPortion(date.coerceToDate()));
}
REGISTER_EXPRESSION("$millisecond", ExpressionMillisecond::parse);
diff --git a/src/mongo/db/pipeline/value.cpp b/src/mongo/db/pipeline/value.cpp
index 9139f0cdbed..26f01e85d2e 100644
--- a/src/mongo/db/pipeline/value.cpp
+++ b/src/mongo/db/pipeline/value.cpp
@@ -426,22 +426,7 @@ namespace mongo {
}
time_t Value::coerceToTimeT() const {
- long long millis = coerceToDate();
- if (millis < 0) {
- // We want the division below to truncate toward -inf rather than 0
- // eg Dec 31, 1969 23:59:58.001 should be -2 seconds rather than -1
- // This is needed to get the correct values from coerceToTM
- if ( -1999 / 1000 != -2) { // this is implementation defined
- millis -= 1000-1;
- }
- }
- const long long seconds = millis / 1000;
-
- uassert(16421, "Can't handle date values outside of time_t range",
- seconds >= std::numeric_limits<time_t>::min() &&
- seconds <= std::numeric_limits<time_t>::max());
-
- return static_cast<time_t>(seconds);
+ return millisToTimeT(coerceToDate());
}
tm Value::coerceToTm() const {
// See implementation in Date_t.
diff --git a/src/mongo/util/time_support.cpp b/src/mongo/util/time_support.cpp
index 76190501e14..fc7b71eef1f 100644
--- a/src/mongo/util/time_support.cpp
+++ b/src/mongo/util/time_support.cpp
@@ -112,12 +112,13 @@ namespace mongo {
const int bufSize = 32;
char buf[bufSize];
struct tm t;
- time_t_to_Struct(date.toTimeT(), &t, local);
+ time_t_to_Struct(millisToTimeT(static_cast<long long>(date.millis)), &t, local);
int pos = strftime(buf, bufSize, MONGO_ISO_DATE_FMT_NO_TZ, &t);
fassert(16981, 0 < pos);
char* cur = buf + pos;
int bufRemaining = bufSize - pos;
- pos = snprintf(cur, bufRemaining, ".%03d", static_cast<int32_t>(date.asInt64() % 1000));
+ pos = snprintf(cur, bufRemaining, ".%03d",
+ extractMillisPortion(static_cast<long long>(date.millis)));
fassert(16982, bufRemaining > pos && pos > 0);
cur += pos;
bufRemaining -= pos;
@@ -182,6 +183,30 @@ namespace mongo {
return millis / 1000;
}
+ time_t millisToTimeT(long long millis) {
+ if (millis < 0) {
+ // We want the division below to truncate toward -inf rather than 0
+ // eg Dec 31, 1969 23:59:58.001 should be -2 seconds rather than -1
+ // This is needed to get the correct values from coerceToTM
+ if ( -1999 / 1000 != -2) { // this is implementation defined
+ millis -= 1000-1;
+ }
+ }
+ const long long seconds = millis / 1000;
+
+ uassert(16421, "Can't handle date values outside of time_t range",
+ seconds >= std::numeric_limits<time_t>::min() &&
+ seconds <= std::numeric_limits<time_t>::max());
+
+ return static_cast<time_t>(seconds);
+ }
+
+ int extractMillisPortion(long long millisSinceEpoch) {
+ const int ms = millisSinceEpoch % 1000LL;
+ // adding 1000 since dates before 1970 would have negative ms
+ return ms >= 0 ? ms : 1000 + ms;
+ }
+
std::string dateToCtimeString(Date_t date) {
time_t t = date.toTimeT();
char buf[64];
diff --git a/src/mongo/util/time_support.h b/src/mongo/util/time_support.h
index 46cee85e97a..1043a658142 100644
--- a/src/mongo/util/time_support.h
+++ b/src/mongo/util/time_support.h
@@ -78,6 +78,18 @@ namespace mongo {
*/
std::string dateToCtimeString(Date_t date);
+ /**
+ * Converts millis to time_t, doing correct division for negative millis, and uasserting that
+ * the result falls within the valid range of a time_t.
+ */
+ time_t millisToTimeT(long long millis);
+
+ /**
+ * Returns the millis since the last whole second of the given millis since epoch, and correctly
+ * handles dates before epoch.
+ */
+ int extractMillisPortion(long long millisSinceEpoch);
+
boost::gregorian::date currentDate();
// parses time of day in "hh:mm" format assuming 'hh' is 00-23
diff --git a/src/mongo/util/time_support_test.cpp b/src/mongo/util/time_support_test.cpp
index c459c124645..51848153133 100644
--- a/src/mongo/util/time_support_test.cpp
+++ b/src/mongo/util/time_support_test.cpp
@@ -63,6 +63,52 @@ namespace {
dateToISOStringUTC(Date_t(2781455351100ULL)));
ASSERT_EQUALS(std::string("2013-02-20T18:29:11.100Z"),
dateToISOStringUTC(Date_t(1361384951100ULL)));
+
+ // Basic test
+#ifndef _WIN32 // Negative Dates don't currently work on Windows
+ ASSERT_EQUALS(std::string("1960-01-02T03:04:05.006Z"),
+ dateToISOStringUTC(Date_t(-315521754994LL)));
+#endif
+
+ // Testing special rounding rules for seconds
+#ifndef _WIN32 // Negative Dates don't currently work on Windows
+ ASSERT_EQUALS(std::string("1960-01-02T03:04:04.999Z"),
+ dateToISOStringUTC(Date_t(-315521755001LL))); // second = 4
+ ASSERT_EQUALS(std::string("1960-01-02T03:04:05.000Z"),
+ dateToISOStringUTC(Date_t(-315521755000LL))); // second = 5
+ ASSERT_EQUALS(std::string("1960-01-02T03:04:05.001Z"),
+ dateToISOStringUTC(Date_t(-315521754999LL))); // second = 5
+ ASSERT_EQUALS(std::string("1960-01-02T03:04:05.999Z"),
+ dateToISOStringUTC(Date_t(-315521754001LL))); // second = 5
+#endif
+
+ // Test date before 1900 (negative tm_year values from gmtime)
+#ifndef _WIN32 // Negative Dates don't currently work on Windows
+ ASSERT_EQUALS(std::string("1860-01-02T03:04:05.006Z"),
+ dateToISOStringUTC(Date_t(-3471195354994LL)));
+#endif
+
+ // Test with time_t == -1
+#ifndef _WIN32 // Negative Dates don't currently work on Windows
+ ASSERT_EQUALS(std::string("1969-12-31T23:59:59.000Z"),
+ dateToISOStringUTC(Date_t(-1000LL)));
+#endif
+
+ // Testing dates between 1970 and 2000
+ ASSERT_EQUALS(std::string("1970-01-01T00:00:00.000Z"),
+ dateToISOStringUTC(Date_t(0ULL)));
+ ASSERT_EQUALS(std::string("1970-01-01T00:00:00.999Z"),
+ dateToISOStringUTC(Date_t(999ULL)));
+ ASSERT_EQUALS(std::string("1980-05-20T12:54:04.834Z"),
+ dateToISOStringUTC(Date_t(327675244834ULL)));
+ ASSERT_EQUALS(std::string("1999-12-31T00:00:00.000Z"),
+ dateToISOStringUTC(Date_t(946598400000ULL)));
+ ASSERT_EQUALS(std::string("1999-12-31T23:59:59.999Z"),
+ dateToISOStringUTC(Date_t(946684799999ULL)));
+
+ // Test date > 2000 for completeness (using now)
+ ASSERT_EQUALS(std::string("2013-10-11T23:20:12.072Z"),
+ dateToISOStringUTC(Date_t(1381533612072ULL)));
}
TEST(TimeFormatting, DateAsISO8601Local) {