1 /******************************************************************************
3 * Copyright (C) 2012-2018 Icinga Development Team (https://www.icinga.com/) *
5 * This program is free software; you can redistribute it and/or *
6 * modify it under the terms of the GNU General Public License *
7 * as published by the Free Software Foundation; either version 2 *
8 * of the License, or (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the Free Software Foundation *
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
18 ******************************************************************************/
20 #include "icinga/legacytimeperiod.hpp"
21 #include "base/function.hpp"
22 #include "base/convert.hpp"
23 #include "base/exception.hpp"
24 #include "base/objectlock.hpp"
25 #include "base/logger.hpp"
26 #include "base/debug.hpp"
27 #include "base/utility.hpp"
28 #include <boost/algorithm/string/split.hpp>
29 #include <boost/algorithm/string/classification.hpp>
31 using namespace icinga;
33 REGISTER_SCRIPTFUNCTION_NS(Internal, LegacyTimePeriod, &LegacyTimePeriod::ScriptFunc, "tp:begin:end");
35 bool LegacyTimePeriod::IsInTimeRange(tm *begin, tm *end, int stride, tm *reference)
37 time_t tsbegin, tsend, tsref;
38 tsbegin = mktime(begin);
40 tsref = mktime(reference);
42 if (tsref < tsbegin || tsref > tsend)
45 int daynumber = (tsref - tsbegin) / (24 * 60 * 60);
47 if (stride > 1 && daynumber % stride == 0)
53 void LegacyTimePeriod::FindNthWeekday(int wday, int n, tm *reference)
63 /* Negative days are relative to the next month. */
69 reference->tm_mday = 1;
74 if (reference->tm_wday == wday) {
81 reference->tm_mday += dir;
85 int LegacyTimePeriod::WeekdayFromString(const String& daydef)
87 if (daydef == "sunday")
89 else if (daydef == "monday")
91 else if (daydef == "tuesday")
93 else if (daydef == "wednesday")
95 else if (daydef == "thursday")
97 else if (daydef == "friday")
99 else if (daydef == "saturday")
105 int LegacyTimePeriod::MonthFromString(const String& monthdef)
107 if (monthdef == "january")
109 else if (monthdef == "february")
111 else if (monthdef == "march")
113 else if (monthdef == "april")
115 else if (monthdef == "may")
117 else if (monthdef == "june")
119 else if (monthdef == "july")
121 else if (monthdef == "august")
123 else if (monthdef == "september")
125 else if (monthdef == "october")
127 else if (monthdef == "november")
129 else if (monthdef == "december")
135 void LegacyTimePeriod::ParseTimeSpec(const String& timespec, tm *begin, tm *end, tm *reference)
137 /* Let mktime() figure out whether we're in DST or not. */
138 reference->tm_isdst = -1;
141 if (timespec.GetLength() == 10 && timespec[4] == '-' && timespec[7] == '-') {
142 int year = Convert::ToLong(timespec.SubStr(0, 4));
143 int month = Convert::ToLong(timespec.SubStr(5, 2));
144 int day = Convert::ToLong(timespec.SubStr(8, 2));
146 if (month < 1 || month > 12)
147 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid month in time specification: " + timespec));
148 if (day < 1 || day > 31)
149 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid day in time specification: " + timespec));
153 begin->tm_year = year - 1900;
154 begin->tm_mon = month - 1;
155 begin->tm_mday = day;
163 end->tm_year = year - 1900;
164 end->tm_mon = month - 1;
174 std::vector<String> tokens;
175 boost::algorithm::split(tokens, timespec, boost::is_any_of(" "));
179 if (tokens.size() > 1 && (tokens[0] == "day" || (mon = MonthFromString(tokens[0])) != -1)) {
181 mon = reference->tm_mon;
183 int mday = Convert::ToLong(tokens[1]);
188 begin->tm_mday = mday;
193 /* Negative days are relative to the next month. */
195 begin->tm_mday = mday * -1 - 1;
208 /* Negative days are relative to the next month. */
210 end->tm_mday = mday * -1 - 1;
220 if (tokens.size() >= 1 && (wday = WeekdayFromString(tokens[0])) != -1) {
221 tm myref = *reference;
223 if (tokens.size() > 2) {
224 mon = MonthFromString(tokens[2]);
227 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid month in time specification: " + timespec));
234 if (tokens.size() > 1)
235 n = Convert::ToLong(tokens[1]);
240 if (tokens.size() > 1)
241 FindNthWeekday(wday, n, begin);
243 begin->tm_mday += (7 - begin->tm_wday + wday) % 7;
253 if (tokens.size() > 1)
254 FindNthWeekday(wday, n, end);
256 end->tm_mday += (7 - end->tm_wday + wday) % 7;
267 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid time specification: " + timespec));
270 void LegacyTimePeriod::ParseTimeRange(const String& timerange, tm *begin, tm *end, int *stride, tm *reference)
272 String def = timerange;
274 /* Figure out the stride. */
275 size_t pos = def.FindFirstOf('/');
277 if (pos != String::NPos) {
278 String strStride = def.SubStr(pos + 1).Trim();
279 *stride = Convert::ToLong(strStride);
281 /* Remove the stride parameter from the definition. */
282 def = def.SubStr(0, pos);
284 *stride = 1; /* User didn't specify anything, assume default. */
287 /* Figure out whether the user has specified two dates. */
288 pos = def.Find("- ");
290 if (pos != String::NPos) {
291 String first = def.SubStr(0, pos).Trim();
293 String second = def.SubStr(pos + 1).Trim();
295 ParseTimeSpec(first, begin, nullptr, reference);
297 /* If the second definition starts with a number we need
298 * to add the first word from the first definition, e.g.:
299 * day 1 - 15 --> "day 15" */
300 bool is_number = true;
301 size_t xpos = second.FindFirstOf(' ');
302 String fword = second.SubStr(0, xpos);
305 Convert::ToLong(fword);
311 xpos = first.FindFirstOf(' ');
312 ASSERT(xpos != String::NPos);
313 second = first.SubStr(0, xpos + 1) + second;
316 ParseTimeSpec(second, nullptr, end, reference);
318 ParseTimeSpec(def, begin, end, reference);
322 bool LegacyTimePeriod::IsInDayDefinition(const String& daydef, tm *reference)
327 ParseTimeRange(daydef, &begin, &end, &stride, reference);
329 Log(LogDebug, "LegacyTimePeriod")
330 << "ParseTimeRange: '" << daydef << "' => " << mktime(&begin)
331 << " -> " << mktime(&end) << ", stride: " << stride;
333 return IsInTimeRange(&begin, &end, stride, reference);
336 void LegacyTimePeriod::ProcessTimeRangeRaw(const String& timerange, tm *reference, tm *begin, tm *end)
338 std::vector<String> times;
340 boost::algorithm::split(times, timerange, boost::is_any_of("-"));
342 if (times.size() != 2)
343 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid timerange: " + timerange));
345 std::vector<String> hd1, hd2;
346 boost::algorithm::split(hd1, times[0], boost::is_any_of(":"));
349 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid time specification: " + times[0]));
351 boost::algorithm::split(hd2, times[1], boost::is_any_of(":"));
354 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid time specification: " + times[1]));
358 begin->tm_min = Convert::ToLong(hd1[1]);
359 begin->tm_hour = Convert::ToLong(hd1[0]);
363 end->tm_min = Convert::ToLong(hd2[1]);
364 end->tm_hour = Convert::ToLong(hd2[0]);
366 if (begin->tm_hour * 3600 + begin->tm_min * 60 + begin->tm_sec >=
367 end->tm_hour * 3600 + end->tm_min * 60 + end->tm_sec)
368 BOOST_THROW_EXCEPTION(std::invalid_argument("Time period segment ends before it begins"));
371 Dictionary::Ptr LegacyTimePeriod::ProcessTimeRange(const String& timestamp, tm *reference)
375 ProcessTimeRangeRaw(timestamp, reference, &begin, &end);
377 Dictionary::Ptr segment = new Dictionary();
378 segment->Set("begin", (long)mktime(&begin));
379 segment->Set("end", (long)mktime(&end));
383 void LegacyTimePeriod::ProcessTimeRanges(const String& timeranges, tm *reference, const Array::Ptr& result)
385 std::vector<String> ranges;
387 boost::algorithm::split(ranges, timeranges, boost::is_any_of(","));
389 for (const String& range : ranges) {
390 Dictionary::Ptr segment = ProcessTimeRange(range, reference);
392 if (segment->Get("begin") >= segment->Get("end"))
395 result->Add(segment);
399 Dictionary::Ptr LegacyTimePeriod::FindNextSegment(const String& daydef, const String& timeranges, tm *reference)
401 tm begin, end, iter, ref;
402 time_t tsend, tsiter, tsref;
405 for (int pass = 1; pass <= 2; pass++) {
413 tsref = mktime(&ref);
415 ParseTimeRange(daydef, &begin, &end, &stride, &ref);
419 tsend = mktime(&end);
422 if (IsInTimeRange(&begin, &end, stride, &iter)) {
423 Array::Ptr segments = new Array();
424 ProcessTimeRanges(timeranges, &iter, segments);
426 Dictionary::Ptr bestSegment;
429 ObjectLock olock(segments);
430 for (const Dictionary::Ptr& segment : segments) {
431 double begin = segment->Get("begin");
436 if (!bestSegment || begin < bestBegin) {
437 bestSegment = segment;
450 tsiter = mktime(&iter);
451 } while (tsiter < tsend);
457 Array::Ptr LegacyTimePeriod::ScriptFunc(const TimePeriod::Ptr& tp, double begin, double end)
459 Array::Ptr segments = new Array();
461 Dictionary::Ptr ranges = tp->GetRanges();
464 for (int i = 0; i <= (end - begin) / (24 * 60 * 60); i++) {
465 time_t refts = begin + i * 24 * 60 * 60;
466 tm reference = Utility::LocalTime(refts);
469 Log(LogDebug, "LegacyTimePeriod")
470 << "Checking reference time " << refts;
471 #endif /* I2_DEBUG */
473 ObjectLock olock(ranges);
474 for (const Dictionary::Pair& kv : ranges) {
475 if (!IsInDayDefinition(kv.first, &reference)) {
477 Log(LogDebug, "LegacyTimePeriod")
478 << "Not in day definition '" << kv.first << "'.";
479 #endif /* I2_DEBUG */
484 Log(LogDebug, "LegacyTimePeriod")
485 << "In day definition '" << kv.first << "'.";
486 #endif /* I2_DEBUG */
488 ProcessTimeRanges(kv.second, &reference, segments);
493 Log(LogDebug, "LegacyTimePeriod")
494 << "Legacy timeperiod update returned " << segments->GetLength() << " segments.";