1 /******************************************************************************
3 * Copyright (C) 2012-2018 Icinga Development Team (https://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"
29 using namespace icinga;
31 REGISTER_FUNCTION_NONCONST(Internal, LegacyTimePeriod, &LegacyTimePeriod::ScriptFunc, "tp:begin:end");
33 bool LegacyTimePeriod::IsInTimeRange(tm *begin, tm *end, int stride, tm *reference)
35 time_t tsbegin, tsend, tsref;
36 tsbegin = mktime(begin);
38 tsref = mktime(reference);
40 if (tsref < tsbegin || tsref > tsend)
43 int daynumber = (tsref - tsbegin) / (24 * 60 * 60);
45 if (stride > 1 && daynumber % stride > 0)
51 void LegacyTimePeriod::FindNthWeekday(int wday, int n, tm *reference)
61 /* Negative days are relative to the next month. */
67 reference->tm_mday = 1;
72 if (reference->tm_wday == wday) {
79 reference->tm_mday += dir;
83 int LegacyTimePeriod::WeekdayFromString(const String& daydef)
85 if (daydef == "sunday")
87 else if (daydef == "monday")
89 else if (daydef == "tuesday")
91 else if (daydef == "wednesday")
93 else if (daydef == "thursday")
95 else if (daydef == "friday")
97 else if (daydef == "saturday")
103 int LegacyTimePeriod::MonthFromString(const String& monthdef)
105 if (monthdef == "january")
107 else if (monthdef == "february")
109 else if (monthdef == "march")
111 else if (monthdef == "april")
113 else if (monthdef == "may")
115 else if (monthdef == "june")
117 else if (monthdef == "july")
119 else if (monthdef == "august")
121 else if (monthdef == "september")
123 else if (monthdef == "october")
125 else if (monthdef == "november")
127 else if (monthdef == "december")
133 void LegacyTimePeriod::ParseTimeSpec(const String& timespec, tm *begin, tm *end, tm *reference)
135 /* Let mktime() figure out whether we're in DST or not. */
136 reference->tm_isdst = -1;
139 if (timespec.GetLength() == 10 && timespec[4] == '-' && timespec[7] == '-') {
140 int year = Convert::ToLong(timespec.SubStr(0, 4));
141 int month = Convert::ToLong(timespec.SubStr(5, 2));
142 int day = Convert::ToLong(timespec.SubStr(8, 2));
144 if (month < 1 || month > 12)
145 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid month in time specification: " + timespec));
146 if (day < 1 || day > 31)
147 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid day in time specification: " + timespec));
151 begin->tm_year = year - 1900;
152 begin->tm_mon = month - 1;
153 begin->tm_mday = day;
161 end->tm_year = year - 1900;
162 end->tm_mon = month - 1;
172 std::vector<String> tokens = timespec.Split(" ");
176 if (tokens.size() > 1 && (tokens[0] == "day" || (mon = MonthFromString(tokens[0])) != -1)) {
178 mon = reference->tm_mon;
180 int mday = Convert::ToLong(tokens[1]);
185 begin->tm_mday = mday;
190 /* Negative days are relative to the next month. */
192 begin->tm_mday = mday * -1 - 1;
205 /* Negative days are relative to the next month. */
207 end->tm_mday = mday * -1 - 1;
217 if (tokens.size() >= 1 && (wday = WeekdayFromString(tokens[0])) != -1) {
218 tm myref = *reference;
220 if (tokens.size() > 2) {
221 mon = MonthFromString(tokens[2]);
224 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid month in time specification: " + timespec));
231 if (tokens.size() > 1)
232 n = Convert::ToLong(tokens[1]);
237 if (tokens.size() > 1)
238 FindNthWeekday(wday, n, begin);
240 begin->tm_mday += (7 - begin->tm_wday + wday) % 7;
250 if (tokens.size() > 1)
251 FindNthWeekday(wday, n, end);
253 end->tm_mday += (7 - end->tm_wday + wday) % 7;
264 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid time specification: " + timespec));
267 void LegacyTimePeriod::ParseTimeRange(const String& timerange, tm *begin, tm *end, int *stride, tm *reference)
269 String def = timerange;
271 /* Figure out the stride. */
272 size_t pos = def.FindFirstOf('/');
274 if (pos != String::NPos) {
275 String strStride = def.SubStr(pos + 1).Trim();
276 *stride = Convert::ToLong(strStride);
278 /* Remove the stride parameter from the definition. */
279 def = def.SubStr(0, pos);
281 *stride = 1; /* User didn't specify anything, assume default. */
284 /* Figure out whether the user has specified two dates. */
285 pos = def.Find("- ");
287 if (pos != String::NPos) {
288 String first = def.SubStr(0, pos).Trim();
290 String second = def.SubStr(pos + 1).Trim();
292 ParseTimeSpec(first, begin, nullptr, reference);
294 /* If the second definition starts with a number we need
295 * to add the first word from the first definition, e.g.:
296 * day 1 - 15 --> "day 15" */
297 bool is_number = true;
298 size_t xpos = second.FindFirstOf(' ');
299 String fword = second.SubStr(0, xpos);
302 Convert::ToLong(fword);
308 xpos = first.FindFirstOf(' ');
309 ASSERT(xpos != String::NPos);
310 second = first.SubStr(0, xpos + 1) + second;
313 ParseTimeSpec(second, nullptr, end, reference);
315 ParseTimeSpec(def, begin, end, reference);
319 bool LegacyTimePeriod::IsInDayDefinition(const String& daydef, tm *reference)
324 ParseTimeRange(daydef, &begin, &end, &stride, reference);
326 Log(LogDebug, "LegacyTimePeriod")
327 << "ParseTimeRange: '" << daydef << "' => " << mktime(&begin)
328 << " -> " << mktime(&end) << ", stride: " << stride;
330 return IsInTimeRange(&begin, &end, stride, reference);
333 void LegacyTimePeriod::ProcessTimeRangeRaw(const String& timerange, tm *reference, tm *begin, tm *end)
335 std::vector<String> times = timerange.Split("-");
337 if (times.size() != 2)
338 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid timerange: " + timerange));
340 std::vector<String> hd1 = times[0].Split(":");
343 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid time specification: " + times[0]));
345 std::vector<String> hd2 = times[1].Split(":");
348 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid time specification: " + times[1]));
352 begin->tm_min = Convert::ToLong(hd1[1]);
353 begin->tm_hour = Convert::ToLong(hd1[0]);
357 end->tm_min = Convert::ToLong(hd2[1]);
358 end->tm_hour = Convert::ToLong(hd2[0]);
360 if (begin->tm_hour * 3600 + begin->tm_min * 60 + begin->tm_sec >=
361 end->tm_hour * 3600 + end->tm_min * 60 + end->tm_sec)
362 BOOST_THROW_EXCEPTION(std::invalid_argument("Time period segment ends before it begins"));
365 Dictionary::Ptr LegacyTimePeriod::ProcessTimeRange(const String& timestamp, tm *reference)
369 ProcessTimeRangeRaw(timestamp, reference, &begin, &end);
371 return new Dictionary({
372 { "begin", (long)mktime(&begin) },
373 { "end", (long)mktime(&end) }
377 void LegacyTimePeriod::ProcessTimeRanges(const String& timeranges, tm *reference, const Array::Ptr& result)
379 std::vector<String> ranges = timeranges.Split(",");
381 for (const String& range : ranges) {
382 Dictionary::Ptr segment = ProcessTimeRange(range, reference);
384 if (segment->Get("begin") >= segment->Get("end"))
387 result->Add(segment);
391 Dictionary::Ptr LegacyTimePeriod::FindRunningSegment(const String& daydef, const String& timeranges, tm *reference)
394 time_t tsend, tsiter, tsref;
397 tsref = mktime(reference);
399 ParseTimeRange(daydef, &begin, &end, &stride, reference);
403 tsend = mktime(&end);
406 if (IsInTimeRange(&begin, &end, stride, &iter)) {
407 Array::Ptr segments = new Array();
408 ProcessTimeRanges(timeranges, &iter, segments);
410 Dictionary::Ptr bestSegment;
413 ObjectLock olock(segments);
414 for (const Dictionary::Ptr& segment : segments) {
415 double begin = segment->Get("begin");
416 double end = segment->Get("end");
418 if (begin >= tsref || end < tsref)
421 if (!bestSegment || end > bestEnd) {
422 bestSegment = segment;
435 tsiter = mktime(&iter);
436 } while (tsiter < tsend);
441 Dictionary::Ptr LegacyTimePeriod::FindNextSegment(const String& daydef, const String& timeranges, tm *reference)
443 tm begin, end, iter, ref;
444 time_t tsend, tsiter, tsref;
447 for (int pass = 1; pass <= 2; pass++) {
455 tsref = mktime(&ref);
457 ParseTimeRange(daydef, &begin, &end, &stride, &ref);
461 tsend = mktime(&end);
464 if (IsInTimeRange(&begin, &end, stride, &iter)) {
465 Array::Ptr segments = new Array();
466 ProcessTimeRanges(timeranges, &iter, segments);
468 Dictionary::Ptr bestSegment;
471 ObjectLock olock(segments);
472 for (const Dictionary::Ptr& segment : segments) {
473 double begin = segment->Get("begin");
478 if (!bestSegment || begin < bestBegin) {
479 bestSegment = segment;
492 tsiter = mktime(&iter);
493 } while (tsiter < tsend);
499 Array::Ptr LegacyTimePeriod::ScriptFunc(const TimePeriod::Ptr& tp, double begin, double end)
501 Array::Ptr segments = new Array();
503 Dictionary::Ptr ranges = tp->GetRanges();
506 for (int i = 0; i <= (end - begin) / (24 * 60 * 60); i++) {
507 time_t refts = begin + i * 24 * 60 * 60;
508 tm reference = Utility::LocalTime(refts);
511 Log(LogDebug, "LegacyTimePeriod")
512 << "Checking reference time " << refts;
513 #endif /* I2_DEBUG */
515 ObjectLock olock(ranges);
516 for (const Dictionary::Pair& kv : ranges) {
517 if (!IsInDayDefinition(kv.first, &reference)) {
519 Log(LogDebug, "LegacyTimePeriod")
520 << "Not in day definition '" << kv.first << "'.";
521 #endif /* I2_DEBUG */
526 Log(LogDebug, "LegacyTimePeriod")
527 << "In day definition '" << kv.first << "'.";
528 #endif /* I2_DEBUG */
530 ProcessTimeRanges(kv.second, &reference, segments);
535 Log(LogDebug, "LegacyTimePeriod")
536 << "Legacy timeperiod update returned " << segments->GetLength() << " segments.";