| 105 | // } |
| 106 | |
| 107 | public static Long parse8601(Object datetime2) { |
| 108 | if (!(datetime2 instanceof String)) { |
| 109 | return null; |
| 110 | } |
| 111 | |
| 112 | String datetime = stripUtcSuffix(((String) datetime2).trim()); |
| 113 | if (datetime.isEmpty()) { |
| 114 | return null; |
| 115 | } |
| 116 | |
| 117 | try { |
| 118 | if (datetime.indexOf('T') >= 0) { |
| 119 | if (datetime.endsWith("Z")) { |
| 120 | return Instant.parse(datetime).toEpochMilli(); |
| 121 | } |
| 122 | // Offset form: "+HH:MM", "+HHMM", "-HH:MM", "-HHMM" at the tail |
| 123 | if (datetime.matches(".*[+-]\\d{2}:?\\d{2}$")) { |
| 124 | // ISO_OFFSET_DATE_TIME requires "+HH:MM"; insert colon if absent |
| 125 | String normalized = datetime.replaceFirst("([+-]\\d{2})(\\d{2})$", "$1:$2"); |
| 126 | return OffsetDateTime.parse(normalized, DateTimeFormatter.ISO_OFFSET_DATE_TIME) |
| 127 | .toInstant().toEpochMilli(); |
| 128 | } |
| 129 | // No zone/offset: treat as UTC |
| 130 | LocalDateTime ldt = LocalDateTime.parse(datetime); |
| 131 | return ldt.toInstant(ZoneOffset.UTC).toEpochMilli(); |
| 132 | } |
| 133 | |
| 134 | // Space format, e.g. "2019-08-12 13:20:00" (treat as UTC) |
| 135 | LocalDateTime ldt = LocalDateTime.parse(datetime, SPACE_FORMAT); |
| 136 | return ldt.toInstant(ZoneOffset.UTC).toEpochMilli(); |
| 137 | |
| 138 | } catch (DateTimeParseException ex) { |
| 139 | return null; |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | // Aftermath (and a few other exchanges) return datetimes like |
| 144 | // "2025-12-29 22:43:54.639 UTC". JS's Date.parse and Python's dateutil |