From cacb98768fbe1796ec289c049ccfad0772a7cd3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E4=B8=99?= Date: Fri, 14 Mar 2025 03:24:59 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96CP56Time2a?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../util/codec/CP56Time2aUtil.java | 117 ++++++++++++------ .../util/codec/CP56Time2aUtilTest.java | 11 +- .../cmd/YunKuaiChongV150LoginAckDLCmd.java | 4 +- ...unKuaiChongV150TransactionRecordULCmd.java | 7 +- 4 files changed, 90 insertions(+), 49 deletions(-) diff --git a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/codec/CP56Time2aUtil.java b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/codec/CP56Time2aUtil.java index 527c1c0..25af2da 100644 --- a/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/codec/CP56Time2aUtil.java +++ b/jcpp-infrastructure-util/src/main/java/sanbing/jcpp/infrastructure/util/codec/CP56Time2aUtil.java @@ -6,62 +6,97 @@ */ package sanbing.jcpp.infrastructure.util.codec; -import java.time.Instant; import java.time.LocalDateTime; -import java.time.ZoneId; public class CP56Time2aUtil { /** - * 解码 CP56Time2a 字节数组为 Instant 对象 + * 高性能解码 CP56Time2a 字节数组为本地时间对象 * - * @param bytes 字节数组 - * @return Instant 对象 + * @param bytes 7字节的CP56Time2a数组 + * @return 解码后的本地时间(系统默认时区) + * @throws IllegalArgumentException 当输入不符合规范时抛出异常 */ - public static Instant decode(byte[] bytes) { - // 将字节数组解释为各个时间部分 - int milliseconds = ((bytes[0] & 0xFF) + ((bytes[1] & 0xFF) << 8)); // 处理字节的无符号值 - int minutes = bytes[2] & 0x3F; - int hours = bytes[3] & 0x1F; - int days = bytes[4] & 0x1F; - int months = bytes[5] & 0x0F; - int years = bytes[6] & 0x7F; + public static LocalDateTime decode(byte[] bytes) { + if (bytes.length != 7) { + throw new IllegalArgumentException("Invalid CP56Time2a format: 需要7字节"); + } - // 将 CP56Time2a 转换为 LocalDateTime - LocalDateTime dateTime = LocalDateTime.of( - years + 2000, - months, - days, - hours, - minutes, - milliseconds / 1000 // 秒数 - ); + // 预处理字节为 Unsigned Int(避免重复转换) + final int b6 = bytes[6] & 0xFF; + final int b5 = bytes[5] & 0xFF; + final int b4 = bytes[4] & 0xFF; + final int b3 = bytes[3] & 0xFF; + final int b2 = bytes[2] & 0xFF; + final int b1 = bytes[1] & 0xFF; + final int b0 = bytes[0] & 0xFF; - // 返回对应的 Instant 对象 - return dateTime.atZone(ZoneId.systemDefault()).toInstant(); + // 年份(2000~2127): 7位无符号 + final int year = 2000 + (b6 & 0x7F); + + // 月份(1~12): 低4位 + final int month = b5 & 0x0F; + if (month < 1 || month > 12) { // 内联校验 + throw new IllegalArgumentException("非法月份值:" + month); + } + + // 日期(1~31): 低5位 + final int day = b4 & 0x1F; + if (day < 1) { // 内联校验 + throw new IllegalArgumentException("非法日期值:" + day); + } + + // 小时(0~23): 低5位 + final int hour = b3 & 0x1F; + if (hour > 23) { // 内联校验 + throw new IllegalArgumentException("非法小时值:" + hour); + } + + // 分钟(0~59): 低6位 + final int minute = b2 & 0x3F; + if (minute > 59) { // 内联校验 + throw new IllegalArgumentException("非法分钟值:" + minute); + } + + // 合并秒和毫秒(小端序优化) + final int combined = (b1 << 8) | b0; + final int second = combined / 1000; + if (second > 59) { // 内联校验 + throw new IllegalArgumentException("非法秒数值:" + second); + } + + return LocalDateTime.of(year, month, day, hour, minute, second, (combined % 1000) * 1_000_000); } /** - * 编码 Instant 对象为 CP56Time2a 字节数组 + * 高性能编码本地时间对象为 CP56Time2a 字节数组 * - * @param instant Instant 对象 - * @return 字节数组 + * @param dateTime 本地时间对象(需保证为系统默认时区) + * @return 7字节的CP56Time2a数组 */ - public static byte[] encode(Instant instant) { - // 将 Instant 转换到 LocalDateTime - LocalDateTime aTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); + public static byte[] encode(LocalDateTime dateTime) { + final byte[] cp56Time2a = new byte[7]; - byte[] result = new byte[7]; - int milliseconds = aTime.getSecond() * 1000; // 获取毫秒部分 + // 年份(2000~2127) + final int year = dateTime.getYear(); + cp56Time2a[6] = (byte) ((year - 2000) & 0x7F); // 7位掩码优化 - // 填充字节数组 - result[0] = (byte) (milliseconds % 256); - result[1] = (byte) (milliseconds / 256); - result[2] = (byte) aTime.getMinute(); - result[3] = (byte) aTime.getHour(); - result[4] = (byte) aTime.getDayOfMonth(); - result[5] = (byte) aTime.getMonthValue(); // 1-12 - result[6] = (byte) (aTime.getYear() % 100); // 00-99 + // 月份(1~12) + final int month = dateTime.getMonthValue(); + cp56Time2a[5] = (byte) month; // 直接赋值,协议层保证低4位有效 - return result; + // 日期(1~31) + cp56Time2a[4] = (byte) dateTime.getDayOfMonth(); // 协议层保证低5位有效 + + // 时间字段(直接赋值,协议层保证掩码) + cp56Time2a[3] = (byte) dateTime.getHour(); + cp56Time2a[2] = (byte) dateTime.getMinute(); + + // 合并秒和毫秒(避免中间变量) + final int nano = dateTime.getNano(); + final int combined = dateTime.getSecond() * 1000 + (nano / 1_000_000); + cp56Time2a[1] = (byte) (combined >>> 8); // 小端序高字节 + cp56Time2a[0] = (byte) combined; // 小端序低字节 + + return cp56Time2a; } } \ No newline at end of file diff --git a/jcpp-infrastructure-util/src/test/java/sanbing/jcpp/infrastructure/util/codec/CP56Time2aUtilTest.java b/jcpp-infrastructure-util/src/test/java/sanbing/jcpp/infrastructure/util/codec/CP56Time2aUtilTest.java index 7c1b6f6..e3e2125 100644 --- a/jcpp-infrastructure-util/src/test/java/sanbing/jcpp/infrastructure/util/codec/CP56Time2aUtilTest.java +++ b/jcpp-infrastructure-util/src/test/java/sanbing/jcpp/infrastructure/util/codec/CP56Time2aUtilTest.java @@ -6,22 +6,27 @@ */ package sanbing.jcpp.infrastructure.util.codec; +import cn.hutool.core.util.HexUtil; import org.junit.jupiter.api.Test; -import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.Arrays; class CP56Time2aUtilTest { @Test void encodeTest() { - Instant time = Instant.ofEpochMilli(1727798453000L); + LocalDateTime time = LocalDateTime.of(2025, 1, 22, 14, 30, 45, 123_000_000); byte[] bytes = CP56Time2aUtil.encode(time); System.out.println(Arrays.toString(bytes)); + System.out.println(HexUtil.encodeHex(bytes)); + System.out.println(time.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); - Instant decode = CP56Time2aUtil.decode(bytes); + LocalDateTime decode = CP56Time2aUtil.decode(bytes); + System.out.println(decode.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); assert time.equals(decode); } diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150LoginAckDLCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150LoginAckDLCmd.java index d20a6b0..deadec3 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150LoginAckDLCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150LoginAckDLCmd.java @@ -23,7 +23,7 @@ import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage; import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage; import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd; -import java.time.Instant; +import java.time.LocalDateTime; import java.util.Arrays; import java.util.concurrent.TimeUnit; @@ -109,7 +109,7 @@ public class YunKuaiChongV150LoginAckDLCmd extends YunKuaiChongDownlinkCmdExe { log.info("{} 云快充1.5.0开始下发对时报文", tcpSession); ByteBuf syncTimeMsgBody = Unpooled.buffer(14); syncTimeMsgBody.writeBytes(pileCodeBytes); - syncTimeMsgBody.writeBytes(CP56Time2aUtil.encode(Instant.now())); + syncTimeMsgBody.writeBytes(CP56Time2aUtil.encode(LocalDateTime.now())); encodeAndWriteFlush(SYNC_TIME, tcpSession.nextSeqNo(SequenceNumberLength.SHORT), diff --git a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150TransactionRecordULCmd.java b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150TransactionRecordULCmd.java index 3bd719c..41a3d45 100644 --- a/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150TransactionRecordULCmd.java +++ b/jcpp-protocol-yunkuaichong/src/main/java/sanbing/jcpp/protocol/yunkuaichong/v150/cmd/YunKuaiChongV150TransactionRecordULCmd.java @@ -24,6 +24,7 @@ import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.time.Instant; +import java.time.ZoneId; /** * 云快充1.5.0 交易记录 @@ -57,12 +58,12 @@ public class YunKuaiChongV150TransactionRecordULCmd extends YunKuaiChongUplinkCm // 4.开始时间 byte[] startTimeBytes = new byte[7]; byteBuf.readBytes(startTimeBytes); - Instant startTime = CP56Time2aUtil.decode(startTimeBytes); + Instant startTime = CP56Time2aUtil.decode(startTimeBytes).atZone(ZoneId.systemDefault()).toInstant(); // 5.结束时间 byte[] endTimeBytes = new byte[7]; byteBuf.readBytes(endTimeBytes); - Instant endTime = CP56Time2aUtil.decode(endTimeBytes); + Instant endTime = CP56Time2aUtil.decode(endTimeBytes).atZone(ZoneId.systemDefault()).toInstant(); // 6.尖单价 BigDecimal topPrice = reduceMagnification(byteBuf.readUnsignedIntLE(), 100000); @@ -141,7 +142,7 @@ public class YunKuaiChongV150TransactionRecordULCmd extends YunKuaiChongUplinkCm // 29.交易日期、时间 byte[] tradeTimeBytes = new byte[7]; byteBuf.readBytes(tradeTimeBytes); - Instant tradeTime = CP56Time2aUtil.decode(tradeTimeBytes); + Instant tradeTime = CP56Time2aUtil.decode(tradeTimeBytes).atZone(ZoneId.systemDefault()).toInstant(); // 30.停止原因 byte stopReasonByte = byteBuf.readByte();