diff --git a/date_test.go b/date_test.go index c5cccf5..e775dfc 100644 --- a/date_test.go +++ b/date_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/ncruces/zenity" + "go.uber.org/goleak" ) func ExampleCalendar() { @@ -17,21 +18,28 @@ func ExampleCalendar() { } func TestCalendarTimeout(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second/10) + defer goleak.VerifyNone(t) + ctx, cancel := context.WithTimeout(context.Background(), time.Second/5) + defer cancel() _, err := zenity.Calendar("", zenity.Context(ctx)) + if skip, err := skip(err); skip { + t.Skip("skipping:", err) + } if !os.IsTimeout(err) { t.Error("did not timeout:", err) } - - cancel() } func TestCalendarCancel(t *testing.T) { + defer goleak.VerifyNone(t) ctx, cancel := context.WithCancel(context.Background()) cancel() _, err := zenity.Calendar("", zenity.Context(ctx)) + if skip, err := skip(err); skip { + t.Skip("skipping:", err) + } if !errors.Is(err, context.Canceled) { t.Error("was not canceled:", err) } diff --git a/internal/zenutil/date.go b/internal/zenutil/date.go index 375603d..46a5f9f 100644 --- a/internal/zenutil/date.go +++ b/internal/zenutil/date.go @@ -2,127 +2,114 @@ package zenutil import "strings" -// https://strftime.org/ -var strftimeTable = map[byte]string{ - 'B': "January", - 'b': "Jan", - 'h': "Jan", - 'm': "01", - 'A': "Monday", - 'a': "Mon", - 'e': "_2", - 'd': "02", - 'j': "002", - 'H': "15", - 'I': "03", - 'M': "04", - 'S': "05", - 'Y': "2006", - 'y': "06", - 'p': "PM", - 'Z': "MST", - 'z': "-0700", - 'L': "000", - 'f': "000000", - - '+': "Mon Jan _2 03:04:05 PM MST 2006", - 'c': "Mon Jan _2 15:04:05 2006", - 'F': "2006-01-02", - 'D': "01/02/06", - 'x': "01/02/06", - 'r': "03:04:05 PM", - 'T': "15:04:05", - 'X': "15:04:05", - 'R': "15:04", - - '%': "%", - 't': "\t", - 'n': LineBreak, -} - // Strftime is internal. func Strftime(fmt string) string { - var res strings.Builder - res.Grow(len(fmt)) + // https://strftime.org/ + return strftime(fmt, map[byte]string{ + 'B': "January", + 'b': "Jan", + 'h': "Jan", + 'm': "01", + 'A': "Monday", + 'a': "Mon", + 'e': "_2", + 'd': "02", + 'j': "002", + 'H': "15", + 'I': "03", + 'M': "04", + 'S': "05", + 'Y': "2006", + 'y': "06", + 'p': "PM", + 'Z': "MST", + 'z': "-0700", + 'L': "000", + 'f': "000000", + 'N': "000000000", - const ( - initial = iota - special - ) + '+': "Mon Jan _2 15:04:05 MST 2006", + 'c': "Mon Jan _2 15:04:05 2006", + 'F': "2006-01-02", + 'D': "01/02/06", + 'x': "01/02/06", + 'r': "03:04:05 PM", + 'T': "15:04:05", + 'X': "15:04:05", + 'R': "15:04", - state := initial - for _, b := range []byte(fmt) { - switch state { - case initial: - if b == '%' { - state = special - } else { - res.WriteByte(b) - state = initial - } - - case special: - s, ok := strftimeTable[b] - if ok { - res.WriteString(s) - } else { - res.WriteByte(b) - } - state = initial - } - } - - return res.String() -} - -// https://nsdateformatter.com/ -var strftimeUTS35Table = map[byte]string{ - 'B': "MMMM", - 'b': "MMM", - 'h': "MMM", - 'm': "MM", - 'A': "EEEE", - 'a': "E", - 'd': "dd", - 'j': "DDD", - 'H': "HH", - 'I': "hh", - 'M': "mm", - 'S': "ss", - 'Y': "yyyy", - 'y': "yy", - 'G': "YYYY", - 'g': "YY", - 'V': "ww", - 'p': "a", - 'Z': "zzz", - 'z': "Z", - 'L': "SSS", - 'f': "SSSSSS", - - '+': "E MMM d hh:mm:ss a zzz yyyy", - 'c': "E MMM d HH:mm:ss yyyy", - 'F': "yyyy-MM-dd", - 'D': "MM/dd/yy", - 'x': "MM/dd/yy", - 'r': "hh:mm:ss a", - 'T': "HH:mm:ss", - 'X': "HH:mm:ss", - 'R': "HH:mm", - - '%': "%", - 't': "\t", - 'n': LineBreak, + '%': "%", + 't': "\t", + 'n': LineBreak, + }, map[byte]string{ + 'm': "1", + 'd': "2", + 'I': "3", + 'M': "4", + 'S': "5", + }) } // StrftimeUTS35 is internal. func StrftimeUTS35(fmt string) string { + // https://nsdateformatter.com/ + return strftime(fmt, map[byte]string{ + 'B': "MMMM", + 'b': "MMM", + 'h': "MMM", + 'm': "MM", + 'A': "EEEE", + 'a': "E", + 'd': "dd", + 'j': "DDD", + 'H': "HH", + 'I': "hh", + 'M': "mm", + 'S': "ss", + 'Y': "yyyy", + 'y': "yy", + 'G': "YYYY", + 'g': "YY", + 'V': "ww", + 'p': "a", + 'Z': "zzz", + 'z': "Z", + 'L': "SSS", + 'f': "SSSSSS", + 'N': "SSSSSSSSS", + + '+': "E MMM d HH:mm:ss zzz yyyy", + 'c': "E MMM d HH:mm:ss yyyy", + 'F': "yyyy-MM-dd", + 'D': "MM/dd/yy", + 'x': "MM/dd/yy", + 'r': "hh:mm:ss a", + 'T': "HH:mm:ss", + 'X': "HH:mm:ss", + 'R': "HH:mm", + + '%': "%", + 't': "\t", + 'n': LineBreak, + }, map[byte]string{ + 'm': "M", + 'd': "d", + 'j': "D", + 'H': "H", + 'I': "h", + 'M': "m", + 'S': "s", + }) +} + +func strftime(fmt string, formats, unpadded map[byte]string) string { var res strings.Builder res.Grow(len(fmt)) const ( initial = iota special + padding ) state := initial @@ -133,19 +120,39 @@ func StrftimeUTS35(fmt string) string { state = special } else { res.WriteByte(b) - state = initial } case special: - s, ok := strftimeUTS35Table[b] - if ok { + if b == '-' { + state = padding + continue + } + if s, ok := formats[b]; ok { res.WriteString(s) } else { + res.WriteByte('%') + res.WriteByte(b) + } + state = initial + + case padding: + if s, ok := unpadded[b]; ok { + res.WriteString(s) + } else if s, ok := formats[b]; ok { + res.WriteString(s) + } else { + res.WriteString("%-") res.WriteByte(b) } state = initial } } + switch state { + case padding: + res.WriteString("%-") + case special: + res.WriteByte('%') + } return res.String() }