package ted import ( "fmt" "strconv" "strings" "time" "tm/internal/notice" ) // 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 } // unixToDateString converts Unix to date string for TenderID generation func UnixToDateString(unix int64) string { if unix == 0 { return "00000000" } return time.Unix(unix, 0).Format("20060102") } 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 } // padOrTruncate pads a string with zeros or truncates it to the specified length func PadOrTruncate(str string, length int) string { // Remove any non-alphanumeric characters and convert to uppercase clean := strings.ToUpper(strings.ReplaceAll(str, "-", "")) clean = strings.ReplaceAll(clean, " ", "") if len(clean) >= length { return clean[:length] } // Pad with zeros return clean + strings.Repeat("0", length-len(clean)) } // 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 "" } // generateTenderID generates a unique tender ID using the format: // {Source}{BUYER_CODE}{TED_CODE}{DATE}{LOCATION_CODE} func GenerateTenderID(t *notice.Notice) string { var parts []string // Source (TED) parts = append(parts, "1") // TED code (Notice Publication ID, first 8 chars) tedCode := "00000000" if t.NoticePublicationID != "" { tedCode = PadOrTruncate(t.NoticePublicationID, 8) } parts = append(parts, tedCode) // Buyer code (first 8 chars of buyer organization ID, or 8 zeros if not available) // buyerCode := "00000000" // if t.BuyerOrganization != nil && t.BuyerOrganization.ID != "" { // buyerCode = padOrTruncate(t.BuyerOrganization.ID, 8) // } // parts = append(parts, buyerCode) // Date (YYYYMMDD format from issue date) parts = append(parts, PadOrTruncate(UnixToDateString(t.IssueDate), 4)) // Location code (country code + region code, padded to 6 chars) locationCode := "EU" if t.CountryCode != "" { locationCode = t.CountryCode } parts = append(parts, locationCode) return strings.Join(parts, "") }