mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-17 16:38:54 +00:00
Miller's strftime delegates to the full-featured lestrrat-go/strftime library, but strptime uses an in-tree fork of a small subset-only package, so strftime output couldn't always be parsed back by strptime with the same format string. This adds the format codes needed to round-trip common formats like "%a %b %e %T %Y": * %a / %A (weekday name) and %h (alias of %b): straightforward formatMap entries, same pattern as the existing %b/%B. * %e (space-padded day of month): needed dedicated width-detection logic, since %e's own optional leading pad space is otherwise indistinguishable from a literal separator space when the code searches for the next literal text to bound a field -- this was the root cause of the round-trip failure in the linked issue. * %c, %x, %X: shorthand aliases (expanding to %a %b %e %H:%M:%S %Y, %m/%d/%y, and %H:%M:%S respectively), same mechanism already used for %T/%D/%F/%R/%r. Also documents the %D/%F/%r/%R/%T shorthands in the strptime table in reference-dsl-time.md.in, which were already supported but missing from the docs -- this was the exact confusion reported in the discussion linked from #1518. Round-trip tests added in pkg/bifs/datetime_test.go assert strptime(strftime(t, fmt), fmt) == t across the newly-supported formats, plus table-driven cases in pkg/pbnjay-strptime/strptime_test.go covering %e's edge cases (padded/unpadded single digit, double digit, adjacent to another code with no separator, at end of string). Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
98 lines
3.7 KiB
Go
98 lines
3.7 KiB
Go
package bifs
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/johnkerl/miller/v6/pkg/mlrval"
|
|
)
|
|
|
|
// TestBIF_strftime_strptime_roundtrip checks that for each of a set of formats
|
|
// exercising the newly-added %a %A %e %h %x %X %c strptime codes,
|
|
// strptime(strftime(t, fmt), fmt) == t. This is the exact failure mode from
|
|
// https://github.com/johnkerl/miller/issues/1518, where strptime accepted fewer
|
|
// format codes than strftime produced.
|
|
func TestBIF_strftime_strptime_roundtrip(t *testing.T) {
|
|
formats := []string{
|
|
"%a %b %e %T %Y",
|
|
"%A %b %e %T %Y",
|
|
"%a %h %e %T %Y",
|
|
"%A %h %e %T %Y",
|
|
"%c",
|
|
"%x %X",
|
|
"%Y-%m-%d %H:%M:%S",
|
|
}
|
|
|
|
epochSecondsValues := []int64{
|
|
0, // 1970-01-01, single-digit day
|
|
1709552468, // 2024-03-04 11:41:08, single-digit day
|
|
1729555200, // 2024-10-22 00:00:00, double-digit day
|
|
1735732805, // 2025-01-01 12:00:05, single-digit day, new year
|
|
}
|
|
|
|
for _, format := range formats {
|
|
for _, epochSeconds := range epochSecondsValues {
|
|
formatted := BIF_strftime(mlrval.FromInt(epochSeconds), mlrval.FromString(format))
|
|
formattedString, isString := formatted.GetStringValue()
|
|
assert.True(t, isString, "strftime(%d, %q) produced non-string %v", epochSeconds, format, formatted)
|
|
|
|
parsed := BIF_strptime(mlrval.FromString(formattedString), mlrval.FromString(format))
|
|
parsedSeconds, isNumeric := parsed.GetNumericToFloatValue()
|
|
assert.True(t, isNumeric, "strptime(%q, %q) produced non-numeric %v", formattedString, format, parsed)
|
|
|
|
assert.Equal(
|
|
t, float64(epochSeconds), parsedSeconds,
|
|
"round-trip mismatch for format %q: strftime(%d) = %q, strptime(...) = %v",
|
|
format, epochSeconds, formattedString, parsedSeconds,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestBIF_strptime_new_format_codes exercises the newly-supported %a %A %e %h
|
|
// %x %X %c strptime format codes directly, including %e's space-padding edge
|
|
// cases (single-digit day with/without padding, double-digit day, %e at the
|
|
// end of the string, and %e directly adjacent to another format code).
|
|
func TestBIF_strptime_new_format_codes(t *testing.T) {
|
|
type testCase struct {
|
|
input string
|
|
format string
|
|
want float64
|
|
}
|
|
|
|
cases := []testCase{
|
|
{"Mon Mar 4 11:41:08 2024", "%a %b %e %T %Y", 1709552468},
|
|
{"Monday Mar 4 11:41:08 2024", "%A %b %e %T %Y", 1709552468},
|
|
{"Mon Mar 4 11:41:08 2024", "%a %h %e %T %Y", 1709552468},
|
|
{"Tue Oct 22 00:00:00 2024", "%a %b %e %T %Y", 1729555200},
|
|
{"Mon Mar 4 11:41:08 2024", "%c", 1709552468},
|
|
{"03/04/24 11:41:08", "%x %X", 1709552468},
|
|
// %e with a single (non-padded) space before a single-digit day.
|
|
{"Mar 4 2024", "%b %e %Y", 1709510400},
|
|
// %e with two-digit day.
|
|
{"Mar 14 2024", "%b %e %Y", 1710374400},
|
|
// %e directly adjacent to another format code, no intervening text.
|
|
{"4Mar2024", "%e%b%Y", 1709510400},
|
|
{"14Mar2024", "%e%b%Y", 1710374400},
|
|
// %e at the very end of the string.
|
|
{"2024-03-4", "%Y-%m-%e", 1709510400},
|
|
{"2024-03-14", "%Y-%m-%e", 1710374400},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
output := BIF_strptime(mlrval.FromString(c.input), mlrval.FromString(c.format))
|
|
seconds, isNumeric := output.GetNumericToFloatValue()
|
|
assert.True(t, isNumeric, "strptime(%q, %q) produced non-numeric %v", c.input, c.format, output)
|
|
assert.Equal(t, c.want, seconds, "strptime(%q, %q)", c.input, c.format)
|
|
}
|
|
}
|
|
|
|
// TestBIF_strptime_still_unsupported_format_code checks that a format code
|
|
// which remains unsupported (%U, week-of-year) still errors, i.e. that adding
|
|
// the new codes didn't accidentally cause unsupported codes to be silently
|
|
// ignored.
|
|
func TestBIF_strptime_still_unsupported_format_code(t *testing.T) {
|
|
output := BIF_strptime(mlrval.FromString("2024-03-04 09"), mlrval.FromString("%Y-%m-%d %U"))
|
|
assert.True(t, output.IsError())
|
|
}
|