diff --git a/src/main/java/com/gaotao/modules/base/entity/ReportLabelList.java b/src/main/java/com/gaotao/modules/base/entity/ReportLabelList.java index f316966..c98f761 100644 --- a/src/main/java/com/gaotao/modules/base/entity/ReportLabelList.java +++ b/src/main/java/com/gaotao/modules/base/entity/ReportLabelList.java @@ -46,6 +46,7 @@ public class ReportLabelList { private String dateExtractType; private String firstWeekDate; private String firstDayOfWeek; + private Integer dateOffsetDays; private String dateFormat; private String dateSeparator; private String yearDigits; diff --git a/src/main/java/com/gaotao/modules/base/service/Impl/LabelDataProcessorServiceImpl.java b/src/main/java/com/gaotao/modules/base/service/Impl/LabelDataProcessorServiceImpl.java index 4c515f2..fa802e9 100644 --- a/src/main/java/com/gaotao/modules/base/service/Impl/LabelDataProcessorServiceImpl.java +++ b/src/main/java/com/gaotao/modules/base/service/Impl/LabelDataProcessorServiceImpl.java @@ -1,6 +1,7 @@ package com.gaotao.modules.base.service.Impl; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import com.gaotao.modules.api.entity.IfsBoxLabel; import com.gaotao.modules.api.service.IfsApiService; import com.gaotao.modules.base.entity.LabelSettingData; @@ -17,6 +18,7 @@ import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.DecimalFormat; +import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.ZoneId; import java.time.format.DateTimeFormatter; @@ -787,6 +789,12 @@ public class LabelDataProcessorServiceImpl implements LabelDataProcessorService return dateValue; } + // 日期偏移处理:正数加天,负数减天 + Integer dateOffsetDays = element.getDateOffsetDays(); + if (dateOffsetDays != null && dateOffsetDays != 0) { + date = date.plusDays(dateOffsetDays.longValue()); + } + switch (extractType) { case "year": return String.valueOf(date.getYear()); @@ -813,6 +821,23 @@ public class LabelDataProcessorServiceImpl implements LabelDataProcessorService * 解析日期字符串 */ private LocalDate parseDate(String dateValue) { + String normalizedValue = dateValue != null ? dateValue.trim() : ""; + if (normalizedValue.isEmpty()) { + return null; + } + + // 去除字符串两端引号,兼容 "\"2024-09-14T00:00:00\"" 这类数据 + if ((normalizedValue.startsWith("\"") && normalizedValue.endsWith("\"")) + || (normalizedValue.startsWith("'") && normalizedValue.endsWith("'"))) { + normalizedValue = normalizedValue.substring(1, normalizedValue.length() - 1).trim(); + } + + // 优先使用java.time处理,避免宽松解析导致错误日期(例如 7203-12-23) + LocalDate javaTimeParsedDate = parseDateByJavaTime(normalizedValue); + if (javaTimeParsedDate != null) { + return javaTimeParsedDate; + } + String[] patterns = { // 年月日 "yyyy-MM-dd", "yyyy/MM/dd", "yyyy.MM.dd", @@ -826,6 +851,14 @@ public class LabelDataProcessorServiceImpl implements LabelDataProcessorService "MM-dd-yyyy HH:mm:ss", "MM/dd/yyyy HH:mm:ss", "MM.dd.yyyy HH:mm:ss", "yyyyMMdd HHmmss", + // ISO时间格式(T分隔) + "yyyy-MM-dd'T'HH:mm:ss", + "yyyy-MM-dd'T'HH:mm:ss.SSS", + "yyyy-MM-dd'T'HH:mm:ssX", + "yyyy-MM-dd'T'HH:mm:ss.SSSX", + "yyyy-MM-dd'T'HH:mm:ssXXX", + "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", + // 年月日 + 时分 "yyyy-MM-dd HH:mm", "yyyy/MM/dd HH:mm", "yyyy.MM.dd HH:mm", "dd-MM-yyyy HH:mm", "dd/MM/yyyy HH:mm", "dd.MM.yyyy HH:mm", @@ -833,11 +866,13 @@ public class LabelDataProcessorServiceImpl implements LabelDataProcessorService "yyyyMMdd HHmm", // 带毫秒 - "yyyy-MM-dd HH:mm:ss.SSS", "yyyy/MM/dd HH:mm:ss.SSS", "yyyy.MM.dd HH:mm:ss.SSS" + "yyyy-MM-dd HH:mm:ss.S", "yyyy-MM-dd HH:mm:ss.SS", "yyyy-MM-dd HH:mm:ss.SSS", + "yyyy/MM/dd HH:mm:ss.S", "yyyy/MM/dd HH:mm:ss.SS", "yyyy/MM/dd HH:mm:ss.SSS", + "yyyy.MM.dd HH:mm:ss.S", "yyyy.MM.dd HH:mm:ss.SS", "yyyy.MM.dd HH:mm:ss.SSS" }; try { - Date date = DateUtils.parseDate(dateValue.trim(), patterns); + Date date = DateUtils.parseDateStrictly(normalizedValue, patterns); return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); } catch (Exception e) { log.debug("日期解析失败: {}", dateValue); @@ -845,6 +880,97 @@ public class LabelDataProcessorServiceImpl implements LabelDataProcessorService } } + /** + * 使用java.time严格解析日期,优先处理ISO格式与数据库常见时间戳字符串 + */ + private LocalDate parseDateByJavaTime(String dateValue) { + // 兼容纯数字时间戳(秒/毫秒/微秒/纳秒) + LocalDate epochDate = parseEpochTimestamp(dateValue); + if (epochDate != null) { + return epochDate; + } + + try { + return LocalDate.parse(dateValue, DateTimeFormatter.ISO_LOCAL_DATE); + } catch (Exception ignored) { + } + + try { + return java.time.LocalDateTime.parse(dateValue, DateTimeFormatter.ISO_LOCAL_DATE_TIME).toLocalDate(); + } catch (Exception ignored) { + } + + try { + return java.time.OffsetDateTime.parse(dateValue, DateTimeFormatter.ISO_OFFSET_DATE_TIME).toLocalDate(); + } catch (Exception ignored) { + } + + try { + return java.time.ZonedDateTime.parse(dateValue, DateTimeFormatter.ISO_ZONED_DATE_TIME).toLocalDate(); + } catch (Exception ignored) { + } + + try { + return java.time.Instant.parse(dateValue).atZone(ZoneId.systemDefault()).toLocalDate(); + } catch (Exception ignored) { + } + + // 兼容数据库常见字符串:2024-09-14 00:00:00.0 + try { + java.time.format.DateTimeFormatter formatter = new java.time.format.DateTimeFormatterBuilder() + .appendPattern("yyyy-MM-dd HH:mm:ss") + .optionalStart() + .appendFraction(java.time.temporal.ChronoField.NANO_OF_SECOND, 1, 9, true) + .optionalEnd() + .toFormatter(); + return java.time.LocalDateTime.parse(dateValue, formatter).toLocalDate(); + } catch (Exception ignored) { + } + + // 兼容数据库常见字符串:2024/09/14 00:00:00.0 + try { + java.time.format.DateTimeFormatter formatter = new java.time.format.DateTimeFormatterBuilder() + .appendPattern("yyyy/MM/dd HH:mm:ss") + .optionalStart() + .appendFraction(java.time.temporal.ChronoField.NANO_OF_SECOND, 1, 9, true) + .optionalEnd() + .toFormatter(); + return java.time.LocalDateTime.parse(dateValue, formatter).toLocalDate(); + } catch (Exception ignored) { + } + + return null; + } + + /** + * 解析纯数字时间戳字符串 + */ + private LocalDate parseEpochTimestamp(String dateValue) { + if (dateValue == null || !dateValue.matches("^-?\\d+(\\.\\d+)?$")) { + return null; + } + + try { + BigDecimal numericValue = new BigDecimal(dateValue); + long epoch = numericValue.longValue(); + long absEpoch = Math.abs(epoch); + + // 判定时间戳单位并转换为毫秒 + // 秒:10位左右;毫秒:13位左右;微秒:16位左右;纳秒:19位左右 + if (absEpoch >= 1_000_000_000_000_000_000L) { + epoch = epoch / 1_000_000L; // 纳秒 -> 毫秒 + } else if (absEpoch >= 1_000_000_000_000_000L) { + epoch = epoch / 1_000L; // 微秒 -> 毫秒 + } else if (absEpoch < 100_000_000_000L) { + epoch = epoch * 1_000L; // 秒 -> 毫秒 + } + + return java.time.Instant.ofEpochMilli(epoch).atZone(ZoneId.systemDefault()).toLocalDate(); + } catch (Exception ignored) { + return null; + } + } + /** * 计算周数 */ @@ -1113,6 +1239,7 @@ public class LabelDataProcessorServiceImpl implements LabelDataProcessorService copy.setDateExtractType(original.getDateExtractType()); copy.setFirstWeekDate(original.getFirstWeekDate()); copy.setFirstDayOfWeek(original.getFirstDayOfWeek()); + copy.setDateOffsetDays(original.getDateOffsetDays()); copy.setDateFormat(original.getDateFormat()); copy.setDateSeparator(original.getDateSeparator()); copy.setYearDigits(original.getYearDigits()); @@ -1598,6 +1725,9 @@ public class LabelDataProcessorServiceImpl implements LabelDataProcessorService if (ifsBoxLabels != null && !ifsBoxLabels.isEmpty()) { // 将IfsBoxLabel实体类转换为Map ObjectMapper objectMapper = new ObjectMapper(); + // 日期字段转为标准字符串,避免默认序列化为时间戳数字 + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); for (IfsBoxLabel label : ifsBoxLabels) { @SuppressWarnings("unchecked") Map dataMap = objectMapper.convertValue(label, Map.class);