Files

173 lines
4.4 KiB
Go

package ted
import (
"fmt"
"strconv"
"strings"
"time"
)
// ParseIssueDateTimeToUnix combines cbc:IssueDate and cbc:IssueTime (with timezone offsets) into one Unix timestamp.
func ParseIssueDateTimeToUnix(issueDate, issueTime string) int64 {
issueDate = strings.TrimSpace(issueDate)
issueTime = strings.TrimSpace(issueTime)
if issueDate == "" {
return 0
}
if issueTime == "" {
return ParseDateToUnix(issueDate)
}
dateCore := issueDate
if len(issueDate) >= 10 && issueDate[4] == '-' && issueDate[7] == '-' {
dateCore = issueDate[:10]
}
combined := dateCore + "T" + issueTime
if t, err := ParseDate(combined); err == nil {
return t.Unix()
}
return ParseDateToUnix(issueDate)
}
// TransmissionDispatchUnix reads BT-803 (eSender) transmission date/time from extension content (root or eForms extension).
func TransmissionDispatchUnix(ext *UBLExtensions) int64 {
if ext == nil || ext.UBLExtension == nil || ext.UBLExtension.ExtensionContent == nil {
return 0
}
ec := ext.UBLExtension.ExtensionContent
if u := ParseIssueDateTimeToUnix(ec.TransmissionDate, ec.TransmissionTime); u != 0 {
return u
}
if ec.EformsExtension != nil {
if u := ParseIssueDateTimeToUnix(ec.EformsExtension.TransmissionDate, ec.EformsExtension.TransmissionTime); u != 0 {
return u
}
}
return 0
}
// ParseEuropeanAmount normalizes TED amount strings (spaces, comma decimal) for strconv.ParseFloat.
func ParseEuropeanAmount(raw string) (float64, bool) {
s := strings.TrimSpace(raw)
s = strings.ReplaceAll(s, "\u00a0", "")
s = strings.ReplaceAll(s, " ", "")
s = strings.ReplaceAll(s, ",", ".")
if s == "" {
return 0, false
}
v, err := strconv.ParseFloat(s, 64)
return v, err == nil
}
// parseDate parses various date formats used in TED XML
func ParseDate(dateStr string) (time.Time, error) {
if dateStr == "" {
return time.Time{}, fmt.Errorf("empty date string")
}
// Common TED date formats
formats := []string{
"2006-01-02+07:00",
"2006-01-02-07:00",
"2006-01-02T15:04:05+07:00",
"2006-01-02T15:04:05-07:00",
"2006-01-02T15:04:05Z",
"2006-01-02Z",
"2006-01-02",
time.RFC3339,
time.RFC3339Nano,
}
for _, format := range formats {
if parsedTime, err := time.Parse(format, dateStr); err == nil {
return parsedTime, nil
}
}
return time.Time{}, fmt.Errorf("unable to parse date: %s", dateStr)
}
// parseDateToUnixMilli converts string date to Unix milliseconds (int64)
func ParseDateToUnix(dateStr string) int64 {
if dateStr == "" {
return 0
}
if parsedTime, err := ParseDate(dateStr); err == nil {
return parsedTime.Unix()
}
return 0
}
// parseTimeToUnix converts string time to Unix milliseconds (int64)
func ParseTimeToUnix(timeStr string) int64 {
if timeStr == "" {
return 0
}
// If time string is just time without date, use today's date
if !strings.Contains(timeStr, "T") && !strings.Contains(timeStr, " ") {
// Assume it's just a time like "15:04:05"
today := time.Now().Format("2006-01-02")
timeStr = fmt.Sprintf("%sT%s", today, timeStr)
}
if parsedTime, err := ParseDate(timeStr); err == nil {
return parsedTime.Unix()
}
return 0
}
func CalculateSubmissionDeadline(publicationDate time.Time) time.Time {
deadline := publicationDate
workingHoursAdded := 0
for workingHoursAdded < 48 {
deadline = deadline.Add(1 * time.Hour)
if deadline.Weekday() != time.Saturday && deadline.Weekday() != time.Sunday {
workingHoursAdded++
}
}
return deadline
}
func CalculateApplicationDeadline(tenderDeadline time.Time) time.Time {
applicationDeadline := tenderDeadline
workingDaysSubtracted := 0
for workingDaysSubtracted < 7 {
applicationDeadline = applicationDeadline.AddDate(0, 0, -1)
if applicationDeadline.Weekday() != time.Saturday && applicationDeadline.Weekday() != time.Sunday {
workingDaysSubtracted++
}
}
return applicationDeadline
}
// generateTenderURL generates the TED tender URL from NoticePublicationID
func GenerateTenderURL(noticePublicationID string) string {
if noticePublicationID == "" {
return ""
}
// Remove leading zeros and format for URL
// Example: 00522942-2025 -> https://ted.europa.eu/en/notice/-/detail/522942-2025
parts := strings.Split(noticePublicationID, "-")
if len(parts) != 2 {
return ""
}
// Convert to int and back to string to remove leading zeros
if num, err := strconv.Atoi(parts[0]); err == nil {
cleanID := fmt.Sprintf("%d-%s", num, parts[1])
return fmt.Sprintf("https://ted.europa.eu/en/notice/-/detail/%s", cleanID)
}
return ""
}