summaryrefslogtreecommitdiff
path: root/src/mongo/db/query/datetime
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/query/datetime')
-rw-r--r--src/mongo/db/query/datetime/date_time_support.cpp145
-rw-r--r--src/mongo/db/query/datetime/date_time_support.h52
-rw-r--r--src/mongo/db/query/datetime/date_time_support_test.cpp315
3 files changed, 510 insertions, 2 deletions
diff --git a/src/mongo/db/query/datetime/date_time_support.cpp b/src/mongo/db/query/datetime/date_time_support.cpp
index 85a0fde53ac..498a44880be 100644
--- a/src/mongo/db/query/datetime/date_time_support.cpp
+++ b/src/mongo/db/query/datetime/date_time_support.cpp
@@ -40,6 +40,7 @@
#include "mongo/base/init.h"
#include "mongo/bson/util/builder.h"
#include "mongo/db/service_context.h"
+#include "mongo/platform/overflow_arithmetic.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/ctype.h"
#include "mongo/util/duration.h"
@@ -582,4 +583,148 @@ StatusWith<std::string> TimeZone::formatDate(StringData format, Date_t date) con
else
return formatted.str();
}
+
+namespace {
+auto const kMonthsInOneYear = 12LL;
+auto const kDaysInNonLeapYear = 365LL;
+auto const kHoursPerDay = 24LL;
+auto const kMinutesPerHour = 60LL;
+auto const kSecondsPerMinute = 60LL;
+auto const kDaysPerWeek = 7LL;
+auto const kQuartersPerYear = 4LL;
+auto const kQuarterLengthInMonths = 3LL;
+auto const kLeapYearReferencePoint = -1000000000L;
+
+/**
+ * Determines a number of leap years in a year range (leap year reference point; 'year'].
+ */
+inline long leapYearsSinceReferencePoint(long year) {
+ // Count a number of leap years that happened since the reference point, where a leap year is
+ // when year%4==0, excluding years when year%100==0, except when year%400==0.
+ auto yearsSinceReferencePoint = year - kLeapYearReferencePoint;
+ return yearsSinceReferencePoint / 4 - yearsSinceReferencePoint / 100 +
+ yearsSinceReferencePoint / 400;
+}
+
+/**
+ * Sums the number of days in the Gregorian calendar in years: 'startYear',
+ * 'startYear'+1, .., 'endYear'-1.
+ */
+inline long long daysBetweenYears(long startYear, long endYear) {
+ return leapYearsSinceReferencePoint(endYear - 1) - leapYearsSinceReferencePoint(startYear - 1) +
+ (endYear - startYear) * kDaysInNonLeapYear;
+}
+
+/**
+ * Determines a correction needed in number of hours when calculating passed hours between two time
+ * instants 'startInstant' and 'endInstant' due to the Daylight Savings Time. Returns 0, if both
+ * time instants 'startInstant' and 'endInstant' are either in Standard Time (ST) or in Daylight
+ * Saving Time (DST); returns 1, if 'endInstant' is in ST and 'startInstant' is in DST and
+ * 'endInstant' > 'startInstant' or 'endInstant' is in DST and 'startInstant' is in ST and
+ * 'endInstant' < 'startInstant'; otherwise returns -1.
+ */
+inline long long dstCorrection(timelib_time* startInstant, timelib_time* endInstant) {
+ return (startInstant->z - endInstant->z) / (kMinutesPerHour * kSecondsPerMinute);
+}
+
+inline long long dateDiffYear(timelib_time* startInstant, timelib_time* endInstant) {
+ return endInstant->y - startInstant->y;
+}
+
+/**
+ * Determines which quarter month 'month' belongs to. 'month' value range is 1..12. Returns a number
+ * of a quarter, where 0 corresponds to the first quarter.
+ */
+inline int quarter(int month) {
+ return (month - 1) / kQuarterLengthInMonths;
+}
+inline long long dateDiffQuarter(timelib_time* startInstant, timelib_time* endInstant) {
+ return quarter(endInstant->m) - quarter(startInstant->m) +
+ dateDiffYear(startInstant, endInstant) * kQuartersPerYear;
+}
+inline long long dateDiffMonth(timelib_time* startInstant, timelib_time* endInstant) {
+ return endInstant->m - startInstant->m +
+ dateDiffYear(startInstant, endInstant) * kMonthsInOneYear;
+}
+inline long long dateDiffDay(timelib_time* startInstant, timelib_time* endInstant) {
+ return timelib_day_of_year(endInstant->y, endInstant->m, endInstant->d) -
+ timelib_day_of_year(startInstant->y, startInstant->m, startInstant->d) +
+ daysBetweenYears(startInstant->y, endInstant->y);
+}
+inline long long dateDiffWeek(timelib_time* startInstant, timelib_time* endInstant) {
+ // We use 'timelib_iso_day_of_week()' since it considers Monday as the first day of the week.
+ return (dateDiffDay(startInstant, endInstant) +
+ timelib_iso_day_of_week(startInstant->y, startInstant->m, startInstant->d) -
+ timelib_iso_day_of_week(endInstant->y, endInstant->m, endInstant->d)) /
+ kDaysPerWeek;
+}
+inline long long dateDiffHour(timelib_time* startInstant, timelib_time* endInstant) {
+ return endInstant->h - startInstant->h + dateDiffDay(startInstant, endInstant) * kHoursPerDay +
+ dstCorrection(startInstant, endInstant);
+}
+inline long long dateDiffMinute(timelib_time* startInstant, timelib_time* endInstant) {
+ return endInstant->i - startInstant->i +
+ dateDiffHour(startInstant, endInstant) * kMinutesPerHour;
+}
+inline long long dateDiffSecond(timelib_time* startInstant, timelib_time* endInstant) {
+ return endInstant->s - startInstant->s +
+ dateDiffMinute(startInstant, endInstant) * kSecondsPerMinute;
+}
+inline long long dateDiffMillisecond(Date_t startDate, Date_t endDate) {
+ long long result;
+ uassert(5166308,
+ "dateDiff overflowed",
+ !overflow::sub(endDate.toMillisSinceEpoch(), startDate.toMillisSinceEpoch(), &result));
+ return result;
+}
+} // namespace
+
+long long dateDiff(Date_t startDate, Date_t endDate, TimeUnit unit, const TimeZone& timezone) {
+ if (TimeUnit::millisecond == unit) {
+ return dateDiffMillisecond(startDate, endDate);
+ }
+
+ // Translate the time instants to the given timezone.
+ auto startDateInTimeZone = timezone.getTimelibTime(startDate);
+ auto endDateInTimeZone = timezone.getTimelibTime(endDate);
+ switch (unit) {
+ case TimeUnit::year:
+ return dateDiffYear(startDateInTimeZone.get(), endDateInTimeZone.get());
+ case TimeUnit::quarter:
+ return dateDiffQuarter(startDateInTimeZone.get(), endDateInTimeZone.get());
+ case TimeUnit::month:
+ return dateDiffMonth(startDateInTimeZone.get(), endDateInTimeZone.get());
+ case TimeUnit::week:
+ return dateDiffWeek(startDateInTimeZone.get(), endDateInTimeZone.get());
+ case TimeUnit::day:
+ return dateDiffDay(startDateInTimeZone.get(), endDateInTimeZone.get());
+ case TimeUnit::hour:
+ return dateDiffHour(startDateInTimeZone.get(), endDateInTimeZone.get());
+ case TimeUnit::minute:
+ return dateDiffMinute(startDateInTimeZone.get(), endDateInTimeZone.get());
+ case TimeUnit::second:
+ return dateDiffSecond(startDateInTimeZone.get(), endDateInTimeZone.get());
+ default:
+ MONGO_UNREACHABLE;
+ }
+}
+
+TimeUnit parseTimeUnit(const std::string& unitName) {
+ static const StringMap<TimeUnit> timeUnitNameToTimeUnitMap{
+ {"year", TimeUnit::year},
+ {"quarter", TimeUnit::quarter},
+ {"month", TimeUnit::month},
+ {"week", TimeUnit::week},
+ {"day", TimeUnit::day},
+ {"hour", TimeUnit::hour},
+ {"minute", TimeUnit::minute},
+ {"second", TimeUnit::second},
+ {"millisecond", TimeUnit::millisecond},
+ };
+ auto iterator = timeUnitNameToTimeUnitMap.find(unitName);
+ uassert(ErrorCodes::FailedToParse,
+ str::stream() << "unknown time unit value: " << unitName,
+ iterator != timeUnitNameToTimeUnitMap.end());
+ return iterator->second;
+}
} // namespace mongo
diff --git a/src/mongo/db/query/datetime/date_time_support.h b/src/mongo/db/query/datetime/date_time_support.h
index 0bc4a779180..0394b661ed2 100644
--- a/src/mongo/db/query/datetime/date_time_support.h
+++ b/src/mongo/db/query/datetime/date_time_support.h
@@ -311,10 +311,9 @@ public:
*/
static void validateToStringFormat(StringData format);
static void validateFromStringFormat(StringData format);
-
-private:
std::unique_ptr<_timelib_time, TimelibTimeDeleter> getTimelibTime(Date_t) const;
+private:
/**
* Only works with 1 <= spaces <= 4 and 0 <= number <= 9999. If spaces is less than the digit
* count of number we simply insert the number without padding.
@@ -472,4 +471,53 @@ private:
std::unique_ptr<_timelib_tzdb, TimeZoneDBDeleter> _timeZoneDatabase;
};
+/**
+ * A set of standard measures of time used to express a length of time interval.
+ */
+enum class TimeUnit {
+ year,
+ quarter, // A quarter of a year.
+ month,
+ week,
+ day,
+ hour,
+ minute,
+ second,
+ millisecond
+};
+
+/**
+ * Parses a string representation of an enumerator of TimeUnit type 'unitName' into a value of type
+ * TimeUnit. Throws an exception with error code ErrorCodes::FailedToParse when passed an invalid
+ * name.
+ */
+TimeUnit parseTimeUnit(const std::string& unitName);
+
+/**
+ * Determines the number of upper boundaries of time intervals crossed when moving from time instant
+ * 'startDate' to time instant 'endDate' in time zone 'timezone'. The time intervals are of length
+ * equal to one 'unit' and aligned so that the lower/upper bound is located in time axis at instant
+ * n*'unit', where n is an integer.
+ *
+ * If 'endDate' < 'startDate', then the returned number of crossed boundaries is negative.
+ *
+ * For 'unit' values 'hour' and smaller, when there is a transition from Daylight Saving Time to
+ * standard time the function behaves as if standard time intervals overlap Daylight Saving Time
+ * intervals. When there is a transition from standard time to Daylight Saving Time the function
+ * behaves as if the last interval in standard time is longer by one hour.
+ *
+ * An example: if startDate=2011-01-31T00:00:00 (in 'timezone'), endDate=2011-02-01T00:00:00 (in
+ * 'timezone'), unit='month', then the function returns 1, since a month boundary at
+ * 2011-02-01T00:00:00 was crossed.
+ *
+ * The function operates in the Gregorian calendar. The function does not account for leap seconds.
+ * For time instants before year 1583 the proleptic Gregorian calendar is used.
+ *
+ * startDate - starting time instant in UTC time zone.
+ * endDate - ending time instant in UTC time zone.
+ * unit - length of time intervals.
+ * timezone - determines the timezone used for counting the boundaries as well as Daylight Saving
+ * Time rules.
+ */
+long long dateDiff(Date_t startDate, Date_t endDate, TimeUnit unit, const TimeZone& timezone);
} // namespace mongo
diff --git a/src/mongo/db/query/datetime/date_time_support_test.cpp b/src/mongo/db/query/datetime/date_time_support_test.cpp
index fba52b3e029..247ed2b8e2b 100644
--- a/src/mongo/db/query/datetime/date_time_support_test.cpp
+++ b/src/mongo/db/query/datetime/date_time_support_test.cpp
@@ -29,7 +29,9 @@
#include "mongo/platform/basic.h"
+#include <limits>
#include <sstream>
+#include <timelib.h>
#include "mongo/db/query/datetime/date_time_support.h"
#include "mongo/unittest/unittest.h"
@@ -1191,5 +1193,318 @@ TEST(DayOfWeek, DayNumber) {
}
}
+// Time zones for testing 'dateDiff()'.
+const TimeZone kNewYorkTimeZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+const TimeZone kAustraliaEuclaTimeZone =
+ kDefaultTimeZoneDatabase.getTimeZone("Australia/Eucla"); // UTC offset +08:45
+
+// Verifies 'dateDiff()' with TimeUnit::year.
+TEST(DateDiff, Year) {
+ ASSERT_EQ(0,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2000, 12, 31, 23, 59, 59, 500),
+ kNewYorkTimeZone.createFromDateParts(2000, 12, 31, 23, 59, 59, 999),
+ TimeUnit::year,
+ kNewYorkTimeZone));
+ ASSERT_EQ(1,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2000, 12, 31, 23, 59, 59, 500),
+ kNewYorkTimeZone.createFromDateParts(2001, 1, 1, 0, 0, 0, 0),
+ TimeUnit::year,
+ kNewYorkTimeZone));
+ ASSERT_EQ(-1,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2001, 1, 1, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2000, 12, 31, 23, 59, 59, 500),
+ TimeUnit::year,
+ kNewYorkTimeZone));
+ ASSERT_EQ(999,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(1002, 1, 1, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2001, 1, 1, 0, 0, 0, 0),
+ TimeUnit::year,
+ kNewYorkTimeZone));
+}
+
+// Verifies 'dateDiff()' with TimeUnit::month.
+TEST(DateDiff, Month) {
+ ASSERT_EQ(0,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 2, 28, 23, 59, 59, 500),
+ kNewYorkTimeZone.createFromDateParts(2020, 2, 29, 23, 59, 59, 999),
+ TimeUnit::month,
+ kNewYorkTimeZone));
+ ASSERT_EQ(1,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 2, 29, 23, 59, 59, 999),
+ kNewYorkTimeZone.createFromDateParts(2020, 3, 1, 0, 0, 0, 0),
+ TimeUnit::month,
+ kNewYorkTimeZone));
+ ASSERT_EQ(-14,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2010, 2, 28, 23, 59, 59, 999),
+ kNewYorkTimeZone.createFromDateParts(2008, 12, 31, 23, 59, 59, 500),
+ TimeUnit::month,
+ kNewYorkTimeZone));
+ ASSERT_EQ(1500 * 12,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(520, 3, 1, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2020, 3, 1, 0, 0, 0, 0),
+ TimeUnit::month,
+ kNewYorkTimeZone));
+}
+
+// Verifies 'dateDiff()' with TimeUnit::quarter.
+TEST(DateDiff, Quarter) {
+ ASSERT_EQ(0,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 1, 1, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2020, 3, 31, 23, 59, 59, 999),
+ TimeUnit::quarter,
+ kNewYorkTimeZone));
+ ASSERT_EQ(1,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 1, 1, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2020, 4, 1, 0, 0, 0, 0),
+ TimeUnit::quarter,
+ kNewYorkTimeZone));
+ ASSERT_EQ(-2001,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2000, 12, 31, 23, 59, 59, 500),
+ kNewYorkTimeZone.createFromDateParts(1500, 9, 30, 23, 59, 59, 999),
+ TimeUnit::quarter,
+ kNewYorkTimeZone));
+}
+
+// Verifies 'dateDiff()' with TimeUnit::week.
+TEST(DateDiff, Week) {
+ ASSERT_EQ(1,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 2, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 9, 0, 0, 0, 0),
+ TimeUnit::week,
+ kNewYorkTimeZone));
+ ASSERT_EQ(1,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 2, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 15, 0, 0, 0, 0),
+ TimeUnit::week,
+ kNewYorkTimeZone));
+ ASSERT_EQ(0,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 2, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 0, 0, 0, 0),
+ TimeUnit::week,
+ kNewYorkTimeZone));
+ ASSERT_EQ(0,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 2, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 2, 0, 0, 0, 0),
+ TimeUnit::week,
+ kNewYorkTimeZone));
+ ASSERT_EQ(0,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 2, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 3, 0, 0, 0, 0),
+ TimeUnit::week,
+ kNewYorkTimeZone));
+ ASSERT_EQ(1,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 9, 0, 0, 0, 0),
+ TimeUnit::week,
+ kNewYorkTimeZone));
+ ASSERT_EQ(1,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 15, 0, 0, 0, 0),
+ TimeUnit::week,
+ kNewYorkTimeZone));
+ ASSERT_EQ(-5,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 10, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2020, 10, 8, 0, 0, 0, 0),
+ TimeUnit::week,
+ kNewYorkTimeZone));
+}
+
+// Verifies 'dateDiff()' with TimeUnit::day.
+TEST(DateDiff, Day) {
+ ASSERT_EQ(0,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 23, 59, 59, 999),
+ TimeUnit::day,
+ kNewYorkTimeZone));
+ ASSERT_EQ(1,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 9, 0, 0, 0, 0),
+ TimeUnit::day,
+ kNewYorkTimeZone));
+ ASSERT_EQ(-1,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 9, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 23, 59, 59, 999),
+ TimeUnit::day,
+ kNewYorkTimeZone));
+
+ // Verifies number of days in a year calculation.
+ ASSERT_EQ(369,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(1999, 12, 30, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2001, 1, 2, 0, 0, 0, 0),
+ TimeUnit::day,
+ kNewYorkTimeZone));
+ ASSERT_EQ(6575,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(1583, 1, 1, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(1601, 1, 1, 0, 0, 0, 0),
+ TimeUnit::day,
+ kNewYorkTimeZone));
+ ASSERT_EQ(-6575,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(1601, 1, 1, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(1583, 1, 1, 0, 0, 0, 0),
+ TimeUnit::day,
+ kNewYorkTimeZone));
+ ASSERT_EQ(29,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2004, 2, 10, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2004, 3, 10, 0, 0, 0, 0),
+ TimeUnit::day,
+ kNewYorkTimeZone));
+ ASSERT_EQ(28,
+ dateDiff(kAustraliaEuclaTimeZone.createFromDateParts(2005, 2, 10, 0, 0, 0, 0),
+ kAustraliaEuclaTimeZone.createFromDateParts(2005, 3, 10, 0, 0, 0, 0),
+ TimeUnit::day,
+ kAustraliaEuclaTimeZone));
+
+ // Use timelib_day_of_year as an oracle to verify day calculations.
+ for (int year = -1000; year < 3000; ++year) {
+ int expectedNumberOfDays = timelib_day_of_year(year, 12, 31) + 1;
+ ASSERT_EQ(expectedNumberOfDays,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(year, 2, 3, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(year + 1, 2, 3, 0, 0, 0, 0),
+ TimeUnit::day,
+ kNewYorkTimeZone));
+ }
+}
+
+// Verifies 'dateDiff()' with TimeUnit::hour.
+TEST(DateDiff, Hour) {
+ ASSERT_EQ(0,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 1, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 1, 59, 59, 999),
+ TimeUnit::hour,
+ kNewYorkTimeZone));
+ ASSERT_EQ(1,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 1, 59, 59, 999),
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 2, 0, 0, 0),
+ TimeUnit::hour,
+ kNewYorkTimeZone));
+ ASSERT_EQ(-25,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 10, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 23, 59, 59, 999),
+ TimeUnit::hour,
+ kNewYorkTimeZone));
+
+ // Hour difference calculation in UTC offset +08:45 time zone.
+ ASSERT_EQ(
+ 1,
+ dateDiff(
+ kAustraliaEuclaTimeZone.createFromDateParts(2020, 11, 10, 20, 55, 0, 0) /*UTC 12:10*/,
+ kAustraliaEuclaTimeZone.createFromDateParts(2020, 11, 10, 21, 5, 0, 0) /*UTC 12:20*/,
+ TimeUnit::hour,
+ kAustraliaEuclaTimeZone));
+
+ // Test of transition from DST to standard time.
+ ASSERT_EQ(1,
+ dateDiff(kDefaultTimeZone.createFromDateParts(
+ 2020, 11, 1, 5, 0, 0, 0) /* America/New_York 1:00AM EDT (UTC-4)*/,
+ kDefaultTimeZone.createFromDateParts(
+ 2020, 11, 1, 6, 0, 0, 0) /* America/New_York 1:00AM EST (UTC-5)*/,
+ TimeUnit::hour,
+ kNewYorkTimeZone));
+ ASSERT_EQ(-1,
+ dateDiff(kDefaultTimeZone.createFromDateParts(
+ 2020, 11, 1, 6, 0, 0, 0) /* America/New_York 1:00AM EST (UTC-5)*/,
+ kDefaultTimeZone.createFromDateParts(
+ 2020, 11, 1, 5, 0, 0, 0) /* America/New_York 1:00AM EDT (UTC-4)*/,
+ TimeUnit::hour,
+ kNewYorkTimeZone));
+
+ // Test of transition from standard time to DST.
+ ASSERT_EQ(1,
+ dateDiff(kDefaultTimeZone.createFromDateParts(
+ 2020, 3, 8, 6, 45, 0, 0) /* America/New_York 1:45AM EST (UTC-5)*/,
+ kDefaultTimeZone.createFromDateParts(
+ 2020, 3, 8, 7, 0, 0, 0) /* America/New_York 3:00AM EDT (UTC-4)*/,
+ TimeUnit::hour,
+ kNewYorkTimeZone));
+ ASSERT_EQ(-1,
+ dateDiff(kDefaultTimeZone.createFromDateParts(
+ 2020, 3, 8, 7, 0, 0, 0) /* America/New_York 3:00AM EDT (UTC-4)*/,
+ kDefaultTimeZone.createFromDateParts(
+ 2020, 3, 8, 6, 45, 0, 0) /* America/New_York 1:45AM EST (UTC-5)*/,
+ TimeUnit::hour,
+ kNewYorkTimeZone));
+
+ // Longer period test.
+ ASSERT_EQ(17545,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(1999, 1, 1, 0, 0, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2001, 1, 1, 1, 0, 0, 0),
+ TimeUnit::hour,
+ kNewYorkTimeZone));
+}
+
+// Verifies 'dateDiff()' with TimeUnit::minute.
+TEST(DateDiff, Minute) {
+ ASSERT_EQ(0,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 1, 30, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 1, 30, 59, 999),
+ TimeUnit::minute,
+ kNewYorkTimeZone));
+ ASSERT_EQ(1,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 1, 30, 59, 999),
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 1, 31, 0, 0),
+ TimeUnit::minute,
+ kNewYorkTimeZone));
+ ASSERT_EQ(-25,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 1, 55, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 1, 30, 59, 999),
+ TimeUnit::minute,
+ kNewYorkTimeZone));
+ ASSERT_EQ(234047495,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(1585, 11, 8, 1, 55, 0, 0),
+ kNewYorkTimeZone.createFromDateParts(2030, 11, 8, 1, 30, 59, 999),
+ TimeUnit::minute,
+ kNewYorkTimeZone));
+}
+
+// Verifies 'dateDiff()' with TimeUnit::second.
+TEST(DateDiff, Second) {
+ ASSERT_EQ(0,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 1, 30, 15, 0),
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 1, 30, 15, 999),
+ TimeUnit::second,
+ kNewYorkTimeZone));
+ ASSERT_EQ(1,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 1, 30, 15, 999),
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 1, 30, 16, 0),
+ TimeUnit::second,
+ kNewYorkTimeZone));
+ ASSERT_EQ(-2401,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 2, 10, 16, 999),
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 1, 30, 15, 999),
+ TimeUnit::second,
+ kNewYorkTimeZone));
+ ASSERT_EQ(1604971816,
+ dateDiff(kDefaultTimeZone.createFromDateParts(1970, 1, 1, 0, 0, 0, 0),
+ kDefaultTimeZone.createFromDateParts(2020, 11, 10, 1, 30, 16, 0),
+ TimeUnit::second,
+ kNewYorkTimeZone));
+}
+
+// Verifies 'dateDiff()' with TimeUnit::millisecond.
+TEST(DateDiff, Millisecond) {
+ ASSERT_EQ(100,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 1, 30, 15, 0),
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 1, 30, 15, 100),
+ TimeUnit::millisecond,
+ kNewYorkTimeZone));
+ ASSERT_EQ(-1500,
+ dateDiff(kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 1, 30, 16, 500),
+ kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 1, 30, 15, 0),
+ TimeUnit::millisecond,
+ kNewYorkTimeZone));
+ ASSERT_EQ(1604971816000,
+ dateDiff(kDefaultTimeZone.createFromDateParts(1970, 1, 1, 0, 0, 0, 0),
+ kDefaultTimeZone.createFromDateParts(2020, 11, 10, 1, 30, 16, 0),
+ TimeUnit::millisecond,
+ kNewYorkTimeZone));
+
+ // Verifies numeric overflow handling.
+ ASSERT_THROWS_CODE(dateDiff(Date_t::fromMillisSinceEpoch(std::numeric_limits<long long>::min()),
+ Date_t::fromMillisSinceEpoch(std::numeric_limits<long long>::max()),
+ TimeUnit::millisecond,
+ kNewYorkTimeZone),
+ AssertionException,
+ 5166308);
+}
} // namespace
} // namespace mongo