From 86a8fafbea4d668cc7ea4b20053954d2f8df3573 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Tue, 20 Oct 2026 13:46:05 -0500 Subject: [PATCH 1/2] xport: preserve trailing period when reconstructing formats xport_construct_format() only emitted a period when the format had decimals, so a format written as DATE9. was read back as DATE9. Since the format name, width, and decimals are stored separately in the namestr, the trailing period must be re-added whenever a width is present (formats with no width, like $, are unaffected). Reported in tidyverse/haven#782. --- src/sas/readstat_xport_read.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sas/readstat_xport_read.c b/src/sas/readstat_xport_read.c index f5d22a8d..d3959619 100644 --- a/src/sas/readstat_xport_read.c +++ b/src/sas/readstat_xport_read.c @@ -314,6 +314,8 @@ static readstat_error_t xport_construct_format(char *dst, size_t dst_len, } if (decimals) { pos += snprintf(pos, dst_len-(pos-dst), ".%d", decimals); + } else if (width) { + pos += snprintf(pos, dst_len-(pos-dst), "."); } free(format); From 6f7f7a8c14c20a98dfd2ad43c64037fe22bcdedd Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Tue, 20 Oct 2026 17:22:15 -0500 Subject: [PATCH 2/2] Use canonical SAS format strings in round-trip tests XPORT can't record whether a format had a trailing period, and now always reconstructs it, so test with formats that include it. --- src/test/test_list.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/test_list.h b/src/test/test_list.h index 31390334..b24944c4 100644 --- a/src/test/test_list.h +++ b/src/test/test_list.h @@ -1479,7 +1479,7 @@ static rt_test_group_t _test_groups[] = { .test_formats = RT_FORMAT_SAS, .columns = { { .name = "VAR1", .type = READSTAT_TYPE_DOUBLE, .format = "10.3", .label_set = "10.3" }, - { .name = "VAR2", .type = READSTAT_TYPE_STRING, .format = "$CHAR3", .label_set = "$CHAR3" } + { .name = "VAR2", .type = READSTAT_TYPE_STRING, .format = "$CHAR3.", .label_set = "$CHAR3." } } }, { @@ -1487,7 +1487,7 @@ static rt_test_group_t _test_groups[] = { .test_formats = RT_FORMAT_SAS7BDAT | RT_FORMAT_XPORT_8, .columns = { { .name = "VAR1", .type = READSTAT_TYPE_DOUBLE, .informat = "10.3" }, - { .name = "VAR2", .type = READSTAT_TYPE_STRING, .informat = "$CHAR3" } + { .name = "VAR2", .type = READSTAT_TYPE_STRING, .informat = "$CHAR3." } } }, { @@ -1503,9 +1503,9 @@ static rt_test_group_t _test_groups[] = { .columns = { { .name = "VAR1", .type = READSTAT_TYPE_DOUBLE, .format = "10.3", .label_set = "10.3" }, { .name = "VAR2", .type = READSTAT_TYPE_DOUBLE, .format = "F", .label_set = "F" }, - { .name = "VAR3", .type = READSTAT_TYPE_STRING, .format = "$CHAR3", .label_set = "$CHAR3" }, + { .name = "VAR3", .type = READSTAT_TYPE_STRING, .format = "$CHAR3.", .label_set = "$CHAR3." }, { .name = "VAR4", .type = READSTAT_TYPE_STRING, .format = "COMMA10.3", .label_set = "COMMA10.3" }, - { .name = "VAR5", .type = READSTAT_TYPE_DOUBLE, .format = "YYQP1", .label_set = "YYQP1" } + { .name = "VAR5", .type = READSTAT_TYPE_DOUBLE, .format = "YYQP1.", .label_set = "YYQP1." } } }, {