* `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.
} 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;
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;
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);
/*
_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;
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;
+}