Parcourir la source

数据拟合数据过滤调整

王波 il y a 3 mois
Parent
commit
edc8fdbb0e

+ 213 - 123
runeconomy-xk/src/main/java/com/gyee/runeconomy/model/PowerProcessALG.java

@@ -6,10 +6,7 @@ import com.gyee.runeconomy.dto.result.PowerPointData;
 
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 /**
  * 数据预处理算法
@@ -40,7 +37,7 @@ public class PowerProcessALG {
      */
     public static List<PowerPointData> dataProcess(List<PowerPointData> list, Map<Double, Double> map,
                                                    Double maxs, Double mins, Double maxp, Double minp, Boolean isfbw,
-                                                   Boolean isfhl, Boolean isbw, Boolean istj, Boolean isglpc, Boolean isqfh, Integer qfhdj){
+                                                   Boolean isfhl, Boolean isbw, Boolean istj, Boolean isglpc, Boolean isqfh, Integer qfhdj) {
         String timeBW = DateUtil.format(new Date(0), DATE_TIME_PATTERN);
         List<PowerPointData> tempei = new ArrayList<>();
         List<PowerPointData> tempqf = new ArrayList<>();
@@ -49,7 +46,7 @@ public class PowerProcessALG {
         for (PowerPointData item : list) {
             int filter = 0;
             int fjstatus = 0;
-            if (timeBW == DateUtil.format(new Date(0), DATE_TIME_PATTERN)){
+            if (timeBW == DateUtil.format(new Date(0), DATE_TIME_PATTERN)) {
                 timeBW = item.getTime();
             }
             // 过滤非并网值  风机状态不等于2
@@ -144,123 +141,139 @@ public class PowerProcessALG {
         return list;
     }
 
+    /**
+     * 数据预处理
+     *
+     * @param list   预处理的数据
+     * @param map    风速对应的保证功率
+     * @param maxs   最大风速
+     * @param mins   最小风速
+     * @param maxp   最大功率
+     * @param minp   最小功率
+     * @param isfbw  是否并网
+     * @param isfhl  是否合理值
+     * @param isbw   并网后10分钟
+     * @param istj   停机前10分钟
+     * @param isglpc 功率曲线偏差
+     * @param isqfh  是否欠符合
+     * @param qfhdj  欠符合等级
+     * @return
+     */
 
-    public static List<PowerPointData> dataProcess11(List<PowerPointData> list, Map<Double, Double> map,
-                                                   Double maxs, Double mins, Double maxp, Double minp, Boolean isfbw,
-                                                   Boolean isfhl, Boolean isbw, Boolean istj, Boolean isglpc, Boolean isqfh, Integer qfhdj) {
+    public static List<PowerPointData> dataProcess11(
+            List<PowerPointData> list, Map<Double, Double> map,
+            Double maxs, Double mins, Double maxp, Double minp, Boolean isfbw,
+            Boolean isfhl, Boolean isbw, Boolean istj, Boolean isglpc, Boolean isqfh, Integer qfhdj) {
 
         if (list == null || list.isEmpty()) return list;
 
-        // 并网起点时间(第一次遇到并网或者从非并网->并网时初始化)
-        String gridStartTime = null;
+        // ---------- 设置 ----------
+        String gridStartTime = null; // 并网参考点(第一次并网或从非并网->并网时设置)
+        // tempei: 并网期间索引队列(用于停机前10分钟回溯)
+        Deque<Integer> tempeiIndex = new LinkedList<>();
+        // tempqf: 欠发期间索引队列(用于欠发5分钟回溯)
+        Deque<Integer> tempqfIndex = new LinkedList<>();
 
-        // 用索引保存 tempei / tempqf 窗口,避免存对象引用导致连带修改
-        List<Integer> tempeiIndex = new ArrayList<>();
-        List<Integer> tempqfIndex = new ArrayList<>();
+        // 为了追踪每条数据为什么被过滤,记录原因(仅用于调试打印)
+        Map<Integer, String> filterReasons = new HashMap<>();
+
+        // 容忍度:在 map key 查找最近风速的容差(单位 m/s),可以按需要调整
+        final double SPEED_TOLERANCE = 0.05;
 
-        // 遍历原始列表,按业务条件判断 filter(最终写回到每个 item 中)
         for (int i = 0; i < list.size(); i++) {
             PowerPointData item = list.get(i);
-            int filter = 0; // 0 保留,1 过滤
+            int filter = 0; // 0 keep, 1 filtered
+            String reason = null;
 
             String currentTime = item.getTime();
             double speed = item.getSpeed();
             double power = item.getPower();
-            int mxzt = item.getMxzt();
+            int mxzt = item.getMxzt(); // 风机状态,2=并网
 
-            // 状态判断
-            boolean isGrid = (mxzt == 2);      // 并网
-            boolean isNonGrid = !isGrid;       // 非并网
+            boolean isGrid = (mxzt == 2);
+            boolean isNonGrid = !isGrid;
 
-            // ---------------------------
-            // 1) 过滤非并网值(如果开关 isfbw 为 true)
-            //    原逻辑中非并网要被过滤
-            // ---------------------------
-            if (isfbw != null && isfbw && isNonGrid) {
+            // ---------- 0. 边界保护 ----------
+            if (currentTime == null) {
+                // 无时间信息,过滤并记录原因
                 filter = 1;
-                // 标记:当发生非并网事件时,我们需要把之前记录的 tempeiIndex (并网期间的索引)标记为停机前10分钟过滤
-                // 这里的处理会在 istj 分支统一处理(以保持行为一致),但保留当前行为:非并网条目本身也被过滤
-            }
-
-            // ---------------------------
-            // 2) 若发现是并网且 gridStartTime 未初始化,则初始化(第一次并网或刚开始的数据)
-            //    这样能保证“第一条并网数据不会被误过滤”
-            // ---------------------------
-            if (isGrid && gridStartTime == null) {
-                gridStartTime = currentTime;
-                // 不对当前 item 做并网后 10 分钟过滤(作为起点)
-                // 注意:如果你想只在从非并网->并网才初始化,可以改为在 isNonGrid->isGrid 的过渡时初始化
+                reason = "no_time";
+                item.setFilter(filter);
+                filterReasons.put(i, reason);
+                System.out.println("Index " + i + " filtered: " + reason);
+                continue;
             }
 
-            // ---------------------------
-            // 3) 风速功率范围过滤(按mins/maxs/minp/maxp)
-            // ---------------------------
+            // ---------- 1. 风速/功率范围过滤(尽早做) ----------
             if (!Double.isNaN(speed) && !Double.isNaN(power)) {
                 if ((mins != null && speed < mins) || (maxs != null && speed > maxs)
                         || (minp != null && power < minp) || (maxp != null && power > maxp)) {
                     filter = 1;
+                    reason = "out_of_range";
                 }
             }
 
-            // ---------------------------
-            // 4) 过滤非合理值(并网状态下功率小于等于 minp 或速度 < 0) - 保持与你原逻辑一致
-            // ---------------------------
-            if (filter == 0 && isfhl != null && isfhl) {
-                // 如果速度 < 0 且功率 <= minp 则认为不合理(参考原逻辑
-                if (speed < 0 && !Double.isNaN(power) && minp != null && power <= minp) {
-                    filter = 1;
-                }
+            // ---------- 2. 修正并网起点(当从非并网 -> 并网时设置) ----------
+            // 如果 gridStartTime 还没设置,我们尽量在真正的过渡(非并网->并网)时设置它,
+            // 但为了兼容你的数据(如果整个序列从并网开始),我们也可以在首次并网时设置。
+            if (isGrid && gridStartTime == null) {
+                // 首次遇到并网(或序列从并网开始)时初始化为该时刻(作为并网基准
+                gridStartTime = currentTime;
+                // 不将该时刻误判为并网后十分钟
+                // 备注:如果你想严格只在 "非并网->并网" 过渡时初始化,请替换为状态机检测
             }
 
-            // ---------------------------
-            // 5) 并网后 10 分钟过滤(isbw 开关)
-            //    注意:gridStartTime 必须有效(非 null),getTimeDiff 返回 -1 时不做过滤
-            // ---------------------------
+            // ---------- 3. 非并网过滤(isfbw) ----------
+            // isfbw=true 表示 "过滤非并网点"(只保留 mxzt==2)
+            if (isfbw != null && isfbw && isNonGrid) {
+                filter = 1;
+                reason = appendReason(reason, "non_grid");
+            }
+
+            // ---------- 4. 并网后10分钟过滤(isbw) ----------
             if (filter == 0 && isbw != null && isbw && gridStartTime != null) {
                 int diff = getTimeDiff(currentTime, gridStartTime);
                 if (diff >= 0 && diff <= 10) {
                     filter = 1;
+                    reason = appendReason(reason, "post_grid_within_10min");
                 }
             }
 
-            // ---------------------------
-            // 6) 停机前 10 分钟过滤(istj 开关)
-            //    语义:当检测到非并网(isNonGrid)时,将之前记录的并网期间最近的若干条(最多覆盖10分钟窗口)标记为过滤
-            //    我们通过 tempeiIndex 在并网期间保存索引;一旦发现非并网 -> 标记这些索引
-            // ---------------------------
+            // ---------- 5. 停机前10分钟回溯标记(istj) ----------
             if (istj != null && istj) {
                 if (isNonGrid) {
-                    // 发生非并网事件:把 tempeiIndex 中的候选项都标记为过滤
+                    // 发生非并网事件:将 tempeiIndex 中的并网点都标记为过滤(停机前10分钟)
                     for (Integer idx : tempeiIndex) {
                         if (idx >= 0 && idx < list.size()) {
-                            list.get(idx).setFilter(1);
+                            PowerPointData prev = list.get(idx);
+                            prev.setFilter(1);
+                            // 记录原因(覆盖原有 reason 可能)
+                            filterReasons.put(idx, appendReason(filterReasons.get(idx), "pre_shutdown_10min"));
                         }
                     }
-                    // 清空窗口
                     tempeiIndex.clear();
+                    // 当前非并网点自身通常也会被过滤(见上面的 non_grid)
                 } else {
-                    // 当前仍为并网:维护 tempeiIndex,使其只保存最近不超过 10 分钟的并网索引
-                    if (!tempeiIndex.isEmpty()) {
-                        int firstIdx = tempeiIndex.get(0);
+                    // 当前仍为并网:维护 tempeiIndex(保存最近不超过 10 分钟的并网索引)
+                    // 移除过旧的索引(超过 10 分钟)
+                    while (!tempeiIndex.isEmpty()) {
+                        int firstIdx = tempeiIndex.peekFirst();
                         String firstTime = list.get(firstIdx).getTime();
-                        int diff = getTimeDiff(firstTime, currentTime);
-                        if (diff >= 0 && diff >= 10) {
-                            // 超过 10 分钟,则移除最旧的(可以循环移除,但原逻辑是单次检查)
-                            tempeiIndex.remove(0);
+                        int d = getTimeDiff(firstTime, currentTime);
+                        if (d >= 0 && d >= 10) {
+                            tempeiIndex.pollFirst();
+                        } else {
+                            break;
                         }
                     }
-                    // 添加当前并网索引到窗口头(保持与原逻辑一致的顺序
-                    tempeiIndex.add(i);
+                    // 将当前并网点加入队列末尾(表示最新
+                    tempeiIndex.addLast(i);
                 }
             }
 
-            // ---------------------------
-            // 7) 欠发过滤(isqfh 开关 + qfhdj 等级判断)
-            //    逻辑:如果当前点满足欠发条件 -> 过滤,并把 tempqfIndex 中的索引标记为过滤
-            // ---------------------------
+            // ---------- 6. 欠发逻辑(isqfh)及回溯5分钟(tempqfIndex) ----------
             boolean needQF = false;
             if (filter == 0 && isqfh != null && isqfh) {
-                // qfzt 为设备点的欠发状态等级
                 int qfzt = item.getQfzt();
                 if (!Double.isNaN(speed)) {
                     if (speed >= 6 && speed < 12.5 && qfhdj != null && qfhdj < qfzt) {
@@ -270,66 +283,137 @@ public class PowerProcessALG {
                     }
                 }
             }
-
             if (needQF) {
+                // 标记当前点
                 filter = 1;
+                reason = appendReason(reason, "under_generation_detected");
+                // 回溯标记 tempqfIndex 里最近 5 分钟的数据为过滤
                 for (Integer idx : tempqfIndex) {
                     if (idx >= 0 && idx < list.size()) {
                         list.get(idx).setFilter(1);
+                        filterReasons.put(idx, appendReason(filterReasons.get(idx), "pre_under_gen_5min"));
                     }
                 }
             }
 
-            // 更新 tempqfIndex:保持一个 5 分钟窗口(与原逻辑相同
-            if (!tempqfIndex.isEmpty()) {
-                int firstIdx = tempqfIndex.get(0);
+            // 维护 tempqfIndex(保持最近不超过 5 分钟
+            while (!tempqfIndex.isEmpty()) {
+                int firstIdx = tempqfIndex.peekFirst();
                 String firstTime = list.get(firstIdx).getTime();
-                int diff = getTimeDiff(firstTime, currentTime);
-                if (diff >= 0 && diff >= 5) {
-                    tempqfIndex.remove(0);
+                int d = getTimeDiff(firstTime, currentTime);
+                if (d >= 0 && d >= 5) {
+                    tempqfIndex.pollFirst();
+                } else {
+                    break;
                 }
             }
-            // 将当前索引放到 tempqfIndex 的头部(和你原来 add(0, item) 的语义对应)
-            tempqfIndex.add(0, i);
-
-            // ---------------------------
-            // 8) 功率曲线偏差(isglpc 开关)
-            //    使用 map 提供的保证功率(map.get(speed))和 map.get(24.0) 作为最大保证功率
-            // ---------------------------
-            if (filter == 0 && isglpc != null && isglpc && map != null && map.containsKey(speed)) {
-                Double guaranteePower = map.get(speed);
-                Double maxGuarantee = map.get(24.0);
-                if (guaranteePower != null && guaranteePower > 0 && !Double.isNaN(power) && power > 0) {
-                    double k = power / guaranteePower; // 实际功率 / 保证功率(注意:与你原 k 定义可换)
-                    // 按你原始逻辑规则决定阈值(此处保留原来的判断条件)
-                    if (k < 0.95 && maxGuarantee != null && maxGuarantee <= guaranteePower) filter = 1;
-                    if (k < 0.9 && maxGuarantee != null && maxGuarantee > guaranteePower) filter = 1;
-                    if (k < 0.85 && speed < 6 && speed > 4) filter = 1;
-                    if (k < 0.9 && speed <= 4 && speed > 3.5) filter = 1;
+            // 将当前索引放到 tempqfIndex 尾部
+            tempqfIndex.addLast(i);
+
+            // ---------- 7. 功率曲线偏差(isglpc) - 使用最近的 map key ----------
+            if (filter == 0 && isglpc != null && isglpc && map != null && !map.isEmpty()) {
+                Double key = findNearestMapKey(map, speed, SPEED_TOLERANCE);
+                if (key != null) {
+                    Double guaranteePower = map.get(key);
+                    Double maxGuarantee = map.get(24.0);
+                    if (guaranteePower != null && guaranteePower > 0 && !Double.isNaN(power) && power > 0) {
+                        double k = power / guaranteePower; // 实际/保证
+                        // 阈值逻辑保留并以 k 判定
+                        if (k < 0.95 && maxGuarantee != null && maxGuarantee <= guaranteePower) {
+                            filter = 1;
+                            reason = appendReason(reason, "curve_dev_k_lt_0.95");
+                        } else if (k < 0.9 && maxGuarantee != null && maxGuarantee > guaranteePower) {
+                            filter = 1;
+                            reason = appendReason(reason, "curve_dev_k_lt_0.9");
+                        } else if (k < 0.85 && speed < 6 && speed > 4) {
+                            filter = 1;
+                            reason = appendReason(reason, "curve_dev_k_lt_0.85_speed_4_6");
+                        } else if (k < 0.9 && speed <= 4 && speed > 3.5) {
+                            filter = 1;
+                            reason = appendReason(reason, "curve_dev_k_lt_0.9_speed_3.5_4");
+                        }
+                    } else {
+                        // power<=0 or guaranteePower invalid -> optional filter, 这里不强制
+                    }
+                } else {
+                    // 未找到合适的风速 key:你可以选择把它视作不可判断(不过滤),或者做备选处理
+                    // 目前我们不因为找不到 key 就过滤(保持宽松)
                 }
             }
 
-            // ---------------------------
-            // 9) 最终设置当前项的 filter(0 或 1)
-            // ---------------------------
+            // ---------- 8. 非合理值过滤(isfhl) - 你原始条件是速度 < 0 且功率 <= minp
+            // 此外,如果 qfzt 明确表示异常(非 0),也可认为是不合理点(此处不强制,但我提供注释)
+            if (filter == 0 && isfhl != null && isfhl) {
+                // 保留原始:速度 < 0 && power <= minp
+                if (speed < 0 && !Double.isNaN(power) && minp != null && power <= minp) {
+                    filter = 1;
+                    reason = appendReason(reason, "not_reasonable_speed_power");
+                }
+                // 如果你希望 qfzt>0 一定被当作不合理点可以打开下面注释:
+                // if (item.getQfzt() > 0) { filter = 1; reason = appendReason(reason, "qfzt_nonzero"); }
+            }
+
+            // ---------- 9. 设置并输出结果 ----------
             item.setFilter(filter);
+            if (filter == 1) {
+                if (reason == null) reason = "generic_filtered";
+                filterReasons.put(i, reason);
+                // 输出调试信息(你可以改为日志)
+//                System.out.println("Index " + i + " filtered: " + reason + " (time=" + currentTime + ", speed=" + speed + ", power=" + power + ", mxzt=" + mxzt + ", qfzt=" + item.getQfzt() + ")");
+            }
+
+            // 注意:tempeiIndex/tempqfIndex 的维护在循环各自分支里完成(tempei 在 istj 分支中)
+            // tempqfIndex 已在上面维护
+            // tempeiIndex 的维护若未启用 istj 则不会改变
         }
 
-        // 返回原 list(对象被按需修改 filter 字段;但没有把对象引用放到临时集合中造成额外污染)
+        // (可选) 将 filterReasons 结果打印为汇总,便于检查
+        // System.out.println("filterReasons summary: " + filterReasons);
+
         return list;
     }
 
+    /**
+     * 将新 reason 附加到旧 reason 上(用逗号分隔)
+     */
+    private static String appendReason(String oldReason, String newReason) {
+        if (oldReason == null || oldReason.isEmpty()) return newReason;
+        if (newReason == null || newReason.isEmpty()) return oldReason;
+        return oldReason + "," + newReason;
+    }
+
+
+    /**
+     * 在 map 的 key(风速)中查找与 targetSpeed 最接近的 key(绝对差 <= tolerance)
+     * 如果找不到合适 key,返回 null
+     */
+    private static Double findNearestMapKey(Map<Double, Double> map, double targetSpeed, double tolerance) {
+        if (map == null || map.isEmpty()) return null;
+        Double bestKey = null;
+        double bestDiff = Double.MAX_VALUE;
+        for (Double k : map.keySet()) {
+            double d = Math.abs(k - targetSpeed);
+            if (d < bestDiff) {
+                bestDiff = d;
+                bestKey = k;
+            }
+        }
+        if (bestKey == null) return null;
+        return (bestDiff <= tolerance) ? bestKey : null;
+    }
+
 
     /**
      * 按照给定风俗功率过滤
+     *
      * @param list
-     * @param maxs  最大风速
-     * @param mins  最小风速
-     * @param maxp  最大功率
-     * @param minp  最小风速
+     * @param maxs 最大风速
+     * @param mins 最小风速
+     * @param maxp 最大功率
+     * @param minp 最小风速
      * @return
      */
-    public static List<PowerPointData> dataProcessPS(List<PowerPointData> list, Double maxs, Double mins, Double maxp, Double minp){
+    public static List<PowerPointData> dataProcessPS(List<PowerPointData> list, Double maxs, Double mins, Double maxp, Double minp) {
         //TODO 数据过滤  // 0正常,1过滤掉
         for (PowerPointData item : list) {
             int filter = 0;
@@ -346,10 +430,11 @@ public class PowerProcessALG {
 
     /**
      * 过滤非并网值
+     *
      * @param list
      * @return
      */
-    public static List<PowerPointData> dataProcessFBW(List<PowerPointData> list){
+    public static List<PowerPointData> dataProcessFBW(List<PowerPointData> list) {
         //TODO 数据过滤  // 0正常,1过滤掉
         for (PowerPointData item : list) {
             int filter = 0;
@@ -366,10 +451,11 @@ public class PowerProcessALG {
 
     /**
      * 过滤非合理值
+     *
      * @param list
      * @return
      */
-    public static List<PowerPointData> dataProcessFHLZ(List<PowerPointData> list){
+    public static List<PowerPointData> dataProcessFHLZ(List<PowerPointData> list) {
         //TODO 数据过滤  // 0正常,1过滤掉
         for (PowerPointData item : list) {
             int filter = 0;
@@ -386,20 +472,21 @@ public class PowerProcessALG {
 
     /**
      * 过滤并网后几分钟内数据
+     *
      * @param list
-     * @param minute  分钟
+     * @param minute 分钟
      * @return
      */
-    public static List<PowerPointData> dataProcessBWH(List<PowerPointData> list, int minute){
+    public static List<PowerPointData> dataProcessBWH(List<PowerPointData> list, int minute) {
         String timeBW = DateUtil.format(new Date(0), DATE_TIME_PATTERN);
         //TODO 数据过滤  // 0正常,1过滤掉
         for (PowerPointData item : list) {
             int filter = 0;
-            if (timeBW == DateUtil.format(new Date(0), DATE_TIME_PATTERN)){
+            if (timeBW == DateUtil.format(new Date(0), DATE_TIME_PATTERN)) {
                 timeBW = item.getTime();
             }
             // 过滤并网后十分钟
-            if (filter == 0 && getTimeDiff(item.getTime(), timeBW) <= minute){
+            if (filter == 0 && getTimeDiff(item.getTime(), timeBW) <= minute) {
                 filter = 1;
             }
 
@@ -413,10 +500,10 @@ public class PowerProcessALG {
     /**
      * 过滤停机前几分钟内数据
      * @param list
-     * @param minute  分钟
+     * @param minute 分钟
      * @return
      */
-    public static List<PowerPointData> dataProcessTJQ(List<PowerPointData> list, int minute){
+    public static List<PowerPointData> dataProcessTJQ(List<PowerPointData> list, int minute) {
         String timeBW = DateUtil.format(new Date(0), DATE_TIME_PATTERN);
         List<PowerPointData> tempei = new ArrayList<>();
 
@@ -424,7 +511,7 @@ public class PowerProcessALG {
         for (PowerPointData item : list) {
             int filter = 0;
             int fjstatus = 0;
-            if (timeBW == DateUtil.format(new Date(0), DATE_TIME_PATTERN)){
+            if (timeBW == DateUtil.format(new Date(0), DATE_TIME_PATTERN)) {
                 timeBW = item.getTime();
             }
             if (filter == 0 && item.getMxzt() != 2) {
@@ -462,7 +549,7 @@ public class PowerProcessALG {
      * @param map  风速对应的保证功率
      * @return
      */
-    public static List<PowerPointData> dataProcessQXPC(List<PowerPointData> list, Map<Double, Double> map){
+    public static List<PowerPointData> dataProcessQXPC(List<PowerPointData> list, Map<Double, Double> map) {
         //TODO 数据过滤  // 0正常,1过滤掉
         for (PowerPointData item : list) {
             if (map.containsKey(item.getSpeed())) {
@@ -502,7 +589,7 @@ public class PowerProcessALG {
      * @param qfhdj 签发等级
      * @return
      */
-    public static List<PowerPointData> dataProcessQF(List<PowerPointData> list, int qfhdj){
+    public static List<PowerPointData> dataProcessQF(List<PowerPointData> list, int qfhdj) {
         List<PowerPointData> tempqf = new ArrayList<>();
         //TODO 数据过滤  // 0正常,1过滤掉
         for (PowerPointData item : list) {
@@ -547,16 +634,19 @@ public class PowerProcessALG {
 //
 //        return diff;
 //    }
-    // ---------- 时间差工具(请替换原有实现) ----------
+
+    /**
+     * getTimeDiff: 计算两个时间字符串差(分钟)
+     * 返回 -1 表示无法计算(null 或解析失败)
+     */
     public static int getTimeDiff(String oldTime, String newTime) {
         if (oldTime == null || newTime == null) return -1;
         SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         try {
-            long OTime = df.parse(oldTime).getTime();
-            long NTime = df.parse(newTime).getTime();
-            return (int) (Math.abs(NTime - OTime) / 1000 / 60);
+            long t1 = df.parse(oldTime).getTime();
+            long t2 = df.parse(newTime).getTime();
+            return (int) (Math.abs(t2 - t1) / 1000 / 60);
         } catch (ParseException e) {
-            // 解析失败视为无法比较
             return -1;
         }
     }