From 7f4c886870713753f8e59302ee32974fbc27dbdf Mon Sep 17 00:00:00 2001 From: Alexey Spiridonov Date: Mon, 17 Mar 2014 15:59:25 -0700 Subject: [PATCH] Move commited bits out of folly Summary: Preparing to land the other diffs. No code changes here, just moving files around. Test Plan: fbconfig bistro/common/cron/test bistro/common/cron/utils/test ; fbmake runtests Reviewed By: yinwang@fb.com FB internal diff: D1225159 --- folly/experimental/cron/CrontabSelector.h | 273 ------------------ folly/experimental/cron/date_time_utils.cpp | 189 ------------ folly/experimental/cron/date_time_utils.h | 187 ------------ .../cron/test/test_crontab_selector.cpp | 156 ---------- .../cron/test/test_date_time_utils.cpp | 212 -------------- 5 files changed, 1017 deletions(-) delete mode 100644 folly/experimental/cron/CrontabSelector.h delete mode 100644 folly/experimental/cron/date_time_utils.cpp delete mode 100644 folly/experimental/cron/date_time_utils.h delete mode 100644 folly/experimental/cron/test/test_crontab_selector.cpp delete mode 100644 folly/experimental/cron/test/test_date_time_utils.cpp diff --git a/folly/experimental/cron/CrontabSelector.h b/folly/experimental/cron/CrontabSelector.h deleted file mode 100644 index e5fe0bf8..00000000 --- a/folly/experimental/cron/CrontabSelector.h +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright 2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include -#include -#include - -#include "folly/Conv.h" -#include "folly/dynamic.h" -#include "folly/Format.h" -#include "folly/json.h" - -// Add stateful Cron support for more robustness, see README for a design. - -namespace folly { namespace cron { - -/** - * A CrontabSelector is a wildcard for matching some periodic value (e.g. - * days of week, years, minutes, ...). This is a base class, so Cron itself - * uses a bunch of specialized descendants. - * - * The periodic values being matched are integers of varying signedness and - * bit-widths. - * - * CrontabSelector is a union type, representing either: - * - A list of one or more values to match. E.g. January & March. - * - A start, an end, and an interval. This range type does not wrap: - * starting from Tuesday with an interval of 6 is equivalent to only - * Tuesday. - * - * A selector knows its minimum and maximum permissible value. For example, - * a day-of-month is between 1 and 31, but real months may have fewer days, - * which is handled at a higher level -- the selector is meant to apply - * uniformly to all months. - * - * A selector also supports 3-character string-prefix aliases for the - * integer value. For example, "sun", "Sund", and "Sunday" would all map to - * 1, while "sAt" is 7. In the month type, "july" maps to 7, etc. Note - * that these strings are not localized -- specs are English-only, but this - * is a feature, since otherwise a Cron configuration might be ambiguous - * depending on the system locale. Also note that the day-of-week numbering - * is similarly unlocalized English, so the Russians who think Sunday == 7 - * will have to cope. - * - * Selectors are specified using JSON. Here are some month selectors: - * - * 5 // The value 5, or May - * "Apr" // The value 4, or April - * ["Jun", "Jul", 12] // June, July, or December - * {"interval": 1} // Match any of the 12 months - * {"start": "Aug", "end": 11} // August through November, inclusive - * {"interval": 2, "end": "Jul"} // January, March, May, or July - * - * We do not implement the traditional Cron syntax because it's hard to read - * and verify, and implies the presence of some Cron misfeatures that we do - * not support. A reasonable patch adding such parser would be accepted, - * however. - */ -template -class CrontabSelector { -public: - - CrontabSelector(const CrontabSelector&) = delete; // can neither copy - CrontabSelector& operator=(const CrontabSelector&) = delete; // nor assign - virtual ~CrontabSelector() {} - - /** - * Initialize the selector from a JSON object (see the class docblock). - */ - void loadDynamic(const folly::dynamic &d) { - if (initialized_) { // This restriction could be lifted if necessary. - throw std::runtime_error("loadDynamic cannot be called twice"); - } - switch (d.type()) { - case folly::dynamic::Type::INT64: - case folly::dynamic::Type::STRING: - sorted_values_.emplace_back(parseValue(d)); - break; - case folly::dynamic::Type::ARRAY: - for (const auto& val : d) { - sorted_values_.emplace_back(parseValue(val)); - } - // If somebody specifies [] for a selector, we have to silently - // accept it, since PHP's JSON library can morph {} into [], and {} - // must mean "default selector accepting all values." - break; - case folly::dynamic::Type::OBJECT: - for (const auto& pair : d.items()) { - // Interval is first so that it doesn't accept strings like "jan" - if (pair.first == "interval") { - interval_ = pair.second.asInt(); - if (interval_ < 1 || interval_ >= max_val_ - min_val_) { - throw std::runtime_error(folly::format( - "interval not in [1, {}]: {}", max_val_ - min_val_, interval_ - ).str()); - } - continue; - } - // For start & end, we are happy to accept string names - auto val = parseValue(pair.second); - if (pair.first == "start") { - start_ = val; - } else if (pair.first == "end") { - end_ = val; - } else { - throw std::runtime_error(folly::format( - "Unknown key: {}", pair.first - ).str()); - } - } - // If we got an empty object, no problem -- this selector will - // follow the default of "match everything". - break; - default: - throw std::runtime_error(folly::format( - "Bad type for crontab selector: {}", d.typeName() - ).str()); - } - std::sort(sorted_values_.begin(), sorted_values_.end()); - initialized_ = true; - } - - /** - * Returns the first t_match >= t such that t_match matches this selector. - * If no match is found, wraps around to the selector's first element, and - * sets the "carry" bool to true. Otherwise, that value is false. - * - * Note: no modular arithmetic happens here -- as soon as we exceed end_, - * we simply reset to start_. This is not the full story for - * day-of-month, so StandardCrontabItem has to do some extra work there. - * The simple "wrap to start" behavior is correct because with modular - * arithmetic a "week" selector starting on Tuesday with a stride of 6 - * would accept any day of the week, which is far more surprising than - * only accepting Tuesday. - */ - std::pair findFirstMatch(Num t) const { - if (!initialized_) { - throw std::runtime_error("Selector not initialized"); - } - - if (!sorted_values_.empty()) { - // If this were stateful, we could remember where the previous item was, - // but as is, we incur the log(n) search time every time. - auto i = - std::lower_bound(sorted_values_.begin(), sorted_values_.end(), t); - if (i == sorted_values_.end()) { // Wrap to the start - return std::make_pair(*sorted_values_.begin(), true); - } - return std::make_pair(*i, false); - } - - if (t < start_) { - return std::make_pair(start_, false); - } - int64_t next = t + (interval_ - (t - start_) % interval_) % interval_; - if (next > end_) { - return std::make_pair(start_, true); - } - return std::make_pair(next, false); - } - - bool isInitialized() const { return initialized_; } - - /** - * A compact string representation for unit tests or debugging, which sort - * of emulates standard cron syntax. This function is subject to change - * without notice -- send a patch with toDynamic() if you need to inspect - * the contents of a selector in production code. We will NOT fix your - * code if you are using this function. You have been warned. - */ - std::string getPrintable() const { - std::string s; - for (const auto &v : sorted_values_) { - folly::toAppend(v, ',', &s); - } - if (start_ != min_val_ || end_ != max_val_ || interval_ != 1) { - if (!sorted_values_.empty()) { - s[s.size() - 1] = '/'; - } - folly::toAppend(start_, '-', end_, ':', interval_, &s); - } else if (sorted_values_.empty()) { - folly::toAppend('*', &s); - } else { - s.pop_back(); - } - return s; - } - - Num getMin() const { return min_val_; } - Num getMax() const { return max_val_; } - -protected: - typedef std::unordered_map PrefixMap; - - CrontabSelector(Num min_val, Num max_val) - : initialized_(false), - start_(min_val), - end_(max_val), - interval_(1), - min_val_(min_val), - max_val_(max_val) {} - - /** - * Converts 3-letter string prefixes to numeric values, e.g. Jan=>1, Wed=>3 - */ - virtual const PrefixMap& getPrefixMap() const { return emptyPrefixMap_; } - -private: - Num parseValue(const folly::dynamic& d) { - Num res; - if (d.isInt()) { - res = d.asInt(); - } else if (d.isString()) { - // TODO: This prefix-matching could be more robust... we don't even - // check if the prefix map is populated with 3-character strings. - auto s = d.asString().substr(0, 3).toStdString(); - std::transform(s.begin(), s.end(), s.begin(), ::tolower); - auto prefix_map = getPrefixMap(); - auto i = prefix_map.find(s); - if (i == prefix_map.end()) { - throw std::runtime_error(folly::format( - "Cannot convert prefix to int: {}", s - ).str()); - } - res = i->second; - } else { - throw std::runtime_error(folly::format( - "Cannot parse {}", folly::toJson(d) - ).str()); - } - if (res < min_val_ || res > max_val_) { - throw std::runtime_error(folly::format( - "Value {} out of range [{}, {}]", res, min_val_, max_val_ - ).str()); - } - return res; - } - - bool initialized_; - - std::vector sorted_values_; - // These three are unused when sorted_values_ is set - Num start_; - Num end_; - Num interval_; - - // Default values for start & end, also used for range-checking. - const Num min_val_; - const Num max_val_; - - // Should be static but it's too hard to do that with templates. - // TODO(agoder): Make this better. - const std::unordered_map emptyPrefixMap_; -}; - -}} diff --git a/folly/experimental/cron/date_time_utils.cpp b/folly/experimental/cron/date_time_utils.cpp deleted file mode 100644 index d518250d..00000000 --- a/folly/experimental/cron/date_time_utils.cpp +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright 2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "folly/experimental/cron/date_time_utils.h" - -#include -#include - -#include "folly/Format.h" - -namespace folly { namespace cron { - -using namespace boost::local_time; -using namespace boost::posix_time; -using namespace std; - -// NB: The exceptions below are intended to confirm that the underlying -// libraries behave in a sane way. This makes them untestable. I got each -// of them to fire by temporarily changing their checks, so if they do fire, -// the printouts should be okay. It's fine to change them to CHECKs. - -time_t getUTCOffset(time_t utc_time, time_zone_ptr tz) { - auto utc_pt = from_time_t(utc_time); - auto local_pt = utcPTimeToTimezoneLocalPTime(utc_pt, tz); - return (local_pt - utc_pt).total_seconds(); -} - -ptime utcPTimeToTimezoneLocalPTime(ptime utc_pt, time_zone_ptr tz) { - if (tz) { - return local_date_time{utc_pt, tz}.local_time(); - } else { - return boost::date_time::c_local_adjustor::utc_to_local(utc_pt); - } -} - -UTCTimestampsForLocalTime _boostTimezoneLocalPTimeToUTCTimestamps( - ptime local_pt, - time_zone_ptr tz -) { - UTCTimestampsForLocalTime res; - auto local_date = local_pt.date(); - auto local_time = local_pt.time_of_day(); - - auto save_timestamp_if_valid = [&](bool is_dst, time_t *out) { - try { - auto local_dt = local_date_time(local_date, local_time, tz, is_dst); - // local_date_time() ignores is_dst if the timezone does not have - // DST (instead of throwing dst_not_valid). So, we must confirm - // that our is_dst guess was correct to avoid storing the same - // timestamp in both fields of res (same as problem (b) in the - // localtime_r code path). - if (local_dt.is_dst() == is_dst) { - *out = (local_dt.utc_time() - from_time_t(0)).total_seconds(); - } - } catch (dst_not_valid& e) { - // Continue, we're trying both values of is_dst - } - }; - - try { - save_timestamp_if_valid(true, &res.dst_time); - save_timestamp_if_valid(false, &res.non_dst_time); - } catch (time_label_invalid& e) { - // This local time label was skipped by DST, so res will be empty. - } - return res; -} - -UTCTimestampsForLocalTime _systemTimezoneLocalPTimeToUTCTimestamps( - ptime local_pt -) { - UTCTimestampsForLocalTime res; - struct tm tm = to_tm(local_pt); - auto save_timestamp_if_valid = [tm, &local_pt](int is_dst, time_t *out) { - // Try to make a UTC timestamp based on our DST guess and local time. - struct tm tmp_tm = tm; // Make a copy since mktime changes the tm - tmp_tm.tm_isdst = is_dst; - time_t t = mktime(&tmp_tm); - if (t == -1) { // Not sure of the error cause or how to handle it. - throw runtime_error(folly::format( - "{}: mktime error {}", to_simple_string(local_pt), errno - ).str()); - } - - // Convert the timestamp to a local time to see if the guess was right. - struct tm new_tm; - auto out_tm = localtime_r(&t, &new_tm); - if (out_tm == nullptr) { // Not sure if such errors can be handled. - throw runtime_error(folly::format( - "{}: localtime_r error {}", to_simple_string(local_pt), errno - ).str()); - } - - // Does the original tm argree with the tm generated from the mktime() - // UTC timestamp? (We'll check tm_isdst separately.) - // - // This test never passes when we have a local time label that is - // skipped when a DST change moves the clock forward. - // - // A valid local time label always has one or two valid DST values. - // When the timezone has not DST, that value is "false". - // - // This test always passes when: - // - The DST value is ambiguous (due to the local clock moving back). - // - We guessed the uniquely valid DST value. - // - // The test may or may not always pass (implementation-dependent) when - // we did not guess a valid DST value. - // (a) If it does not pass, we are good, because we also try the other - // DST value, which will make the test pass, and then res will have - // a unique timestamp. - // (b) If it does pass, we're in more trouble, because it means that - // the implementation ignored our is_dst value. Then, the timestamp - // t is the same as for the other is_dst value. But, we don't want - // res to be labeled ambiguous, and we don't want to randomly pick - // a DST value to set to kNotATime, because clients may want to - // know the real DST value. The solution is the extra test below. - if ( - tm.tm_sec == new_tm.tm_sec && tm.tm_min == new_tm.tm_min && - tm.tm_hour == new_tm.tm_hour && tm.tm_mday == new_tm.tm_mday && - tm.tm_mon == new_tm.tm_mon && tm.tm_year == new_tm.tm_year && - // To fix problem (b) above, we must assume that localtime_r returns - // the correct tm_isdst (if not, it's a system bug anyhow). Then, we - // can just check our DST guess against the truth. If our guess was - // invalid, we shouldn't store the result, avoiding (b). - !( // tm_isdst can also be negative but we'll check that later - (new_tm.tm_isdst == 0 && is_dst) || (new_tm.tm_isdst > 0 && !is_dst) - ) - ) { - *out = t; - } - return new_tm.tm_isdst < 0; // Used for a sanity-check below. - }; - - bool neg_isdst1 = save_timestamp_if_valid(1, &res.dst_time); - bool neg_isdst2 = save_timestamp_if_valid(0, &res.non_dst_time); - - // The only legitimate way for localtime_r() to give back a negative - // tm_isdst is if the input local time label is ambiguous due to DST. - if (neg_isdst1 || neg_isdst2) { - if (neg_isdst1 ^ neg_isdst2) { // Can't be ambiguous half the time - throw runtime_error(folly::format( - "{}: one tm_isdst negative but not both", to_simple_string(local_pt) - ).str()); - } - if (!res.isAmbiguous()) { - throw runtime_error(folly::format( - "{}: negative tm_isdst but time label is unambiguous", - to_simple_string(local_pt) - ).str()); - } - } - return res; -} - -UTCTimestampsForLocalTime timezoneLocalPTimeToUTCTimestamps( - ptime local_pt, - time_zone_ptr tz -) { - UTCTimestampsForLocalTime res; - if (tz) { - res = _boostTimezoneLocalPTimeToUTCTimestamps(local_pt, tz); - } else { - res = _systemTimezoneLocalPTimeToUTCTimestamps(local_pt); - } - // Both code paths have fixes to prevent this (see e.g. problem (b) above). - if (res.isAmbiguous() && res.dst_time == res.non_dst_time) { - throw runtime_error(folly::format( - "{}: local time maps to {} regardless of tm_isdst", - to_simple_string(local_pt), res.dst_time - ).str()); - } - return res; -} - -}} diff --git a/folly/experimental/cron/date_time_utils.h b/folly/experimental/cron/date_time_utils.h deleted file mode 100644 index 04a27d71..00000000 --- a/folly/experimental/cron/date_time_utils.h +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This is a small extension on top of boost::date_time, which handles - * conversions between "local time labels" (e.g. "2am, March 10, 2013") and - * POSIX UTC timestamps. - * - * Our library hides two sources of complexity: - * - * - Time zones. You can easily use the system time zone (pass a NULL - * pointer), or provide a boost::time_zone_ptr (usually created via a - * POSIX-like timezone format, or from the boost::date_time timezone DB). - * - * - The one-to-many relationship between time labels and UTC timestamps. - * - * UTC timestamps are effectively monotonic (aside from leap seconds, - * which are ignored in POSIX time, and are irrelevant for Cron). - * - * Local time labels, on the other hand, can move forward or backward due - * to daylight-savings changes. Thus, when the local clock rewinds due - * to DST, some local time labels become ambiguous (is it 1:30am before - * or after the DST rewind?). When the local time moves forward due to - * DST, some local time labels are skipped (in the US Pacific timezone, - * 2:30am never happened on March 10, 2013). - * - * As a consequence, timezoneLocalPTimeToUTCTimestamps() returns a struct - * UTCTimestampsForLocalTime that can represent 0, 1, or 2 UTC timestamps. - * - * The ambiguity could be avoided by adding an 'is_dst' flag to the local - * time label, but this is not useful for the purposes of Cron (and is - * handled adequately in existing libraries). - * - * Going from UTC to a local time label is easy and unambiguous, see - * utcPTimeToTimezoneLocalPTime(). - * - * CAVEAT: We use boost::posix_time::ptime to represent both UTC timestamps, - * *and* local time labels. This is confusing -- it would be better if - * local time labels should used a separate type. However, a ptime is very - * convenient for the purpose, since it supports all the usual time - * operations you might want to do. Patches are welcome. - * - * Our library thus accounts for the following deficiencies of - * boost::date_time: - * - * - boost::date_time has almost no support for the system timezone (the only - * related feature is the hacky "c_local_adjustor"). In contrast, our - * library interprets a time_zone_ptr value of NULL as referring to the - * system timezone, and then does the right thing. - * - * - boost::date_time has a rather annoying exception-based API for - * determining whether a local time label is ambiguous, nonexistent, or - * unique. Our struct is much more usable. - */ - -#pragma once - -#include -#include -#include -#include -#include - -#include "folly/Format.h" - -namespace folly { namespace cron { - -/** - * How many seconds must be added to UTC in order to get the local time for - * the given time point? - */ -time_t getUTCOffset(time_t utc_time, boost::local_time::time_zone_ptr tz); - -/** - * Convert a UTC ptime into a timezone-local ptime. - * - * If tz is a null pointer, use the local timezone. - * - * This is a lossy transformation, since the UTC offset of a timezone - * is not constant -- see timezoneLocalPTimeToUTCTimestamps() - * for a detailed explanation. - */ -boost::posix_time::ptime utcPTimeToTimezoneLocalPTime( - boost::posix_time::ptime utc_pt, - boost::local_time::time_zone_ptr tz -); - -/** - * A local time label can correspond to 0, 1, or 2 UTC timestamps due to - * DST time shifts: - * - If the clock went back and your label lands in the repeated interval, - * you'll get both timestamps. - * - If the clock went forward, and your label landed in the skipped time, - * you get neither. - * - For all other labels you get exactly one timestamp. - * See also timezoneLocalPTimeToUTCTimestamps(). - */ -struct UTCTimestampsForLocalTime { - static const time_t kNotATime = -1; // Might not portable, but easy. - - UTCTimestampsForLocalTime() : dst_time{kNotATime}, non_dst_time{kNotATime} {} - - bool isNotATime() const { - return dst_time == kNotATime && non_dst_time == kNotATime; - } - - bool isAmbiguous() const { - return dst_time != kNotATime && non_dst_time != kNotATime; - } - - time_t getUnique() const { - if (isAmbiguous()) { - throw std::runtime_error(folly::format( - "Local time maps to both {} and {}", dst_time, non_dst_time - ).str()); - } else if (dst_time != kNotATime) { - return dst_time; - } else if (non_dst_time != kNotATime) { - return non_dst_time; - } else { - throw std::runtime_error("This local time was skipped due to DST"); - } - } - - /** - * For ambiguous local time labels, return the pair of (lesser UTC - * timestamp, greater UTC timestamp). - * - * NOTE: This may not be strictly necessary, since DST is probably less - * than non-DST in all real timezones, but it's better to be safe than - * sorry. - * - * More specifically, the POSIX timezone specification (IEEE Std 1003.1) - * allows DST to be either ahead or behind of the regular timezone, so the - * local timezone could shift either way. The docs for - * boost::local_time::posix_time_zone (which is not even a POSIX-compliant - * implementation, see README) are ambiguous, but can be read as intending - * to forbid DST that sets the clock backwards. - */ - std::pair getBothInOrder() const { - if (!isAmbiguous()) { - throw std::runtime_error(folly::format( - "{} and {} is not ambiguous", dst_time, non_dst_time - ).str()); - } - if (dst_time < non_dst_time) { - return std::make_pair(dst_time, non_dst_time); - } - return std::make_pair(non_dst_time, dst_time); - } - - time_t dst_time; - time_t non_dst_time; -}; - -/** - * Convert a timezone-local ptime into UTC epoch timestamp(s). - * - * If tz is a null pointer, use the local timezone. - * - * WARNING 1: When DST sets back the clock, some local times become - * ambiguous -- you cannot tell if the timestamp lies before or after the - * DST change. For example, "November 3 01:30:00 2013" could be either PST - * or PDT, with a difference of one hour. - * - * WARNING 2: You can inadvertently make a local time that does not exist - * because a daylight savings change skips that time period. - */ -UTCTimestampsForLocalTime timezoneLocalPTimeToUTCTimestamps( - boost::posix_time::ptime local_pt, - boost::local_time::time_zone_ptr tz -); - -}} diff --git a/folly/experimental/cron/test/test_crontab_selector.cpp b/folly/experimental/cron/test/test_crontab_selector.cpp deleted file mode 100644 index 9fbd7afa..00000000 --- a/folly/experimental/cron/test/test_crontab_selector.cpp +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#include "folly/experimental/cron/CrontabSelector.h" -#include "folly/json.h" - -using namespace folly::cron; -using namespace folly; -using namespace std; - -// Templates lead to poor error messages, so the preprocessor rules this land - -#define check_parse_impl(Selector, json, expected_printable) { \ - Selector c; \ - c.loadDynamic(parseJson(json)); \ - EXPECT_EQ(expected_printable, c.getPrintable()); \ -} - -#define check_first_match_impl(Selector, Num, json, t, match, carry) { \ - Num match_ = match; /* Make the input the right type */ \ - Selector c; \ - c.loadDynamic(parseJson(json)); \ - EXPECT_EQ(make_pair(match_, carry), c.findFirstMatch(t)); \ -} - -#define check_parse(json, expected_printable) \ - check_parse_impl(ClownCrontabSelector, json, expected_printable) - -#define check_first_match(json, t, match, carry) \ - check_first_match_impl( \ - ClownCrontabSelector, unsigned short, json, t, match, carry \ - ) - -/** - * There are 7 clowns in the world, numbered from 1 through 7, with the - * names listed below. - */ -class ClownCrontabSelector : public CrontabSelector { -public: - ClownCrontabSelector() : CrontabSelector(1, 7) {} -protected: - virtual const PrefixMap& getPrefixMap() const { return clownToInt_; } -private: - const static PrefixMap clownToInt_; -}; -const ClownCrontabSelector::PrefixMap ClownCrontabSelector::clownToInt_{ - {"sun", 1}, // Sunny - {"mon", 2}, // Monica - {"tue", 3}, // Tuelly - {"wed", 4}, // Wedder - {"thu", 5}, // Thurmond - {"fri", 6}, // Friedrich - {"sat", 7}, // Satay -}; - -TEST(TestCrontabSelector, CheckParseRanges) { - check_parse("{}", "*"); - check_parse("{\"interval\": 3}", "1-7:3"); - check_parse("{\"start\": 2}", "2-7:1"); - check_parse("{\"start\": \"Sunny\"}", "*"); - check_parse("{\"end\": \"Thu\"}", "1-5:1"); - check_parse("{\"start\": \"Tuelly\", \"end\": \"Wed\"}", "3-4:1"); - check_parse("{\"end\": \"Fri\", \"interval\": 2}", "1-6:2"); -} - -TEST(TestCrontabSelector, CheckParseValues) { - check_parse("5", "5"); - check_parse("\"Thu\"", "5"); - check_parse("[\"Sat\", 1, \"Fri\"]", "1,6,7"); -} - -TEST(TestCrontabSelector, CheckMatchValues) { - for (int64_t i = 0; i < 10; ++i) { // A single value never changes - check_first_match("5", i, 5, i > 5); - } - - // Now check a list of values - check_first_match("[2,4,5]", 0, 2, false); - check_first_match("[2,4,5]", 1, 2, false); - check_first_match("[2,4,5]", 2, 2, false); - check_first_match("[2,4,5]", 3, 4, false); - check_first_match("[2,4,5]", 4, 4, false); - check_first_match("[2,4,5]", 5, 5, false); - check_first_match("[2,4,5]", 6, 2, true); - check_first_match("[2,4,5]", 7, 2, true); -} - -TEST(TestCrontabSelector, CheckMatchDefaultRange) { - check_first_match("{}", 0, 1, false); - check_first_match("[]", 1, 1, false); - check_first_match("[]", 6, 6, false); - check_first_match("{}", 7, 7, false); - check_first_match("{}", 8, 1, true); - check_first_match("[]", 10, 1, true); -} - -TEST(TestCrontabSelector, CheckMatchNontrivialRange) { - string range = "{\"start\": 3, \"end\": 7, \"interval\": 2}"; - check_first_match(range, 1, 3, false); - check_first_match(range, 2, 3, false); - check_first_match(range, 3, 3, false); - check_first_match(range, 4, 5, false); - check_first_match(range, 5, 5, false); - check_first_match(range, 6, 7, false); - check_first_match(range, 7, 7, false); - check_first_match(range, 8, 3, true); -} - -TEST(TestCrontabSelector, CheckInit) { - ClownCrontabSelector s; - - EXPECT_THROW(s.findFirstMatch(5), runtime_error); // Not initialized - - // Initialized and functional - s.loadDynamic(dynamic::object()); - EXPECT_EQ(make_pair((unsigned short)5, false), s.findFirstMatch(5)); - - // Cannot double-initialize - EXPECT_THROW(s.loadDynamic(dynamic::object()), runtime_error); -} - -TEST(TestCrontabSelector, CheckParseErrors) { - // Out-of-range intervals - EXPECT_THROW(check_parse("{\"interval\": 0}", ""), runtime_error); - EXPECT_THROW(check_parse("{\"interval\": -1}", ""), runtime_error); - EXPECT_THROW(check_parse("{\"interval\": 7}", ""), runtime_error); - - // Out-of-range start or end - EXPECT_THROW(check_parse("{\"start\": 0}", ""), runtime_error); - EXPECT_THROW(check_parse("{\"end\": 8}", ""), runtime_error); - - // Problematic JSON inputs - EXPECT_THROW(check_parse("{\"bad_key\": 3}", ""), runtime_error); - EXPECT_THROW(check_parse("0.1", ""), runtime_error); // no floats - - // Invalid values - EXPECT_THROW(check_parse("\"Chicken\"", ""), runtime_error); - EXPECT_THROW(check_parse("\"Th\"", ""), runtime_error); // Need >= 3 chars - EXPECT_THROW(check_parse("\"S\"", ""), runtime_error); - EXPECT_THROW(check_parse("{\"start\": 0.3}", ""), runtime_error); // float -} diff --git a/folly/experimental/cron/test/test_date_time_utils.cpp b/folly/experimental/cron/test/test_date_time_utils.cpp deleted file mode 100644 index d338da0d..00000000 --- a/folly/experimental/cron/test/test_date_time_utils.cpp +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2014 Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#include -#include // for posix_time_zone -#include // for setenv() - -#include "folly/experimental/cron/date_time_utils.h" - -using namespace folly::cron; -using namespace boost::local_time; -using namespace boost::gregorian; -using namespace boost::posix_time; -using namespace std; - -enum class Ambiguity { - Unique, - Ambiguous, - Unknown, -}; - -void check_not_a_local_time(ptime local_pt, time_zone_ptr tz) { - EXPECT_TRUE( - timezoneLocalPTimeToUTCTimestamps(local_pt, tz).isNotATime() - ); -} - -void check_local_ptime(time_t utc_t, time_zone_ptr tz, ptime expected_pt) { - EXPECT_EQ( - expected_pt, utcPTimeToTimezoneLocalPTime(from_time_t(utc_t), tz) - ); -} - -void check_to_local_and_back(time_t utc_t, time_zone_ptr tz, Ambiguity n) { - auto utc_ts = timezoneLocalPTimeToUTCTimestamps( - utcPTimeToTimezoneLocalPTime(from_time_t(utc_t), tz), tz - ); - if (n == Ambiguity::Unique) { - EXPECT_FALSE(utc_ts.isAmbiguous()); - EXPECT_FALSE(utc_ts.isNotATime()); - } else if (n == Ambiguity::Ambiguous) { - EXPECT_TRUE(utc_ts.isAmbiguous()); - } - EXPECT_FALSE(utc_ts.isNotATime()); // Cannot get here from a UTC timestamp - if (utc_ts.isAmbiguous()) { - EXPECT_PRED3( - [](time_t t1, time_t t2, time_t t3){ return t1 == t3 || t2 == t3; }, - utc_ts.dst_time, utc_ts.non_dst_time, utc_t - ); - } else { - EXPECT_EQ(utc_t, utc_ts.getUnique()); - } -} - -const int k1980_Feb29_4AM_PST = 320673600; -const time_t kTestTimestamps[] = { - 0, k1980_Feb29_4AM_PST, // Some edge cases - // Random values from [randrange(0, int(time())) for i in range(200)] - 851763124, 261861130, 743855544, 30239098, 569168784, 850101954, - 1113053877, 1364858223, 1082354444, 1294020427, 258495434, 1121030318, - 192467213, 484525368, 579184768, 167376207, 689233030, 1351587900, - 1214561991, 661713049, 381308132, 665152213, 94230657, 1349426746, - 298195324, 1257615713, 682132890, 1018217831, 916554585, 995955072, - 1117317370, 802646927, 608115326, 633743809, 109769810, 543272111, - 609037871, 104418231, 264752638, 306399494, 1035358804, 766418015, - 1128611920, 181391436, 839616511, 796842798, 653512454, 1010622273, - 875647954, 708203495, 822980713, 991547420, 1265028641, 1347606382, - 1002331337, 1164592802, 31466919, 1065361177, 1225097252, 631276316, - 527190864, 492850662, 327182508, 869358924, 140894012, 1146198515, - 501023608, 933017248, 324137101, 710311561, 556527520, 38622381, - 203388537, 475269797, 724361468, 814834023, 208189749, 815722762, - 45610280, 761977400, 933451311, 660014659, 494207495, 765580653, - 1243453093, 234300455, 1345693003, 158935011, 1173706097, 315858792, - 1184431509, 477296062, 276535773, 928860110, 103635291, 708434135, - 51126476, 160505670, 153146671, 354980180, 890292051, 1155669986, - 630375563, 349261331, 620264499, 477756621, 901672130, 618524356, - 252709868, 1213920374, 233303580, 3012130, 969038324, 202252395, - 1187016766, 669825568, 257426556, 214600753, 995259569, 360335117, - 1199390931, 925221855, 616957946, 745607758, 1304023574, 383936310, - 952313824, 251320075, 1018206981, 18254870, 949794553, 794223010, - 22167074, 971353751, 836775665, 132713147, 1385328705, 564225254, - 89489672, 970288768, 727638691, 1384138213, 295605253, 565194711, - 268066246, 262980328, 878120933, 501014040, 950529654, 899180133, - 452320225, 1232572199, 894784724, 24260103, 331355470, 593434097, - 986752149, 590771435, 36704582, 1081058342, 231884390, 418573190, - 580513906, 416611430, 778410883, 393299067, 891265387, 545143528, - 242177530, 43413747, 774970054, 623606322, 1088170511, 925487121, - 276552897, 904380544, 407117624, 877143874, 901504406, 1060658206, - 378376447, 566370202, 903180278, 299280550, 1064440994, 742066503, - 402041226, 1388625249, 1316863228, 749053705, 426181185, 1239538923, - 221164890, 1049484190, 98669029, 414059052, 930992061, 34048214, - 496162677, 206881990 -}; - -void check_timezone_without_dst(time_zone_ptr tz) { - for (time_t utc_t: kTestTimestamps) { - check_to_local_and_back(utc_t, tz, Ambiguity::Unique); - } -} - -void check_timezone_with_dst( - time_zone_ptr tz, int amb_first, int amb_mid, int amb_last, int after_skip -) { - // DST-ambiguous values - for (time_t utc_t : {amb_first, amb_mid, amb_mid + 1, amb_last}) { - check_to_local_and_back(utc_t, tz, Ambiguity::Ambiguous); - } - - // Timestamps bordering the DST transitions - for (time_t utc_t : { - amb_first - 1, amb_last + 1, // The ambiguous range is tight - after_skip, after_skip - 1 // The DST-skipped interval has no impact - }) { - check_to_local_and_back(utc_t, tz, Ambiguity::Unique); - } - - // Lots of random timestamps - for (time_t utc_t: kTestTimestamps) { - check_to_local_and_back(utc_t, tz, Ambiguity::Unknown); - } -} - -// These are 3 hours apart with the same DST rules in 2013, so it's easy to -// check UTC => local ptime conversions, and invalid local time labels. -void check_us_eastern_or_pacific(time_zone_ptr tz, int offset_from_pacific) { - // 2013: Nov 3 - 1AM PDT, 1:59:59AM PDT, 1:59:59AM PST; Mar 10 - 3AM PDT - time_t amb_start = 1383465600 + offset_from_pacific; - time_t amb_mid = 1383469199 + offset_from_pacific; - time_t amb_end = 1383472799 + offset_from_pacific; - time_t after_skip = 1362909600 + offset_from_pacific; - ptime amb_start_pt(date(2013, 11, 3), hours(1)); - ptime amb_mid_end_pt(date(2013, 11, 3), time_duration(1, 59, 59)); - ptime before_skip_pt(date(2013, 3, 10), time_duration(1, 59, 59)); - ptime after_skip_pt(date(2013, 3, 10), hours(3)); - - // Test mapping to local ptimes and back to UTC timestamps. - check_timezone_with_dst(tz, amb_start, amb_mid, amb_end, after_skip); - check_local_ptime(amb_start, tz, amb_start_pt); - check_local_ptime(amb_mid, tz, amb_mid_end_pt); - check_local_ptime(amb_end, tz, amb_mid_end_pt); - check_local_ptime(after_skip - 1, tz, before_skip_pt); - check_local_ptime( - k1980_Feb29_4AM_PST + offset_from_pacific, tz, - ptime(date(1980, 2, 29), hours(4)) - ); - check_not_a_local_time(before_skip_pt + seconds(1), tz); - check_not_a_local_time(before_skip_pt + seconds(1800), tz); - check_not_a_local_time(before_skip_pt + seconds(3600), tz); - check_not_a_local_time(after_skip_pt - seconds(1), tz); - check_not_a_local_time(after_skip_pt - seconds(1800), tz); - check_not_a_local_time(after_skip_pt - seconds(3600), tz); - check_local_ptime(after_skip, tz, after_skip_pt); - - // A light test for getUTCOffset(), since its constituents are well-tested. - EXPECT_EQ(-25200 - offset_from_pacific, getUTCOffset(amb_start, tz)); - EXPECT_EQ(-25200 - offset_from_pacific, getUTCOffset(amb_mid, tz)); - EXPECT_EQ(-28800 - offset_from_pacific, getUTCOffset(amb_mid + 1, tz)); - EXPECT_EQ(-28800 - offset_from_pacific, getUTCOffset(amb_end, tz)); - EXPECT_EQ( - -28800 - offset_from_pacific, getUTCOffset(k1980_Feb29_4AM_PST, tz) - ); -} - -TEST(TestDateTimeUtils, AllTheThings) { - - // Exercise the local timezone code path: US Pacific & US Eastern - time_zone_ptr tz; - setenv("TZ", "PST+8PDT,M3.2.0,M11.1.0", 1); - tzset(); - check_us_eastern_or_pacific(tz, 0); - setenv("TZ", "EST+5EDT,M3.2.0,M11.1.0", 1); - tzset(); - check_us_eastern_or_pacific(tz, -10800); - - // Local timezone code with DST-free timezones - for (auto& tz_name : {"MST+7", "GMT-14", "GMT+12", "GMT-4:30"}) { - setenv("TZ", tz_name, 1); - tzset(); - check_timezone_without_dst(tz); - } - - // Also US Pacific & US Eastern, but with the boost::date_time code. - // The signs differ from the setenv() calls above, since boost::local_time - // incorrectly implements the standard. Compare these: - // http://tools.ietf.org/html/draft-ietf-dhc-timezone-01 - // http://www.boost.org/doc/libs/1_55_0/doc/html/date_time/local_time.html#date_time.local_time.posix_time_zone - tz.reset(new posix_time_zone{"PST-8PDT,M3.2.0,M11.1.0"}); - check_us_eastern_or_pacific(tz, 0); - tz.reset(new posix_time_zone{"EST-5EDT,M3.2.0,M11.1.0"}); - check_us_eastern_or_pacific(tz, -10800); - - // DST-free timezones with the boost::date_time code (signs also flipped) - for (auto& tz_name : {"MST-7", "GMT+14", "GMT-12", "GMT+4:30"}) { - tz.reset(new posix_time_zone{tz_name}); - check_timezone_without_dst(tz); - } -} -- 2.34.1