summaryrefslogtreecommitdiff
path: root/src/mongo/db/query/datetime
diff options
context:
space:
mode:
authorCharlie Swanson <charlie.swanson@mongodb.com>2017-05-26 17:27:30 -0400
committerCharlie Swanson <charlie.swanson@mongodb.com>2017-06-15 11:07:59 -0400
commit9822df6e71b0c5793676f9358e5d212c103c19a7 (patch)
tree6dcd8bfafabdc1127a5f3d71488ed33eb50179a6 /src/mongo/db/query/datetime
parent9f9ee7c6d3551980a0cc0d164b5deaa08c06c785 (diff)
downloadmongo-9822df6e71b0c5793676f9358e5d212c103c19a7.tar.gz
SERVER-28611 Use UTC TimeZone class for date expressions.
Diffstat (limited to 'src/mongo/db/query/datetime')
-rw-r--r--src/mongo/db/query/datetime/SConscript10
-rw-r--r--src/mongo/db/query/datetime/date_time_support.cpp176
-rw-r--r--src/mongo/db/query/datetime/date_time_support.h232
-rw-r--r--src/mongo/db/query/datetime/date_time_support_test.cpp404
-rw-r--r--src/mongo/db/query/datetime/init_timezone_data.cpp10
5 files changed, 788 insertions, 44 deletions
diff --git a/src/mongo/db/query/datetime/SConscript b/src/mongo/db/query/datetime/SConscript
index 84216ac941e..1334867552e 100644
--- a/src/mongo/db/query/datetime/SConscript
+++ b/src/mongo/db/query/datetime/SConscript
@@ -27,3 +27,13 @@ timeZoneEnv.Library(
'$BUILD_DIR/third_party/shim_timelib',
]
)
+
+timeZoneEnv.CppUnitTest(
+ target='date_time_support_test',
+ source=[
+ 'date_time_support_test.cpp',
+ ],
+ LIBDEPS=[
+ 'date_time_support',
+ ]
+)
diff --git a/src/mongo/db/query/datetime/date_time_support.cpp b/src/mongo/db/query/datetime/date_time_support.cpp
index eb169cff9be..a0da22210be 100644
--- a/src/mongo/db/query/datetime/date_time_support.cpp
+++ b/src/mongo/db/query/datetime/date_time_support.cpp
@@ -36,7 +36,7 @@
#include "mongo/db/query/datetime/date_time_support.h"
#include "mongo/base/init.h"
-#include "mongo/db/server_options.h"
+#include "mongo/bson/util/builder.h"
#include "mongo/db/service_context.h"
#include "mongo/stdx/memory.h"
#include "mongo/util/assert_util.h"
@@ -47,42 +47,52 @@
namespace mongo {
namespace {
-const auto getDateTimeSupport =
- ServiceContext::declareDecoration<std::unique_ptr<DateTimeSupport>>();
+const auto getTimeZoneDatabase =
+ ServiceContext::declareDecoration<std::unique_ptr<TimeZoneDatabase>>();
+
+// Converts a date to a number of seconds, being careful to round appropriately for negative numbers
+// of seconds.
+long long seconds(Date_t date) {
+ auto millis = date.toMillisSinceEpoch();
+ 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;
+ }
+ }
+ return durationCount<Seconds>(Milliseconds(millis));
+}
+
} // namespace
-const DateTimeSupport* DateTimeSupport::get(ServiceContext* serviceContext) {
- invariant(getDateTimeSupport(serviceContext));
- return getDateTimeSupport(serviceContext).get();
+const TimeZoneDatabase* TimeZoneDatabase::get(ServiceContext* serviceContext) {
+ invariant(getTimeZoneDatabase(serviceContext));
+ return getTimeZoneDatabase(serviceContext).get();
}
-void DateTimeSupport::set(ServiceContext* serviceContext,
- std::unique_ptr<DateTimeSupport> dateTimeSupport) {
- getDateTimeSupport(serviceContext) = std::move(dateTimeSupport);
+void TimeZoneDatabase::set(ServiceContext* serviceContext,
+ std::unique_ptr<TimeZoneDatabase> dateTimeSupport) {
+ getTimeZoneDatabase(serviceContext) = std::move(dateTimeSupport);
}
-DateTimeSupport::DateTimeSupport() {
+TimeZoneDatabase::TimeZoneDatabase() {
loadTimeZoneInfo({const_cast<timelib_tzdb*>(timelib_builtin_db()), TimeZoneDBDeleter()});
}
-DateTimeSupport::DateTimeSupport(
+TimeZoneDatabase::TimeZoneDatabase(
std::unique_ptr<timelib_tzdb, TimeZoneDBDeleter> timeZoneDatabase) {
loadTimeZoneInfo(std::move(timeZoneDatabase));
}
-void DateTimeSupport::TimeZoneDBDeleter::operator()(timelib_tzdb* timeZoneDatabase) {
+void TimeZoneDatabase::TimeZoneDBDeleter::operator()(timelib_tzdb* timeZoneDatabase) {
if (timeZoneDatabase != timelib_builtin_db()) {
timelib_zoneinfo_dtor(timeZoneDatabase);
}
}
-DateTimeSupport::~DateTimeSupport() {
- for (auto&& entry : _timeZones) {
- timelib_tzinfo_dtor(entry.second);
- }
-}
-
-void DateTimeSupport::loadTimeZoneInfo(
+void TimeZoneDatabase::loadTimeZoneInfo(
std::unique_ptr<timelib_tzdb, TimeZoneDBDeleter> timeZoneDatabase) {
invariant(timeZoneDatabase);
_timeZoneDatabase = std::move(timeZoneDatabase);
@@ -104,8 +114,134 @@ void DateTimeSupport::loadTimeZoneInfo(
<< timelib_get_error_message(errorCode)});
}
invariant(errorCode == TIMELIB_ERROR_NO_ERROR);
- _timeZones[entry.id] = tzInfo;
+ _timeZones[entry.id] = TimeZone{tzInfo};
+ }
+}
+
+TimeZone TimeZoneDatabase::utcZone() {
+ return TimeZone{nullptr};
+}
+
+TimeZone::DateParts::DateParts(const timelib_time& timelib_time, Date_t date)
+ : year(timelib_time.y),
+ month(timelib_time.m),
+ dayOfMonth(timelib_time.d),
+ hour(timelib_time.h),
+ minute(timelib_time.i),
+ second(timelib_time.s) {
+ const int ms = date.toMillisSinceEpoch() % 1000LL;
+ // Add 1000 since dates before 1970 would have negative milliseconds.
+ millisecond = ms >= 0 ? ms : 1000 + ms;
+}
+
+void TimeZone::TimelibTZInfoDeleter::operator()(timelib_tzinfo* tzInfo) {
+ if (tzInfo) {
+ timelib_tzinfo_dtor(tzInfo);
+ }
+}
+
+TimeZone::TimeZone(timelib_tzinfo* tzInfo) : _tzInfo(tzInfo, TimelibTZInfoDeleter()) {}
+
+timelib_time TimeZone::getTimelibTime(Date_t date) const {
+ timelib_time time{};
+ if (_tzInfo) {
+ timelib_set_timezone(&time, _tzInfo.get());
+ timelib_unixtime2local(&time, seconds(date));
+ } else {
+ timelib_unixtime2gmt(&time, seconds(date));
+ }
+ return time;
+}
+
+TimeZone::DateParts TimeZone::dateParts(Date_t date) const {
+ auto time = getTimelibTime(date);
+ return DateParts(time, date);
+}
+
+int TimeZone::dayOfWeek(Date_t date) const {
+ auto time = getTimelibTime(date);
+ // timelib_day_of_week() returns a number in the range [0,6], we want [1,7], so add one.
+ return timelib_day_of_week(time.y, time.m, time.d) + 1;
+}
+
+int TimeZone::week(Date_t date) const {
+ int weekDay = dayOfWeek(date);
+ int yearDay = dayOfYear(date);
+ int prevSundayDayOfYear = yearDay - weekDay; // may be negative
+ int nextSundayDayOfYear = prevSundayDayOfYear + 7; // must be positive
+
+ // Return the zero based index of the week of the next sunday, equal to the one based index
+ // of the week of the previous sunday, which is to be returned.
+ int nextSundayWeek = nextSundayDayOfYear / 7;
+
+ return nextSundayWeek;
+}
+
+int TimeZone::dayOfYear(Date_t date) const {
+ auto time = getTimelibTime(date);
+ // timelib_day_of_year() returns a number in the range [0,365], we want [1,366], so add one.
+ return timelib_day_of_year(time.y, time.m, time.d) + 1;
+}
+
+int TimeZone::isoDayOfWeek(Date_t date) const {
+ auto time = getTimelibTime(date);
+ return timelib_iso_day_of_week(time.y, time.m, time.d);
+}
+
+int TimeZone::isoWeek(Date_t date) const {
+ auto time = getTimelibTime(date);
+ long long isoWeek;
+ long long isoYear;
+ timelib_isoweek_from_date(time.y, time.m, time.d, &isoWeek, &isoYear);
+ return isoWeek;
+}
+
+long long TimeZone::isoYear(Date_t date) const {
+ auto time = getTimelibTime(date);
+ long long isoWeek;
+ long long isoYear;
+ timelib_isoweek_from_date(time.y, time.m, time.d, &isoWeek, &isoYear);
+ return isoYear;
+}
+
+void TimeZone::validateFormat(StringData format) {
+ for (auto it = format.begin(); it != format.end(); ++it) {
+ if (*it != '%') {
+ continue;
+ }
+
+ ++it; // next character must be format modifier
+ uassert(18535, "Unmatched '%' at end of $dateToString format string", it != format.end());
+
+
+ switch (*it) {
+ // all of these fall through intentionally
+ case '%':
+ case 'Y':
+ case 'm':
+ case 'd':
+ case 'H':
+ case 'M':
+ case 'S':
+ case 'L':
+ case 'j':
+ case 'w':
+ case 'U':
+ case 'G':
+ case 'V':
+ case 'u':
+ break;
+ default:
+ uasserted(18536,
+ str::stream() << "Invalid format character '%" << *it
+ << "' in $dateToString format string");
+ }
}
}
+std::string TimeZone::formatDate(StringData format, Date_t date) const {
+ StringBuilder formatted;
+ outputDateWithFormat(formatted, format, date);
+ return formatted.str();
+}
} // 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 1c4540bbcbe..fe13d566c1d 100644
--- a/src/mongo/db/query/datetime/date_time_support.h
+++ b/src/mongo/db/query/datetime/date_time_support.h
@@ -29,21 +29,213 @@
#pragma once
#include <memory>
+#include <string>
#include "mongo/base/disallow_copying.h"
+#include "mongo/db/service_context.h"
#include "mongo/util/string_map.h"
#include "mongo/util/time_support.h"
+struct timelib_time;
+struct _timelib_tzdb;
+struct timelib_tzinfo;
+
namespace mongo {
-class ServiceContext;
+/**
+ * A TimeZone object represents one way of formatting/reading dates to compute things like the day
+ * of the week or the hour of a given date. Different TimeZone objects may report different answers
+ * for the hour, minute, or second of a date, even when given the same date.
+ */
+class TimeZone {
+public:
+ /**
+ * A struct with member variables describing the different parts of the date.
+ */
+ struct DateParts {
+ DateParts(const timelib_time&, Date_t);
+
+ int year;
+ int month;
+ int dayOfMonth;
+ int hour;
+ int minute;
+ int second;
+ int millisecond;
+ };
+
+ explicit TimeZone(timelib_tzinfo* tzInfo);
+ TimeZone() = default;
+
+ /**
+ * Returns a struct with members for each piece of the date.
+ */
+ DateParts dateParts(Date_t) const;
+
+ /**
+ * Returns the year according to the ISO 8601 standard. For example, Dec 31, 2014 is considered
+ * part of 2014 by the ISO standard.
+ */
+ long long isoYear(Date_t) const;
+
+ /**
+ * Returns the weekday number, ranging from 1 (for Sunday) to 7 (for Saturday).
+ */
+ int dayOfWeek(Date_t) const;
+
+ /**
+ * Returns the weekday number in ISO 8601 format, ranging from 1 (for Monday) to 7 (for Sunday).
+ */
+ int isoDayOfWeek(Date_t) const;
+
+ /**
+ * Returns the day of the year, ranging from 1 to 366.
+ */
+ int dayOfYear(Date_t) const;
+
+ /**
+ * Returns the week number for a date as a number between 0 (the partial week that precedes the
+ * first Sunday of the year) and 53.
+ */
+ int week(Date_t) const;
+
+ /**
+ * Returns the week number in ISO 8601 format, ranging from 1 to 53. Week numbers start at 1
+ * with the week (Monday through Sunday) that contains the year’s first Thursday.
+ */
+ int isoWeek(Date_t) const;
+
+ /**
+ * Converts a date object to a string according to 'format'. 'format' can be any string literal,
+ * containing 0 or more format specifiers like %Y (year) or %d (day of month). Callers must pass
+ * a valid format string for 'format', i.e. one that has already been passed to
+ * validateFormat().
+ */
+ std::string formatDate(StringData format, Date_t) const;
+
+ /**
+ * Like formatDate, except outputs to an output stream like a std::ostream or a StringBuilder.
+ */
+ template <typename OutputStream>
+ void outputDateWithFormat(OutputStream& os, StringData format, Date_t date) const {
+ auto parts = dateParts(date);
+ for (auto&& it = format.begin(); it != format.end(); ++it) {
+ if (*it != '%') {
+ os << *it;
+ continue;
+ }
+
+ ++it; // next character is format modifier
+ invariant(it != format.end()); // checked in validateFormat
+
+ switch (*it) {
+ case '%': // Escaped literal %
+ os << '%';
+ break;
+ case 'Y': // Year
+ {
+ auto year = parts.year;
+ uassert(18537,
+ str::stream() << "$dateToString is only defined on year 0-9999,"
+ << " tried to use year "
+ << year,
+ (year >= 0) && (year <= 9999));
+ insertPadded(os, year, 4);
+ break;
+ }
+ case 'm': // Month
+ insertPadded(os, parts.month, 2);
+ break;
+ case 'd': // Day of month
+ insertPadded(os, parts.dayOfMonth, 2);
+ break;
+ case 'H': // Hour
+ insertPadded(os, parts.hour, 2);
+ break;
+ case 'M': // Minute
+ insertPadded(os, parts.minute, 2);
+ break;
+ case 'S': // Second
+ insertPadded(os, parts.second, 2);
+ break;
+ case 'L': // Millisecond
+ insertPadded(os, parts.millisecond, 3);
+ break;
+ case 'j': // Day of year
+ insertPadded(os, dayOfYear(date), 3);
+ break;
+ case 'w': // Day of week
+ insertPadded(os, dayOfWeek(date), 1);
+ break;
+ case 'U': // Week
+ insertPadded(os, week(date), 2);
+ break;
+ case 'G': // Iso year of week
+ insertPadded(os, isoYear(date), 4);
+ break;
+ case 'V': // Iso week
+ insertPadded(os, isoWeek(date), 2);
+ break;
+ case 'u': // Iso day of week
+ insertPadded(os, isoDayOfWeek(date), 1);
+ break;
+ default:
+ // Should never happen as format is pre-validated
+ invariant(false);
+ }
+ }
+ }
+
+ /**
+ * Verifies that any '%' is followed by a valid format character, and that 'format' string
+ * ends with an even number of '%' symbols
+ */
+ static void validateFormat(StringData format);
+
+private:
+ timelib_time getTimelibTime(Date_t) const;
+
+ /**
+ * 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.
+ */
+ template <typename OutputStream>
+ void insertPadded(OutputStream& os, int number, int width) const {
+ invariant(width >= 1);
+ invariant(width <= 4);
+ invariant(number >= 0);
+ invariant(number <= 9999);
+
+ int digits = 1;
+
+ if (number >= 1000) {
+ digits = 4;
+ } else if (number >= 100) {
+ digits = 3;
+ } else if (number >= 10) {
+ digits = 2;
+ }
+
+ if (width > digits) {
+ os.write("0000", width - digits);
+ }
+ os << number;
+ }
+
+ struct TimelibTZInfoDeleter {
+ void operator()(timelib_tzinfo* tzInfo);
+ };
+
+ // null if this TimeZone represents the default UTC TimeZone.
+ std::shared_ptr<timelib_tzinfo> _tzInfo;
+};
/**
* A C++ interface wrapping the third-party timelib library. A single instance of this class can be
* accessed via the global service context.
*/
-class DateTimeSupport {
- MONGO_DISALLOW_COPYING(DateTimeSupport);
+class TimeZoneDatabase {
+ MONGO_DISALLOW_COPYING(TimeZoneDatabase);
public:
/**
@@ -52,47 +244,49 @@ public:
*/
struct TimeZoneDBDeleter {
TimeZoneDBDeleter() = default;
- void operator()(timelib_tzdb* timeZoneDatabase);
+ void operator()(_timelib_tzdb* timeZoneDatabase);
};
/**
- * Creates a DateTimeSupport object with time zone data loaded from timelib's built-in timezone
- * rules.
+ * Returns the TimeZoneDatabase object associated with the specified service context.
*/
- DateTimeSupport();
+ static const TimeZoneDatabase* get(ServiceContext* serviceContext);
/**
- * Creates a DateTimeSupport object using time zone rules given by 'timeZoneDatabase'.
+ * Sets the TimeZoneDatabase object associated with the specified service context.
*/
- DateTimeSupport(std::unique_ptr<timelib_tzdb, TimeZoneDBDeleter> timeZoneDatabase);
+ static void set(ServiceContext* serviceContext,
+ std::unique_ptr<TimeZoneDatabase> timeZoneDatabase);
- ~DateTimeSupport();
+ /**
+ * Returns a TimeZone object representing the UTC time zone.
+ */
+ static TimeZone utcZone();
/**
- * Returns the DateTimeSupport object associated with the specified service context. This method
- * must only be called if a DateTimeSupport has been set on the service context.
+ * Creates a TimeZoneDatabase object with time zone data loaded from timelib's built-in timezone
+ * rules.
*/
- static const DateTimeSupport* get(ServiceContext* serviceContext);
+ TimeZoneDatabase();
/**
- * Sets the DateTimeSupport object associated with the specified service context.
+ * Creates a TimeZoneDatabase object using time zone rules given by 'timeZoneDatabase'.
*/
- static void set(ServiceContext* serviceContext,
- std::unique_ptr<DateTimeSupport> dateTimeSupport);
+ TimeZoneDatabase(std::unique_ptr<_timelib_tzdb, TimeZoneDBDeleter> timeZoneDatabase);
private:
/**
* Populates '_timeZones' with parsed time zone rules for each timezone specified by
* 'timeZoneDatabase'.
*/
- void loadTimeZoneInfo(std::unique_ptr<timelib_tzdb, TimeZoneDBDeleter> timeZoneDatabase);
+ void loadTimeZoneInfo(std::unique_ptr<_timelib_tzdb, TimeZoneDBDeleter> timeZoneDatabase);
// A map from the time zone name to the struct describing the timezone. These are pre-populated
// at startup to avoid reading the source files repeatedly.
- StringMap<timelib_tzinfo*> _timeZones;
+ StringMap<TimeZone> _timeZones;
// The timelib structure which provides timezone information.
- std::unique_ptr<timelib_tzdb, TimeZoneDBDeleter> _timeZoneDatabase;
+ std::unique_ptr<_timelib_tzdb, TimeZoneDBDeleter> _timeZoneDatabase;
};
} // 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
new file mode 100644
index 00000000000..503e9cc3645
--- /dev/null
+++ b/src/mongo/db/query/datetime/date_time_support_test.cpp
@@ -0,0 +1,404 @@
+/**
+ * Copyright (C) 2017 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include <sstream>
+
+#include "mongo/db/query/datetime/date_time_support.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+TEST(UTCTimeBeforeEpoch, DoesExtractDateParts) {
+ // Dec 30, 1969 13:42:23:211
+ auto date = Date_t::fromMillisSinceEpoch(-123456789LL);
+ auto dateParts = TimeZoneDatabase::utcZone().dateParts(date);
+ ASSERT_EQ(dateParts.year, 1969);
+ ASSERT_EQ(dateParts.month, 12);
+ ASSERT_EQ(dateParts.dayOfMonth, 30);
+ ASSERT_EQ(dateParts.hour, 13);
+ ASSERT_EQ(dateParts.minute, 42);
+ ASSERT_EQ(dateParts.second, 23);
+ ASSERT_EQ(dateParts.millisecond, 211);
+}
+
+TEST(UTCTimeBeforeEpoch, DoesComputeISOYear) {
+ // Sunday, December 28, 1969.
+ auto date = Date_t::fromMillisSinceEpoch(-345600000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoYear(date), 1969);
+ // Tue, December 30, 1969, part of the following year.
+ date = Date_t::fromMillisSinceEpoch(-123456000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoYear(date), 1970);
+ // Saturday, January 1, 1966, part of the previous year.
+ date = Date_t::fromMillisSinceEpoch(-126230400000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoYear(date), 1965);
+}
+
+TEST(UTCTimeBeforeEpoch, DoesComputeDayOfWeek) {
+ // Sunday, December 28, 1969.
+ auto date = Date_t::fromMillisSinceEpoch(-345600000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfWeek(date), 1);
+ // Tuesday, December 30, 1969.
+ date = Date_t::fromMillisSinceEpoch(-123456000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfWeek(date), 3);
+ // Saturday, January 1, 1966.
+ date = Date_t::fromMillisSinceEpoch(-126230400000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfWeek(date), 7);
+}
+
+TEST(UTCTimeBeforeEpoch, DoesComputeISODayOfWeek) {
+ // Sunday, December 28, 1969.
+ auto date = Date_t::fromMillisSinceEpoch(-345600000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoDayOfWeek(date), 7);
+ // Tue, December 30, 1969.
+ date = Date_t::fromMillisSinceEpoch(-123456000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoDayOfWeek(date), 2);
+ // Saturday, January 1, 1966.
+ date = Date_t::fromMillisSinceEpoch(-126230400000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoDayOfWeek(date), 6);
+}
+
+TEST(UTCTimeBeforeEpoch, DoesComputeDayOfYear) {
+ // December 30, 1969.
+ auto date = Date_t::fromMillisSinceEpoch(-123456000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 364);
+ // January 1, 1966.
+ date = Date_t::fromMillisSinceEpoch(-126230400000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 1);
+
+ // Feb 28, 1960 (leap year).
+ date = Date_t::fromMillisSinceEpoch(-310608000000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 59);
+ // Feb 29, 1960 (leap year).
+ date = Date_t::fromMillisSinceEpoch(-310521600000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 60);
+ // Mar 1, 1960 (leap year).
+ date = Date_t::fromMillisSinceEpoch(-310435200000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 61);
+ // December 31, 1960 (leap year).
+ date = Date_t::fromMillisSinceEpoch(-284083200000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 366);
+
+ // Feb 28, 1900 (not leap year).
+ date = Date_t::fromMillisSinceEpoch(-2203977600000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 59);
+ // Mar 1, 1900 (not leap year).
+ date = Date_t::fromMillisSinceEpoch(-2203891200000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 60);
+ // December 31, 1900 (not leap year).
+ date = Date_t::fromMillisSinceEpoch(-2177539200000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 365);
+}
+
+TEST(UTCTimeBeforeEpoch, DoesComputeWeek) {
+ // Sunday, December 28, 1969.
+ auto date = Date_t::fromMillisSinceEpoch(-345600000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().week(date), 52);
+ // Saturday, January 1, 1966.
+ date = Date_t::fromMillisSinceEpoch(-126230400000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().week(date), 0);
+}
+
+TEST(UTCTimeBeforeEpoch, DoesComputeISOWeek) {
+ // Sunday, December 28, 1969.
+ auto date = Date_t::fromMillisSinceEpoch(-345600000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoWeek(date), 52);
+ // Tuesday, December 30, 1969.
+ date = Date_t::fromMillisSinceEpoch(-123456000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoWeek(date), 1);
+ // Saturday, January 1, 1966, part of previous year.
+ date = Date_t::fromMillisSinceEpoch(-126230400000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoWeek(date), 52);
+ // Tuesday, December 29, 1959.
+ date = Date_t::fromMillisSinceEpoch(-315878400000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoWeek(date), 53);
+ // Saturday, January 2, 1960, part of previous ISO year.
+ date = Date_t::fromMillisSinceEpoch(-315532800000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoWeek(date), 53);
+}
+
+TEST(UTCTimeBeforeEpoch, DoesFormatDate) {
+ // Tuesday, Dec 30, 1969 13:42:23:211
+ auto date = Date_t::fromMillisSinceEpoch(-123456789LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().formatDate("%Y/%m/%d %H:%M:%S:%L, dayOfYear: %j, "
+ "dayOfWeek: %w, week: %U, isoYear: %G, "
+ "isoWeek: %V, isoDayOfWeek: %u, percent: %%",
+ date),
+ "1969/12/30 13:42:23:211, dayOfYear: 364, dayOfWeek: 3, week: 52, isoYear: 1970, "
+ "isoWeek: 01, isoDayOfWeek: 2, percent: %");
+}
+
+TEST(UTCTimeBeforeEpoch, DoesOutputFormatDate) {
+ // Tuesday, Dec 30, 1969 13:42:23:211
+ auto date = Date_t::fromMillisSinceEpoch(-123456789LL);
+ std::ostringstream os;
+ TimeZoneDatabase::utcZone().outputDateWithFormat(os,
+ "%Y/%m/%d %H:%M:%S:%L, dayOfYear: %j, "
+ "dayOfWeek: %w, week: %U, isoYear: %G, "
+ "isoWeek: %V, isoDayOfWeek: %u, percent: %%",
+ date);
+ ASSERT_EQ(os.str(),
+ "1969/12/30 13:42:23:211, dayOfYear: 364, dayOfWeek: 3, week: 52, isoYear: 1970, "
+ "isoWeek: 01, isoDayOfWeek: 2, percent: %");
+}
+
+TEST(UTCTimeAtEpoch, DoesExtractDateParts) {
+ // Jan 1, 1970 00:00:00:000
+ auto date = Date_t::fromMillisSinceEpoch(0);
+ auto dateParts = TimeZoneDatabase::utcZone().dateParts(date);
+ ASSERT_EQ(dateParts.year, 1970);
+ ASSERT_EQ(dateParts.month, 1);
+ ASSERT_EQ(dateParts.dayOfMonth, 1);
+ ASSERT_EQ(dateParts.hour, 0);
+ ASSERT_EQ(dateParts.minute, 0);
+ ASSERT_EQ(dateParts.second, 0);
+ ASSERT_EQ(dateParts.millisecond, 0);
+}
+
+TEST(UTCTimeAtEpoch, DoesComputeISOYear) {
+ // Thursday, January 1, 1970.
+ auto date = Date_t::fromMillisSinceEpoch(0);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoYear(date), 1970);
+}
+
+TEST(UTCTimeAtEpoch, DoesComputeDayOfWeek) {
+ // Thursday, January 1, 1970.
+ auto date = Date_t::fromMillisSinceEpoch(0);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfWeek(date), 5);
+}
+
+TEST(UTCTimeAtEpoch, DoesComputeISODayOfWeek) {
+ // Thursday, January 1, 1970.
+ auto date = Date_t::fromMillisSinceEpoch(0);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoDayOfWeek(date), 4);
+}
+
+TEST(UTCTimeAtEpoch, DoesComputeDayOfYear) {
+ // Thursday, January 1, 1970.
+ auto date = Date_t::fromMillisSinceEpoch(0);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 1);
+}
+
+TEST(UTCTimeAtEpoch, DoesComputeWeek) {
+ // Thursday, January 1, 1970.
+ auto date = Date_t::fromMillisSinceEpoch(0);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().week(date), 0);
+}
+
+TEST(UTCTimeAtEpoch, DoesComputeISOWeek) {
+ // Thursday, January 1, 1970.
+ auto date = Date_t::fromMillisSinceEpoch(0);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoWeek(date), 1);
+}
+
+TEST(UTCTimeAtEpoch, DoesFormatDate) {
+ // Thu, Jan 1, 1970 00:00:00:000
+ auto date = Date_t::fromMillisSinceEpoch(0);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().formatDate("%Y/%m/%d %H:%M:%S:%L, dayOfYear: %j, "
+ "dayOfWeek: %w, week: %U, isoYear: %G, "
+ "isoWeek: %V, isoDayOfWeek: %u, percent: %%",
+ date),
+ "1970/01/01 00:00:00:000, dayOfYear: 001, dayOfWeek: 5, week: 00, isoYear: 1970, "
+ "isoWeek: 01, isoDayOfWeek: 4, percent: %");
+}
+
+TEST(UTCTimeAtEpoch, DoesOutputFormatDate) {
+ auto date = Date_t::fromMillisSinceEpoch(0);
+ std::ostringstream os;
+ TimeZoneDatabase::utcZone().outputDateWithFormat(os,
+ "%Y/%m/%d %H:%M:%S:%L, dayOfYear: %j, "
+ "dayOfWeek: %w, week: %U, isoYear: %G, "
+ "isoWeek: %V, isoDayOfWeek: %u, percent: %%",
+ date);
+ ASSERT_EQ(os.str(),
+ "1970/01/01 00:00:00:000, dayOfYear: 001, dayOfWeek: 5, week: 00, isoYear: 1970, "
+ "isoWeek: 01, isoDayOfWeek: 4, percent: %");
+}
+
+TEST(UTCTimeAfterEpoch, DoesExtractDateParts) {
+ // Jun 6, 2017 19:38:43:123.
+ auto date = Date_t::fromMillisSinceEpoch(1496777923123LL);
+ auto dateParts = TimeZoneDatabase::utcZone().dateParts(date);
+ ASSERT_EQ(dateParts.year, 2017);
+ ASSERT_EQ(dateParts.month, 6);
+ ASSERT_EQ(dateParts.dayOfMonth, 6);
+ ASSERT_EQ(dateParts.hour, 19);
+ ASSERT_EQ(dateParts.minute, 38);
+ ASSERT_EQ(dateParts.second, 43);
+ ASSERT_EQ(dateParts.millisecond, 123);
+}
+
+TEST(UTCTimeAfterEpoch, DoesComputeISOYear) {
+ // Tuesday, June 6, 2017.
+ auto date = Date_t::fromMillisSinceEpoch(1496777923000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoYear(date), 2017);
+ // Saturday, January 1, 2005, part of the previous year.
+ date = Date_t::fromMillisSinceEpoch(1104537600000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoYear(date), 2004);
+ // Monday, January 1, 2007.
+ date = Date_t::fromMillisSinceEpoch(1167609600000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoYear(date), 2007);
+ // Monday, December 31, 2007, part of the next year.
+ date = Date_t::fromMillisSinceEpoch(1199059200000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoYear(date), 2008);
+}
+
+TEST(UTCTimeAfterEpoch, DoesComputeDayOfWeek) {
+ // Tuesday, June 6, 2017.
+ auto date = Date_t::fromMillisSinceEpoch(1496777923000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfWeek(date), 3);
+ // Saturday, January 1, 2005.
+ date = Date_t::fromMillisSinceEpoch(1104537600000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfWeek(date), 7);
+ // Monday, January 1, 2007.
+ date = Date_t::fromMillisSinceEpoch(1167609600000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfWeek(date), 2);
+}
+
+TEST(UTCTimeAfterEpoch, DoesComputeISODayOfWeek) {
+ // Tuesday, June 6, 2017.
+ auto date = Date_t::fromMillisSinceEpoch(1496777923000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoDayOfWeek(date), 2);
+ // Saturday, January 1, 2005.
+ date = Date_t::fromMillisSinceEpoch(1104537600000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoDayOfWeek(date), 6);
+ // Monday, January 1, 2007.
+ date = Date_t::fromMillisSinceEpoch(1167609600000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoDayOfWeek(date), 1);
+}
+
+TEST(UTCTimeAfterEpoch, DoesComputeDayOfYear) {
+ // June 6, 2017.
+ auto date = Date_t::fromMillisSinceEpoch(1496777923000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 157);
+ // January 1, 2005.
+ date = Date_t::fromMillisSinceEpoch(1104537600000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 1);
+ // Feb 28, 2008 (leap year).
+ date = Date_t::fromMillisSinceEpoch(1204156800000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 59);
+ // Feb 29, 2008 (leap year).
+ date = Date_t::fromMillisSinceEpoch(1204243200000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 60);
+ // Mar 1, 2008 (leap year).
+ date = Date_t::fromMillisSinceEpoch(1204329600000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 61);
+ // December 31, 2008 (leap year).
+ date = Date_t::fromMillisSinceEpoch(1230681600000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 366);
+
+ // Feb 28, 2001 (not leap year).
+ date = Date_t::fromMillisSinceEpoch(983318400000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 59);
+ // Mar 1, 2001 (not leap year).
+ date = Date_t::fromMillisSinceEpoch(983404800000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 60);
+ // December 31, 2001 (not leap year).
+ date = Date_t::fromMillisSinceEpoch(1009756800000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 365);
+}
+
+TEST(UTCTimeAfterEpoch, DoesComputeWeek) {
+ // Tuesday, June 6, 2017.
+ auto date = Date_t::fromMillisSinceEpoch(1496777923000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().week(date), 23);
+ // Saturday, January 1, 2005, before first Sunday.
+ date = Date_t::fromMillisSinceEpoch(1104537600000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().week(date), 0);
+ // Monday, January 1, 2007, before first Sunday.
+ date = Date_t::fromMillisSinceEpoch(1167609600000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().week(date), 0);
+}
+
+TEST(UTCTimeAfterEpoch, DoesComputeISOWeek) {
+ // Tuesday, June 6, 2017.
+ auto date = Date_t::fromMillisSinceEpoch(1496777923000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoWeek(date), 23);
+ // Saturday, January 1, 2005, considered part of 2004, which was a leap year.
+ date = Date_t::fromMillisSinceEpoch(1104537600000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoWeek(date), 53);
+ // Monday, January 1, 2007.
+ date = Date_t::fromMillisSinceEpoch(1167609600000LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().isoWeek(date), 1);
+}
+
+TEST(UTCTimeAfterEpoch, DoesFormatDate) {
+ // Tue, Jun 6, 2017 19:38:43:234.
+ auto date = Date_t::fromMillisSinceEpoch(1496777923234LL);
+ ASSERT_EQ(TimeZoneDatabase::utcZone().formatDate("%Y/%m/%d %H:%M:%S:%L, dayOfYear: %j, "
+ "dayOfWeek: %w, week: %U, isoYear: %G, "
+ "isoWeek: %V, isoDayOfWeek: %u, percent: %%",
+ date),
+ "2017/06/06 19:38:43:234, dayOfYear: 157, dayOfWeek: 3, week: 23, isoYear: 2017, "
+ "isoWeek: 23, isoDayOfWeek: 2, percent: %");
+}
+
+TEST(UTCTimeAfterEpoch, DoesOutputFormatDate) {
+ // Tue, Jun 6, 2017 19:38:43:234.
+ auto date = Date_t::fromMillisSinceEpoch(1496777923234LL);
+ std::ostringstream os;
+ TimeZoneDatabase::utcZone().outputDateWithFormat(os,
+ "%Y/%m/%d %H:%M:%S:%L, dayOfYear: %j, "
+ "dayOfWeek: %w, week: %U, isoYear: %G, "
+ "isoWeek: %V, isoDayOfWeek: %u, percent: %%",
+ date);
+ ASSERT_EQ(os.str(),
+ "2017/06/06 19:38:43:234, dayOfYear: 157, dayOfWeek: 3, week: 23, isoYear: 2017, "
+ "isoWeek: 23, isoDayOfWeek: 2, percent: %");
+}
+
+TEST(DateFormat, ThrowsUserExceptionIfGivenUnrecognizedFormatter) {
+ ASSERT_THROWS_CODE(TimeZoneDatabase::utcZone().validateFormat("%x"), UserException, 18536);
+}
+
+TEST(DateFormat, ThrowsUserExceptionIfGivenUnmatchedPercent) {
+ ASSERT_THROWS_CODE(TimeZoneDatabase::utcZone().validateFormat("%"), UserException, 18535);
+ ASSERT_THROWS_CODE(TimeZoneDatabase::utcZone().validateFormat("%%%"), UserException, 18535);
+ ASSERT_THROWS_CODE(
+ TimeZoneDatabase::utcZone().validateFormat("blahblah%"), UserException, 18535);
+}
+
+TEST(DateFormat, ThrowsUserExceptionIfGivenDateBeforeYear0) {
+ const long long kMillisPerYear = 31556926000;
+ ASSERT_THROWS_CODE(TimeZoneDatabase::utcZone().formatDate(
+ "%Y", Date_t::fromMillisSinceEpoch(-(kMillisPerYear * 1971))),
+ UserException,
+ 18537);
+ ASSERT_EQ("0000",
+ TimeZoneDatabase::utcZone().formatDate(
+ "%Y", Date_t::fromMillisSinceEpoch(-(kMillisPerYear * 1970))));
+}
+
+TEST(DateFormat, ThrowsUserExceptionIfGivenDateAfterYear9999) {
+ ASSERT_THROWS_CODE(
+ TimeZoneDatabase::utcZone().formatDate("%Y", Date_t::max()), UserException, 18537);
+}
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/query/datetime/init_timezone_data.cpp b/src/mongo/db/query/datetime/init_timezone_data.cpp
index d9c73085239..b86159e3976 100644
--- a/src/mongo/db/query/datetime/init_timezone_data.cpp
+++ b/src/mongo/db/query/datetime/init_timezone_data.cpp
@@ -44,20 +44,20 @@ MONGO_INITIALIZER_WITH_PREREQUISITES(
(InitializerContext* context) {
auto serviceContext = getGlobalServiceContext();
if (!serverGlobalParams.timeZoneInfoPath.empty()) {
- std::unique_ptr<timelib_tzdb, DateTimeSupport::TimeZoneDBDeleter> timeZoneDatabase(
+ std::unique_ptr<timelib_tzdb, TimeZoneDatabase::TimeZoneDBDeleter> timeZoneDatabase(
timelib_zoneinfo(const_cast<char*>(serverGlobalParams.timeZoneInfoPath.c_str())),
- DateTimeSupport::TimeZoneDBDeleter());
+ TimeZoneDatabase::TimeZoneDBDeleter());
if (!timeZoneDatabase) {
return {ErrorCodes::FailedToParse,
str::stream() << "failed to load time zone database from path \""
<< serverGlobalParams.timeZoneInfoPath
<< "\""};
}
- DateTimeSupport::set(serviceContext,
- stdx::make_unique<DateTimeSupport>(std::move(timeZoneDatabase)));
+ TimeZoneDatabase::set(serviceContext,
+ stdx::make_unique<TimeZoneDatabase>(std::move(timeZoneDatabase)));
} else {
// No 'zoneInfo' specified on the command line, fall back to the built-in rules.
- DateTimeSupport::set(serviceContext, stdx::make_unique<DateTimeSupport>());
+ TimeZoneDatabase::set(serviceContext, stdx::make_unique<TimeZoneDatabase>());
}
return Status::OK();
}