Commit 80b953f76f for asterisk.org

commit 80b953f76ff41943034f0d95e81b2edd21a542c4
Author: George Joseph <gjoseph@sangoma.com>
Date:   Tue Mar 31 10:16:48 2026 -0600

    res_cdrel_custom: Resolve several formatting issues.

    Several issues are resolved:

    * Internally, floats were used for timestamp values but this could result
    in wrapping so they've been changed to doubles.

    * Historically, the default CEL eventtime format is `<seconds>.<microseconds>`
    with `<microseconds>` always being 6 digits.  This should have continued to be
    the case but res_cdrel_custom wasn't checking the `dateformat` setting in
    cel.conf and was defaulting to `%F %T`.  res_cdrel_custom now gets the default
    date format from cel.conf, which will be whatever the `dateformat` parameter
    is set to or `<seconds>.<microseconds>` if not set.

    * The timeval field formatter for both CDR and CEL wasn't handling custom
    strftime format strings correctly.  This is now fixed so you should be able
    to specifiy custom strftime format strings for the CEL `eventtime` and CDR
    `start`, `answer` and `end` fields.  For example: `eventtime(%FT%T%z)`.

    Resolves: #1844
    Resolves: #1845

diff --git a/configs/samples/cdr_custom.conf.sample b/configs/samples/cdr_custom.conf.sample
index 760c039eba..3a120428a3 100644
--- a/configs/samples/cdr_custom.conf.sample
+++ b/configs/samples/cdr_custom.conf.sample
@@ -106,7 +106,8 @@
     ;
     ; The default output format for the "start", "answer" and "end" timestamp fields
     ; is the "%Y-%m-%d %T" strftime string format however you can also format those
-    ; fields as an int64 or a float: `start(int64),answer(float),end`.
+    ; fields as an int64 or a double: `start(int64)` or `start(double)` or
+    ; provide your own strftime format string: `start(%FT%T%z)`.
     ;
     ; The "disposition" and "amaflags" are formatted as their string names like
     ; "ANSWERED" and "DOCUMENTATION" by default but if you just want the numbers and
diff --git a/configs/samples/cel_custom.conf.sample b/configs/samples/cel_custom.conf.sample
index 03debc6e29..e025dd6bf1 100644
--- a/configs/samples/cel_custom.conf.sample
+++ b/configs/samples/cel_custom.conf.sample
@@ -111,9 +111,12 @@
     ; you can force the uniqueid field to not be quoted with `uniqueid(noquote)`. The
     ; example in fields above shows this.
     ;
-    ; The default output format for the "EventTime" timestamp field is the "%Y-%m-%d %T"
-    ; strftime string format however you can also format the field as an int64 or a
-    ; float: `eventtime(int64)` or `eventtime(float)`.
+    ; The default output format for the "EventTime" timestamp field is taken from the
+    ; "dateformat" parameter in cel.conf.  If that's not set, the default is
+    ; "<seconds>.<microseconds>" where "<microseconds>" is always 6 digits with
+    ; leading zeros.  You can also format the field as an int64 or a double:
+    ; `eventtime(int64)` or `eventtime(double)` or provide your own strftime
+    ; format string: `eventtime(%FT%T%z)`.
     ;
     ; Unlike CDRs, the "amaflags" field is output as its numerical value by default
     ; for historical reasons.  You can output it as its friendly string with
diff --git a/res/cdrel_custom/cdrel.h b/res/cdrel_custom/cdrel.h
index 84787840e9..e1542b7db4 100644
--- a/res/cdrel_custom/cdrel.h
+++ b/res/cdrel_custom/cdrel.h
@@ -78,12 +78,13 @@ enum cdrel_data_type {
 	cdrel_type_uservar,
 	cdrel_type_event_type,
 	cdrel_type_event_enum,
+	cdrel_type_cel_timefmt,
 	cdrel_data_type_strings_end,
 	cdrel_type_int32,
 	cdrel_type_uint32,
 	cdrel_type_int64,
 	cdrel_type_uint64,
-	cdrel_type_float,
+	cdrel_type_double,
 	cdrel_data_type_end
 };

@@ -180,7 +181,7 @@ struct cdrel_value {
 		int64_t int64;
 		uint64_t uint64;
 		struct timeval tv;
-		float floater;
+		double doubler;
 	} values;
 };

diff --git a/res/cdrel_custom/config.c b/res/cdrel_custom/config.c
index d4655f0792..49543df3d2 100644
--- a/res/cdrel_custom/config.c
+++ b/res/cdrel_custom/config.c
@@ -432,6 +432,7 @@ static struct cdrel_field *field_alloc(struct cdrel_config *config, const char *
 			if (strchr(qualifier, '%') != NULL) {
 				data_swap = ast_strdupa(qualifier);
 				ast_set_flag(&field_flags, cdrel_flag_format_spec);
+				forced_output_data_type = cdrel_type_string;
 				ast_debug(3, "   Using qualifier '%s' for field '%s' flags: %s\n", qualifier,
 					field_name, ast_str_tmp(128, cdrel_get_field_flags(&field_flags, &STR_TMP)));
 			}
@@ -466,6 +467,17 @@ static struct cdrel_field *field_alloc(struct cdrel_config *config, const char *
 		return NULL;
 	}

+	if (ast_test_flag(&field_flags, cdrel_flag_format_spec)
+		&& registered_field->input_data_type != cdrel_type_timeval) {
+		ast_log(LOG_WARNING, "%s->%s: Custom format '%s' ignored for field '%s'."
+			" Only timeval types can use custom format strings.\n",
+			cdrel_basename(config->config_filename), cdrel_basename(config->output_filename),
+			data, field_name);
+		forced_output_data_type = cdrel_data_type_end;
+		ast_clear_flag(&field_flags, cdrel_flag_format_spec);
+		data = NULL;
+	}
+
 	field = ast_calloc(1, sizeof(*registered_field) + strlen(input_field_template) + 1);
 	if (!field) {
 		return NULL;
@@ -1127,7 +1139,7 @@ static struct cdrel_config *load_text_file_legacy_config(enum cdrel_record_type
 		return NULL;
 	}

-	ast_log(LOG_NOTICE, "%s->%s: Logging %s records\n",
+	ast_log(LOG_NOTICE, "%s->%s: Logging legacy %s records as advanced\n",
 		cdrel_basename(config->config_filename), cdrel_basename(config->output_filename),
 		RECORD_TYPE_STR(config->record_type));

diff --git a/res/cdrel_custom/formatters.c b/res/cdrel_custom/formatters.c
index fc4e22a5dc..2be6d26cb6 100644
--- a/res/cdrel_custom/formatters.c
+++ b/res/cdrel_custom/formatters.c
@@ -118,7 +118,7 @@ DEFINE_FORMATTER(uint32, uint32, uint32_t, "%u")
 DEFINE_FORMATTER(int32, int32, int32_t, "%d")
 DEFINE_FORMATTER(uint64, uint64, uint64_t, "%lu")
 DEFINE_FORMATTER(int64, int64, int64_t, "%ld")
-DEFINE_FORMATTER(float, floater, float, "%.1f")
+DEFINE_FORMATTER(double, doubler, double, "%.6f")

 static int format_timeval(struct cdrel_config *config,
 	struct cdrel_field *field, struct cdrel_value *input_value, struct cdrel_value *output_value)
@@ -134,11 +134,20 @@ static int format_timeval(struct cdrel_config *config,
 		output_value->data_type = cdrel_type_int64;
 		output_value->values.int64 = input_value->values.tv.tv_sec;
 		return format_int64(config, field, output_value, output_value);
-	} else if (field->output_data_type == cdrel_type_float) {
-		output_value->data_type = cdrel_type_float;
-		output_value->values.floater = ((float)input_value->values.tv.tv_sec) + ((float)input_value->values.tv.tv_usec) / 1000000.0;
-		return format_float(config, field, output_value, output_value);
-	} else 	if (!ast_strlen_zero(field->data)) {
+	} else if (field->output_data_type == cdrel_type_double) {
+		output_value->data_type = cdrel_type_double;
+		output_value->values.doubler = ((double)input_value->values.tv.tv_sec) + ((double)input_value->values.tv.tv_usec) / 1000000.0;
+		return format_double(config, field, output_value, output_value);
+	} else if (field->output_data_type == cdrel_type_cel_timefmt) {
+		res = ast_cel_format_eventtime(input_value->values.tv, tempbuf, 64);
+		if (res != 0) {
+			return res;
+		}
+		input_value->values.string = tempbuf;
+		input_value->data_type = cdrel_type_string;
+		output_value->data_type = cdrel_type_string;
+		return format_string(config, field, input_value, output_value);
+	} else if (!ast_strlen_zero(field->data)) {
 		format = field->data;
 	}

@@ -186,7 +195,7 @@ int load_formatters(void)
 	cdrel_field_formatters[cdrel_type_int64] = format_int64;
 	cdrel_field_formatters[cdrel_type_uint64] = format_uint64;
 	cdrel_field_formatters[cdrel_type_timeval] = format_timeval;
-	cdrel_field_formatters[cdrel_type_float] = format_float;
+	cdrel_field_formatters[cdrel_type_double] = format_double;
 	cdrel_field_formatters[cdrel_type_amaflags] = format_amaflags;
 	cdrel_field_formatters[cdrel_type_disposition] = format_disposition;

diff --git a/res/cdrel_custom/getters_cdr.c b/res/cdrel_custom/getters_cdr.c
index 45667db57e..8e87cb25a1 100644
--- a/res/cdrel_custom/getters_cdr.c
+++ b/res/cdrel_custom/getters_cdr.c
@@ -53,7 +53,7 @@ DEFINE_CDR_GETTER(uint32, uint32, uint32_t)
 DEFINE_CDR_GETTER(int64, int64, int64_t)
 DEFINE_CDR_GETTER(uint64, uint64, uint64_t)
 DEFINE_CDR_GETTER(tv, timeval, struct timeval)
-DEFINE_CDR_GETTER(floater, float, float)
+DEFINE_CDR_GETTER(doubler, double, double)

 static int cdr_get_literal(void *record, struct cdrel_config *config,
 	struct cdrel_field *field, struct cdrel_value *value)
@@ -108,7 +108,7 @@ int load_cdr(void)
 	cdrel_field_getters[cdrel_record_cdr][cdrel_type_int64] = cdr_get_int64;
 	cdrel_field_getters[cdrel_record_cdr][cdrel_type_uint64] = cdr_get_uint64;
 	cdrel_field_getters[cdrel_record_cdr][cdrel_type_timeval] = cdr_get_timeval;
-	cdrel_field_getters[cdrel_record_cdr][cdrel_type_float] = cdr_get_float;
+	cdrel_field_getters[cdrel_record_cdr][cdrel_type_double] = cdr_get_double;
 	cdrel_field_getters[cdrel_record_cdr][cdrel_type_uservar] = cdr_get_uservar;
 	cdrel_dummy_channel_allocators[cdrel_record_cdr] = dummy_chan_alloc_cdr;

diff --git a/res/cdrel_custom/registry.c b/res/cdrel_custom/registry.c
index 472b147c45..da678aee03 100644
--- a/res/cdrel_custom/registry.c
+++ b/res/cdrel_custom/registry.c
@@ -47,7 +47,7 @@
 static const struct cdrel_field cdrel_field_registry[] = {
 	REGISTER_FIELD(cdrel_record_cel, AST_EVENT_IE_CEL_EVENT_ENUM, "eventenum", cdrel_type_event_enum, cdrel_type_string),
 	REGISTER_FIELD(cdrel_record_cel, AST_EVENT_IE_CEL_EVENT_TYPE, "eventtype", cdrel_type_event_type, cdrel_type_string),
-	REGISTER_FIELD(cdrel_record_cel, AST_EVENT_IE_CEL_EVENT_TIME, "eventtime", cdrel_type_timeval, cdrel_type_string),
+	REGISTER_FIELD(cdrel_record_cel, AST_EVENT_IE_CEL_EVENT_TIME, "eventtime", cdrel_type_timeval, cdrel_type_cel_timefmt),
 	REGISTER_FIELD(cdrel_record_cel, AST_EVENT_IE_CEL_EVENT_TIME_USEC, "eventtimeusec", cdrel_type_uint32, cdrel_type_uint32),
 	REGISTER_FIELD(cdrel_record_cel, AST_EVENT_IE_CEL_USEREVENT_NAME, "usereventname", cdrel_type_string, cdrel_type_string),
 	REGISTER_FIELD(cdrel_record_cel, AST_EVENT_IE_CEL_USEREVENT_NAME, "userdeftype", cdrel_type_string, cdrel_type_string),
diff --git a/res/res_cdrel_custom.c b/res/res_cdrel_custom.c
index 179db87acc..773007a345 100644
--- a/res/res_cdrel_custom.c
+++ b/res/res_cdrel_custom.c
@@ -166,12 +166,13 @@ const char *cdrel_data_type_map[] = {
 	[cdrel_type_uservar] = "uservar",
 	[cdrel_type_event_type] = "event_type",
 	[cdrel_type_event_enum] = "event_enum",
+	[cdrel_type_cel_timefmt] = "cel_timefmt",
 	[cdrel_data_type_strings_end] = "!!STRINGS END!!",
 	[cdrel_type_int32] = "int32",
 	[cdrel_type_uint32] = "uint32",
 	[cdrel_type_int64] = "int64",
 	[cdrel_type_uint64] = "uint64",
-	[cdrel_type_float] = "float",
+	[cdrel_type_double] = "double",
 	[cdrel_data_type_end] = "!!END!!",
 };