Date formatting.
This commit is contained in:
parent
f21574baa6
commit
0086cdcbf0
11 changed files with 337 additions and 228 deletions
|
@ -18,7 +18,7 @@ go run github.com/randall77/makefat zenity zenity_macos_x64 zenity_macos_arm &&
|
|||
zip -9 zenity_macos.zip zenity
|
||||
|
||||
zip -9 zenity_brew.zip zenity zenity.exe
|
||||
|
||||
rm zenity zenity_macos_* zenity.exe
|
||||
|
||||
GOOS=linux go build -tags dev
|
||||
go build -tags dev
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ncruces/zenity"
|
||||
"github.com/ncruces/zenity/internal/strftime"
|
||||
"github.com/ncruces/zenity/internal/zenutil"
|
||||
)
|
||||
|
||||
|
@ -522,7 +523,7 @@ func lstResult(l []string, err error) {
|
|||
|
||||
func calResult(d time.Time, err error) {
|
||||
errResult(err)
|
||||
os.Stdout.WriteString(zenutil.Strftime(zenutil.DateFormat, d))
|
||||
os.Stdout.WriteString(strftime.Format(zenutil.DateFormat, d))
|
||||
os.Stdout.WriteString(zenutil.LineBreak)
|
||||
}
|
||||
|
||||
|
|
|
@ -3,16 +3,21 @@ package zenity
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/zenity/internal/strftime"
|
||||
"github.com/ncruces/zenity/internal/zenutil"
|
||||
)
|
||||
|
||||
func calendar(text string, opts options) (time.Time, error) {
|
||||
func calendar(text string, opts options) (t time.Time, err error) {
|
||||
var date zenutil.Date
|
||||
|
||||
date.OK, date.Cancel, date.Extra = getAlertButtons(opts)
|
||||
date.Format = zenutil.StrftimeUTS35(zenutil.DateFormat)
|
||||
date.Format, err = strftime.UTS35(zenutil.DateFormat)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if opts.time != nil {
|
||||
date.Date = opts.time.Unix()
|
||||
unix := opts.time.Unix()
|
||||
date.Date = &unix
|
||||
}
|
||||
|
||||
if opts.title != nil {
|
||||
|
@ -25,8 +30,7 @@ func calendar(text string, opts options) (time.Time, error) {
|
|||
out, err := zenutil.Run(opts.ctx, "date", date)
|
||||
str, err := strResult(opts, out, err)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
return
|
||||
}
|
||||
layout := zenutil.StrftimeLayout(zenutil.DateFormat)
|
||||
return time.Parse(layout, str)
|
||||
return strftime.Parse(zenutil.DateFormat, str)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/ncruces/zenity/internal/strftime"
|
||||
"github.com/ncruces/zenity/internal/zenutil"
|
||||
)
|
||||
|
||||
|
@ -27,6 +28,5 @@ func calendar(text string, opts options) (time.Time, error) {
|
|||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
layout := zenutil.StrftimeLayout(zenutil.DateFormat)
|
||||
return time.Parse(layout, str)
|
||||
return strftime.Parse(zenutil.DateFormat, str)
|
||||
}
|
||||
|
|
65
internal/strftime/parser.go
Normal file
65
internal/strftime/parser.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package strftime
|
||||
|
||||
type parser struct {
|
||||
fmt string
|
||||
specs map[byte]string
|
||||
unpadded map[byte]string
|
||||
writeLit func(byte) error
|
||||
writeFmt func(string) error
|
||||
fallback func(spec byte, pad bool) error
|
||||
}
|
||||
|
||||
func (p *parser) parse() error {
|
||||
const (
|
||||
initial = iota
|
||||
specifier
|
||||
nopadding
|
||||
)
|
||||
|
||||
var err error
|
||||
state := initial
|
||||
for _, b := range []byte(p.fmt) {
|
||||
switch state {
|
||||
default:
|
||||
if b == '%' {
|
||||
state = specifier
|
||||
} else {
|
||||
err = p.writeLit(b)
|
||||
}
|
||||
|
||||
case specifier:
|
||||
if b == '-' {
|
||||
state = nopadding
|
||||
continue
|
||||
}
|
||||
if s, ok := p.specs[b]; ok {
|
||||
err = p.writeFmt(s)
|
||||
} else {
|
||||
err = p.fallback(b, true)
|
||||
}
|
||||
state = initial
|
||||
|
||||
case nopadding:
|
||||
if s, ok := p.unpadded[b]; ok {
|
||||
err = p.writeFmt(s)
|
||||
} else if s, ok := p.specs[b]; ok {
|
||||
err = p.writeFmt(s)
|
||||
} else {
|
||||
err = p.fallback(b, false)
|
||||
}
|
||||
state = initial
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
switch state {
|
||||
case specifier:
|
||||
return p.writeLit('%')
|
||||
case nopadding:
|
||||
return p.writeLit('-')
|
||||
}
|
||||
return nil
|
||||
}
|
102
internal/strftime/specifiers.go
Normal file
102
internal/strftime/specifiers.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
package strftime
|
||||
|
||||
func (p *parser) goSpecifiers() {
|
||||
// https://strftime.org/
|
||||
p.specs = 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",
|
||||
|
||||
'+': "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",
|
||||
|
||||
'%': "%",
|
||||
't': "\t",
|
||||
'n': "\n",
|
||||
}
|
||||
|
||||
p.unpadded = map[byte]string{
|
||||
'm': "1",
|
||||
'd': "2",
|
||||
'I': "3",
|
||||
'M': "4",
|
||||
'S': "5",
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) uts35Specifiers() { // https://nsdateformatter.com/
|
||||
p.specs = 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': "\n",
|
||||
}
|
||||
|
||||
p.unpadded = map[byte]string{
|
||||
'm': "M",
|
||||
'd': "d",
|
||||
'j': "D",
|
||||
'H': "H",
|
||||
'I': "h",
|
||||
'M': "m",
|
||||
'S': "s",
|
||||
}
|
||||
}
|
148
internal/strftime/strftime.go
Normal file
148
internal/strftime/strftime.go
Normal file
|
@ -0,0 +1,148 @@
|
|||
package strftime
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Format(fmt string, t time.Time) string {
|
||||
var res strings.Builder
|
||||
|
||||
var parser parser
|
||||
parser.fmt = fmt
|
||||
parser.goSpecifiers()
|
||||
|
||||
parser.writeLit = res.WriteByte
|
||||
|
||||
parser.writeFmt = func(fmt string) error {
|
||||
switch fmt {
|
||||
default:
|
||||
res.WriteString(t.Format(fmt))
|
||||
case "000", "000000", "000000000":
|
||||
res.WriteString(t.Format("." + fmt)[1:])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
parser.fallback = func(spec byte, pad bool) error {
|
||||
switch spec {
|
||||
default:
|
||||
return errors.New("strftime: unsupported specifier: %" + string(spec))
|
||||
case 'C':
|
||||
s := t.Format("2006")
|
||||
res.WriteString(s[:len(s)-2])
|
||||
case 'g':
|
||||
y, _ := t.ISOWeek()
|
||||
res.WriteString(time.Date(y, 1, 1, 0, 0, 0, 0, time.UTC).Format("06"))
|
||||
case 'G':
|
||||
y, _ := t.ISOWeek()
|
||||
res.WriteString(time.Date(y, 1, 1, 0, 0, 0, 0, time.UTC).Format("2006"))
|
||||
case 'V':
|
||||
_, w := t.ISOWeek()
|
||||
if w < 10 && pad {
|
||||
res.WriteByte('0')
|
||||
}
|
||||
res.WriteString(strconv.Itoa(w))
|
||||
case 'w':
|
||||
w := int(t.Weekday())
|
||||
res.WriteString(strconv.Itoa(w))
|
||||
case 'u':
|
||||
if w := int(t.Weekday()); w == 0 {
|
||||
res.WriteByte('7')
|
||||
} else {
|
||||
res.WriteString(strconv.Itoa(w))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
parser.parse()
|
||||
return res.String()
|
||||
}
|
||||
|
||||
func Parse(fmt, value string) (time.Time, error) {
|
||||
layout, err := Layout(fmt)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return time.Parse(layout, value)
|
||||
}
|
||||
|
||||
func Layout(fmt string) (string, error) {
|
||||
var res strings.Builder
|
||||
|
||||
var parser parser
|
||||
parser.fmt = fmt
|
||||
parser.goSpecifiers()
|
||||
|
||||
parser.writeLit = func(b byte) error {
|
||||
if bytes.IndexByte([]byte("MonJan_0123456789"), b) >= 0 {
|
||||
return errors.New("strftime: unsupported literal: " + string(b))
|
||||
}
|
||||
res.WriteByte(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
parser.writeFmt = func(s string) error {
|
||||
res.WriteString(s)
|
||||
return nil
|
||||
}
|
||||
|
||||
parser.fallback = func(spec byte, pad bool) error {
|
||||
return errors.New("strftime: unsupported specifier: %" + string(spec))
|
||||
}
|
||||
|
||||
if err := parser.parse(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
parser.writeFmt("")
|
||||
return res.String(), nil
|
||||
}
|
||||
|
||||
func UTS35(fmt string) (string, error) {
|
||||
var parser parser
|
||||
parser.fmt = fmt
|
||||
parser.uts35Specifiers()
|
||||
|
||||
const quote = '\''
|
||||
var literal bool
|
||||
var res strings.Builder
|
||||
|
||||
parser.writeLit = func(b byte) error {
|
||||
if b == quote {
|
||||
res.WriteByte(quote)
|
||||
res.WriteByte(quote)
|
||||
return nil
|
||||
}
|
||||
if !literal && ('a' <= b && b <= 'z' || 'A' <= b && b <= 'Z') {
|
||||
literal = true
|
||||
res.WriteByte(quote)
|
||||
}
|
||||
res.WriteByte(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
parser.writeFmt = func(s string) error {
|
||||
if literal {
|
||||
literal = false
|
||||
res.WriteByte(quote)
|
||||
}
|
||||
res.WriteString(s)
|
||||
return nil
|
||||
}
|
||||
|
||||
parser.fallback = func(spec byte, pad bool) error {
|
||||
return errors.New("strftime: unsupported specifier: %" + string(spec))
|
||||
}
|
||||
|
||||
if err := parser.parse(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
parser.writeFmt("")
|
||||
return res.String(), nil
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
package zenutil
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Strftime is internal.
|
||||
func Strftime(fmt string, t time.Time) string {
|
||||
var res strings.Builder
|
||||
writeLit := res.WriteByte
|
||||
writeFmt := func(fmt string) (int, error) {
|
||||
return res.WriteString(t.Format(fmt))
|
||||
}
|
||||
strftimeGo(fmt, writeLit, writeFmt)
|
||||
return res.String()
|
||||
}
|
||||
|
||||
// StrftimeLayout is internal.
|
||||
func StrftimeLayout(fmt string) string {
|
||||
var res strings.Builder
|
||||
strftimeGo(fmt, res.WriteByte, res.WriteString)
|
||||
return res.String()
|
||||
}
|
||||
|
||||
func strftimeGo(fmt string, writeLit func(byte) error, writeFmt func(string) (int, error)) {
|
||||
// https://strftime.org/
|
||||
fmts := 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",
|
||||
|
||||
'+': "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",
|
||||
|
||||
'%': "%",
|
||||
't': "\t",
|
||||
'n': LineBreak,
|
||||
}
|
||||
|
||||
unpaded := map[byte]string{
|
||||
'm': "1",
|
||||
'd': "2",
|
||||
'I': "3",
|
||||
'M': "4",
|
||||
'S': "5",
|
||||
}
|
||||
|
||||
parser(fmt, fmts, unpaded, writeLit, writeFmt)
|
||||
}
|
||||
|
||||
// StrftimeUTS35 is internal.
|
||||
func StrftimeUTS35(fmt string) string {
|
||||
// https://nsdateformatter.com/
|
||||
fmts := 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,
|
||||
}
|
||||
|
||||
unpaded := map[byte]string{
|
||||
'm': "M",
|
||||
'd': "d",
|
||||
'j': "D",
|
||||
'H': "H",
|
||||
'I': "h",
|
||||
'M': "m",
|
||||
'S': "s",
|
||||
}
|
||||
|
||||
const quote = '\''
|
||||
var literal bool
|
||||
var res strings.Builder
|
||||
|
||||
writeLit := func(b byte) error {
|
||||
if b == quote {
|
||||
res.WriteByte(quote)
|
||||
return res.WriteByte(quote)
|
||||
}
|
||||
if !literal && ('a' <= b && b <= 'z' || 'A' <= b && b <= 'Z') {
|
||||
literal = true
|
||||
res.WriteByte(quote)
|
||||
}
|
||||
return res.WriteByte(b)
|
||||
}
|
||||
|
||||
writeFmt := func(s string) (int, error) {
|
||||
if literal {
|
||||
literal = false
|
||||
res.WriteByte(quote)
|
||||
}
|
||||
return res.WriteString(s)
|
||||
}
|
||||
|
||||
parser(fmt, fmts, unpaded, writeLit, writeFmt)
|
||||
writeFmt("")
|
||||
|
||||
return res.String()
|
||||
}
|
||||
|
||||
func parser(
|
||||
fmt string,
|
||||
formats, unpadded map[byte]string,
|
||||
writeLit func(byte) error, writeFmt func(string) (int, error)) {
|
||||
|
||||
const (
|
||||
initial = iota
|
||||
special
|
||||
padding
|
||||
)
|
||||
|
||||
state := initial
|
||||
for _, b := range []byte(fmt) {
|
||||
switch state {
|
||||
case initial:
|
||||
if b == '%' {
|
||||
state = special
|
||||
} else {
|
||||
writeLit(b)
|
||||
}
|
||||
|
||||
case special:
|
||||
if b == '-' {
|
||||
state = padding
|
||||
continue
|
||||
}
|
||||
if s, ok := formats[b]; ok {
|
||||
writeFmt(s)
|
||||
} else {
|
||||
writeLit('%')
|
||||
writeLit(b)
|
||||
}
|
||||
state = initial
|
||||
|
||||
case padding:
|
||||
if s, ok := unpadded[b]; ok {
|
||||
writeFmt(s)
|
||||
} else if s, ok := formats[b]; ok {
|
||||
writeFmt(s)
|
||||
} else {
|
||||
writeLit('%')
|
||||
writeLit('-')
|
||||
writeLit(b)
|
||||
}
|
||||
state = initial
|
||||
}
|
||||
}
|
||||
|
||||
switch state {
|
||||
case padding:
|
||||
writeLit('%')
|
||||
fallthrough
|
||||
case special:
|
||||
writeLit('-')
|
||||
}
|
||||
}
|
|
@ -29,8 +29,10 @@ ObjC.import('stdlib')
|
|||
var date=$.NSDatePicker.alloc.init
|
||||
date.setDatePickerStyle($.NSDatePickerStyleClockAndCalendar)
|
||||
date.setDatePickerElements($.NSDatePickerElementFlagYearMonthDay)
|
||||
date.setDateValue($.NSDate.dateWithTimeIntervalSince1970({{.Date}}))
|
||||
date.setFrameSize(date.fittingSize)
|
||||
{{- if .Date}}
|
||||
date.setDateValue($.NSDate.dateWithTimeIntervalSince1970({{.Date}}))
|
||||
{{- end}}
|
||||
var alert=$.NSAlert.alloc.init
|
||||
alert.setAccessoryView(date)
|
||||
alert.setMessageText({{json .Text}})
|
||||
|
|
|
@ -9,8 +9,10 @@ ObjC.import('stdlib')
|
|||
var date = $.NSDatePicker.alloc.init
|
||||
date.setDatePickerStyle($.NSDatePickerStyleClockAndCalendar)
|
||||
date.setDatePickerElements($.NSDatePickerElementFlagYearMonthDay)
|
||||
date.setDateValue($.NSDate.dateWithTimeIntervalSince1970({{.Date}}))
|
||||
date.setFrameSize(date.fittingSize)
|
||||
{{- if .Date}}
|
||||
date.setDateValue($.NSDate.dateWithTimeIntervalSince1970({{.Date}}))
|
||||
{{- end}}
|
||||
|
||||
var alert = $.NSAlert.alloc.init
|
||||
alert.setAccessoryView(date)
|
||||
|
|
|
@ -220,7 +220,7 @@ type Progress struct {
|
|||
|
||||
// Date is internal.
|
||||
type Date struct {
|
||||
Date int64
|
||||
Date *int64
|
||||
Text string
|
||||
Info string
|
||||
Format string
|
||||
|
|
Loading…
Reference in a new issue