summaryrefslogtreecommitdiff
path: root/src/mongo/db
diff options
context:
space:
mode:
authorCharlie Swanson <charlie.swanson@mongodb.com>2017-07-07 10:28:54 -0400
committerCharlie Swanson <charlie.swanson@mongodb.com>2017-07-07 10:28:54 -0400
commit56c8b7660fd85c02b5a8ade15e1dfdb49cbfea46 (patch)
tree1b5251064b4b2cb913a4ba045697e0145887374a /src/mongo/db
parent2b198988a626df6f36c9e606b08b266c462cceb4 (diff)
downloadmongo-56c8b7660fd85c02b5a8ade15e1dfdb49cbfea46.tar.gz
ERVER-28611 Add timezone argument for date expressions
Diffstat (limited to 'src/mongo/db')
-rw-r--r--src/mongo/db/pipeline/expression.cpp207
-rw-r--r--src/mongo/db/pipeline/expression.h346
-rw-r--r--src/mongo/db/pipeline/expression_test.cpp308
-rw-r--r--src/mongo/db/query/datetime/date_time_support_test.cpp423
4 files changed, 1026 insertions, 258 deletions
diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp
index 223e0db6ff0..798d05ca285 100644
--- a/src/mongo/db/pipeline/expression.cpp
+++ b/src/mongo/db/pipeline/expression.cpp
@@ -207,6 +207,22 @@ size_t getCodePointLength(char charByte) {
}
} // namespace
+/* ------------------------- Register Date Expressions ----------------------------- */
+
+REGISTER_EXPRESSION(dayOfMonth, ExpressionDayOfMonth::parse);
+REGISTER_EXPRESSION(dayOfWeek, ExpressionDayOfWeek::parse);
+REGISTER_EXPRESSION(dayOfYear, ExpressionDayOfYear::parse);
+REGISTER_EXPRESSION(hour, ExpressionHour::parse);
+REGISTER_EXPRESSION(isoDayOfWeek, ExpressionIsoDayOfWeek::parse);
+REGISTER_EXPRESSION(isoWeek, ExpressionIsoWeek::parse);
+REGISTER_EXPRESSION(isoWeekYear, ExpressionIsoWeekYear::parse);
+REGISTER_EXPRESSION(millisecond, ExpressionMillisecond::parse);
+REGISTER_EXPRESSION(minute, ExpressionMinute::parse);
+REGISTER_EXPRESSION(month, ExpressionMonth::parse);
+REGISTER_EXPRESSION(second, ExpressionSecond::parse);
+REGISTER_EXPRESSION(week, ExpressionWeek::parse);
+REGISTER_EXPRESSION(year, ExpressionYear::parse);
+
/* ----------------------- ExpressionAbs ---------------------------- */
Value ExpressionAbs::evaluateNumericArg(const Value& numericArg) const {
@@ -920,14 +936,14 @@ intrusive_ptr<Expression> ExpressionConstant::parse(
intrusive_ptr<ExpressionConstant> ExpressionConstant::create(
- const intrusive_ptr<ExpressionContext>& expCtx, const Value& pValue) {
- intrusive_ptr<ExpressionConstant> pEC(new ExpressionConstant(expCtx, pValue));
+ const intrusive_ptr<ExpressionContext>& expCtx, const Value& value) {
+ intrusive_ptr<ExpressionConstant> pEC(new ExpressionConstant(expCtx, value));
return pEC;
}
ExpressionConstant::ExpressionConstant(const boost::intrusive_ptr<ExpressionContext>& expCtx,
- const Value& pTheValue)
- : Expression(expCtx), pValue(pTheValue) {}
+ const Value& value)
+ : Expression(expCtx), _value(value) {}
intrusive_ptr<Expression> ExpressionConstant::optimize() {
@@ -940,11 +956,11 @@ void ExpressionConstant::addDependencies(DepsTracker* deps) const {
}
Value ExpressionConstant::evaluate(const Document& root) const {
- return pValue;
+ return _value;
}
Value ExpressionConstant::serialize(bool explain) const {
- return serializeConstant(pValue);
+ return serializeConstant(_value);
}
REGISTER_EXPRESSION(const, ExpressionConstant::parse);
@@ -959,10 +975,6 @@ const char* ExpressionConstant::getOpName() const {
namespace {
-bool isNullOrConstant(intrusive_ptr<Expression> exp) {
- return !exp || dynamic_cast<ExpressionConstant*>(exp.get());
-}
-
boost::optional<TimeZone> makeTimeZone(const TimeZoneDatabase* tzdb,
const Document& root,
intrusive_ptr<Expression> _timeZone) {
@@ -1130,11 +1142,17 @@ intrusive_ptr<Expression> ExpressionDateFromParts::optimize() {
_timeZone = _timeZone->optimize();
}
- if (isNullOrConstant(_year) && isNullOrConstant(_month) && isNullOrConstant(_day) &&
- isNullOrConstant(_hour) && isNullOrConstant(_minute) && isNullOrConstant(_second) &&
- isNullOrConstant(_milliseconds) && isNullOrConstant(_isoYear) &&
- isNullOrConstant(_isoWeekYear) && isNullOrConstant(_isoDayOfWeek) &&
- isNullOrConstant(_timeZone)) {
+ if (ExpressionConstant::allNullOrConstant({_year,
+ _month,
+ _day,
+ _hour,
+ _minute,
+ _second,
+ _milliseconds,
+ _isoYear,
+ _isoWeekYear,
+ _isoDayOfWeek,
+ _timeZone})) {
// Everything is a constant, so we can turn into a constant.
return ExpressionConstant::create(getExpressionContext(), evaluate(Document{}));
}
@@ -1353,8 +1371,7 @@ intrusive_ptr<Expression> ExpressionDateToParts::optimize() {
_iso8601 = _iso8601->optimize();
}
- if (dynamic_cast<ExpressionConstant*>(_date.get()) && isNullOrConstant(_iso8601) &&
- isNullOrConstant(_timeZone)) {
+ if (ExpressionConstant::allNullOrConstant({_date, _iso8601, _timeZone})) {
// Everything is a constant, so we can turn into a constant.
return ExpressionConstant::create(getExpressionContext(), evaluate(Document{}));
}
@@ -1511,42 +1528,6 @@ void ExpressionDateToString::addDependencies(DepsTracker* deps) const {
_date->addDependencies(deps);
}
-/* ---------------------- ExpressionDayOfMonth ------------------------- */
-
-Value ExpressionDayOfMonth::evaluate(const Document& root) const {
- Value date(vpOperand[0]->evaluate(root));
- return Value(TimeZoneDatabase::utcZone().dateParts(date.coerceToDate()).dayOfMonth);
-}
-
-REGISTER_EXPRESSION(dayOfMonth, ExpressionDayOfMonth::parse);
-const char* ExpressionDayOfMonth::getOpName() const {
- return "$dayOfMonth";
-}
-
-/* ------------------------- ExpressionDayOfWeek ----------------------------- */
-
-Value ExpressionDayOfWeek::evaluate(const Document& root) const {
- Value date(vpOperand[0]->evaluate(root));
- return Value(TimeZoneDatabase::utcZone().dayOfWeek(date.coerceToDate()));
-}
-
-REGISTER_EXPRESSION(dayOfWeek, ExpressionDayOfWeek::parse);
-const char* ExpressionDayOfWeek::getOpName() const {
- return "$dayOfWeek";
-}
-
-/* ------------------------- ExpressionDayOfYear ----------------------------- */
-
-Value ExpressionDayOfYear::evaluate(const Document& root) const {
- Value date(vpOperand[0]->evaluate(root));
- return Value(TimeZoneDatabase::utcZone().dayOfYear(date.coerceToDate()));
-}
-
-REGISTER_EXPRESSION(dayOfYear, ExpressionDayOfYear::parse);
-const char* ExpressionDayOfYear::getOpName() const {
- return "$dayOfYear";
-}
-
/* ----------------------- ExpressionDivide ---------------------------- */
Value ExpressionDivide::evaluate(const Document& root) const {
@@ -2249,30 +2230,6 @@ void ExpressionMeta::addDependencies(DepsTracker* deps) const {
}
}
-/* ------------------------- ExpressionMillisecond ----------------------------- */
-
-Value ExpressionMillisecond::evaluate(const Document& root) const {
- auto date = vpOperand[0]->evaluate(root).coerceToDate();
- return Value(TimeZoneDatabase::utcZone().dateParts(date).millisecond);
-}
-
-REGISTER_EXPRESSION(millisecond, ExpressionMillisecond::parse);
-const char* ExpressionMillisecond::getOpName() const {
- return "$millisecond";
-}
-
-/* ------------------------- ExpressionMinute -------------------------- */
-
-Value ExpressionMinute::evaluate(const Document& root) const {
- Value date(vpOperand[0]->evaluate(root));
- return Value(TimeZoneDatabase::utcZone().dateParts(date.coerceToDate()).minute);
-}
-
-REGISTER_EXPRESSION(minute, ExpressionMinute::parse);
-const char* ExpressionMinute::getOpName() const {
- return "$minute";
-}
-
/* ----------------------- ExpressionMod ---------------------------- */
Value ExpressionMod::evaluate(const Document& root) const {
@@ -2330,18 +2287,6 @@ const char* ExpressionMod::getOpName() const {
return "$mod";
}
-/* ------------------------ ExpressionMonth ----------------------------- */
-
-Value ExpressionMonth::evaluate(const Document& root) const {
- auto date = vpOperand[0]->evaluate(root).coerceToDate();
- return Value(TimeZoneDatabase::utcZone().dateParts(date).month);
-}
-
-REGISTER_EXPRESSION(month, ExpressionMonth::parse);
-const char* ExpressionMonth::getOpName() const {
- return "$month";
-}
-
/* ------------------------- ExpressionMultiply ----------------------------- */
Value ExpressionMultiply::evaluate(const Document& root) const {
@@ -2405,18 +2350,6 @@ const char* ExpressionMultiply::getOpName() const {
return "$multiply";
}
-/* ------------------------- ExpressionHour ----------------------------- */
-
-Value ExpressionHour::evaluate(const Document& root) const {
- auto date = vpOperand[0]->evaluate(root).coerceToDate();
- return Value(TimeZoneDatabase::utcZone().dateParts(date).hour);
-}
-
-REGISTER_EXPRESSION(hour, ExpressionHour::parse);
-const char* ExpressionHour::getOpName() const {
- return "$hour";
-}
-
/* ----------------------- ExpressionIfNull ---------------------------- */
Value ExpressionIfNull::evaluate(const Document& root) const {
@@ -3306,18 +3239,6 @@ const char* ExpressionReverseArray::getOpName() const {
return "$reverseArray";
}
-/* ------------------------- ExpressionSecond ----------------------------- */
-
-Value ExpressionSecond::evaluate(const Document& root) const {
- auto date = vpOperand[0]->evaluate(root).coerceToDate();
- return Value(TimeZoneDatabase::utcZone().dateParts(date).second);
-}
-
-REGISTER_EXPRESSION(second, ExpressionSecond::parse);
-const char* ExpressionSecond::getOpName() const {
- return "$second";
-}
-
namespace {
ValueSet arrayToSet(const Value& val, const ValueComparator& valueComparator) {
const vector<Value>& array = val.getArray();
@@ -4219,66 +4140,6 @@ const char* ExpressionType::getOpName() const {
return "$type";
}
-/* ------------------------- ExpressionWeek ----------------------------- */
-
-Value ExpressionWeek::evaluate(const Document& root) const {
- auto date = vpOperand[0]->evaluate(root).coerceToDate();
- return Value(TimeZoneDatabase::utcZone().week(date));
-}
-
-REGISTER_EXPRESSION(week, ExpressionWeek::parse);
-const char* ExpressionWeek::getOpName() const {
- return "$week";
-}
-
-/* ------------------------- ExpressionIsoDayOfWeek --------------------- */
-
-Value ExpressionIsoDayOfWeek::evaluate(const Document& root) const {
- Value date(vpOperand[0]->evaluate(root));
- return Value(TimeZoneDatabase::utcZone().isoDayOfWeek(date.coerceToDate()));
-}
-
-REGISTER_EXPRESSION(isoDayOfWeek, ExpressionIsoDayOfWeek::parse);
-const char* ExpressionIsoDayOfWeek::getOpName() const {
- return "$isoDayOfWeek";
-}
-
-/* ------------------------- ExpressionIsoWeekYear ---------------------- */
-
-Value ExpressionIsoWeekYear::evaluate(const Document& root) const {
- Value date(vpOperand[0]->evaluate(root));
- return Value(TimeZoneDatabase::utcZone().isoYear(date.coerceToDate()));
-}
-
-REGISTER_EXPRESSION(isoWeekYear, ExpressionIsoWeekYear::parse);
-const char* ExpressionIsoWeekYear::getOpName() const {
- return "$isoWeekYear";
-}
-
-/* ------------------------- ExpressionIsoWeek -------------------------- */
-
-Value ExpressionIsoWeek::evaluate(const Document& root) const {
- Value date(vpOperand[0]->evaluate(root));
- return Value(TimeZoneDatabase::utcZone().isoWeek(date.coerceToDate()));
-}
-
-REGISTER_EXPRESSION(isoWeek, ExpressionIsoWeek::parse);
-const char* ExpressionIsoWeek::getOpName() const {
- return "$isoWeek";
-}
-
-/* ------------------------- ExpressionYear ----------------------------- */
-
-Value ExpressionYear::evaluate(const Document& root) const {
- Value date(vpOperand[0]->evaluate(root));
- return Value(TimeZoneDatabase::utcZone().dateParts(date.coerceToDate()).year);
-}
-
-REGISTER_EXPRESSION(year, ExpressionYear::parse);
-const char* ExpressionYear::getOpName() const {
- return "$year";
-}
-
/* -------------------------- ExpressionZip ------------------------------ */
REGISTER_EXPRESSION(zip, ExpressionZip::parse);
diff --git a/src/mongo/db/pipeline/expression.h b/src/mongo/db/pipeline/expression.h
index 85451aeefcf..e14e451964d 100644
--- a/src/mongo/db/pipeline/expression.h
+++ b/src/mongo/db/pipeline/expression.h
@@ -30,6 +30,7 @@
#include "mongo/platform/basic.h"
+#include <algorithm>
#include <boost/intrusive_ptr.hpp>
#include <map>
#include <string>
@@ -411,6 +412,200 @@ public:
virtual Value evaluateNumericArg(const Value& numericArg) const = 0;
};
+/**
+ * A constant expression. Repeated calls to evaluate() will always return the same thing.
+ */
+class ExpressionConstant final : public Expression {
+public:
+ boost::intrusive_ptr<Expression> optimize() final;
+ void addDependencies(DepsTracker* deps) const final;
+ Value evaluate(const Document& root) const final;
+ Value serialize(bool explain) const final;
+
+ const char* getOpName() const;
+
+ /**
+ * Creates a new ExpressionConstant with value 'value'.
+ */
+ static boost::intrusive_ptr<ExpressionConstant> create(
+ const boost::intrusive_ptr<ExpressionContext>& expCtx, const Value& value);
+
+ static boost::intrusive_ptr<Expression> parse(
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ BSONElement bsonExpr,
+ const VariablesParseState& vps);
+
+ /**
+ * Returns true if 'expression' is nullptr or if 'expression' is an instance of an
+ * ExpressionConstant.
+ */
+ static bool isNullOrConstant(boost::intrusive_ptr<Expression> expression) {
+ return !expression || dynamic_cast<ExpressionConstant*>(expression.get());
+ }
+
+ /**
+ * Returns true if every expression in 'expressions' is either a nullptr or an instance of an
+ * ExpressionConstant.
+ */
+ static bool allNullOrConstant(
+ const std::initializer_list<boost::intrusive_ptr<Expression>>& expressions) {
+ return std::all_of(expressions.begin(), expressions.end(), [](auto exp) {
+ return ExpressionConstant::isNullOrConstant(exp);
+ });
+ }
+
+ /**
+ * Returns the constant value represented by this Expression.
+ */
+ Value getValue() const {
+ return _value;
+ }
+
+private:
+ ExpressionConstant(const boost::intrusive_ptr<ExpressionContext>& expCtx, const Value& value);
+
+ Value _value;
+};
+
+/**
+ * Inherit from this class if your expression works with date types, and accepts either a single
+ * argument which is a date, or an object {date: <date>, timezone: <string>}.
+ */
+template <typename SubClass>
+class DateExpressionAcceptingTimeZone : public Expression {
+public:
+ virtual ~DateExpressionAcceptingTimeZone() {}
+
+ Value evaluate(const Document& root) const final {
+ auto dateVal = _date->evaluate(root);
+ if (dateVal.nullish()) {
+ return Value(BSONNULL);
+ }
+ auto date = dateVal.coerceToDate();
+
+ if (!_timeZone) {
+ return evaluateDate(date, TimeZoneDatabase::utcZone());
+ }
+ auto timeZoneId = _timeZone->evaluate(root);
+ if (timeZoneId.nullish()) {
+ return Value(BSONNULL);
+ }
+
+ uassert(40533,
+ str::stream() << _opName
+ << " requires a string for the timezone argument, but was given a "
+ << typeName(timeZoneId.getType())
+ << " ("
+ << timeZoneId.toString()
+ << ")",
+ timeZoneId.getType() == BSONType::String);
+ auto timeZone = TimeZoneDatabase::get(getExpressionContext()->opCtx->getServiceContext())
+ ->getTimeZone(timeZoneId.getString());
+ return evaluateDate(date, timeZone);
+ }
+
+ void addDependencies(DepsTracker* deps) const final {
+ _date->addDependencies(deps);
+ if (_timeZone) {
+ _timeZone->addDependencies(deps);
+ }
+ }
+
+ /**
+ * Always serializes to the full {date: <date arg>, timezone: <timezone arg>} format, leaving
+ * off the timezone if not specified.
+ */
+ Value serialize(bool explain) const final {
+ return Value(Document{
+ {_opName,
+ Document{{"date", _date->serialize(explain)},
+ {"timezone", _timeZone ? _timeZone->serialize(explain) : Value()}}}});
+ }
+
+ boost::intrusive_ptr<Expression> optimize() final {
+ _date = _date->optimize();
+ if (_timeZone) {
+ _timeZone = _timeZone->optimize();
+ }
+ if (ExpressionConstant::allNullOrConstant({_date, _timeZone})) {
+ // Everything is a constant, so we can turn into a constant.
+ return ExpressionConstant::create(getExpressionContext(), evaluate(Document{}));
+ }
+ return this;
+ }
+
+ static boost::intrusive_ptr<Expression> parse(
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ BSONElement operatorElem,
+ const VariablesParseState& variablesParseState) {
+ boost::intrusive_ptr<DateExpressionAcceptingTimeZone> dateExpression{new SubClass(expCtx)};
+ if (operatorElem.type() == BSONType::Object) {
+ if (operatorElem.embeddedObject().firstElementFieldName()[0] == '$') {
+ // Assume this is an expression specification representing the date argument
+ // like {$add: [<date>, 1000]}.
+ dateExpression->_date = Expression::parseObject(
+ expCtx, operatorElem.embeddedObject(), variablesParseState);
+ return dateExpression;
+ } else {
+ // It's an object specifying the date and timezone options like {date: <date>,
+ // timezone: <timezone>}.
+ for (const auto& subElem : operatorElem.embeddedObject()) {
+ auto argName = subElem.fieldNameStringData();
+ if (argName == "date"_sd) {
+ dateExpression->_date =
+ Expression::parseOperand(expCtx, subElem, variablesParseState);
+ } else if (argName == "timezone"_sd) {
+ dateExpression->_timeZone =
+ Expression::parseOperand(expCtx, subElem, variablesParseState);
+ } else {
+ auto opName = operatorElem.fieldNameStringData();
+ uasserted(40535,
+ str::stream() << "unrecognized option to " << opName << ": \""
+ << argName
+ << "\"");
+ }
+ }
+ }
+ return dateExpression;
+ } else if (operatorElem.type() == BSONType::Array) {
+ auto elems = operatorElem.Array();
+ uassert(
+ 40536,
+ str::stream() << dateExpression->_opName
+ << " accepts exactly one argument if given an array, but was given "
+ << elems.size(),
+ elems.size() == 1);
+ // We accept an argument wrapped in a single array. For example, either {$week: <date>}
+ // or {$week: [<date>]} are valid, but not {$week: [{date: <date>}]}.
+ operatorElem = elems[0];
+ }
+ // It's some literal value, or the first element of a user-specified array. Either way it
+ // should be treated as the date argument.
+ dateExpression->_date = Expression::parseOperand(expCtx, operatorElem, variablesParseState);
+ return dateExpression;
+ }
+
+protected:
+ explicit DateExpressionAcceptingTimeZone(StringData opName,
+ const boost::intrusive_ptr<ExpressionContext>& expCtx)
+ : Expression(expCtx), _opName(opName) {}
+
+ /**
+ * Subclasses should implement this to do their actual date-related logic. Uses 'timezone' to
+ * evaluate the expression against 'data'. If the user did not specify a time zone, 'timezone'
+ * will represent the UTC zone.
+ */
+ virtual Value evaluateDate(Date_t date, const TimeZone& timezone) const = 0;
+
+private:
+ // The name of this expression, e.g. $week or $month.
+ StringData _opName;
+
+ // The expression representing the date argument.
+ boost::intrusive_ptr<Expression> _date;
+ // The expression representing the timezone argument, nullptr if not specified.
+ boost::intrusive_ptr<Expression> _timeZone = nullptr;
+};
class ExpressionAbs final : public ExpressionSingleNumericArg<ExpressionAbs> {
public:
@@ -629,39 +824,6 @@ private:
typedef ExpressionFixedArity<ExpressionCond, 3> Base;
};
-
-class ExpressionConstant final : public Expression {
-public:
- boost::intrusive_ptr<Expression> optimize() final;
- void addDependencies(DepsTracker* deps) const final;
- Value evaluate(const Document& root) const final;
- Value serialize(bool explain) const final;
-
- const char* getOpName() const;
-
- static boost::intrusive_ptr<ExpressionConstant> create(
- const boost::intrusive_ptr<ExpressionContext>& expCtx, const Value& pValue);
-
- static boost::intrusive_ptr<Expression> parse(
- const boost::intrusive_ptr<ExpressionContext>& expCtx,
- BSONElement bsonExpr,
- const VariablesParseState& vps);
-
- /*
- Get the constant value represented by this Expression.
-
- @returns the value
- */
- Value getValue() const {
- return pValue;
- }
-
-private:
- ExpressionConstant(const boost::intrusive_ptr<ExpressionContext>& expCtx, const Value& pValue);
-
- Value pValue;
-};
-
class ExpressionDateFromParts final : public Expression {
public:
boost::intrusive_ptr<Expression> optimize() final;
@@ -762,33 +924,36 @@ private:
boost::intrusive_ptr<Expression> _date;
};
-class ExpressionDayOfMonth final : public ExpressionFixedArity<ExpressionDayOfMonth, 1> {
+class ExpressionDayOfMonth final : public DateExpressionAcceptingTimeZone<ExpressionDayOfMonth> {
public:
explicit ExpressionDayOfMonth(const boost::intrusive_ptr<ExpressionContext>& expCtx)
- : ExpressionFixedArity<ExpressionDayOfMonth, 1>(expCtx) {}
+ : DateExpressionAcceptingTimeZone<ExpressionDayOfMonth>("$dayOfMonth", expCtx) {}
- Value evaluate(const Document& root) const final;
- const char* getOpName() const final;
+ Value evaluateDate(Date_t date, const TimeZone& timeZone) const final {
+ return Value(timeZone.dateParts(date).dayOfMonth);
+ }
};
-class ExpressionDayOfWeek final : public ExpressionFixedArity<ExpressionDayOfWeek, 1> {
+class ExpressionDayOfWeek final : public DateExpressionAcceptingTimeZone<ExpressionDayOfWeek> {
public:
explicit ExpressionDayOfWeek(const boost::intrusive_ptr<ExpressionContext>& expCtx)
- : ExpressionFixedArity<ExpressionDayOfWeek, 1>(expCtx) {}
+ : DateExpressionAcceptingTimeZone<ExpressionDayOfWeek>("$dayOfWeek", expCtx) {}
- Value evaluate(const Document& root) const final;
- const char* getOpName() const final;
+ Value evaluateDate(Date_t date, const TimeZone& timeZone) const final {
+ return Value(timeZone.dayOfWeek(date));
+ }
};
-class ExpressionDayOfYear final : public ExpressionFixedArity<ExpressionDayOfYear, 1> {
+class ExpressionDayOfYear final : public DateExpressionAcceptingTimeZone<ExpressionDayOfYear> {
public:
explicit ExpressionDayOfYear(const boost::intrusive_ptr<ExpressionContext>& expCtx)
- : ExpressionFixedArity<ExpressionDayOfYear, 1>(expCtx) {}
+ : DateExpressionAcceptingTimeZone<ExpressionDayOfYear>("$dayOfYear", expCtx) {}
- Value evaluate(const Document& root) const final;
- const char* getOpName() const final;
+ Value evaluateDate(Date_t date, const TimeZone& timeZone) const final {
+ return Value(timeZone.dayOfYear(date));
+ }
};
@@ -916,13 +1081,14 @@ public:
};
-class ExpressionHour final : public ExpressionFixedArity<ExpressionHour, 1> {
+class ExpressionHour final : public DateExpressionAcceptingTimeZone<ExpressionHour> {
public:
explicit ExpressionHour(const boost::intrusive_ptr<ExpressionContext>& expCtx)
- : ExpressionFixedArity<ExpressionHour, 1>(expCtx) {}
+ : DateExpressionAcceptingTimeZone<ExpressionHour>("$hour", expCtx) {}
- Value evaluate(const Document& root) const final;
- const char* getOpName() const final;
+ Value evaluateDate(Date_t date, const TimeZone& timeZone) const final {
+ return Value(timeZone.dateParts(date).hour);
+ }
};
@@ -1089,23 +1255,25 @@ private:
MetaType _metaType;
};
-class ExpressionMillisecond final : public ExpressionFixedArity<ExpressionMillisecond, 1> {
+class ExpressionMillisecond final : public DateExpressionAcceptingTimeZone<ExpressionMillisecond> {
public:
explicit ExpressionMillisecond(const boost::intrusive_ptr<ExpressionContext>& expCtx)
- : ExpressionFixedArity<ExpressionMillisecond, 1>(expCtx) {}
+ : DateExpressionAcceptingTimeZone<ExpressionMillisecond>("$millisecond", expCtx) {}
- Value evaluate(const Document& root) const final;
- const char* getOpName() const final;
+ Value evaluateDate(Date_t date, const TimeZone& timeZone) const final {
+ return Value(timeZone.dateParts(date).millisecond);
+ }
};
-class ExpressionMinute final : public ExpressionFixedArity<ExpressionMinute, 1> {
+class ExpressionMinute final : public DateExpressionAcceptingTimeZone<ExpressionMinute> {
public:
explicit ExpressionMinute(const boost::intrusive_ptr<ExpressionContext>& expCtx)
- : ExpressionFixedArity<ExpressionMinute, 1>(expCtx) {}
+ : DateExpressionAcceptingTimeZone<ExpressionMinute>("$minute", expCtx) {}
- Value evaluate(const Document& root) const final;
- const char* getOpName() const final;
+ Value evaluateDate(Date_t date, const TimeZone& timeZone) const final {
+ return Value(timeZone.dateParts(date).minute);
+ }
};
@@ -1137,13 +1305,14 @@ public:
};
-class ExpressionMonth final : public ExpressionFixedArity<ExpressionMonth, 1> {
+class ExpressionMonth final : public DateExpressionAcceptingTimeZone<ExpressionMonth> {
public:
explicit ExpressionMonth(const boost::intrusive_ptr<ExpressionContext>& expCtx)
- : ExpressionFixedArity<ExpressionMonth, 1>(expCtx) {}
+ : DateExpressionAcceptingTimeZone<ExpressionMonth>("$month", expCtx) {}
- Value evaluate(const Document& root) const final;
- const char* getOpName() const final;
+ Value evaluateDate(Date_t date, const TimeZone& timeZone) const final {
+ return Value(timeZone.dateParts(date).month);
+ }
};
@@ -1272,13 +1441,14 @@ private:
};
-class ExpressionSecond final : public ExpressionFixedArity<ExpressionSecond, 1> {
+class ExpressionSecond final : public DateExpressionAcceptingTimeZone<ExpressionSecond> {
public:
explicit ExpressionSecond(const boost::intrusive_ptr<ExpressionContext>& expCtx)
- : ExpressionFixedArity<ExpressionSecond, 1>(expCtx) {}
+ : DateExpressionAcceptingTimeZone<ExpressionSecond>("$second", expCtx) {}
- Value evaluate(const Document& root) const final;
- const char* getOpName() const final;
+ Value evaluateDate(Date_t date, const TimeZone& timeZone) const final {
+ return Value(timeZone.dateParts(date).second);
+ }
};
@@ -1537,53 +1707,59 @@ public:
};
-class ExpressionWeek final : public ExpressionFixedArity<ExpressionWeek, 1> {
+class ExpressionWeek final : public DateExpressionAcceptingTimeZone<ExpressionWeek> {
public:
explicit ExpressionWeek(const boost::intrusive_ptr<ExpressionContext>& expCtx)
- : ExpressionFixedArity<ExpressionWeek, 1>(expCtx) {}
+ : DateExpressionAcceptingTimeZone<ExpressionWeek>("$week"_sd, expCtx) {}
- Value evaluate(const Document& root) const final;
- const char* getOpName() const final;
+ Value evaluateDate(Date_t date, const TimeZone& timeZone) const final {
+ return Value(timeZone.week(date));
+ }
};
-class ExpressionIsoWeekYear final : public ExpressionFixedArity<ExpressionIsoWeekYear, 1> {
+class ExpressionIsoWeekYear final : public DateExpressionAcceptingTimeZone<ExpressionIsoWeekYear> {
public:
explicit ExpressionIsoWeekYear(const boost::intrusive_ptr<ExpressionContext>& expCtx)
- : ExpressionFixedArity<ExpressionIsoWeekYear, 1>(expCtx) {}
+ : DateExpressionAcceptingTimeZone<ExpressionIsoWeekYear>("$isoWeekYear"_sd, expCtx) {}
- Value evaluate(const Document& root) const final;
- const char* getOpName() const final;
+ Value evaluateDate(Date_t date, const TimeZone& timeZone) const final {
+ return Value(timeZone.isoYear(date));
+ }
};
-class ExpressionIsoDayOfWeek final : public ExpressionFixedArity<ExpressionIsoDayOfWeek, 1> {
+class ExpressionIsoDayOfWeek final
+ : public DateExpressionAcceptingTimeZone<ExpressionIsoDayOfWeek> {
public:
explicit ExpressionIsoDayOfWeek(const boost::intrusive_ptr<ExpressionContext>& expCtx)
- : ExpressionFixedArity<ExpressionIsoDayOfWeek, 1>(expCtx) {}
+ : DateExpressionAcceptingTimeZone<ExpressionIsoDayOfWeek>("$isoDayOfWeek"_sd, expCtx) {}
- Value evaluate(const Document& root) const final;
- const char* getOpName() const final;
+ Value evaluateDate(Date_t date, const TimeZone& timeZone) const final {
+ return Value(timeZone.isoDayOfWeek(date));
+ }
};
-class ExpressionIsoWeek final : public ExpressionFixedArity<ExpressionIsoWeek, 1> {
+class ExpressionIsoWeek final : public DateExpressionAcceptingTimeZone<ExpressionIsoWeek> {
public:
explicit ExpressionIsoWeek(const boost::intrusive_ptr<ExpressionContext>& expCtx)
- : ExpressionFixedArity<ExpressionIsoWeek, 1>(expCtx) {}
+ : DateExpressionAcceptingTimeZone<ExpressionIsoWeek>("$isoWeek", expCtx) {}
- Value evaluate(const Document& root) const final;
- const char* getOpName() const final;
+ Value evaluateDate(Date_t date, const TimeZone& timeZone) const final {
+ return Value(timeZone.isoWeek(date));
+ }
};
-class ExpressionYear final : public ExpressionFixedArity<ExpressionYear, 1> {
+class ExpressionYear final : public DateExpressionAcceptingTimeZone<ExpressionYear> {
public:
explicit ExpressionYear(const boost::intrusive_ptr<ExpressionContext>& expCtx)
- : ExpressionFixedArity<ExpressionYear, 1>(expCtx) {}
+ : DateExpressionAcceptingTimeZone<ExpressionYear>("$year", expCtx) {}
- Value evaluate(const Document& root) const final;
- const char* getOpName() const final;
+ Value evaluateDate(Date_t date, const TimeZone& timeZone) const final {
+ return Value(timeZone.dateParts(date).year);
+ }
};
diff --git a/src/mongo/db/pipeline/expression_test.cpp b/src/mongo/db/pipeline/expression_test.cpp
index 0bb9a2e7c7b..eabb8208be7 100644
--- a/src/mongo/db/pipeline/expression_test.cpp
+++ b/src/mongo/db/pipeline/expression_test.cpp
@@ -4566,6 +4566,314 @@ TEST_F(ExpressionDateToPartsTest, OptimizesToConstantIfAllInputsAreConstant) {
} // namespace ExpressionDateToPartsTest
+namespace DateExpressionsTest {
+
+std::vector<StringData> dateExpressions = {"$year"_sd,
+ "$isoWeekYear"_sd,
+ "$month"_sd,
+ "$dayOfMonth"_sd,
+ "$hour"_sd,
+ "$minute"_sd,
+ "$second"_sd,
+ "$millisecond"_sd,
+ "$week"_sd,
+ "$isoWeek"_sd,
+ "$dayOfYear"_sd};
+
+// This provides access to an ExpressionContext that has a valid ServiceContext with a
+// TimeZoneDatabase via getExpCtx(), but we'll use a different name for this test suite.
+using DateExpressionTest = AggregationContextFixture;
+
+TEST_F(DateExpressionTest, ParsingAcceptsAllFormats) {
+ auto expCtx = getExpCtx();
+ for (auto&& expName : dateExpressions) {
+ auto possibleSyntaxes = {
+ // Single argument.
+ BSON(expName << Date_t{}),
+ BSON(expName << "$date"),
+ BSON(expName << BSON("$add" << BSON_ARRAY(Date_t{} << 1000))),
+ // Single argument wrapped in an array.
+ BSON(expName << BSON_ARRAY("$date")),
+ BSON(expName << BSON_ARRAY(Date_t{})),
+ BSON(expName << BSON_ARRAY(BSON("$add" << BSON_ARRAY(Date_t{} << 1000)))),
+ // Object literal syntax.
+ BSON(expName << BSON("date" << Date_t{})),
+ BSON(expName << BSON("date"
+ << "$date")),
+ BSON(expName << BSON("date" << BSON("$add" << BSON_ARRAY("$date" << 1000)))),
+ BSON(expName << BSON("date" << Date_t{} << "timezone"
+ << "Europe/London")),
+ BSON(expName << BSON("date" << Date_t{} << "timezone"
+ << "$tz"))};
+ for (auto&& syntax : possibleSyntaxes) {
+ Expression::parseExpression(expCtx, syntax, expCtx->variablesParseState);
+ }
+ }
+}
+
+TEST_F(DateExpressionTest, ParsingRejectsUnrecognizedFieldsInObjectSpecification) {
+ auto expCtx = getExpCtx();
+ for (auto&& expName : dateExpressions) {
+ BSONObj spec = BSON(expName << BSON("date" << Date_t{} << "timezone"
+ << "Europe/London"
+ << "extra"
+ << 4));
+ ASSERT_THROWS_CODE(Expression::parseExpression(expCtx, spec, expCtx->variablesParseState),
+ UserException,
+ 40535);
+ }
+}
+
+TEST_F(DateExpressionTest, RejectsEmptyArray) {
+ auto expCtx = getExpCtx();
+ for (auto&& expName : dateExpressions) {
+ BSONObj spec = BSON(expName << BSONArray());
+ // It will parse as an ExpressionArray, and fail at runtime.
+ ASSERT_THROWS_CODE(Expression::parseExpression(expCtx, spec, expCtx->variablesParseState),
+ UserException,
+ 40536);
+ }
+}
+
+TEST_F(DateExpressionTest, RejectsArraysWithMoreThanOneElement) {
+ auto expCtx = getExpCtx();
+ for (auto&& expName : dateExpressions) {
+ BSONObj spec = BSON(expName << BSON_ARRAY("$date"
+ << "$tz"));
+ // It will parse as an ExpressionArray, and fail at runtime.
+ ASSERT_THROWS_CODE(Expression::parseExpression(expCtx, spec, expCtx->variablesParseState),
+ UserException,
+ 40536);
+ }
+}
+
+TEST_F(DateExpressionTest, RejectsArraysWithinObjectSpecification) {
+ auto expCtx = getExpCtx();
+ for (auto&& expName : dateExpressions) {
+ BSONObj spec = BSON(expName << BSON("date" << BSON_ARRAY(Date_t{}) << "timezone"
+ << "Europe/London"));
+ // It will parse as an ExpressionArray, and fail at runtime.
+ auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ auto contextDoc = Document{{"_id", 0}};
+ ASSERT_THROWS_CODE(dateExp->evaluate(contextDoc), UserException, 16006);
+
+ // Test that it rejects an array for the timezone option.
+ spec =
+ BSON(expName << BSON("date" << Date_t{} << "timezone" << BSON_ARRAY("Europe/London")));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ contextDoc = Document{{"_id", 0}};
+ ASSERT_THROWS_CODE(dateExp->evaluate(contextDoc), UserException, 40533);
+ }
+}
+
+TEST_F(DateExpressionTest, RejectsTypesThatCannotCoerceToDate) {
+ auto expCtx = getExpCtx();
+ for (auto&& expName : dateExpressions) {
+ BSONObj spec = BSON(expName << "$stringField");
+ auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ auto contextDoc = Document{{"stringField", "string"_sd}};
+ ASSERT_THROWS_CODE(dateExp->evaluate(contextDoc), UserException, 16006);
+ }
+}
+
+TEST_F(DateExpressionTest, AcceptsObjectIds) {
+ auto expCtx = getExpCtx();
+ for (auto&& expName : dateExpressions) {
+ BSONObj spec = BSON(expName << "$oid");
+ auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ auto contextDoc = Document{{"oid", OID::gen()}};
+ dateExp->evaluate(contextDoc); // Should not throw.
+ }
+}
+
+TEST_F(DateExpressionTest, AcceptsTimestamps) {
+ auto expCtx = getExpCtx();
+ for (auto&& expName : dateExpressions) {
+ BSONObj spec = BSON(expName << "$ts");
+ auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ auto contextDoc = Document{{"ts", Timestamp{Date_t{}}}};
+ dateExp->evaluate(contextDoc); // Should not throw.
+ }
+}
+
+TEST_F(DateExpressionTest, RejectsNonStringTimezone) {
+ auto expCtx = getExpCtx();
+ for (auto&& expName : dateExpressions) {
+ BSONObj spec = BSON(expName << BSON("date" << Date_t{} << "timezone"
+ << "$intField"));
+ auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ auto contextDoc = Document{{"intField", 4}};
+ ASSERT_THROWS_CODE(dateExp->evaluate(contextDoc), UserException, 40533);
+ }
+}
+
+TEST_F(DateExpressionTest, RejectsUnrecognizedTimeZoneSpecification) {
+ auto expCtx = getExpCtx();
+ for (auto&& expName : dateExpressions) {
+ BSONObj spec = BSON(expName << BSON("date" << Date_t{} << "timezone"
+ << "UNRECOGNIZED!"));
+ auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ auto contextDoc = Document{{"_id", 0}};
+ ASSERT_THROWS_CODE(dateExp->evaluate(contextDoc), UserException, 40485);
+ }
+}
+
+TEST_F(DateExpressionTest, SerializesToObjectSyntax) {
+ auto expCtx = getExpCtx();
+ for (auto&& expName : dateExpressions) {
+ // Test that it serializes to the full format if given an object specification.
+ BSONObj spec = BSON(expName << BSON("date" << Date_t{} << "timezone"
+ << "Europe/London"));
+ auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ auto expectedSerialization =
+ Value(Document{{expName,
+ Document{{"date", Document{{"$const", Date_t{}}}},
+ {"timezone", Document{{"$const", "Europe/London"_sd}}}}}});
+ ASSERT_VALUE_EQ(dateExp->serialize(true), expectedSerialization);
+ ASSERT_VALUE_EQ(dateExp->serialize(false), expectedSerialization);
+
+ // Test that it serializes to the full format if given a date.
+ spec = BSON(expName << Date_t{});
+ expectedSerialization =
+ Value(Document{{expName, Document{{"date", Document{{"$const", Date_t{}}}}}}});
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_EQ(dateExp->serialize(true), expectedSerialization);
+ ASSERT_VALUE_EQ(dateExp->serialize(false), expectedSerialization);
+
+ // Test that it serializes to the full format if given a date within an array.
+ spec = BSON(expName << BSON_ARRAY(Date_t{}));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_EQ(dateExp->serialize(true), expectedSerialization);
+ ASSERT_VALUE_EQ(dateExp->serialize(false), expectedSerialization);
+ }
+}
+
+TEST_F(DateExpressionTest, OptimizesToConstantIfAllInputsAreConstant) {
+ auto expCtx = getExpCtx();
+ for (auto&& expName : dateExpressions) {
+ // Test that it becomes a constant if only date is provided, and it is constant.
+ auto spec = BSON(expName << BSON("date" << Date_t{}));
+ auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT(dynamic_cast<ExpressionConstant*>(dateExp->optimize().get()));
+
+ // Test that it becomes a constant if both date and timezone are provided, and are both
+ // constants.
+ spec = BSON(expName << BSON("date" << Date_t{} << "timezone"
+ << "Europe/London"));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT(dynamic_cast<ExpressionConstant*>(dateExp->optimize().get()));
+
+ // Test that it becomes a constant if both date and timezone are provided, and are both
+ // expressions which evaluate to constants.
+ spec = BSON(expName << BSON("date" << BSON("$add" << BSON_ARRAY(Date_t{} << 1000))
+ << "timezone"
+ << BSON("$concat" << BSON_ARRAY("Europe"
+ << "/"
+ << "London"))));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT(dynamic_cast<ExpressionConstant*>(dateExp->optimize().get()));
+
+ // Test that it does *not* become a constant if both date and timezone are provided, but
+ // date is not a constant.
+ spec = BSON(expName << BSON("date"
+ << "$date"
+ << "timezone"
+ << "Europe/London"));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_FALSE(dynamic_cast<ExpressionConstant*>(dateExp->optimize().get()));
+
+ // Test that it does *not* become a constant if both date and timezone are provided, but
+ // timezone is not a constant.
+ spec = BSON(expName << BSON("date" << Date_t{} << "timezone"
+ << "$tz"));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_FALSE(dynamic_cast<ExpressionConstant*>(dateExp->optimize().get()));
+ }
+}
+
+TEST_F(DateExpressionTest, DoesRespectTimeZone) {
+ // Make sure they each successfully evaluate with a different TimeZone.
+ auto expCtx = getExpCtx();
+ for (auto&& expName : dateExpressions) {
+ auto spec = BSON(expName << BSON("date" << Date_t{} << "timezone"
+ << "America/New_York"));
+ auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ auto contextDoc = Document{{"_id", 0}};
+ dateExp->evaluate(contextDoc); // Should not throw.
+ }
+
+ // Make sure the time zone is used during evaluation.
+ auto date = Date_t::fromMillisSinceEpoch(1496777923000LL); // 2017-06-06T19:38:43:234Z.
+ auto specWithoutTimezone = BSON("$hour" << BSON("date" << date));
+ auto hourWithoutTimezone =
+ Expression::parseExpression(expCtx, specWithoutTimezone, expCtx->variablesParseState)
+ ->evaluate({});
+ ASSERT_VALUE_EQ(hourWithoutTimezone, Value(19));
+
+ auto specWithTimezone = BSON("$hour" << BSON("date" << date << "timezone"
+ << "America/New_York"));
+ auto hourWithTimezone =
+ Expression::parseExpression(expCtx, specWithTimezone, expCtx->variablesParseState)
+ ->evaluate({});
+ ASSERT_VALUE_EQ(hourWithTimezone, Value(15));
+}
+
+TEST_F(DateExpressionTest, DoesResultInNullIfGivenNullishInput) {
+ // Make sure they each successfully evaluate with a different TimeZone.
+ auto expCtx = getExpCtx();
+ for (auto&& expName : dateExpressions) {
+ auto contextDoc = Document{{"_id", 0}};
+
+ // Test that the expression results in null if the date is nullish and the timezone is not
+ // specified.
+ auto spec = BSON(expName << BSON("date"
+ << "$missing"));
+ auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc));
+
+ spec = BSON(expName << BSON("date" << BSONNULL));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc));
+
+ spec = BSON(expName << BSON("date" << BSONUndefined));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc));
+
+ // Test that the expression results in null if the date is present but the timezone is
+ // nullish.
+ spec = BSON(expName << BSON("date" << Date_t{} << "timezone"
+ << "$missing"));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc));
+
+ spec = BSON(expName << BSON("date" << Date_t{} << "timezone" << BSONNULL));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc));
+
+ spec = BSON(expName << BSON("date" << Date_t{} << "timezone" << BSONUndefined));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc));
+
+ // Test that the expression results in null if the date and timezone both nullish.
+ spec = BSON(expName << BSON("date"
+ << "$missing"
+ << "timezone"
+ << BSONUndefined));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc));
+
+ // Test that the expression results in null if the date is nullish and timezone is present.
+ spec = BSON(expName << BSON("date"
+ << "$missing"
+ << "timezone"
+ << "Europe/London"));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(contextDoc));
+ }
+}
+
+} // namespace DateExpressionsTest
+
class All : public Suite {
public:
All() : Suite("expression") {}
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 503e9cc3645..6601d7f572e 100644
--- a/src/mongo/db/query/datetime/date_time_support_test.cpp
+++ b/src/mongo/db/query/datetime/date_time_support_test.cpp
@@ -36,6 +36,21 @@
namespace mongo {
namespace {
+const TimeZoneDatabase kDefaultTimeZoneDatabase{};
+
+TEST(GetTimeZone, DoesReturnKnownTimeZone) {
+ // Just asserting that these do not throw exceptions.
+ kDefaultTimeZoneDatabase.getTimeZone("UTC");
+ kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+ kDefaultTimeZoneDatabase.getTimeZone("Australia/Sydney");
+}
+
+TEST(GetTimeZone, DoesNotReturnUnknownTimeZone) {
+ ASSERT_THROWS_CODE(kDefaultTimeZoneDatabase.getTimeZone("The moon"), UserException, 40485);
+ ASSERT_THROWS_CODE(kDefaultTimeZoneDatabase.getTimeZone("xyz"), UserException, 40485);
+ ASSERT_THROWS_CODE(kDefaultTimeZoneDatabase.getTimeZone("Jupiter"), UserException, 40485);
+}
+
TEST(UTCTimeBeforeEpoch, DoesExtractDateParts) {
// Dec 30, 1969 13:42:23:211
auto date = Date_t::fromMillisSinceEpoch(-123456789LL);
@@ -49,6 +64,21 @@ TEST(UTCTimeBeforeEpoch, DoesExtractDateParts) {
ASSERT_EQ(dateParts.millisecond, 211);
}
+TEST(NewYorkTimeBeforeEpoch, DoesExtractDateParts) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // 1969-12-30T13:42:23.211Z.
+ auto date = Date_t::fromMillisSinceEpoch(-123456789LL);
+ auto dateParts = newYorkZone.dateParts(date);
+ ASSERT_EQ(dateParts.year, 1969);
+ ASSERT_EQ(dateParts.month, 12);
+ ASSERT_EQ(dateParts.dayOfMonth, 30);
+ ASSERT_EQ(dateParts.hour, 8);
+ 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);
@@ -61,6 +91,22 @@ TEST(UTCTimeBeforeEpoch, DoesComputeISOYear) {
ASSERT_EQ(TimeZoneDatabase::utcZone().isoYear(date), 1965);
}
+TEST(NewYorkTimeBeforeEpoch, DoesComputeISOYear) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // Sunday, December 28, 1969.
+ auto date = Date_t::fromMillisSinceEpoch(-345600000LL);
+ ASSERT_EQ(newYorkZone.isoYear(date), 1969);
+
+ // 1969-12-30T13:42:23.211Z (Tuesday), part of the following year.
+ date = Date_t::fromMillisSinceEpoch(-123456000LL);
+ ASSERT_EQ(newYorkZone.isoYear(date), 1970);
+
+ // 1966-01-01T00:00:00.000Z (Saturday), part of the previous year.
+ date = Date_t::fromMillisSinceEpoch(-126230400000LL);
+ ASSERT_EQ(newYorkZone.isoYear(date), 1965);
+}
+
TEST(UTCTimeBeforeEpoch, DoesComputeDayOfWeek) {
// Sunday, December 28, 1969.
auto date = Date_t::fromMillisSinceEpoch(-345600000LL);
@@ -73,6 +119,19 @@ TEST(UTCTimeBeforeEpoch, DoesComputeDayOfWeek) {
ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfWeek(date), 7);
}
+TEST(NewYorkTimeBeforeEpoch, DoesComputeDayOfWeek) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // 1969-12-28T00:00:00.000Z (Sunday).
+ auto date = Date_t::fromMillisSinceEpoch(-345600000LL);
+ // Part of the previous day (Saturday) in New York.
+ ASSERT_EQ(newYorkZone.dayOfWeek(date), 7);
+
+ // 1969-12-30T13:42:23.211Z (Tuesday).
+ date = Date_t::fromMillisSinceEpoch(-123456000LL);
+ ASSERT_EQ(newYorkZone.dayOfWeek(date), 3);
+}
+
TEST(UTCTimeBeforeEpoch, DoesComputeISODayOfWeek) {
// Sunday, December 28, 1969.
auto date = Date_t::fromMillisSinceEpoch(-345600000LL);
@@ -85,6 +144,19 @@ TEST(UTCTimeBeforeEpoch, DoesComputeISODayOfWeek) {
ASSERT_EQ(TimeZoneDatabase::utcZone().isoDayOfWeek(date), 6);
}
+TEST(NewYorkTimeBeforeEpoch, DoesComputeISODayOfWeek) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // 1969-12-28T00:00:00.000Z (Sunday).
+ auto date = Date_t::fromMillisSinceEpoch(-345600000LL);
+ // Part of the previous day (Saturday) in New York.
+ ASSERT_EQ(newYorkZone.isoDayOfWeek(date), 6);
+
+ // 1969-12-30T13:42:23.211Z (Tuesday).
+ date = Date_t::fromMillisSinceEpoch(-123456000LL);
+ ASSERT_EQ(newYorkZone.isoDayOfWeek(date), 2);
+}
+
TEST(UTCTimeBeforeEpoch, DoesComputeDayOfYear) {
// December 30, 1969.
auto date = Date_t::fromMillisSinceEpoch(-123456000LL);
@@ -117,6 +189,41 @@ TEST(UTCTimeBeforeEpoch, DoesComputeDayOfYear) {
ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 365);
}
+TEST(NewYorkTimeBeforeEpoch, DoesComputeDayOfYear) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // 1969-12-28T13:42:24.000Z (Sunday).
+ auto date = Date_t::fromMillisSinceEpoch(-123456000LL);
+ ASSERT_EQ(newYorkZone.dayOfYear(date), 364);
+
+ // 1966-01-01T00:00:00.000Z (Saturday).
+ date = Date_t::fromMillisSinceEpoch(-126230400000LL);
+ ASSERT_EQ(newYorkZone.dayOfYear(date), 365);
+
+ // 1960-02-28T00:00:00.000Z (leap year).
+ date = Date_t::fromMillisSinceEpoch(-310608000000LL);
+ ASSERT_EQ(newYorkZone.dayOfYear(date), 58);
+ // 1960-02-29T00:00:00.000Z.
+ date = Date_t::fromMillisSinceEpoch(-310521600000LL);
+ ASSERT_EQ(newYorkZone.dayOfYear(date), 59);
+ // 1960-01-01T00:00:00.000Z.
+ date = Date_t::fromMillisSinceEpoch(-310435200000LL);
+ ASSERT_EQ(newYorkZone.dayOfYear(date), 60);
+ // 1960-12-31T00:00:00.000Z.
+ date = Date_t::fromMillisSinceEpoch(-284083200000LL);
+ ASSERT_EQ(newYorkZone.dayOfYear(date), 365);
+
+ // 1900-02-28T00:00:00.000Z (not leap year).
+ date = Date_t::fromMillisSinceEpoch(-2203977600000LL);
+ ASSERT_EQ(newYorkZone.dayOfYear(date), 58);
+ // 1900-03-01T00:00:00.000Z.
+ date = Date_t::fromMillisSinceEpoch(-2203891200000LL);
+ ASSERT_EQ(newYorkZone.dayOfYear(date), 59);
+ // 1900-12-31T00:00:00.000Z.
+ date = Date_t::fromMillisSinceEpoch(-2177539200000LL);
+ ASSERT_EQ(newYorkZone.dayOfYear(date), 364);
+}
+
TEST(UTCTimeBeforeEpoch, DoesComputeWeek) {
// Sunday, December 28, 1969.
auto date = Date_t::fromMillisSinceEpoch(-345600000LL);
@@ -126,6 +233,18 @@ TEST(UTCTimeBeforeEpoch, DoesComputeWeek) {
ASSERT_EQ(TimeZoneDatabase::utcZone().week(date), 0);
}
+TEST(NewYorkTimeBeforeEpoch, DoesComputeWeek) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // 1969-12-28T00:00:00.000Z (Sunday).
+ auto date = Date_t::fromMillisSinceEpoch(-345600000LL);
+ ASSERT_EQ(newYorkZone.week(date), 51);
+
+ // 1966-01-01T00:00:00.000Z (Saturday).
+ date = Date_t::fromMillisSinceEpoch(-126230400000LL);
+ ASSERT_EQ(newYorkZone.week(date), 52);
+}
+
TEST(UTCTimeBeforeEpoch, DoesComputeISOWeek) {
// Sunday, December 28, 1969.
auto date = Date_t::fromMillisSinceEpoch(-345600000LL);
@@ -144,6 +263,28 @@ TEST(UTCTimeBeforeEpoch, DoesComputeISOWeek) {
ASSERT_EQ(TimeZoneDatabase::utcZone().isoWeek(date), 53);
}
+TEST(NewYorkTimeBeforeEpoch, DoesComputeISOWeek) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // 1969-12-28T00:00:00.000Z (Sunday).
+ auto date = Date_t::fromMillisSinceEpoch(-345600000LL);
+ ASSERT_EQ(newYorkZone.isoWeek(date), 52);
+
+ // 1969-12-30T00:00:00.000Z (Tuesday).
+ date = Date_t::fromMillisSinceEpoch(-123456000LL);
+ ASSERT_EQ(newYorkZone.isoWeek(date), 1);
+
+ // 1966-01-01T00:00:00.000Z (Saturday), part of previous year.
+ date = Date_t::fromMillisSinceEpoch(-126230400000LL);
+ ASSERT_EQ(newYorkZone.isoWeek(date), 52);
+ // 1959-12-29T00:00:00.000Z (Tuesday).
+ date = Date_t::fromMillisSinceEpoch(-315878400000LL);
+ ASSERT_EQ(newYorkZone.isoWeek(date), 53);
+ // 1960-01-02T00:00:00.000Z (Saturday), part of previous year.
+ date = Date_t::fromMillisSinceEpoch(-315532800000LL);
+ ASSERT_EQ(newYorkZone.isoWeek(date), 53);
+}
+
TEST(UTCTimeBeforeEpoch, DoesFormatDate) {
// Tuesday, Dec 30, 1969 13:42:23:211
auto date = Date_t::fromMillisSinceEpoch(-123456789LL);
@@ -155,6 +296,19 @@ TEST(UTCTimeBeforeEpoch, DoesFormatDate) {
"isoWeek: 01, isoDayOfWeek: 2, percent: %");
}
+TEST(NewYorkTimeBeforeEpoch, DoesFormatDate) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // Tuesday, Dec 30, 1969 13:42:23:211
+ auto date = Date_t::fromMillisSinceEpoch(-123456789LL);
+ ASSERT_EQ(newYorkZone.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 08: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);
@@ -169,6 +323,22 @@ TEST(UTCTimeBeforeEpoch, DoesOutputFormatDate) {
"isoWeek: 01, isoDayOfWeek: 2, percent: %");
}
+TEST(NewYorkTimeBeforeEpoch, DoesOutputFormatDate) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // Tuesday, Dec 30, 1969 13:42:23:211
+ auto date = Date_t::fromMillisSinceEpoch(-123456789LL);
+ std::ostringstream os;
+ newYorkZone.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 08: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);
@@ -182,42 +352,105 @@ TEST(UTCTimeAtEpoch, DoesExtractDateParts) {
ASSERT_EQ(dateParts.millisecond, 0);
}
+TEST(NewYorkTimeAtEpoch, DoesExtractDateParts) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // Jan 1, 1970 00:00:00:000
+ auto date = Date_t::fromMillisSinceEpoch(0);
+ auto dateParts = newYorkZone.dateParts(date);
+ ASSERT_EQ(dateParts.year, 1969);
+ ASSERT_EQ(dateParts.month, 12);
+ ASSERT_EQ(dateParts.dayOfMonth, 31);
+ ASSERT_EQ(dateParts.hour, 19);
+ 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(NewYorkTimeAtEpoch, DoesComputeISOYear) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // 1970-1-1T00:00:00.000Z (Thursday)
+ auto date = Date_t::fromMillisSinceEpoch(0);
+ ASSERT_EQ(newYorkZone.isoYear(date), 1970); // The Wednesday is still considered part of 1970.
+}
+
TEST(UTCTimeAtEpoch, DoesComputeDayOfWeek) {
// Thursday, January 1, 1970.
auto date = Date_t::fromMillisSinceEpoch(0);
ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfWeek(date), 5);
}
+TEST(NewYorkTimeAtEpoch, DoesComputeDayOfWeek) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // 1970-1-1T00:00:00.000Z (Thursday)
+ auto date = Date_t::fromMillisSinceEpoch(0);
+ ASSERT_EQ(newYorkZone.dayOfWeek(date), 4); // The Wednesday before.
+}
+
TEST(UTCTimeAtEpoch, DoesComputeISODayOfWeek) {
// Thursday, January 1, 1970.
auto date = Date_t::fromMillisSinceEpoch(0);
ASSERT_EQ(TimeZoneDatabase::utcZone().isoDayOfWeek(date), 4);
}
+TEST(NewYorkTimeAtEpoch, DoesComputeISODayOfWeek) {
+ // 1970-1-1T00:00:00.000Z (Thursday)
+ auto date = Date_t::fromMillisSinceEpoch(0);
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+ ASSERT_EQ(newYorkZone.isoDayOfWeek(date), 3); // The Wednesday before.
+}
+
TEST(UTCTimeAtEpoch, DoesComputeDayOfYear) {
// Thursday, January 1, 1970.
auto date = Date_t::fromMillisSinceEpoch(0);
ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 1);
}
+TEST(NewYorkTimeAtEpoch, DoesComputeDayOfYear) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // 1970-1-1T00:00:00.000Z
+ auto date = Date_t::fromMillisSinceEpoch(0);
+ ASSERT_EQ(newYorkZone.dayOfYear(date), 365);
+}
+
TEST(UTCTimeAtEpoch, DoesComputeWeek) {
// Thursday, January 1, 1970.
auto date = Date_t::fromMillisSinceEpoch(0);
ASSERT_EQ(TimeZoneDatabase::utcZone().week(date), 0);
}
+TEST(NewYorkTimeAtEpoch, DoesComputeWeek) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // 1970-1-1T00:00:00.000Z
+ auto date = Date_t::fromMillisSinceEpoch(0);
+ ASSERT_EQ(newYorkZone.week(date), 52);
+}
+
TEST(UTCTimeAtEpoch, DoesComputeISOWeek) {
// Thursday, January 1, 1970.
auto date = Date_t::fromMillisSinceEpoch(0);
ASSERT_EQ(TimeZoneDatabase::utcZone().isoWeek(date), 1);
}
+TEST(NewYorkTimeAtEpoch, DoesComputeISOWeek) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // Thu, Jan 1, 1970 00:00:00:000Z
+ auto date = Date_t::fromMillisSinceEpoch(0);
+ // This is Wednesday in New York, but that is still part of the first week.
+ ASSERT_EQ(newYorkZone.isoWeek(date), 1);
+}
+
TEST(UTCTimeAtEpoch, DoesFormatDate) {
// Thu, Jan 1, 1970 00:00:00:000
auto date = Date_t::fromMillisSinceEpoch(0);
@@ -229,6 +462,19 @@ TEST(UTCTimeAtEpoch, DoesFormatDate) {
"isoWeek: 01, isoDayOfWeek: 4, percent: %");
}
+TEST(NewYorkTimeAtEpoch, DoesFormatDate) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // Thu, Jan 1, 1970 00:00:00:000Z
+ auto date = Date_t::fromMillisSinceEpoch(0);
+ ASSERT_EQ(newYorkZone.formatDate("%Y/%m/%d %H:%M:%S:%L, dayOfYear: %j, "
+ "dayOfWeek: %w, week: %U, isoYear: %G, "
+ "isoWeek: %V, isoDayOfWeek: %u, percent: %%",
+ date),
+ "1969/12/31 19:00:00:000, dayOfYear: 365, dayOfWeek: 4, week: 52, isoYear: 1970, "
+ "isoWeek: 01, isoDayOfWeek: 3, percent: %");
+}
+
TEST(UTCTimeAtEpoch, DoesOutputFormatDate) {
auto date = Date_t::fromMillisSinceEpoch(0);
std::ostringstream os;
@@ -242,6 +488,20 @@ TEST(UTCTimeAtEpoch, DoesOutputFormatDate) {
"isoWeek: 01, isoDayOfWeek: 4, percent: %");
}
+TEST(NewYorkTimeAtEpoch, DoesOutputFormatDate) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+ auto date = Date_t::fromMillisSinceEpoch(0);
+ std::ostringstream os;
+ newYorkZone.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/31 19:00:00:000, dayOfYear: 365, dayOfWeek: 4, week: 52, isoYear: 1970, "
+ "isoWeek: 01, isoDayOfWeek: 3, percent: %");
+}
+
TEST(UTCTimeAfterEpoch, DoesExtractDateParts) {
// Jun 6, 2017 19:38:43:123.
auto date = Date_t::fromMillisSinceEpoch(1496777923123LL);
@@ -255,6 +515,21 @@ TEST(UTCTimeAfterEpoch, DoesExtractDateParts) {
ASSERT_EQ(dateParts.millisecond, 123);
}
+TEST(NewYorkTimeAfterEpoch, DoesExtractDateParts) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // Jun 6, 2017 19:38:43:123Z.
+ auto date = Date_t::fromMillisSinceEpoch(1496777923123LL);
+ auto dateParts = newYorkZone.dateParts(date);
+ ASSERT_EQ(dateParts.year, 2017);
+ ASSERT_EQ(dateParts.month, 6);
+ ASSERT_EQ(dateParts.dayOfMonth, 6);
+ ASSERT_EQ(dateParts.hour, 15);
+ 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);
@@ -270,6 +545,30 @@ TEST(UTCTimeAfterEpoch, DoesComputeISOYear) {
ASSERT_EQ(TimeZoneDatabase::utcZone().isoYear(date), 2008);
}
+TEST(NewYorkTimeAfterEpoch, DoesComputeISOYear) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // 2017-06-06T19:38:43:123Z (Tuesday).
+ auto date = Date_t::fromMillisSinceEpoch(1496777923000LL);
+ ASSERT_EQ(newYorkZone.isoYear(date), 2017);
+
+ // 2005-01-01T00:00:00.000Z (Saturday), part of 2004.
+ date = Date_t::fromMillisSinceEpoch(1104537600000LL);
+ ASSERT_EQ(newYorkZone.isoYear(date), 2004);
+
+ // 2007-01-01T00:00:00.000Z (Monday), part of 2007.
+ date = Date_t::fromMillisSinceEpoch(1167609600000LL);
+ // ISO weeks are Mon-Sun, so this is part of the previous week in New York, so part of the
+ // previous year, 2006.
+ ASSERT_EQ(newYorkZone.isoYear(date), 2006);
+
+ // 2007-12-31T00:00:00.000Z (Monday), part of 2007.
+ date = Date_t::fromMillisSinceEpoch(1199059200000LL);
+ // ISO weeks are Mon-Sun, so this is part of the previous week in New York, so part of the
+ // previous year, 2007.
+ ASSERT_EQ(newYorkZone.isoYear(date), 2007);
+}
+
TEST(UTCTimeAfterEpoch, DoesComputeDayOfWeek) {
// Tuesday, June 6, 2017.
auto date = Date_t::fromMillisSinceEpoch(1496777923000LL);
@@ -282,6 +581,18 @@ TEST(UTCTimeAfterEpoch, DoesComputeDayOfWeek) {
ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfWeek(date), 2);
}
+TEST(NewYorkTimeAfterEpoch, DoesComputeDayOfWeek) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // 2017-06-06T19:38:43.123Z (Tuesday).
+ auto date = Date_t::fromMillisSinceEpoch(1496777923000LL);
+ ASSERT_EQ(newYorkZone.dayOfWeek(date), 3);
+
+ // 2005-01-01T00:00:00.000Z (Saturday).
+ date = Date_t::fromMillisSinceEpoch(1104537600000LL);
+ ASSERT_EQ(newYorkZone.dayOfWeek(date), 6); // Part of the previous day in New York.
+}
+
TEST(UTCTimeAfterEpoch, DoesComputeISODayOfWeek) {
// Tuesday, June 6, 2017.
auto date = Date_t::fromMillisSinceEpoch(1496777923000LL);
@@ -294,6 +605,18 @@ TEST(UTCTimeAfterEpoch, DoesComputeISODayOfWeek) {
ASSERT_EQ(TimeZoneDatabase::utcZone().isoDayOfWeek(date), 1);
}
+TEST(NewYorkTimeAfterEpoch, DoesComputeISODayOfWeek) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // 2017-06-06T19:38:43.123Z (Tuesday).
+ auto date = Date_t::fromMillisSinceEpoch(1496777923000LL);
+ ASSERT_EQ(newYorkZone.isoDayOfWeek(date), 2);
+
+ // 2005-01-01T00:00:00.000Z (Saturday).
+ date = Date_t::fromMillisSinceEpoch(1104537600000LL);
+ ASSERT_EQ(newYorkZone.isoDayOfWeek(date), 5); // Part of the previous day in New York.
+}
+
TEST(UTCTimeAfterEpoch, DoesComputeDayOfYear) {
// June 6, 2017.
auto date = Date_t::fromMillisSinceEpoch(1496777923000LL);
@@ -325,6 +648,47 @@ TEST(UTCTimeAfterEpoch, DoesComputeDayOfYear) {
ASSERT_EQ(TimeZoneDatabase::utcZone().dayOfYear(date), 365);
}
+TEST(NewYorkTimeAfterEpoch, DoesComputeDayOfYear) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // 2017-06-06T19:38:43.123Z.
+ auto date = Date_t::fromMillisSinceEpoch(1496777923000LL);
+ ASSERT_EQ(newYorkZone.dayOfYear(date), 157);
+
+ // 2005-01-01T00:00:00.000Z, part of 2004 in New York, which was a leap year.
+ date = Date_t::fromMillisSinceEpoch(1104537600000LL);
+ ASSERT_EQ(newYorkZone.dayOfYear(date), 366);
+
+ // 2008-02-28T00:00:00.000Z (leap year).
+ date = Date_t::fromMillisSinceEpoch(1204156800000LL);
+ ASSERT_EQ(newYorkZone.dayOfYear(date), 58);
+ // 2008-02-29T00:00:00.000Z.
+ date = Date_t::fromMillisSinceEpoch(1204243200000LL);
+ ASSERT_EQ(newYorkZone.dayOfYear(date), 59);
+ // 2008-03-01T00:00:00.000Z.
+ date = Date_t::fromMillisSinceEpoch(1204329600000LL);
+ ASSERT_EQ(newYorkZone.dayOfYear(date), 60);
+ // 2008-12-31T00:00:00.000Z.
+ date = Date_t::fromMillisSinceEpoch(1230681600000LL);
+ ASSERT_EQ(newYorkZone.dayOfYear(date), 365);
+ // 2009-01-01T00:00:00.000Z, part of the previous year in New York.
+ date = Date_t::fromMillisSinceEpoch(1230768000000LL);
+ ASSERT_EQ(newYorkZone.dayOfYear(date), 366);
+
+ // 2001-02-28T00:00:00.000Z (not leap year).
+ date = Date_t::fromMillisSinceEpoch(983318400000LL);
+ ASSERT_EQ(newYorkZone.dayOfYear(date), 58);
+ // 2001-03-01T00:00:00.000Z.
+ date = Date_t::fromMillisSinceEpoch(983404800000LL);
+ ASSERT_EQ(newYorkZone.dayOfYear(date), 59);
+ // 2001-12-31T00:00:00.000Z.
+ date = Date_t::fromMillisSinceEpoch(1009756800000LL);
+ ASSERT_EQ(newYorkZone.dayOfYear(date), 364);
+ // 2002-01-01T00:00:00.000Z, part of the previous year in New York.
+ date = Date_t::fromMillisSinceEpoch(1009843200000LL);
+ ASSERT_EQ(newYorkZone.dayOfYear(date), 365);
+}
+
TEST(UTCTimeAfterEpoch, DoesComputeWeek) {
// Tuesday, June 6, 2017.
auto date = Date_t::fromMillisSinceEpoch(1496777923000LL);
@@ -337,6 +701,22 @@ TEST(UTCTimeAfterEpoch, DoesComputeWeek) {
ASSERT_EQ(TimeZoneDatabase::utcZone().week(date), 0);
}
+TEST(NewYorkTimeAfterEpoch, DoesComputeWeek) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // Tuesday, June 6, 2017.
+ auto date = Date_t::fromMillisSinceEpoch(1496777923000LL);
+ ASSERT_EQ(newYorkZone.week(date), 23);
+
+ // 2005-01-01T00:00:00.00Z (Saturday).
+ date = Date_t::fromMillisSinceEpoch(1104537600000LL);
+ ASSERT_EQ(newYorkZone.week(date), 52);
+
+ // 2007-01-01T00:00:00.00Z (Monday), the last Sunday of 2006 in New York.
+ date = Date_t::fromMillisSinceEpoch(1167609600000LL);
+ ASSERT_EQ(newYorkZone.week(date), 53);
+}
+
TEST(UTCTimeAfterEpoch, DoesComputeISOWeek) {
// Tuesday, June 6, 2017.
auto date = Date_t::fromMillisSinceEpoch(1496777923000LL);
@@ -349,6 +729,22 @@ TEST(UTCTimeAfterEpoch, DoesComputeISOWeek) {
ASSERT_EQ(TimeZoneDatabase::utcZone().isoWeek(date), 1);
}
+TEST(NewYorkTimeAfterEpoch, DoesComputeISOWeek) {
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+
+ // Tuesday, June 6, 2017.
+ auto date = Date_t::fromMillisSinceEpoch(1496777923000LL);
+ ASSERT_EQ(newYorkZone.isoWeek(date), 23);
+
+ // 2005-01-01T00:00:00.000Z (Saturday), part of 2004.
+ date = Date_t::fromMillisSinceEpoch(1104537600000LL);
+ ASSERT_EQ(newYorkZone.isoWeek(date), 53);
+
+ // 2007-01-01T00:00:00.000Z (Monday), part of 2006 in New York.
+ date = Date_t::fromMillisSinceEpoch(1167609600000LL);
+ ASSERT_EQ(newYorkZone.isoWeek(date), 52);
+}
+
TEST(UTCTimeAfterEpoch, DoesFormatDate) {
// Tue, Jun 6, 2017 19:38:43:234.
auto date = Date_t::fromMillisSinceEpoch(1496777923234LL);
@@ -360,6 +756,18 @@ TEST(UTCTimeAfterEpoch, DoesFormatDate) {
"isoWeek: 23, isoDayOfWeek: 2, percent: %");
}
+TEST(NewYorkTimeAfterEpoch, DoesFormatDate) {
+ // 2017-06-06T19:38:43:234Z (Tuesday).
+ auto date = Date_t::fromMillisSinceEpoch(1496777923234LL);
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+ ASSERT_EQ(newYorkZone.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 15: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);
@@ -374,6 +782,21 @@ TEST(UTCTimeAfterEpoch, DoesOutputFormatDate) {
"isoWeek: 23, isoDayOfWeek: 2, percent: %");
}
+TEST(NewYorkTimeAfterEpoch, DoesOutputFormatDate) {
+ // 2017-06-06T19:38:43:234Z (Tuesday).
+ auto date = Date_t::fromMillisSinceEpoch(1496777923234LL);
+ std::ostringstream os;
+ auto newYorkZone = kDefaultTimeZoneDatabase.getTimeZone("America/New_York");
+ newYorkZone.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 15: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);
}