|
11 | 11 | import java.time.ZoneId; |
12 | 12 | import java.time.ZoneOffset; |
13 | 13 | import java.time.ZonedDateTime; |
| 14 | +import java.time.format.DateTimeFormatter; |
| 15 | +import java.time.format.DateTimeParseException; |
| 16 | +import java.time.temporal.ChronoUnit; |
| 17 | +import java.util.regex.Matcher; |
| 18 | +import java.util.regex.Pattern; |
14 | 19 | import lombok.experimental.UtilityClass; |
15 | 20 | import org.opensearch.sql.data.model.ExprTimeValue; |
16 | 21 | import org.opensearch.sql.data.model.ExprValue; |
|
19 | 24 | @UtilityClass |
20 | 25 | public class DateTimeUtils { |
21 | 26 |
|
| 27 | + private static final Pattern OFFSET_PATTERN = Pattern.compile("([+-])(\\d+)([smhdwMy]?)"); |
| 28 | + private static final DateTimeFormatter DIRECT_FORMATTER = |
| 29 | + DateTimeFormatter.ofPattern("MM/dd/yyyy:HH:mm:ss"); |
| 30 | + |
22 | 31 | /** |
23 | 32 | * Util method to round the date/time with given unit. |
24 | 33 | * |
@@ -151,4 +160,93 @@ public static LocalDate extractDate(ExprValue value, FunctionProperties function |
151 | 160 | ? ((ExprTimeValue) value).dateValue(functionProperties) |
152 | 161 | : value.dateValue(); |
153 | 162 | } |
| 163 | + |
| 164 | + public static ZonedDateTime getRelativeZonedDateTime(String input, ZonedDateTime baseTime) { |
| 165 | + try { |
| 166 | + Instant localDateTime = |
| 167 | + LocalDateTime.parse(input, DIRECT_FORMATTER).toInstant(ZoneOffset.UTC); |
| 168 | + return localDateTime.atZone(baseTime.getZone()); |
| 169 | + } catch (DateTimeParseException ignored) { |
| 170 | + } |
| 171 | + |
| 172 | + if ("now".equalsIgnoreCase(input) || "now()".equalsIgnoreCase(input)) { |
| 173 | + return baseTime; |
| 174 | + } |
| 175 | + |
| 176 | + // 1. extract snap(like @d) |
| 177 | + String snapUnit = null; |
| 178 | + int atIndex = input.indexOf('@'); |
| 179 | + if (atIndex != -1) { |
| 180 | + snapUnit = input.substring(atIndex + 1); |
| 181 | + input = input.substring(0, atIndex); |
| 182 | + } |
| 183 | + |
| 184 | + // 2. apply snap |
| 185 | + ZonedDateTime result = baseTime; |
| 186 | + if (snapUnit != null && !snapUnit.isEmpty()) { |
| 187 | + result = applySnap(result, snapUnit); |
| 188 | + } |
| 189 | + |
| 190 | + // 3. apply offset one by one(like -1d+2h-10m) |
| 191 | + Matcher matcher = OFFSET_PATTERN.matcher(input); |
| 192 | + while (matcher.find()) { |
| 193 | + String sign = matcher.group(1); |
| 194 | + int value = Integer.parseInt(matcher.group(2)); |
| 195 | + String unit = matcher.group(3); |
| 196 | + if (unit == null || unit.isEmpty()) { |
| 197 | + unit = "s"; // default value is second |
| 198 | + } |
| 199 | + result = applyOffset(result, sign, value, unit); |
| 200 | + } |
| 201 | + |
| 202 | + return result; |
| 203 | + } |
| 204 | + |
| 205 | + private static ZonedDateTime applyOffset( |
| 206 | + ZonedDateTime base, String sign, int value, String unit) { |
| 207 | + ChronoUnit chronoUnit = parseUnit(unit); |
| 208 | + return sign.equals("-") ? base.minus(value, chronoUnit) : base.plus(value, chronoUnit); |
| 209 | + } |
| 210 | + |
| 211 | + private static ZonedDateTime applySnap(ZonedDateTime base, String unit) { |
| 212 | + switch (unit) { |
| 213 | + case "s": |
| 214 | + return base.truncatedTo(ChronoUnit.SECONDS); |
| 215 | + case "m": |
| 216 | + return base.truncatedTo(ChronoUnit.MINUTES); |
| 217 | + case "h": |
| 218 | + return base.truncatedTo(ChronoUnit.HOURS); |
| 219 | + case "d": |
| 220 | + return base.truncatedTo(ChronoUnit.DAYS); |
| 221 | + case "w": |
| 222 | + return base.minusDays((base.getDayOfWeek().getValue() % 7)).truncatedTo(ChronoUnit.DAYS); |
| 223 | + case "M": |
| 224 | + return base.withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS); |
| 225 | + case "y": |
| 226 | + return base.withDayOfYear(1).truncatedTo(ChronoUnit.DAYS); |
| 227 | + default: |
| 228 | + throw new IllegalArgumentException("Unsupported snap unit: " + unit); |
| 229 | + } |
| 230 | + } |
| 231 | + |
| 232 | + private static ChronoUnit parseUnit(String unit) { |
| 233 | + switch (unit) { |
| 234 | + case "s": |
| 235 | + return ChronoUnit.SECONDS; |
| 236 | + case "m": |
| 237 | + return ChronoUnit.MINUTES; |
| 238 | + case "h": |
| 239 | + return ChronoUnit.HOURS; |
| 240 | + case "d": |
| 241 | + return ChronoUnit.DAYS; |
| 242 | + case "w": |
| 243 | + return ChronoUnit.WEEKS; |
| 244 | + case "M": |
| 245 | + return ChronoUnit.MONTHS; |
| 246 | + case "y": |
| 247 | + return ChronoUnit.YEARS; |
| 248 | + default: |
| 249 | + throw new IllegalArgumentException("Unsupported time unit: " + unit); |
| 250 | + } |
| 251 | + } |
154 | 252 | } |
0 commit comments