fix(deadline): reject day/month dates that read differently by locale

--deadline 03/04/2026 was silently parsed as March 4. Numeric dates such
as 03/04/2026 or 15.01.2028 are now refused with "the order of day and
month is ambiguous; use YYYY-MM-DD", and the error for other invalid
values no longer suggests clearing on issue create.

Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
bircni committed 2026-10-12 00:14:52 +02:00
1 parent b5fecbde42
commit 8e5289149c
2 files changed
+20 -2

No files matched your search

+10 -1
View File
@@ -5,6 +5,8 @@ package app
import (
"errors"
"fmt"
"regexp"
"strconv"
"time"
@@ -12,18 +14,25 @@ import (
"github.com/araddon/dateparse"
)
// numericDate matches dates such as 03/04/2026, which read as March 4 in the US and April 3
// elsewhere.
var numericDate = regexp.MustCompile(`^\d{1,2}[/.]\d{1,2}[/.]\d{2,4}$`)
// parseDeadline parses a --deadline value; an empty value requests clearing.
// Supplied timezone offsets are kept and Unix timestamps are UTC.
func parseDeadline(value string) (*time.Time, *bool, error) {
if value == "" {
return nil, new(true), nil
}
if numericDate.MatchString(value) {
return nil, nil, clierr.Wrap(clierr.Usage, fmt.Errorf("invalid --deadline %q: the order of day and month is ambiguous; use YYYY-MM-DD", value))
}
date, err := dateparse.ParseAny(value)
if _, numericError := strconv.ParseInt(value, 10, 64); numericError == nil {
date = date.UTC()
}
if err != nil || date.IsZero() || date.Unix() == 0 || date.Year() < 1 || date.Year() > 9999 {
return nil, nil, clierr.Wrap(clierr.Usage, errors.New("invalid --deadline; use YYYY-MM-DD, RFC3339, a Unix timestamp, or an empty value to clear"))
return nil, nil, clierr.Wrap(clierr.Usage, errors.New("invalid --deadline; use YYYY-MM-DD, RFC3339 or a Unix timestamp"))
}
return &date, nil, nil
}
+10 -1
View File
@@ -5,6 +5,7 @@ package app
import (
"context"
"fmt"
"net/http"
"testing"
@@ -16,7 +17,7 @@ func TestDeadlineInputAndExplicitClearing(t *testing.T) {
for _, tc := range []struct{ value, payload string }{
{"2028-01-15", `{"due_date":"2028-01-15T00:00:00Z"}`},
{"2028-01-15T00:30:00+02:00", `{"due_date":"2028-01-15T00:30:00+02:00"}`},
{"01/15/2028", `{"due_date":"2028-01-15T00:00:00Z"}`},
{"Jan 15 2028", `{"due_date":"2028-01-15T00:00:00Z"}`},
{"1704067200000", `{"due_date":"2024-01-01T00:00:00Z"}`},
{"1704067200", `{"due_date":"2024-01-01T00:00:00Z"}`},
{"", `{"unset_due_date":true}`},
@@ -41,6 +42,14 @@ func TestDeadlineInputAndExplicitClearing(t *testing.T) {
}
}
func TestAmbiguousNumericDeadlinesAreRejected(t *testing.T) {
for _, value := range []string{"03/04/2026", "04/03/2026", "01/15/2028", "15.01.2028", "3/4/26"} {
for _, command := range [][]string{{"issue", "edit", "1"}, {"issue", "create", "--title", "x"}, {"milestone", "create", "--title", "x"}} {
requireUsageBeforeRequests(t, fmt.Sprintf("invalid --deadline %q: the order of day and month is ambiguous; use YYYY-MM-DD", value), append(command, "--deadline="+value)...)
}
}
}
func TestInvalidDeadlineBeforeInputCollectionOrNetwork(t *testing.T) {
for _, value := range []string{"bad", "2026-02-30", "0", "not-a-timestamp", "999999999999999999999999999999999999"} {
for _, command := range [][]string{{"issue", "edit", "1"}, {"pr", "edit", "1", "--add-reviewers=alice"}, {"issue", "create"}, {"pr", "create"}} {