The SDK reaches internal/datetime through settings validation, and that package had only a cgo strftime implementation, so statically linked Tentacle packages could not build. Split the locale-aware cgo formatter into its own file and add a C-locale fallback for builds without cgo. The reader itself still uses the cgo implementation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
120 lines
2.4 KiB
Go
120 lines
2.4 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package datetime
|
|
|
|
import "time"
|
|
|
|
type Option struct {
|
|
Value string
|
|
Label string
|
|
}
|
|
|
|
var timeFormats = []struct {
|
|
Value string
|
|
Spec string
|
|
}{
|
|
{"locale", "%X"},
|
|
{"12-hour", "%I:%M %p"},
|
|
{"12-hour-seconds", "%I:%M:%S %p"},
|
|
{"24-hour-seconds", "%H:%M:%S"},
|
|
{"24-hour-seconds-words", "%H hours, %M minutes and %S seconds"},
|
|
{"24-hour", "%H:%M"},
|
|
{"24-hour-words", "%H hours and %M minutes"},
|
|
}
|
|
|
|
var dateFormats = []struct {
|
|
Value string
|
|
Spec string
|
|
}{
|
|
{"locale", "%x"},
|
|
{"numbers-dm", "%d/%m"},
|
|
{"numbers-md", "%m/%d"},
|
|
{"numbers-dmy", "%d/%m/%Y"},
|
|
{"numbers-mdy", "%m/%d/%Y"},
|
|
{"numbers-ymd", "%Y/%m/%d"},
|
|
{"full-dm", "%A, %-d %B"},
|
|
{"full-md", "%A, %B %-d"},
|
|
{"full-dmy", "%A, %-d %B, %Y"},
|
|
{"full-mdy", "%A, %B %-d, %Y"},
|
|
{"full-ymd", "%Y. %B %-d, %A"},
|
|
{"abbreviated-dm", "%a, %-d %b"},
|
|
{"abbreviated-md", "%a, %b %-d"},
|
|
{"abbreviated-dmy", "%a, %-d %b, %Y"},
|
|
{"abbreviated-mdy", "%a, %b %-d, %Y"},
|
|
{"abbreviated-ymd", "%Y. %b %-d, %a"},
|
|
}
|
|
|
|
func TimeOptions(now time.Time) []Option {
|
|
return options(timeFormats, now)
|
|
}
|
|
|
|
func DateOptions(now time.Time) []Option {
|
|
return options(dateFormats, now)
|
|
}
|
|
|
|
func options(formats []struct {
|
|
Value string
|
|
Spec string
|
|
}, now time.Time) []Option {
|
|
result := make([]Option, 0, len(formats))
|
|
for _, item := range formats {
|
|
result = append(result, Option{Value: item.Value, Label: format(now, item.Spec)})
|
|
}
|
|
return result
|
|
}
|
|
|
|
func FormatTime(value string, now time.Time) (string, bool) {
|
|
return formatValue(timeFormats, value, now)
|
|
}
|
|
|
|
func FormatDate(value string, now time.Time) (string, bool) {
|
|
return formatValue(dateFormats, value, now)
|
|
}
|
|
|
|
func ValidTime(value string) bool {
|
|
return valid(timeFormats, value)
|
|
}
|
|
|
|
func ValidDate(value string) bool {
|
|
return valid(dateFormats, value)
|
|
}
|
|
|
|
func NormalizeDate(value string) string {
|
|
switch value {
|
|
case "ymd":
|
|
return "numbers-ymd"
|
|
case "mdy":
|
|
return "numbers-mdy"
|
|
case "dmy":
|
|
return "numbers-dmy"
|
|
default:
|
|
return value
|
|
}
|
|
}
|
|
|
|
func formatValue(formats []struct {
|
|
Value string
|
|
Spec string
|
|
}, value string, now time.Time) (string, bool) {
|
|
for _, item := range formats {
|
|
if item.Value == value {
|
|
return format(now, item.Spec), true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func valid(formats []struct {
|
|
Value string
|
|
Spec string
|
|
}, value string) bool {
|
|
for _, item := range formats {
|
|
if item.Value == value {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|