package utils
import (
"fmt"
"path/filepath"
"regexp"
"strconv"
"time"
)
type DateFormat int
const (
Format8Digit DateFormat = iota Format6YYMMDD Format6DDMMYY )
var (
timestampPattern = regexp.MustCompile(`^(\d{6,8})_(\d{6})\.wav$`)
)
type dateParts struct {
x1 int m int x2 int }
type FilenameTimestamp struct {
Filename string
Timestamp time.Time
Format DateFormat
}
func ParseFilenameTimestamps(filenames []string) ([]FilenameTimestamp, error) {
if len(filenames) == 0 {
return nil, fmt.Errorf("no filenames provided")
}
format, err := detectDateFormat(filenames)
if err != nil {
return nil, err
}
results := make([]FilenameTimestamp, 0, len(filenames))
for _, filename := range filenames {
timestamp, err := parseFilenameWithFormat(filename, format)
if err != nil {
return nil, fmt.Errorf("failed to parse %s: %w", filename, err)
}
results = append(results, FilenameTimestamp{
Filename: filename,
Timestamp: timestamp,
Format: format,
})
}
return results, nil
}
func ApplyTimezoneOffset(timestamps []FilenameTimestamp, timezoneID string) ([]time.Time, error) {
if len(timestamps) == 0 {
return nil, fmt.Errorf("no timestamps provided")
}
loc, err := time.LoadLocation(timezoneID)
if err != nil {
return nil, fmt.Errorf("invalid timezone %s: %w", timezoneID, err)
}
firstUTC := timestamps[0].Timestamp
firstInZone := time.Date(
firstUTC.Year(), firstUTC.Month(), firstUTC.Day(),
firstUTC.Hour(), firstUTC.Minute(), firstUTC.Second(),
0, loc,
)
_, offsetSeconds := firstInZone.Zone()
fixedOffset := time.FixedZone("Fixed", offsetSeconds)
results := make([]time.Time, len(timestamps))
for i, ts := range timestamps {
adjusted := time.Date(
ts.Timestamp.Year(), ts.Timestamp.Month(), ts.Timestamp.Day(),
ts.Timestamp.Hour(), ts.Timestamp.Minute(), ts.Timestamp.Second(),
0, fixedOffset,
)
results[i] = adjusted
}
return results, nil
}
func detectDateFormat(filenames []string) (DateFormat, error) {
var parts []dateParts
var has8Digit bool
for _, filename := range filenames {
basename := filepath.Base(filename)
matches := timestampPattern.FindStringSubmatch(basename)
if matches == nil {
continue
}
dateStr := matches[1]
if len(dateStr) == 8 {
has8Digit = true
continue
}
if len(dateStr) == 6 {
x1, _ := strconv.Atoi(dateStr[0:2])
m, _ := strconv.Atoi(dateStr[2:4])
x2, _ := strconv.Atoi(dateStr[4:6])
parts = append(parts, dateParts{x1: x1, m: m, x2: x2})
}
}
if has8Digit && len(parts) == 0 {
return Format8Digit, nil
}
if has8Digit {
return Format8Digit, nil
}
if len(parts) == 0 {
return 0, fmt.Errorf("no valid timestamp filenames found")
}
uniqueX1 := countUnique(parts, func(p dateParts) int { return p.x1 })
uniqueX2 := countUnique(parts, func(p dateParts) int { return p.x2 })
if uniqueX2 >= uniqueX1 {
return Format6YYMMDD, nil
} else {
return Format6DDMMYY, nil
}
}
func parseFilenameWithFormat(filename string, format DateFormat) (time.Time, error) {
basename := filepath.Base(filename)
matches := timestampPattern.FindStringSubmatch(basename)
if matches == nil {
return time.Time{}, fmt.Errorf("filename does not match timestamp pattern: %s", basename)
}
dateStr := matches[1]
timeStr := matches[2]
var year, month, day int
switch format {
case Format8Digit:
if len(dateStr) != 8 {
return time.Time{}, fmt.Errorf("expected 8-digit date, got %d digits", len(dateStr))
}
year, _ = strconv.Atoi(dateStr[0:4])
month, _ = strconv.Atoi(dateStr[4:6])
day, _ = strconv.Atoi(dateStr[6:8])
case Format6YYMMDD:
if len(dateStr) != 6 {
return time.Time{}, fmt.Errorf("expected 6-digit date, got %d digits", len(dateStr))
}
yy, _ := strconv.Atoi(dateStr[0:2])
month, _ = strconv.Atoi(dateStr[2:4])
day, _ = strconv.Atoi(dateStr[4:6])
year = 2000 + yy
case Format6DDMMYY:
if len(dateStr) != 6 {
return time.Time{}, fmt.Errorf("expected 6-digit date, got %d digits", len(dateStr))
}
day, _ = strconv.Atoi(dateStr[0:2])
month, _ = strconv.Atoi(dateStr[2:4])
yy, _ := strconv.Atoi(dateStr[4:6])
year = 2000 + yy
}
if len(timeStr) != 6 {
return time.Time{}, fmt.Errorf("invalid time format: %s", timeStr)
}
hour, _ := strconv.Atoi(timeStr[0:2])
minute, _ := strconv.Atoi(timeStr[2:4])
second, _ := strconv.Atoi(timeStr[4:6])
timestamp := time.Date(year, time.Month(month), day, hour, minute, second, 0, time.UTC)
if timestamp.Month() != time.Month(month) || timestamp.Day() != day {
return time.Time{}, fmt.Errorf("invalid date: %04d-%02d-%02d", year, month, day)
}
return timestamp, nil
}
func countUnique(parts []dateParts, extractor func(p dateParts) int) int {
seen := make(map[int]bool)
for _, p := range parts {
seen[extractor(p)] = true
}
return len(seen)
}
func HasTimestampFilename(filename string) bool {
basename := filepath.Base(filename)
return timestampPattern.MatchString(basename)
}