Build the Tentacle SDK without cgo
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>
This commit is contained in:
1 parent
cb4d98545f
commit
8e0ac3b258
4 files changed
+153
-36
No files matched your search
@@ -4,17 +4,7 @@
|
||||
|
||||
package datetime
|
||||
|
||||
/*
|
||||
#include <locale.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
import "time"
|
||||
|
||||
type Option struct {
|
||||
Value string
|
||||
@@ -56,12 +46,6 @@ var dateFormats = []struct {
|
||||
{"abbreviated-ymd", "%Y. %b %-d, %a"},
|
||||
}
|
||||
|
||||
func init() {
|
||||
locale := C.CString("")
|
||||
defer C.free(unsafe.Pointer(locale))
|
||||
C.setlocale(C.LC_TIME, locale)
|
||||
}
|
||||
|
||||
func TimeOptions(now time.Time) []Option {
|
||||
return options(timeFormats, now)
|
||||
}
|
||||
@@ -133,22 +117,3 @@ func valid(formats []struct {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func format(now time.Time, spec string) string {
|
||||
cSpec := C.CString(spec)
|
||||
defer C.free(unsafe.Pointer(cSpec))
|
||||
tm := C.struct_tm{
|
||||
tm_year: C.int(now.Year() - 1900),
|
||||
tm_mon: C.int(int(now.Month()) - 1),
|
||||
tm_mday: C.int(now.Day()),
|
||||
tm_hour: C.int(now.Hour()),
|
||||
tm_min: C.int(now.Minute()),
|
||||
tm_sec: C.int(now.Second()),
|
||||
tm_wday: C.int(int(now.Weekday())),
|
||||
tm_yday: C.int(now.YearDay() - 1),
|
||||
tm_isdst: -1,
|
||||
}
|
||||
buffer := make([]C.char, 512)
|
||||
length := C.strftime(&buffer[0], C.size_t(len(buffer)), cSpec, &tm)
|
||||
return C.GoStringN(&buffer[0], C.int(length))
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
//go:build cgo
|
||||
|
||||
package datetime
|
||||
|
||||
/*
|
||||
#include <locale.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func init() {
|
||||
locale := C.CString("")
|
||||
defer C.free(unsafe.Pointer(locale))
|
||||
C.setlocale(C.LC_TIME, locale)
|
||||
}
|
||||
|
||||
func format(now time.Time, spec string) string {
|
||||
cSpec := C.CString(spec)
|
||||
defer C.free(unsafe.Pointer(cSpec))
|
||||
tm := C.struct_tm{
|
||||
tm_year: C.int(now.Year() - 1900),
|
||||
tm_mon: C.int(int(now.Month()) - 1),
|
||||
tm_mday: C.int(now.Day()),
|
||||
tm_hour: C.int(now.Hour()),
|
||||
tm_min: C.int(now.Minute()),
|
||||
tm_sec: C.int(now.Second()),
|
||||
tm_wday: C.int(int(now.Weekday())),
|
||||
tm_yday: C.int(now.YearDay() - 1),
|
||||
tm_isdst: -1,
|
||||
}
|
||||
buffer := make([]C.char, 512)
|
||||
length := C.strftime(&buffer[0], C.size_t(len(buffer)), cSpec, &tm)
|
||||
return C.GoStringN(&buffer[0], C.int(length))
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
//go:build !cgo
|
||||
|
||||
package datetime
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// format is a C-locale strftime subset for builds without cgo, such as the
|
||||
// statically linked Tentacle packages that only validate settings. The reader
|
||||
// itself is built with cgo and uses the locale-aware implementation.
|
||||
func format(now time.Time, spec string) string {
|
||||
var out strings.Builder
|
||||
for index := 0; index < len(spec); index++ {
|
||||
if spec[index] != '%' || index+1 == len(spec) {
|
||||
out.WriteByte(spec[index])
|
||||
continue
|
||||
}
|
||||
index++
|
||||
pad := true
|
||||
if spec[index] == '-' && index+1 < len(spec) {
|
||||
pad = false
|
||||
index++
|
||||
}
|
||||
two := func(value int) string {
|
||||
if pad && value < 10 {
|
||||
return "0" + strconv.Itoa(value)
|
||||
}
|
||||
return strconv.Itoa(value)
|
||||
}
|
||||
switch spec[index] {
|
||||
case 'H':
|
||||
out.WriteString(two(now.Hour()))
|
||||
case 'I':
|
||||
hour := now.Hour() % 12
|
||||
if hour == 0 {
|
||||
hour = 12
|
||||
}
|
||||
out.WriteString(two(hour))
|
||||
case 'M':
|
||||
out.WriteString(two(now.Minute()))
|
||||
case 'S':
|
||||
out.WriteString(two(now.Second()))
|
||||
case 'p':
|
||||
out.WriteString(now.Format("PM"))
|
||||
case 'X':
|
||||
out.WriteString(now.Format("15:04:05"))
|
||||
case 'x':
|
||||
out.WriteString(now.Format("01/02/06"))
|
||||
case 'd':
|
||||
out.WriteString(two(now.Day()))
|
||||
case 'm':
|
||||
out.WriteString(two(int(now.Month())))
|
||||
case 'Y':
|
||||
out.WriteString(strconv.Itoa(now.Year()))
|
||||
case 'y':
|
||||
out.WriteString(now.Format("06"))
|
||||
case 'A':
|
||||
out.WriteString(now.Weekday().String())
|
||||
case 'a':
|
||||
out.WriteString(now.Weekday().String()[:3])
|
||||
case 'B':
|
||||
out.WriteString(now.Month().String())
|
||||
case 'b':
|
||||
out.WriteString(now.Month().String()[:3])
|
||||
case '%':
|
||||
out.WriteByte('%')
|
||||
default:
|
||||
out.WriteByte('%')
|
||||
out.WriteByte(spec[index])
|
||||
}
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
//go:build !cgo
|
||||
|
||||
package datetime
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFormatFallbackMatchesCLocale(t *testing.T) {
|
||||
now := time.Date(2026, time.March, 5, 7, 4, 9, 0, time.UTC)
|
||||
for spec, want := range map[string]string{
|
||||
"%I:%M %p": "07:04 AM", "%H:%M:%S": "07:04:09", "%X": "07:04:09", "%x": "03/05/26",
|
||||
"%A, %-d %B, %Y": "Thursday, 5 March, 2026", "%a, %b %-d": "Thu, Mar 5", "%d/%m/%Y": "05/03/2026",
|
||||
"%H hours and %M minutes": "07 hours and 04 minutes", "100%%": "100%",
|
||||
} {
|
||||
if got := format(now, spec); got != want {
|
||||
t.Fatalf("%q = %q, want %q", spec, got, want)
|
||||
}
|
||||
}
|
||||
if !ValidTime("24-hour") || !ValidDate("full-mdy") {
|
||||
t.Fatal("validation changed without cgo")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user