diff options
author | Charlie Swanson <charlie.swanson@mongodb.com> | 2017-05-26 17:27:30 -0400 |
---|---|---|
committer | Charlie Swanson <charlie.swanson@mongodb.com> | 2017-06-15 11:07:59 -0400 |
commit | 9822df6e71b0c5793676f9358e5d212c103c19a7 (patch) | |
tree | 6dcd8bfafabdc1127a5f3d71488ed33eb50179a6 /src/mongo/db/query/datetime | |
parent | 9f9ee7c6d3551980a0cc0d164b5deaa08c06c785 (diff) | |
download | mongo-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/SConscript | 10 | ||||
-rw-r--r-- | src/mongo/db/query/datetime/date_time_support.cpp | 176 | ||||
-rw-r--r-- | src/mongo/db/query/datetime/date_time_support.h | 232 | ||||
-rw-r--r-- | src/mongo/db/query/datetime/date_time_support_test.cpp | 404 | ||||
-rw-r--r-- | src/mongo/db/query/datetime/init_timezone_data.cpp | 10 |
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(); } |