|
13 | 13 | package com.netflix.conductor.common.run.tasks; |
14 | 14 |
|
15 | 15 | import java.time.Duration; |
| 16 | +import java.time.LocalDate; |
| 17 | +import java.time.LocalDateTime; |
| 18 | +import java.time.ZoneId; |
16 | 19 | import java.time.ZonedDateTime; |
17 | 20 | import java.time.format.DateTimeFormatter; |
18 | 21 | import java.time.format.DateTimeParseException; |
| 22 | +import java.util.regex.Matcher; |
| 23 | +import java.util.regex.Pattern; |
19 | 24 |
|
20 | 25 | import com.netflix.conductor.common.metadata.tasks.Task; |
21 | 26 | import com.netflix.conductor.common.metadata.tasks.TaskType; |
@@ -53,8 +58,19 @@ public class WaitTask extends TypedTask { |
53 | 58 | public static final String DURATION_INPUT = "duration"; |
54 | 59 | public static final String UNTIL_INPUT = "until"; |
55 | 60 |
|
56 | | - public static final DateTimeFormatter DATE_TIME_FORMATTER = |
57 | | - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm z"); |
| 61 | + private static final Pattern DURATION_PATTERN = Pattern.compile( |
| 62 | + "\\s*(?:(\\d+)\\s*(?:days?|d))?" |
| 63 | + + "\\s*(?:(\\d+)\\s*(?:hours?|hrs?|h))?" |
| 64 | + + "\\s*(?:(\\d+)\\s*(?:minutes?|mins?|m))?" |
| 65 | + + "\\s*(?:(\\d+)\\s*(?:seconds?|secs?|s))?" |
| 66 | + + "\\s*", |
| 67 | + Pattern.CASE_INSENSITIVE); |
| 68 | + |
| 69 | + private static final DateTimeFormatter[] DATE_TIME_FORMATTERS = { |
| 70 | + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"), |
| 71 | + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm z"), |
| 72 | + DateTimeFormatter.ofPattern("yyyy-MM-dd") |
| 73 | + }; |
58 | 74 |
|
59 | 75 | /** |
60 | 76 | * Represents the type of wait condition. |
@@ -153,56 +169,61 @@ private Duration parseDuration(String durationStr) { |
153 | 169 | if (durationStr == null || durationStr.isEmpty()) { |
154 | 170 | return null; |
155 | 171 | } |
156 | | - durationStr = durationStr.trim().toLowerCase(); |
157 | | - |
158 | | - if (durationStr.length() < 2) { |
159 | | - // Assume seconds if just a number |
160 | | - try { |
161 | | - return Duration.ofSeconds(Long.parseLong(durationStr)); |
162 | | - } catch (NumberFormatException e) { |
163 | | - return null; |
164 | | - } |
| 172 | + |
| 173 | + Matcher m = DURATION_PATTERN.matcher(durationStr); |
| 174 | + if (!m.matches()) { |
| 175 | + return null; |
165 | 176 | } |
166 | 177 |
|
167 | | - char unit = durationStr.charAt(durationStr.length() - 1); |
168 | | - String valueStr = durationStr.substring(0, durationStr.length() - 1); |
| 178 | + int days = (m.start(1) == -1 ? 0 : Integer.parseInt(m.group(1))); |
| 179 | + int hours = (m.start(2) == -1 ? 0 : Integer.parseInt(m.group(2))); |
| 180 | + int mins = (m.start(3) == -1 ? 0 : Integer.parseInt(m.group(3))); |
| 181 | + int secs = (m.start(4) == -1 ? 0 : Integer.parseInt(m.group(4))); |
169 | 182 |
|
170 | | - try { |
171 | | - long value = Long.parseLong(valueStr); |
172 | | - switch (unit) { |
173 | | - case 's': |
174 | | - return Duration.ofSeconds(value); |
175 | | - case 'm': |
176 | | - return Duration.ofMinutes(value); |
177 | | - case 'h': |
178 | | - return Duration.ofHours(value); |
179 | | - case 'd': |
180 | | - return Duration.ofDays(value); |
181 | | - default: |
182 | | - // If the last char is a digit, assume the whole string is seconds |
183 | | - if (Character.isDigit(unit)) { |
184 | | - return Duration.ofSeconds(Long.parseLong(durationStr)); |
185 | | - } |
186 | | - return null; |
187 | | - } |
188 | | - } catch (NumberFormatException e) { |
| 183 | + // If all components are zero and string is not blank, it's invalid |
| 184 | + if (days == 0 && hours == 0 && mins == 0 && secs == 0 && !durationStr.trim().isEmpty()) { |
189 | 185 | return null; |
190 | 186 | } |
| 187 | + |
| 188 | + return Duration.ofSeconds((days * 86400L) + (hours * 60L + mins) * 60L + secs); |
191 | 189 | } |
192 | 190 |
|
193 | 191 | private ZonedDateTime parseDateTime(String dateTimeStr) { |
194 | 192 | if (dateTimeStr == null || dateTimeStr.isEmpty()) { |
195 | 193 | return null; |
196 | 194 | } |
| 195 | + |
| 196 | + // Try each pattern in order (matching backend DateTimeUtils behavior) |
| 197 | + // Pattern 0: "yyyy-MM-dd HH:mm" - no timezone, use system default |
| 198 | + // Pattern 1: "yyyy-MM-dd HH:mm z" - with timezone |
| 199 | + // Pattern 2: "yyyy-MM-dd" - date only, use system default timezone |
| 200 | + |
| 201 | + // Try pattern with timezone first |
197 | 202 | try { |
198 | | - return ZonedDateTime.parse(dateTimeStr, DATE_TIME_FORMATTER); |
199 | | - } catch (DateTimeParseException e) { |
200 | | - // Try ISO format as fallback |
201 | | - try { |
202 | | - return ZonedDateTime.parse(dateTimeStr); |
203 | | - } catch (DateTimeParseException e2) { |
204 | | - return null; |
205 | | - } |
| 203 | + return ZonedDateTime.parse(dateTimeStr, DATE_TIME_FORMATTERS[1]); |
| 204 | + } catch (DateTimeParseException ignored) { |
206 | 205 | } |
| 206 | + |
| 207 | + // Try datetime without timezone |
| 208 | + try { |
| 209 | + LocalDateTime localDateTime = LocalDateTime.parse(dateTimeStr, DATE_TIME_FORMATTERS[0]); |
| 210 | + return localDateTime.atZone(ZoneId.systemDefault()); |
| 211 | + } catch (DateTimeParseException ignored) { |
| 212 | + } |
| 213 | + |
| 214 | + // Try date only |
| 215 | + try { |
| 216 | + LocalDate localDate = LocalDate.parse(dateTimeStr, DATE_TIME_FORMATTERS[2]); |
| 217 | + return localDate.atStartOfDay(ZoneId.systemDefault()); |
| 218 | + } catch (DateTimeParseException ignored) { |
| 219 | + } |
| 220 | + |
| 221 | + // Try ISO format as fallback |
| 222 | + try { |
| 223 | + return ZonedDateTime.parse(dateTimeStr); |
| 224 | + } catch (DateTimeParseException ignored) { |
| 225 | + } |
| 226 | + |
| 227 | + return null; |
207 | 228 | } |
208 | 229 | } |
0 commit comments