| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | //go:build linux && cgo && ibm_mq |
| 4 | |
| 5 | package pcf |
| 6 | |
| 7 | import ( |
| 8 | "fmt" |
| 9 | "strconv" |
| 10 | "strings" |
| 11 | "time" |
| 12 | ) |
| 13 | |
| 14 | // ParseMQDateTime parses IBM MQ date and time strings into time.Time |
| 15 | // Supports both old format (YYYYMMDD, HHMMSSSS) and new format (YYYY-MM-DD, HH:MM:SS or HH.MM.SS) |
| 16 | // |
| 17 | // CRITICAL: Based on real-world testing, ALL IBM MQ PCF timestamps appear to be UTC: |
| 18 | // - Message timestamps (MQMD PutDate/PutTime): Always UTC |
| 19 | // - Administrative timestamps (start times, alteration times): Also UTC (contrary to some documentation) |
| 20 | // |
| 21 | // Parameters: |
| 22 | // |
| 23 | // date: Date string in format YYYYMMDD or YYYY-MM-DD |
| 24 | // timeStr: Time string in format HHMMSSSS or HH:MM:SS or HH.MM.SS |
| 25 | // isUTC: true for UTC timestamps, false for local time (kept for compatibility) |
| 26 | func ParseMQDateTime(date, timeStr string, isUTC bool) (time.Time, error) { |
| 27 | if date == "" || timeStr == "" { |
| 28 | return time.Time{}, fmt.Errorf("empty date or time") |
| 29 | } |
| 30 | |
| 31 | var parsedTime time.Time |
| 32 | var err error |
| 33 | var timezone *time.Location |
| 34 | |
| 35 | // Set timezone based on timestamp type |
| 36 | if isUTC { |
| 37 | timezone = time.UTC |
| 38 | } else { |
| 39 | timezone = time.Local // Use system local time (queue manager timezone) |
| 40 | } |
| 41 | |
| 42 | // Try new format first: "YYYY-MM-DD" + "HH:MM:SS" or "HH.MM.SS" |
| 43 | if len(date) == 10 && date[4] == '-' { |
| 44 | // New format: "2024-01-15" + "14:30:25" or "14.30.25" |
| 45 | // Replace dots with colons if present |
| 46 | cleanTimeStr := strings.ReplaceAll(timeStr, ".", ":") |
| 47 | datetime := date + "T" + cleanTimeStr |
| 48 | |
| 49 | if isUTC { |
| 50 | datetime += "Z" // Add UTC suffix for message timestamps |
| 51 | parsedTime, err = time.Parse("2006-01-02T15:04:05Z", datetime) |
| 52 | } else { |
| 53 | // Parse in local timezone for administrative timestamps |
| 54 | parsedTime, err = time.ParseInLocation("2006-01-02T15:04:05", datetime, timezone) |
| 55 | } |
| 56 | |
| 57 | if err != nil { |
| 58 | return time.Time{}, fmt.Errorf("failed to parse new format date/time %s + %s (UTC=%v): %v", date, timeStr, isUTC, err) |
| 59 | } |
| 60 | } else { |
| 61 | // Try old format: "YYYYMMDD" + "HHMMSSSS" |
| 62 | if len(date) != 8 { |
| 63 | return time.Time{}, fmt.Errorf("invalid date format: %s (expected YYYYMMDD or YYYY-MM-DD)", date) |
| 64 | } |
| 65 | if len(timeStr) != 8 { |
| 66 | return time.Time{}, fmt.Errorf("invalid time format: %s (expected HHMMSSSS or HH:MM:SS)", timeStr) |
| 67 | } |
| 68 | |
| 69 | // Parse YYYYMMDD |
| 70 | year, _ := strconv.Atoi(date[0:4]) |
| 71 | month, _ := strconv.Atoi(date[4:6]) |
| 72 | day, _ := strconv.Atoi(date[6:8]) |
| 73 | |
| 74 | // Parse HHMMSSSS (HH MM SS SS where last SS is centiseconds) |
| 75 | hour, _ := strconv.Atoi(timeStr[0:2]) |
| 76 | minute, _ := strconv.Atoi(timeStr[2:4]) |
| 77 | second, _ := strconv.Atoi(timeStr[4:6]) |
| 78 | // Ignore centiseconds for uptime calculation |
| 79 | |
| 80 | parsedTime = time.Date(year, time.Month(month), day, hour, minute, second, 0, timezone) |
| 81 | } |
| 82 | |
| 83 | return parsedTime, nil |
| 84 | } |
| 85 | |
| 86 | // ParseMQMessageDateTime parses IBM MQ message timestamps (always UTC) |
| 87 | // Used for MQMD PutDate/PutTime and other message-related timestamps |
| 88 | func ParseMQMessageDateTime(date, timeStr string) (time.Time, error) { |
| 89 | return ParseMQDateTime(date, timeStr, true) // Message timestamps are UTC |
| 90 | } |
| 91 | |
| 92 | // ParseMQAdminDateTime parses IBM MQ administrative timestamps (UTC) |
| 93 | // CORRECTION: Based on real-world testing, ALL IBM MQ PCF timestamps appear to be UTC |
| 94 | // Used for queue manager start times, listener start times, object alteration times |
| 95 | func ParseMQAdminDateTime(date, timeStr string) (time.Time, error) { |
| 96 | return ParseMQDateTime(date, timeStr, true) // ALL PCF timestamps are UTC |
| 97 | } |
| 98 | |
| 99 | // CalculateUptimeSeconds calculates uptime in seconds from IBM MQ administrative start date and time |
| 100 | // CORRECTION: Based on real-world testing, administrative timestamps are actually UTC |
| 101 | func CalculateUptimeSeconds(startDate, startTime string) (int64, error) { |
| 102 | parsedTime, err := ParseMQAdminDateTime(startDate, startTime) |
| 103 | if err != nil { |
| 104 | return 0, err |
| 105 | } |
| 106 | |
| 107 | // Calculate uptime as seconds since start time |
| 108 | uptime := time.Since(parsedTime) |
| 109 | if uptime < 0 { |
| 110 | return 0, fmt.Errorf("start time is in the future: %v", parsedTime) |
| 111 | } |
| 112 | |
| 113 | return int64(uptime.Seconds()), nil |
| 114 | } |