mirror of
https://gitee.com/san-bing/JChargePointProtocol
synced 2026-05-08 20:10:01 +08:00
优化CP56Time2a
This commit is contained in:
@@ -6,62 +6,97 @@
|
|||||||
*/
|
*/
|
||||||
package sanbing.jcpp.infrastructure.util.codec;
|
package sanbing.jcpp.infrastructure.util.codec;
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneId;
|
|
||||||
|
|
||||||
public class CP56Time2aUtil {
|
public class CP56Time2aUtil {
|
||||||
/**
|
/**
|
||||||
* 解码 CP56Time2a 字节数组为 Instant 对象
|
* 高性能解码 CP56Time2a 字节数组为本地时间对象
|
||||||
*
|
*
|
||||||
* @param bytes 字节数组
|
* @param bytes 7字节的CP56Time2a数组
|
||||||
* @return Instant 对象
|
* @return 解码后的本地时间(系统默认时区)
|
||||||
|
* @throws IllegalArgumentException 当输入不符合规范时抛出异常
|
||||||
*/
|
*/
|
||||||
public static Instant decode(byte[] bytes) {
|
public static LocalDateTime decode(byte[] bytes) {
|
||||||
// 将字节数组解释为各个时间部分
|
if (bytes.length != 7) {
|
||||||
int milliseconds = ((bytes[0] & 0xFF) + ((bytes[1] & 0xFF) << 8)); // 处理字节的无符号值
|
throw new IllegalArgumentException("Invalid CP56Time2a format: 需要7字节");
|
||||||
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;
|
|
||||||
|
|
||||||
// 将 CP56Time2a 转换为 LocalDateTime
|
// 预处理字节为 Unsigned Int(避免重复转换)
|
||||||
LocalDateTime dateTime = LocalDateTime.of(
|
final int b6 = bytes[6] & 0xFF;
|
||||||
years + 2000,
|
final int b5 = bytes[5] & 0xFF;
|
||||||
months,
|
final int b4 = bytes[4] & 0xFF;
|
||||||
days,
|
final int b3 = bytes[3] & 0xFF;
|
||||||
hours,
|
final int b2 = bytes[2] & 0xFF;
|
||||||
minutes,
|
final int b1 = bytes[1] & 0xFF;
|
||||||
milliseconds / 1000 // 秒数
|
final int b0 = bytes[0] & 0xFF;
|
||||||
);
|
|
||||||
|
|
||||||
// 返回对应的 Instant 对象
|
// 年份(2000~2127): 7位无符号
|
||||||
return dateTime.atZone(ZoneId.systemDefault()).toInstant();
|
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 对象
|
* @param dateTime 本地时间对象(需保证为系统默认时区)
|
||||||
* @return 字节数组
|
* @return 7字节的CP56Time2a数组
|
||||||
*/
|
*/
|
||||||
public static byte[] encode(Instant instant) {
|
public static byte[] encode(LocalDateTime dateTime) {
|
||||||
// 将 Instant 转换到 LocalDateTime
|
final byte[] cp56Time2a = new byte[7];
|
||||||
LocalDateTime aTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
|
|
||||||
|
|
||||||
byte[] result = new byte[7];
|
// 年份(2000~2127)
|
||||||
int milliseconds = aTime.getSecond() * 1000; // 获取毫秒部分
|
final int year = dateTime.getYear();
|
||||||
|
cp56Time2a[6] = (byte) ((year - 2000) & 0x7F); // 7位掩码优化
|
||||||
|
|
||||||
// 填充字节数组
|
// 月份(1~12)
|
||||||
result[0] = (byte) (milliseconds % 256);
|
final int month = dateTime.getMonthValue();
|
||||||
result[1] = (byte) (milliseconds / 256);
|
cp56Time2a[5] = (byte) month; // 直接赋值,协议层保证低4位有效
|
||||||
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
|
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,22 +6,27 @@
|
|||||||
*/
|
*/
|
||||||
package sanbing.jcpp.infrastructure.util.codec;
|
package sanbing.jcpp.infrastructure.util.codec;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.HexUtil;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
class CP56Time2aUtilTest {
|
class CP56Time2aUtilTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void encodeTest() {
|
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);
|
byte[] bytes = CP56Time2aUtil.encode(time);
|
||||||
|
|
||||||
System.out.println(Arrays.toString(bytes));
|
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);
|
assert time.equals(decode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongDwonlinkMessage;
|
|||||||
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage;
|
import sanbing.jcpp.protocol.yunkuaichong.YunKuaiChongUplinkMessage;
|
||||||
import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd;
|
import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ public class YunKuaiChongV150LoginAckDLCmd extends YunKuaiChongDownlinkCmdExe {
|
|||||||
log.info("{} 云快充1.5.0开始下发对时报文", tcpSession);
|
log.info("{} 云快充1.5.0开始下发对时报文", tcpSession);
|
||||||
ByteBuf syncTimeMsgBody = Unpooled.buffer(14);
|
ByteBuf syncTimeMsgBody = Unpooled.buffer(14);
|
||||||
syncTimeMsgBody.writeBytes(pileCodeBytes);
|
syncTimeMsgBody.writeBytes(pileCodeBytes);
|
||||||
syncTimeMsgBody.writeBytes(CP56Time2aUtil.encode(Instant.now()));
|
syncTimeMsgBody.writeBytes(CP56Time2aUtil.encode(LocalDateTime.now()));
|
||||||
|
|
||||||
encodeAndWriteFlush(SYNC_TIME,
|
encodeAndWriteFlush(SYNC_TIME,
|
||||||
tcpSession.nextSeqNo(SequenceNumberLength.SHORT),
|
tcpSession.nextSeqNo(SequenceNumberLength.SHORT),
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import sanbing.jcpp.protocol.yunkuaichong.annotation.YunKuaiChongCmd;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 云快充1.5.0 交易记录
|
* 云快充1.5.0 交易记录
|
||||||
@@ -57,12 +58,12 @@ public class YunKuaiChongV150TransactionRecordULCmd extends YunKuaiChongUplinkCm
|
|||||||
// 4.开始时间
|
// 4.开始时间
|
||||||
byte[] startTimeBytes = new byte[7];
|
byte[] startTimeBytes = new byte[7];
|
||||||
byteBuf.readBytes(startTimeBytes);
|
byteBuf.readBytes(startTimeBytes);
|
||||||
Instant startTime = CP56Time2aUtil.decode(startTimeBytes);
|
Instant startTime = CP56Time2aUtil.decode(startTimeBytes).atZone(ZoneId.systemDefault()).toInstant();
|
||||||
|
|
||||||
// 5.结束时间
|
// 5.结束时间
|
||||||
byte[] endTimeBytes = new byte[7];
|
byte[] endTimeBytes = new byte[7];
|
||||||
byteBuf.readBytes(endTimeBytes);
|
byteBuf.readBytes(endTimeBytes);
|
||||||
Instant endTime = CP56Time2aUtil.decode(endTimeBytes);
|
Instant endTime = CP56Time2aUtil.decode(endTimeBytes).atZone(ZoneId.systemDefault()).toInstant();
|
||||||
|
|
||||||
// 6.尖单价
|
// 6.尖单价
|
||||||
BigDecimal topPrice = reduceMagnification(byteBuf.readUnsignedIntLE(), 100000);
|
BigDecimal topPrice = reduceMagnification(byteBuf.readUnsignedIntLE(), 100000);
|
||||||
@@ -141,7 +142,7 @@ public class YunKuaiChongV150TransactionRecordULCmd extends YunKuaiChongUplinkCm
|
|||||||
// 29.交易日期、时间
|
// 29.交易日期、时间
|
||||||
byte[] tradeTimeBytes = new byte[7];
|
byte[] tradeTimeBytes = new byte[7];
|
||||||
byteBuf.readBytes(tradeTimeBytes);
|
byteBuf.readBytes(tradeTimeBytes);
|
||||||
Instant tradeTime = CP56Time2aUtil.decode(tradeTimeBytes);
|
Instant tradeTime = CP56Time2aUtil.decode(tradeTimeBytes).atZone(ZoneId.systemDefault()).toInstant();
|
||||||
|
|
||||||
// 30.停止原因
|
// 30.停止原因
|
||||||
byte stopReasonByte = byteBuf.readByte();
|
byte stopReasonByte = byteBuf.readByte();
|
||||||
|
|||||||
Reference in New Issue
Block a user