which returns a _boxed_ time, and not a stupid integer.
Caipirinha,is,a,light,and,refreshing,drink!
```
-// Copyright (C) 2021 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+### %(time:)
+
+Return the current time.
+
+.Return: _date_.
+
+If no argument is passed, it returns the current time. Otherwise if the argument is:
+
+* `now` - returns the current time
+* `request` - returns the time at which the request packet was received (always less than `now`!)
+* any other string is parsed as type `date`, using the normal date parsing routines.
+
+[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
+
+[source,unlang]
+----
+&Acct-Start-Time := %(time:now)
+----
+
+// Copyright (C) 2023 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
// Development of this documentation was sponsored by Network RADIUS SAS.
= Single Letter Expansions
The following are single letter expansions. These expansions do not
-use the typical `%{...}` format. Instead, they are short-cuts for
-simple, common cases.
+use the typical `%{...}` format.
+
+[NOTE]
+====
+In general, these expansions _should not be used_.
+====
+
+The xref:xlat/builtin.adoc[builtin] `%(time:...)` expansion and the
+xref:raddb:mods-available/date.adoc[date] module should be used
+instead of these expansions.
+
== Current Time
`%c`::
The current Unix epoch time in seconds. This is an unsigned decimal number.
-It should be used with time-based calculations.
`%C`::
The microsecond component of the current epoch time. This is an unsigned
-decimal number. It should be used with time-based calculations.
+decimal number.
+Note that creating a "fractional" time from an expansion like `%c.%C`
+should only be used when creating strings, such as for logging. If
+you need to calculate time differences, the time values should be left
+as type `date`, and then you can substract two dates to get a
+`time_delta`. That `time_delta` can then be printed in an appropriate
+precision and scale.
== Request Time
`%l`::
-The Unix timestamp of when the request was received. This is an unsigned
-decimal number. It should be used with time-based calculations.
+The Unix timestamp of when the request was received. This is an
+unsigned decimal number, and returns an integer number of seconds
+since the Unix epoch.
+
+This expansion is only useful where the time resolution is in seconds.
+If more resolution is needed, the xref:xlat/builtin.adoc[builtin]
+`%(time:...)` expansion should be used instead.
`%Y`::
Request timestamp in SQL format, `YYYY-mmm-ddd HH:MM:SS`.
+The xref:raddb:mods-available/date.adoc[date] module should be used
+instead.
+
`%t`::
Request timestamp in _ctime_ format, `Www Mmm dd HH:MM:SS YYYY`.
+The xref:raddb:mods-available/date.adoc[date] module should be used
+instead.
+
`%T`::
Request timestamp in ISO format, `YYYY-mm-ddTHH:MM:SS.000`.
+The xref:raddb:mods-available/date.adoc[date] module should be used
+instead.
-// Copyright (C) 2021 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Copyright (C) 2023 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
// Development of this documentation was sponsored by Network RADIUS SAS.
}
+static xlat_arg_parser_t const xlat_func_time_args[] = {
+ { .required = false, .single = true, .type = FR_TYPE_STRING },
+ XLAT_ARG_PARSER_TERMINATOR
+};
+
+/** Return the time as a #FR_TYPE_DATE
+ *
+@verbatim
+%(time:)
+@endverbatim
+ *
+ * Example:
+@verbatim
+update reply {
+ &Reply-Message := "%{expr:%{time:now} - %{time:request}}
+}
+@endverbatim
+ *
+ * @ingroup xlat_functions
+ */
+static xlat_action_t xlat_func_time(TALLOC_CTX *ctx, fr_dcursor_t *out,
+ UNUSED xlat_ctx_t const *xctx,
+ request_t *request, FR_DLIST_HEAD(fr_value_box_list) *in)
+{
+ fr_value_box_t *arg = fr_value_box_list_head(in);
+ fr_value_box_t *vb;
+ fr_unix_time_t value;
+
+ if (!arg || (strcmp(arg->vb_strvalue, "now") == 0)) {
+ value = fr_time_to_unix_time(fr_time());
+
+ } else if (strcmp(arg->vb_strvalue, "request") == 0) {
+ value = fr_time_to_unix_time(request->packet->timestamp);
+
+ } 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;
+ fr_dcursor_append(out, vb);
+
+ return XLAT_ACTION_DONE;
+}
+
+
/** Change case of a string
*
* If upper is true, change to uppercase, otherwise, change to lowercase
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("trigger", trigger_xlat, FR_TYPE_STRING, trigger_xlat_args);
/*