Time range support for vppinfra
[vpp.git] / src / vppinfra / time_range.c
diff --git a/src/vppinfra/time_range.c b/src/vppinfra/time_range.c
new file mode 100644 (file)
index 0000000..e502ca3
--- /dev/null
@@ -0,0 +1,472 @@
+/*
+ * Copyright (c) 2018 Cisco and/or its affiliates.
+ * 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 <vppinfra/time_range.h>
+
+void
+clib_timebase_init (clib_timebase_t * tb, i32 timezone_offset_in_hours,
+                   clib_timebase_daylight_time_t daylight_type)
+{
+  memset (tb, 0, sizeof (*tb));
+
+  clib_time_init (&tb->clib_time);
+  tb->time_zero = unix_time_now ();
+
+  tb->timezone_offset = ((f64) (timezone_offset_in_hours)) * 3600.0;
+  tb->daylight_time_type = daylight_type;
+  switch (tb->daylight_time_type)
+    {
+    case CLIB_TIMEBASE_DAYLIGHT_NONE:
+      tb->summer_offset = 0.0;
+      break;
+    case CLIB_TIMEBASE_DAYLIGHT_USA:
+      tb->summer_offset = 3600.0;
+      break;
+    default:
+      clib_warning ("unknown daylight type %d", tb->daylight_time_type);
+      tb->daylight_time_type = CLIB_TIMEBASE_DAYLIGHT_NONE;
+      tb->summer_offset = 0.0;
+    }
+}
+
+const static u32 days_per_month[] = {
+  31,                          /* Jan */
+  28,                          /* Feb */
+  31,                          /* Mar */
+  30,                          /* Apr */
+  31,                          /* May */
+  30,                          /* Jun */
+  31,                          /* Jul */
+  31,                          /* Aug */
+  30,                          /* Sep */
+  31,                          /* Oct */
+  30,                          /* Nov */
+  31,                          /* Dec */
+};
+
+const static char *month_short_names[] = {
+  "Jan",
+  "Feb",
+  "Mar",
+  "Apr",
+  "May",
+  "Jun",
+  "Jul",
+  "Aug",
+  "Sep",
+  "Oct",
+  "Nov",
+  "Dec",
+};
+
+const static char *day_names_epoch_order[] = {
+  "Thu",
+  "Fri",
+  "Sat",
+  "Sun",
+  "Mon",
+  "Tue",
+  "Wed",
+};
+
+const static char *day_names_calendar_order[] = {
+  "Sun",
+  "Mon",
+  "Tue",
+  "Wed",
+  "Thu",
+  "Fri",
+  "Sat",
+};
+
+
+void
+clib_timebase_time_to_components (f64 now, clib_timebase_component_t * cp)
+{
+  u32 year, month, hours, minutes, seconds, nanoseconds;
+  u32 days_in_year, days_in_month, day_of_month;
+  u32 days_since_epoch;
+  u32 day_name_index;
+
+  /* Unix epoch is 1/1/1970 00:00:00.00, a Thursday */
+
+  year = 1970;
+  days_since_epoch = 0;
+
+  do
+    {
+      days_in_year = clib_timebase_is_leap_year (year) ? 366 : 365;
+      days_since_epoch += days_in_year;
+      now = now - ((f64) days_in_year) * 86400.0;
+      year++;
+    }
+  while (now > 0.0);
+
+  days_since_epoch -= days_in_year;
+  now += ((f64) days_in_year) * 86400;
+  year--;
+
+  month = 0;
+
+  do
+    {
+      days_in_month = days_per_month[month];
+      if (month == 1 && clib_timebase_is_leap_year (year))
+       days_in_month++;
+
+      days_since_epoch += days_in_month;
+      now = now - ((f64) days_in_month) * 86400.0;
+      month++;
+    }
+  while (now > 0.0);
+
+  days_since_epoch -= days_in_month;
+  now += ((f64) days_in_month) * 86400;
+  month--;
+
+  day_of_month = 1;
+  do
+    {
+      now = now - 86400;
+      day_of_month++;
+      days_since_epoch++;
+    }
+  while (now > 0.0);
+
+  day_of_month--;
+  days_since_epoch--;
+  now += 86400.0;
+
+  day_name_index = days_since_epoch % 7;
+
+  hours = (u32) (now / (3600.0));
+  now -= (f64) (hours * 3600);
+
+  minutes = (u32) (now / 60.0);
+  now -= (f64) (minutes * 60);
+
+  seconds = (u32) (now);
+  now -= (f64) (seconds);
+
+  nanoseconds = (f64) (now * 1e9);
+
+  cp->year = year;
+  cp->month = month;
+  cp->day = day_of_month;
+  cp->day_name_index = day_name_index;
+  cp->hour = hours;
+  cp->minute = minutes;
+  cp->second = seconds;
+  cp->nanosecond = nanoseconds;
+  cp->fractional_seconds = now;
+}
+
+f64
+clib_timebase_components_to_time (clib_timebase_component_t * cp)
+{
+  f64 now = 0;
+  u32 year, days_in_year, month, days_in_month;
+
+  year = 1970;
+
+  while (year < cp->year)
+    {
+      days_in_year = clib_timebase_is_leap_year (year) ? 366 : 365;
+      now += ((f64) days_in_year) * 86400.0;
+      year++;
+    }
+
+  month = 0;
+
+  while (month < cp->month)
+    {
+      days_in_month = days_per_month[month];
+      if (month == 1 && clib_timebase_is_leap_year (year))
+       days_in_month++;
+
+      now += ((f64) days_in_month) * 86400.0;
+      month++;
+    }
+
+  now += ((f64) cp->day - 1) * 86400.0;
+  now += ((f64) cp->hour) * 3600.0;
+  now += ((f64) cp->minute) * 60.0;
+  now += ((f64) cp->second);
+  now += ((f64) cp->nanosecond) * 1e-9;
+
+  return (now);
+}
+
+f64
+clib_timebase_find_sunday_midnight (f64 start_time)
+{
+  clib_timebase_component_t _c, *cp = &_c;
+
+  clib_timebase_time_to_components (start_time, cp);
+
+  /* back up to midnight */
+  cp->hour = cp->minute = cp->second = 0;
+
+  start_time = clib_timebase_components_to_time (cp);
+
+  while (cp->day_name_index != 3 /* sunday */ )
+    {
+      /* Back up one day */
+      start_time -= 86400.0;
+      clib_timebase_time_to_components (start_time, cp);
+    }
+  /* Clean up residual fraction */
+  start_time -= cp->fractional_seconds;
+  start_time += 1e-6;          /* 1us inside Sunday  */
+
+  return (start_time);
+}
+
+f64
+clib_timebase_offset_from_sunday (u8 * day)
+{
+  int i;
+
+  for (i = 0; i < ARRAY_LEN (day_names_calendar_order); i++)
+    {
+      if (!strncmp ((char *) day, day_names_calendar_order[i], 3))
+       return ((f64) i) * 86400.0;
+    }
+  return 0.0;
+}
+
+
+u8 *
+format_clib_timebase_time (u8 * s, va_list * args)
+{
+  f64 now = va_arg (*args, f64);
+  clib_timebase_component_t _c, *cp = &_c;
+
+  clib_timebase_time_to_components (now, cp);
+
+  s = format (s, "%s, %u %s %u %u:%02u:%02u",
+             day_names_epoch_order[cp->day_name_index],
+             cp->day,
+             month_short_names[cp->month],
+             cp->year, cp->hour, cp->minute, cp->second);
+  return (s);
+}
+
+uword
+unformat_clib_timebase_range_hms (unformat_input_t * input, va_list * args)
+{
+  clib_timebase_range_t *rp = va_arg (*args, clib_timebase_range_t *);
+  clib_timebase_component_t _c, *cp = &_c;
+  u32 start_hour, start_minute, start_second;
+  u32 end_hour, end_minute, end_second;
+
+  start_hour = start_minute = start_second
+    = end_hour = end_minute = end_second = 0;
+
+  if (unformat (input, "%u:%u:%u - %u:%u:%u",
+               &start_hour, &start_minute, &start_second,
+               &end_hour, &end_minute, &end_second))
+    ;
+  else if (unformat (input, "%u:%u - %u:%u",
+                    &start_hour, &start_minute, &end_hour, &end_minute))
+    ;
+  else if (unformat (input, "%u - %u", &start_hour, &end_hour))
+    ;
+  else
+    return 0;
+
+  clib_timebase_time_to_components (1e-6, cp);
+
+  cp->hour = start_hour;
+  cp->minute = start_minute;
+  cp->second = start_second;
+
+  rp->start = clib_timebase_components_to_time (cp);
+
+  cp->hour = end_hour;
+  cp->minute = end_minute;
+  cp->second = end_second;
+
+  rp->end = clib_timebase_components_to_time (cp);
+
+  return 1;
+}
+
+uword
+unformat_clib_timebase_range_vector (unformat_input_t * input, va_list * args)
+{
+  clib_timebase_range_t **rpp = va_arg (*args, clib_timebase_range_t **);
+  clib_timebase_range_t _tmp, *tmp = &_tmp;
+  clib_timebase_range_t *rp, *new_rp;
+  int day_range_match = 0;
+  int time_range_match = 0;
+  f64 range_start_time_offset;
+  f64 range_end_time_offset;
+  f64 now;
+  u8 *start_day = 0, *end_day = 0;
+
+  rp = *rpp;
+
+  while (1)
+    {
+      if (!day_range_match
+         && unformat (input, "%s - %s", &start_day, &end_day))
+       {
+         range_start_time_offset
+           = clib_timebase_offset_from_sunday (start_day);
+         range_end_time_offset = clib_timebase_offset_from_sunday (end_day);
+         vec_free (start_day);
+         vec_free (end_day);
+         day_range_match = 1;
+         time_range_match = 0;
+       }
+      else if (!day_range_match && unformat (input, "%s", &start_day))
+       {
+         range_start_time_offset
+           = clib_timebase_offset_from_sunday (start_day);
+         range_end_time_offset = range_start_time_offset + 86399.0;
+         day_range_match = 1;
+         vec_free (start_day);
+         day_range_match = 1;
+         time_range_match = 0;
+       }
+      else if (day_range_match &&
+              unformat (input, "%U", unformat_clib_timebase_range_hms, tmp))
+       {
+         /* Across the week... */
+         for (now = range_start_time_offset; now <= range_end_time_offset;
+              now += 86400.0)
+           {
+             vec_add2 (rp, new_rp, 1);
+             new_rp->start = now + tmp->start;
+             new_rp->end = now + tmp->end;
+           }
+         day_range_match = 0;
+         time_range_match = 1;
+       }
+      else if (time_range_match)
+       break;
+      else
+       {
+         vec_free (rp);
+         *rpp = 0;
+         return 0;
+       }
+    }
+
+  if (time_range_match)
+    {
+      *rpp = rp;
+      return 1;
+    }
+  else
+    {
+      vec_free (rp);
+      *rpp = 0;
+      return 0;
+    }
+}
+
+f64
+clib_timebase_summer_offset (clib_timebase_t * tb, f64 now)
+{
+  clib_timebase_component_t _c, *cp = &_c;
+  f64 second_sunday_march_2am;
+  f64 first_sunday_november_2am;
+
+  if (PREDICT_TRUE
+      (now >= tb->cached_year_start && now <= tb->cached_year_end))
+    {
+      if (now >= tb->cached_summer_start && now <= tb->cached_summer_end)
+       return tb->summer_offset;
+      else
+       return (0.0);
+    }
+
+  clib_timebase_time_to_components (now, cp);
+
+  cp->month = 0;
+  cp->day = 1;
+  cp->hour = 0;
+  cp->minute = 0;
+  cp->second = 1;
+
+  tb->cached_year_start = clib_timebase_components_to_time (cp);
+
+  cp->year += 1;
+
+  tb->cached_year_end = clib_timebase_components_to_time (cp);
+
+  cp->year -= 1;
+
+  /* Search for the second sunday in march, 2am */
+  cp->month = 2;
+  cp->day = 1;
+  cp->hour = 2;
+  cp->second = 0;
+  cp->nanosecond = 1;
+
+  /* March 1st will never be the second sunday... */
+  second_sunday_march_2am = clib_timebase_components_to_time (cp);
+  cp->day_name_index = 0;
+
+  /* Find the first sunday */
+  do
+    {
+      clib_timebase_time_to_components (second_sunday_march_2am, cp);
+      second_sunday_march_2am += 86400.0;
+    }
+  while (cp->day_name_index != 3 /* sunday */ );
+
+  /* Find the second sunday */
+  do
+    {
+      clib_timebase_time_to_components (second_sunday_march_2am, cp);
+      second_sunday_march_2am += 86400.0;
+    }
+  while (cp->day_name_index != 3 /* sunday */ );
+
+  second_sunday_march_2am -= 86400.0;
+
+  tb->cached_summer_start = second_sunday_march_2am;
+
+  /* Find the first sunday in November, which can easily be 11/1 */
+  cp->month = 10;
+  cp->day = 1;
+
+  first_sunday_november_2am = clib_timebase_components_to_time (cp);
+  clib_timebase_time_to_components (first_sunday_november_2am, cp);
+
+  while (cp->day_name_index != 3 /* sunday */ )
+    {
+      first_sunday_november_2am += 86400.0;
+      clib_timebase_time_to_components (first_sunday_november_2am, cp);
+    }
+
+  tb->cached_summer_end = first_sunday_november_2am;
+
+  if (now >= tb->cached_summer_start && now <= tb->cached_summer_end)
+    return tb->summer_offset;
+  else
+    return (0.0);
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */