173 lines
4.4 KiB
Go
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 ""
|
|
}
|