summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMindaugas Malinauskas <mindaugas.malinauskas@mongodb.com>2021-05-06 10:36:05 +0100
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-06-10 16:19:50 +0000
commit2267d1dfd1ed6e0cdde57461c157f36ee3a6edb5 (patch)
treecd395d0794e5f6acd30f9014b8c7952749647b9e
parent75f9277a30e82c8db2b84e545cfd549968ec45ff (diff)
downloadmongo-2267d1dfd1ed6e0cdde57461c157f36ee3a6edb5.tar.gz
SERVER-55224 Fix $dateDiff for timezones with non-whole hour Daylight Savings Time UTC offset changes for minute and second units
-rw-r--r--src/mongo/db/query/datetime/date_time_support.cpp47
-rw-r--r--src/mongo/db/query/datetime/date_time_support_test.cpp77
2 files changed, 111 insertions, 13 deletions
diff --git a/src/mongo/db/query/datetime/date_time_support.cpp b/src/mongo/db/query/datetime/date_time_support.cpp
index a78715d96cf..fb38063b0af 100644
--- a/src/mongo/db/query/datetime/date_time_support.cpp
+++ b/src/mongo/db/query/datetime/date_time_support.cpp
@@ -644,15 +644,29 @@ inline long long daysBetweenYears(long startYear, long endYear) {
/**
* 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.
+ * instants 'startInstant' and 'endInstant' due to different UTC offsets.
*/
-inline long long dstCorrection(timelib_time* startInstant, timelib_time* endInstant) {
+inline long long utcOffsetCorrectionForHours(timelib_time* startInstant, timelib_time* endInstant) {
return (startInstant->z - endInstant->z) / (kMinutesPerHour * kSecondsPerMinute);
}
+
+/**
+ * Determines a correction needed in number of minutes when calculating passed minutes between two
+ * time instants 'startInstant' and 'endInstant' due to different UTC offsets.
+ */
+inline long long utcOffsetCorrectionForMinutes(timelib_time* startInstant,
+ timelib_time* endInstant) {
+ return (startInstant->z - endInstant->z) / kSecondsPerMinute;
+}
+
+/**
+ * Determines a correction needed in number of seconds when calculating passed seconds between two
+ * time instants 'startInstant' and 'endInstant' due to different UTC offsets.
+ */
+inline long long utcOffsetCorrectionForSeconds(timelib_time* startInstant,
+ timelib_time* endInstant) {
+ return startInstant->z - endInstant->z;
+}
inline long long dateDiffYear(Date startInstant, Date endInstant) {
return endInstant.year - startInstant.year;
}
@@ -698,18 +712,27 @@ inline long long dateDiffWeek(Date startInstant, Date endInstant, DayOfWeek star
dayOfWeek(endInstant, startOfWeek)) /
kDaysPerWeek;
}
+inline long long dateDiffHourWithoutUTCOffsetCorrection(timelib_time* startInstant,
+ timelib_time* endInstant) {
+ return endInstant->h - startInstant->h + dateDiffDay(*startInstant, *endInstant) * kHoursPerDay;
+}
inline long long dateDiffHour(timelib_time* startInstant, timelib_time* endInstant) {
- return endInstant->h - startInstant->h +
- dateDiffDay(*startInstant, *endInstant) * kHoursPerDay +
- dstCorrection(startInstant, endInstant);
+ return dateDiffHourWithoutUTCOffsetCorrection(startInstant, endInstant) +
+ utcOffsetCorrectionForHours(startInstant, endInstant);
}
-inline long long dateDiffMinute(timelib_time* startInstant, timelib_time* endInstant) {
+inline long long dateDiffMinuteWithoutUTCOffsetCorrection(timelib_time* startInstant,
+ timelib_time* endInstant) {
return endInstant->i - startInstant->i +
- dateDiffHour(startInstant, endInstant) * kMinutesPerHour;
+ dateDiffHourWithoutUTCOffsetCorrection(startInstant, endInstant) * kMinutesPerHour;
+}
+inline long long dateDiffMinute(timelib_time* startInstant, timelib_time* endInstant) {
+ return dateDiffMinuteWithoutUTCOffsetCorrection(startInstant, endInstant) +
+ utcOffsetCorrectionForMinutes(startInstant, endInstant);
}
inline long long dateDiffSecond(timelib_time* startInstant, timelib_time* endInstant) {
return endInstant->s - startInstant->s +
- dateDiffMinute(startInstant, endInstant) * kSecondsPerMinute;
+ dateDiffMinuteWithoutUTCOffsetCorrection(startInstant, endInstant) * kSecondsPerMinute +
+ utcOffsetCorrectionForSeconds(startInstant, endInstant);
}
inline long long dateDiffMillisecond(Date_t startDate, Date_t endDate) {
long long result;
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 40a85150788..049b7fee220 100644
--- a/src/mongo/db/query/datetime/date_time_support_test.cpp
+++ b/src/mongo/db/query/datetime/date_time_support_test.cpp
@@ -1218,6 +1218,9 @@ const TimeZone kAustraliaSydneyTimeZone =
kDefaultTimeZoneDatabase.getTimeZone("Australia/Sydney"); // UTC offset +11:00.
const TimeZone kUTCMinus10TimeZone =
kDefaultTimeZoneDatabase.getTimeZone("-10:00"); // UTC offset -10:00.
+const TimeZone kAustraliaLordHoweTimeZone =
+ kDefaultTimeZoneDatabase.getTimeZone("Australia/Lord_Howe");
+const TimeZone kEuropeMadridTimeZone = kDefaultTimeZoneDatabase.getTimeZone("Europe/Madrid");
const std::vector<const TimeZone*> kTimezones{&kDefaultTimeZone,
&kNewYorkTimeZone,
&kAustraliaEuclaTimeZone,
@@ -1499,6 +1502,27 @@ TEST(DateDiff, Hour) {
kNewYorkTimeZone.createFromDateParts(2001, 1, 1, 1, 0, 0, 0),
TimeUnit::hour,
kNewYorkTimeZone));
+
+ // Tests with Australia/Lord_Howe time zone that has 00:30 hour Daylight Savings Time (DST) UTC
+ // offset change. 'startDate' and 'endDate' parameters span a transition from/to DST.
+ //
+ // Verify that even when the UTC offset change is 30 minutes on transition from DST to Standard
+ // Time, time difference in hours is based on the local time. In the test 1.5h of real time
+ // passes, but the returned difference is 1h.
+ ASSERT_EQ(1,
+ dateDiff(kAustraliaLordHoweTimeZone.createFromDateParts(2021, 4, 4, 1, 0, 0, 0),
+ kAustraliaLordHoweTimeZone.createFromDateParts(2021, 4, 4, 2, 0, 0, 0),
+ TimeUnit::hour,
+ kAustraliaLordHoweTimeZone));
+
+ // Verify that even when the UTC offset change is 30 minutes on transition from Standard Time to
+ // DST, time difference in hours is based on the local time. In the test 1h of real time passes
+ // and the returned difference is 1h.
+ ASSERT_EQ(1,
+ dateDiff(kAustraliaLordHoweTimeZone.createFromDateParts(2021, 10, 3, 1, 0, 0, 0),
+ kAustraliaLordHoweTimeZone.createFromDateParts(2021, 10, 3, 2, 30, 0, 0),
+ TimeUnit::hour,
+ kAustraliaLordHoweTimeZone));
}
// Verifies 'dateDiff()' with TimeUnit::minute.
@@ -1518,11 +1542,28 @@ TEST(DateDiff, Minute) {
kNewYorkTimeZone.createFromDateParts(2020, 11, 8, 1, 30, 59, 999),
TimeUnit::minute,
kNewYorkTimeZone));
- ASSERT_EQ(234047495,
+ ASSERT_EQ(234047498,
dateDiff(kNewYorkTimeZone.createFromDateParts(1585, 11, 8, 1, 55, 0, 0),
kNewYorkTimeZone.createFromDateParts(2030, 11, 8, 1, 30, 59, 999),
TimeUnit::minute,
kNewYorkTimeZone));
+
+ // Tests with Australia/Lord_Howe time zone that has 00:30 hour Daylight Savings Time (DST) UTC
+ // offset change. 'startDate' and 'endDate' parameters span a transition from/to DST.
+ ASSERT_EQ(90,
+ dateDiff(kAustraliaLordHoweTimeZone.createFromDateParts(
+ 2021, 4, 4, 1, 0, 0, 0), // UTC 2021-04-03T14:00:00
+ kAustraliaLordHoweTimeZone.createFromDateParts(
+ 2021, 4, 4, 2, 0, 0, 0), // UTC 2021-04-03T15:30:00
+ TimeUnit::minute,
+ kAustraliaLordHoweTimeZone));
+ ASSERT_EQ(60,
+ dateDiff(kAustraliaLordHoweTimeZone.createFromDateParts(
+ 2021, 10, 3, 1, 0, 0, 0), // UTC 2021-10-02T14:30:00
+ kAustraliaLordHoweTimeZone.createFromDateParts(
+ 2021, 10, 3, 2, 30, 0, 0), // UTC 2021-10-02T15:30:00
+ TimeUnit::minute,
+ kAustraliaLordHoweTimeZone));
}
// Verifies 'dateDiff()' with TimeUnit::second.
@@ -1547,6 +1588,40 @@ TEST(DateDiff, Second) {
kDefaultTimeZone.createFromDateParts(2020, 11, 10, 1, 30, 16, 0),
TimeUnit::second,
kNewYorkTimeZone));
+
+ // Verify that negative milliseconds from the Unix Epoch are properly handled.
+ ASSERT_EQ(2,
+ dateDiff(kDefaultTimeZone.createFromDateParts(1969, 12, 31, 23, 59, 59, 999),
+ kDefaultTimeZone.createFromDateParts(1970, 1, 1, 0, 0, 1, 0),
+ TimeUnit::second,
+ kDefaultTimeZone));
+
+ // Tests with Australia/Lord_Howe time zone that has 00:30 hour Daylight Savings Time (DST) UTC
+ // offset change. 'startDate' and 'endDate' parameters span a transition from/to DST.
+ const int secondsPerMinute{60};
+ ASSERT_EQ(90 * secondsPerMinute,
+ dateDiff(kAustraliaLordHoweTimeZone.createFromDateParts(
+ 2021, 4, 4, 1, 0, 0, 0), // UTC 2021-04-03T14:00:00
+ kAustraliaLordHoweTimeZone.createFromDateParts(
+ 2021, 4, 4, 2, 0, 0, 0), // UTC 2021-04-03T15:30:00
+ TimeUnit::second,
+ kAustraliaLordHoweTimeZone));
+ ASSERT_EQ(60 * secondsPerMinute,
+ dateDiff(kAustraliaLordHoweTimeZone.createFromDateParts(
+ 2021, 10, 3, 1, 0, 0, 0), // UTC 2021-10-02T14:30:00
+ kAustraliaLordHoweTimeZone.createFromDateParts(
+ 2021, 10, 3, 2, 30, 0, 0), // UTC 2021-10-02T15:30:00
+ TimeUnit::second,
+ kAustraliaLordHoweTimeZone));
+
+ // Verify that UTC offset adjustments are properly accounted for when calculating the time
+ // difference. Time zone Europe/Madrid skips 0:14:44 hours at 1900-12-31 23:45:15 to change the
+ // timezone to UTC.
+ ASSERT_EQ(1,
+ dateDiff(kEuropeMadridTimeZone.createFromDateParts(1900, 12, 31, 23, 45, 15, 0),
+ kEuropeMadridTimeZone.createFromDateParts(1901, 1, 1, 0, 0, 0, 0),
+ TimeUnit::second,
+ kEuropeMadridTimeZone));
}
// Verifies 'dateDiff()' with TimeUnit::millisecond.