From a98c40305deedc58860722fb9710075028c8b9f8 Mon Sep 17 00:00:00 2001 From: Dave Barach Date: Wed, 6 Jun 2018 10:52:08 -0400 Subject: Time range support for vppinfra Change-Id: I2356b1e05fd868b46b4d26ade760900a5739ca4d Signed-off-by: Dave Barach --- src/vppinfra/test_time_range.c | 178 ++++++++++++++++ src/vppinfra/time_range.c | 472 +++++++++++++++++++++++++++++++++++++++++ src/vppinfra/time_range.h | 131 ++++++++++++ 3 files changed, 781 insertions(+) create mode 100644 src/vppinfra/test_time_range.c create mode 100644 src/vppinfra/time_range.c create mode 100644 src/vppinfra/time_range.h (limited to 'src/vppinfra') diff --git a/src/vppinfra/test_time_range.c b/src/vppinfra/test_time_range.c new file mode 100644 index 00000000000..ccb63b20873 --- /dev/null +++ b/src/vppinfra/test_time_range.c @@ -0,0 +1,178 @@ +/* + * 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 + +static int +test_time_range_main (unformat_input_t * input) +{ + clib_timebase_t _tb, *tb = &_tb; + clib_timebase_component_t _c, *cp = &_c; + clib_timebase_range_t *rp = 0; + clib_timebase_range_t *this_rp; + unformat_input_t _input2, *input2 = &_input2; + char *test_range_string; + f64 sunday_midnight; + f64 now, then; + f64 start_time, end_time; + f64 timezone_offset; + + /* Init time base */ + clib_timebase_init (tb, -5 /* EST */ , CLIB_TIMEBASE_DAYLIGHT_USA); + + /* Set up summer time cache */ + now = clib_timebase_now (tb); + + /* Test it */ + now = clib_timebase_now (tb); + + /* show current time */ + fformat (stdout, "Current time in UTC%f, US daylight time rules:\n", + tb->timezone_offset / 3600.0); + fformat (stdout, "%U", format_clib_timebase_time, now); + + /* Test conversion to component structure */ + clib_timebase_time_to_components (now, cp); + now = clib_timebase_components_to_time (cp); + fformat (stdout, " -> %U\n", format_clib_timebase_time, now); + + /* + * test a few other dates, to verify summer time operation + * 2011: started sunday 3/13, ended sunday 11/6 + */ + + fformat (stdout, "Test daylight time rules:\n"); + + memset (cp, 0, sizeof (*cp)); + + /* Just before DST starts */ + cp->year = 2011; + cp->month = 2; + cp->day = 13; + cp->hour = 1; + cp->minute = 59; + cp->second = 59; + then = clib_timebase_components_to_time (cp); + + timezone_offset = clib_timebase_summer_offset_fastpath (tb, then); + + fformat (stdout, "%U should not be in DST, and it %s\n", + format_clib_timebase_time, then, + (timezone_offset != 0.0) ? "is" : "is not"); + + /* add two seconds */ + + then += 2.0; + + timezone_offset = clib_timebase_summer_offset_fastpath (tb, then); + + fformat (stdout, "%U should be in DST, and it %s\n", + format_clib_timebase_time, then, + (timezone_offset != 0.0) ? "is" : "is not"); + + /* Just before DST ends */ + cp->year = 2011; + cp->month = 10; + cp->day = 6; + cp->hour = 1; + cp->minute = 59; + cp->second = 59; + then = clib_timebase_components_to_time (cp); + + timezone_offset = clib_timebase_summer_offset_fastpath (tb, then); + + fformat (stdout, "%U should be in DST, and it %s\n", + format_clib_timebase_time, then, + (timezone_offset != 0.0) ? "is" : "is not"); + + /* add two seconds. */ + + then += 2.0; + + timezone_offset = clib_timebase_summer_offset_fastpath (tb, then); + + fformat (stdout, "%U should not be in DST, and it %s\n", + format_clib_timebase_time, then, + (timezone_offset != 0.0) ? "is" : "is not"); + + /* Back to the future... */ + clib_timebase_time_to_components (now, cp); + + fformat (stdout, "Test time range calculations:\n"); + + /* Find previous Sunday midnight */ + sunday_midnight = now = clib_timebase_find_sunday_midnight (now); + + clib_timebase_time_to_components (now, cp); + + fformat (stdout, "Sunday midnight: %U\n", format_clib_timebase_time, now); + + test_range_string = "Mon 11 - 17 Tue 7 - 11 Wed - Fri 8 - 18"; + + unformat_init_string (input2, test_range_string, + strlen (test_range_string)); + + if (unformat (input2, "%U", unformat_clib_timebase_range_vector, &rp)) + { + vec_foreach (this_rp, rp) + { + start_time = sunday_midnight + this_rp->start; + end_time = sunday_midnight + this_rp->end; + fformat (stdout, "range: %U - %U\n", + format_clib_timebase_time, start_time, + format_clib_timebase_time, end_time); + } + vec_free (rp); + } + else + { + fformat (stdout, "Time convert fail!\n"); + } + + unformat_free (input2); + + return 0; +} + +/* + * GDB callable function: vl - Return vector length of vector + */ +u32 +vl (void *p) +{ + return vec_len (p); +} + +#ifdef CLIB_UNIX +int +main (int argc, char *argv[]) +{ + unformat_input_t i; + int ret; + + unformat_init_command_line (&i, argv); + ret = test_time_range_main (&i); + unformat_free (&i); + + return ret; +} +#endif /* CLIB_UNIX */ +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vppinfra/time_range.c b/src/vppinfra/time_range.c new file mode 100644 index 00000000000..e502ca358db --- /dev/null +++ b/src/vppinfra/time_range.c @@ -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 + +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: + */ diff --git a/src/vppinfra/time_range.h b/src/vppinfra/time_range.h new file mode 100644 index 00000000000..6dcd82d7deb --- /dev/null +++ b/src/vppinfra/time_range.h @@ -0,0 +1,131 @@ +/* + * 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. + */ + +#ifndef included_time_range_h +#define included_time_range_h + +#include +#include + +typedef enum +{ + CLIB_TIMEBASE_DAYLIGHT_NONE = 0, + CLIB_TIMEBASE_DAYLIGHT_USA, +} clib_timebase_daylight_time_t; + +typedef struct +{ + /* provides f64 seconds since clib_time_init was called */ + clib_time_t clib_time; + /* + * time in f64 seconds since Thursday 1 Jan 1970 00:00:00 UTC + * when clib_time_init was called + */ + f64 time_zero; + f64 timezone_offset; + f64 summer_offset; + clib_timebase_daylight_time_t daylight_time_type; + f64 cached_year_start; + f64 cached_year_end; + f64 cached_summer_start; + f64 cached_summer_end; +} clib_timebase_t; + +typedef struct +{ + u32 year, month, day, hour, minute, second, nanosecond; + /* 0 => Thursday */ + u32 day_name_index; + f64 fractional_seconds; +} clib_timebase_component_t; + +typedef struct +{ + f64 start, end; +} clib_timebase_range_t; + +void clib_timebase_init (clib_timebase_t * tb, i32 timezone_offset_in_hours, + clib_timebase_daylight_time_t daylight_type); + +void clib_timebase_time_to_components (f64 now, + clib_timebase_component_t * cp); + +f64 clib_timebase_components_to_time (clib_timebase_component_t * cp); + +f64 clib_timebase_find_sunday_midnight (f64 start_time); +f64 clib_timebase_offset_from_sunday (u8 * day); +f64 clib_timebase_summer_offset (clib_timebase_t * tb, f64 now); + +unformat_function_t unformat_clib_timebase_range_hms; +unformat_function_t unformat_clib_timebase_range_vector; + +format_function_t format_clib_timebase_time; + +static inline f64 clib_timebase_summer_offset_fastpath + (clib_timebase_t * tb, f64 now) +{ + 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; + } + else + return clib_timebase_summer_offset (tb, now); +} + +static inline f64 +clib_timebase_now (clib_timebase_t * tb) +{ + f64 now; + + now = tb->time_zero + clib_time_now (&tb->clib_time); + now += tb->timezone_offset; + now += clib_timebase_summer_offset_fastpath (tb, now); + + return now; +} + +static inline int +clib_timebase_is_leap_year (u32 year) +{ + int rv; + + if (PREDICT_TRUE ((year % 4) != 0)) + return 0; + + rv = 0; + + if ((year % 4) == 0) + rv = 1; + if ((year % 100) == 0) + rv = 0; + if ((year % 400) == 0) + rv = 1; + return rv; +} + +#endif /* included_time_range_h */ + + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ -- cgit 1.2.3-korg