From: Alan T. DeKok Date: Sun, 22 Jan 2023 13:51:30 +0000 (-0500) Subject: add functions to get gmt offset and daylight savings flag X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=c12e2a67b9f47860b1ed601081e3ab7e61148cfd;p=thirdparty%2Ffreeradius-server.git add functions to get gmt offset and daylight savings flag we should note that the server tracks times in UTC, so (date) foo % (time_delta) 1d will return the offset from the UTC day, not from the local time. This could perhaps be more prominent. But tracking times internally in UTC is the only way to make sure we're not screwed up by time zone and DST changes. --- diff --git a/doc/antora/modules/reference/pages/xlat/builtin.adoc b/doc/antora/modules/reference/pages/xlat/builtin.adoc index a48786dceed..5a353b1fe30 100644 --- a/doc/antora/modules/reference/pages/xlat/builtin.adoc +++ b/doc/antora/modules/reference/pages/xlat/builtin.adoc @@ -1141,19 +1141,83 @@ If no argument is passed, it returns the current time. Otherwise if the argumen * `now` - returns the current time * `request` - returns the time at which the request packet was received (always less than `now`!) +* `offset` - returns a `time_delta` of the current time zone offset. This value may be negative. +* `dst` - returns a `bool` indicating whether or not the system is running in daylight savings time. * any other string is parsed as type `date`, using the normal date parsing routines. +.Example + +[source,unlang] +---- +&Acct-Start-Time := %(time:now) +---- + [NOTE] ==== This expansion should be used in preference to the xref:xlat/character.adoc[single letter expansions] `%l`. That expansion returns integer seconds, and is not suitable for millisecond or microsecond resolution. ==== -.Example +Due to limitations in the underlying time funtions (both system and +FreeRADIUS), previous versions of FreeRADIUS did not always handle +dates correctly. When print dates, the time zone information would +sometimes not be printed, or the time zone would sometimes be ignored +when parsed a date from a string. + +Even if the time zone was used, the nature of time zones means that +there may be duplicate time zone names! For example, the time zone +`CST` has three separate (and different) definitions. + +The server now tracks all times internally as UTC, and by default +prints times as UTC, or prints the time zone as a decimal offset from +UTC, instead of printing an ambiguous name. + +This handling of time zones has some minor side effects. When +calculating values like "tomorrow", the default is to return the UTC +version of "tomorrow". This value may not be what you want. + +In order to correctly calculate the local value of "tomorrow", it is +necessary to add the local time zone offset to the UTC time. + +Note that the server will automatically determine (and use) any +daylight savings time differences. So the value of `%(time:offset)` +may change while the server is running! + +The following example calculates the correct value of "tomorrow" in +UTC by using the following steps: +* taking the current time of the request +* calculating how long it has been since the start of the day as a `time_delta` +* subtracting that `time_delta` from the current time + +.Example Calculating the UTC value of "tomorrow" [source,unlang] ---- -&Acct-Start-Time := %(time:now) +&Tmp-Date-0 := %(time:request) + +&Tmp-Time-Delta-0 := &Tmp-Date-0 % (time_delta) 1d + +&Tmp-Date-1 := &Tmp-Date-0 - &Tmp-Time-Delta-0 ---- +The following example calculates the correct value of "tomorrow" in +local time by using the preceding example, but then adding the local +time zone offset to the final value. + +.Example Calculating the local value of "tomorrow" +[source,unlang] +---- +&Tmp-Date-0 := %(time:request) + +&Tmp-Time-Delta-0 := &Tmp-Date-0 % (time_delta) 1d + +&Tmp-Date-1 := &Tmp-Date-0 - &Tmp-Time-Delta-0 + (time_delta) 1d + +&Tmp-Date-1 += %(time:offset) +---- + +This kind of math works well for "tomorrow", but it is less useful for +"next week Monday", or "start of next month". The `%{nexttime:..}` +expansion above should be used for those time operations. + // Copyright (C) 2023 Network RADIUS SAS. Licenced under CC-by-NC 4.0. // Development of this documentation was sponsored by Network RADIUS SAS. diff --git a/src/lib/unlang/xlat_builtin.c b/src/lib/unlang/xlat_builtin.c index 226cda34480..f8c79dfd564 100644 --- a/src/lib/unlang/xlat_builtin.c +++ b/src/lib/unlang/xlat_builtin.c @@ -3510,6 +3510,16 @@ static xlat_action_t xlat_func_time(TALLOC_CTX *ctx, fr_dcursor_t *out, } else if (strcmp(arg->vb_strvalue, "request") == 0) { value = fr_time_to_unix_time(request->packet->timestamp); + } else if (strcmp(arg->vb_strvalue, "offset") == 0) { + MEM(vb = fr_value_box_alloc(ctx, FR_TYPE_TIME_DELTA, NULL, false)); + vb->vb_time_delta = fr_time_gmtoff(); + goto append; + + } else if (strcmp(arg->vb_strvalue, "dst") == 0) { + MEM(vb = fr_value_box_alloc(ctx, FR_TYPE_BOOL, NULL, false)); + vb->vb_bool = fr_time_is_dst(); + goto append; + } else if (fr_unix_time_from_str(&value, arg->vb_strvalue, FR_TIME_RES_SEC) < 0) { REDEBUG("Invalid time specification '%s'", arg->vb_strvalue); return XLAT_ACTION_FAIL; @@ -3517,6 +3527,8 @@ static xlat_action_t xlat_func_time(TALLOC_CTX *ctx, fr_dcursor_t *out, MEM(vb = fr_value_box_alloc(ctx, FR_TYPE_DATE, NULL, false)); vb->vb_date = value; + +append: fr_dcursor_append(out, vb); return XLAT_ACTION_DONE; @@ -4057,7 +4069,7 @@ do { \ XLAT_REGISTER_ARGS("nexttime", xlat_func_next_time, FR_TYPE_UINT64, xlat_func_next_time_args); XLAT_REGISTER_ARGS("pairs", xlat_func_pairs, FR_TYPE_STRING, xlat_func_pairs_args); XLAT_REGISTER_ARGS("subst", xlat_func_subst, FR_TYPE_STRING, xlat_func_subst_args); - XLAT_REGISTER_ARGS("time", xlat_func_time, FR_TYPE_DATE, xlat_func_time_args); + XLAT_REGISTER_ARGS("time", xlat_func_time, FR_TYPE_VOID, xlat_func_time_args); XLAT_REGISTER_ARGS("trigger", trigger_xlat, FR_TYPE_STRING, trigger_xlat_args); /* diff --git a/src/lib/util/time.c b/src/lib/util/time.c index 0003b449396..a9aacf7e358 100644 --- a/src/lib/util/time.c +++ b/src/lib/util/time.c @@ -105,7 +105,7 @@ size_t fr_time_precision_table_len = NUM_ELEMENTS(fr_time_precision_table); _Atomic int64_t our_realtime; //!< realtime at the start of the epoch in nanoseconds. static char const *tz_names[2] = { NULL, NULL }; //!< normal, DST, from localtime_r(), tm_zone static long gmtoff[2] = {0, 0}; //!< from localtime_r(), tm_gmtoff -static int isdst = 0; //!< from localtime_r(), tm_is_dst +static bool isdst = false; //!< from localtime_r(), tm_is_dst #ifdef HAVE_CLOCK_GETTIME int64_t our_epoch; @@ -1282,3 +1282,19 @@ fr_slen_t fr_unix_time_to_str(fr_sbuff_t *out, fr_unix_time_t time, fr_time_res_ FR_SBUFF_SET_RETURN(out, &our_out); } + +/** Get the offset to gmt. + * + */ +fr_time_delta_t fr_time_gmtoff(void) +{ + return fr_time_delta_wrap(gmtoff[isdst]); +} + +/** Whether or not we're daylight savings. + * + */ +bool fr_time_is_dst(void) +{ + return isdst; +} diff --git a/src/lib/util/time.h b/src/lib/util/time.h index b2630a16407..b52f0fab88f 100644 --- a/src/lib/util/time.h +++ b/src/lib/util/time.h @@ -1013,6 +1013,10 @@ int fr_unix_time_from_str(fr_unix_time_t *date, char const *date_str, fr_time_r fr_slen_t fr_unix_time_to_str(fr_sbuff_t *out, fr_unix_time_t time, fr_time_res_t res) CC_HINT(nonnull); +fr_time_delta_t fr_time_gmtoff(void); + +bool fr_time_is_dst(void); + #ifdef __cplusplus } #endif