最近碰到一个有趣的需求,花了一天时间写了代码实现。
需求:给汽车充电时,充电桩是会根据充电的时间段的不同来收费的,比如晚上用电高峰,这时充电会贵,凌晨没人用电,电费会便宜。大概就是分为尖、峰、平、谷四个类型,每个类型可能有多个时间段,那么,比如我充电一次,如何计算每个时间段内冲了多久的电呢?
大概逻辑:
1、对多个时间段进行校验,必须首尾相连
2、对时间段进行排序。找到最小的时间作为第一个,然后依照首尾进行排序
3、确定开始时间、结束时间所在的时间段,然后按照顺序遍历即可,中间的时间段就是满的。
需要注意的地方:
1、24小时制,所以总会有一个时间段是 结束时间小于开始时间
2、确定开始时间、结束时间所在的时间段时。例如开始时间8:00,那么它所属时间段应该是8:00-10:00,而不是6:00-8:00 ,同样,结束时间是8:00,那么它所属时间段应该是6:00-8:00,而不是8:00-10:00
代码如下:
import java.time.Duration;
import java.time.LocalTime;
import java.util.*;
/**
* @author hph
* @date 2022-11-14 10:52
*/
public class TimeSpanCalculateTest {
public static void main(String[] args) throws Exception {
HashMap<String, String> map = new HashMap<>();
map.put("06:00-12:00", "尖");
map.put("12:00-16:00", "峰");
map.put("16:00-20:30", "平");
map.put("20:30-00:00", "峰");
map.put("00:00-06:00", "谷");
String timeSpan = "00:00-08:10";
checkTimeSpan(map);
calculateTimeSpan(sortTimeSpan(map), timeSpan);
}
/**
* 检查 时间段 格式是否正确
* 1、小时、分钟都是两位 且 小时是0-23,分钟是0-59
* 2、首尾相连
* 3、共24小时
*
* @param map
*/
private static void checkTimeSpan(HashMap<String, String> map) throws Exception {
System.out.println("######## 开始 校验时间段#########");
// 校验格式
String regex = "([0-1]+[0-9]|2[0-3]):([0-5][0-9])-([0-1]+[0-9]|2[0-3]):([0-5][0-9])";
for (String key : map.keySet()) {
String[] tt = key.split("-");
if (tt[0].equals(tt[1])) {
throw new Exception("开始时间和结束时间不能相同");
}
if (!key.matches(regex)) {
throw new Exception("时间段 格式错误");
}
}
// 校验首尾相连
// 以第一个为基准
int foundSpan = 0;
String span = "";
String startSpan = "";
String endSpan = "";
int index = 0;
// 最多循环map.keySet().size()次就能找到所有的
// 缺点在于,如果一个时间段一直没找到下一个,还是会循环完,才会退出
// 如何 循环一遍就退出整个循环呢
// 变量太多,不想再加变量了,太乱
for (int i = 0; i < map.keySet().size(); i++) {
if (foundSpan == map.keySet().size()) {
break;
}
// 没办法,还是加个变量吧
int temp = 0 ;
for (String value : map.keySet()) {
String[] tt = value.split("-");
// 第一次
if (index == 0) {
span = tt[1];
startSpan = tt[0];
foundSpan++;
index++;
}
// 找到下一个时间段
if (span.equals(tt[0])) {
foundSpan++;
span = tt[1];
// 最后一次
if (foundSpan == map.keySet().size()) {
endSpan = tt[1];
}
temp++;
continue;
}
}
// 如果 一次循环,temp没有增加,那么就退出,不用再循环了
if(temp == 0){
break;
}
}
if (foundSpan == map.keySet().size() && startSpan.equals(endSpan)) {
System.out.println("######## 时间段 首尾相连#########");
} else {
System.err.println(String.format("找不到 %s 或 %s 的下一个时间段",span,startSpan));
throw new Exception("时间段首尾不相连");
}
// 校验 是否是 24小时
// 按理说 只要首尾相连了,那么肯定是24小时
}
private static void calculateTimeSpan(TreeMap<String, String> map, String timeSpan) {
System.out.println("########开始计算#########");
System.out.println("充电时间段:" + timeSpan);
String startTime = timeSpan.substring(0, 5);
String endTime = timeSpan.substring(6);
String startSpan = "";
String endSpan = "";
for (String key : map.keySet()) {
String span = map.get(key);
if (isStartTimeInTimeSpan(startTime, span)) {
startSpan = key;
}
if (isEndTimeInTimeSpan(endTime, span)) {
endSpan = key;
}
}
System.out.println("开始时间 时间段:" + startSpan);
System.out.println("结束时间 时间段:" + endSpan);
int totalTime = 0;
for (String key : map.keySet()) {
String span = map.get(key);
int i = Integer.valueOf(key.substring(0, 1));
int s = Integer.valueOf(startSpan.substring(0, 1));
int e = Integer.valueOf(endSpan.substring(0, 1));
// 开始时间 和 结束时间 是同一个时间段
if(s == e){
int minutes = calculateMinute(startTime, endTime);
System.out.println(String.format("没有跨段 , 所属时间段:key: %s %s , 耗时(分钟):%s", startSpan, map.get(startSpan), minutes));
totalTime += minutes;
break;
}
if (i == s) {
int minutes = calculateMinute(startTime, span.substring(6));
System.out.println(String.format("开始时间:%s 时间段:key: %s %s , 耗时(分钟):%s", startTime, key, span, minutes));
totalTime += minutes;
}
if (i == e) {
int minutes = calculateMinute(span.substring(0, 5), endTime);
System.out.println(String.format("结束时间:%s 时间段:key: %s %s , 耗时(分钟):%s", endTime, key, span, minutes));
totalTime += minutes;
}
if (i > s && i < e) {
int minutes = calculateMinute(span.substring(0, 5), span.substring(6));
System.out.println(String.format("中间时间 时间段:key: %s %s , 耗时(分钟):%s", key, span, minutes));
totalTime += minutes;
}
if (i < s || i > e) {
continue;
}
}
System.out.println("共用时:" + totalTime);
}
/**
* 计算时间 段内的 分钟数
*
* @param s
* @param e
* @return
*/
private static int calculateMinute(String s, String e) {
int time1 = Integer.valueOf(s.substring(0, 2)) * 60 + Integer.valueOf(s.substring(3));
int time2 = Integer.valueOf(e.substring(0, 2)) * 60 + Integer.valueOf(e.substring(3));
if (time1 < time2) {
return time2 - time1;
} else {
// 例如 23:00 到 01:00
return 24 * 60 + time2 - time1;
}
}
/**
* 判断开始时间 是否在 某个时间段内
*
* @param t
* @param span
* @return
*/
private static boolean isStartTimeInTimeSpan(String t, String span) {
int time = Integer.valueOf(t.substring(0, 2)) * 60 + Integer.valueOf(t.substring(3));
int time1 = Integer.valueOf(span.substring(0, 2)) * 60 + Integer.valueOf(span.substring(3, 5));
int time2 = Integer.valueOf(span.substring(6, 8)) * 60 + Integer.valueOf(span.substring(9));
if (time1 < time2) {
// 例如 开始时间 8:00 时间段 8:00 - 12:00
if (time >= time1 && time < time2) {
return true;
} else {
return false;
}
} else {
// 时间段 23:00 - 2:00 则只需判断开始时间大于 time1即可
if (time >= time1 || time < time2) {
return true;
} else {
return false;
}
}
}
/**
* 判断结束时间 是否在 某个时间段内
*
* @param t
* @param span
* @return
*/
private static boolean isEndTimeInTimeSpan(String t, String span) {
int time = Integer.valueOf(t.substring(0, 2)) * 60 + Integer.valueOf(t.substring(3));
int time1 = Integer.valueOf(span.substring(0, 2)) * 60 + Integer.valueOf(span.substring(3, 5));
int time2 = Integer.valueOf(span.substring(6, 8)) * 60 + Integer.valueOf(span.substring(9));
if (time1 < time2) {
// 例如 结束时间 8:00 时间段 2:00 - 8:00
if (time > time1 && time <= time2) {
return true;
} else {
return false;
}
} else {
// 时间段 23:00 - 2:00 则只需判断开始时间大于 time1即可
if (time > time1 || time <= time2) {
return true;
} else {
return false;
}
}
}
/**
* 把时间段排序
*
* @param map
* @return
*/
private static TreeMap<String, String> sortTimeSpan(Map<String, String> map) {
System.out.println("########开始排序#########");
String minTime = "30";
for (String key : map.keySet()) {
String[] tt = key.split("-");
if (minTime.compareTo(tt[0]) > 0) {
minTime = tt[0];
}
}
System.out.println(String.format("最小的开始时间:%s", minTime));
TreeMap<String, String> map1 = new TreeMap<String, String>(new Comparator<String>() {
public int compare(String a, String b) {
return a.compareTo(b);
}
});
int index = 0;
// 最开始用的while true ,但是万一不首尾相连,就会找不到,所以改为for,万一找不到,最多循环map次,
for (String test : map.keySet()) {
if (map1.size() == map.size()) {
break;
} else {
for (String key : map.keySet()) {
String value = map.get(key);
if (key.startsWith(minTime)) {
map1.put(index + "-" + value, key);
minTime = key.substring(6);
index++;
}
}
}
}
System.out.println("########排序后的结果#########");
map1.keySet().stream().sorted();
for (String key : map1.keySet()) {
System.out.println(String.format("key: %s value : %s", key, map1.get(key)));
}
return map1;
}
}
结果:
######## 开始 校验时间段#########
######## 时间段 首尾相连#########
########开始排序#########
最小的开始时间:00:00
########排序后的结果#########
key: 0-谷 value : 00:00-06:00
key: 1-尖 value : 06:00-12:00
key: 2-峰 value : 12:00-16:00
key: 3-平 value : 16:00-20:30
key: 4-峰 value : 20:30-00:00
########开始计算#########
充电时间段:06:00-21:08
开始时间 时间段:1-尖
结束时间 时间段:4-峰
开始时间:06:00 时间段:key: 1-尖 06:00-12:00 , 耗时(分钟):360
中间时间 时间段:key: 2-峰 12:00-16:00 , 耗时(分钟):240
中间时间 时间段:key: 3-平 16:00-20:30 , 耗时(分钟):270
结束时间:21:08 时间段:key: 4-峰 20:30-00:00 , 耗时(分钟):38
共用时:908
总结:
1、已经很注意变量的命名了,可是有些变量真的不知道该怎么命名,比如tt
2、感觉算法还有很大提升空间,但这是我目前能想到的比较靠谱的了
3、代码太多if else,要是用python应该能好很多,java就这点不好
4、定义了太多变量,容易乱,比如校验首尾相连的时候,没有什么好的办法
有没有大佬能指教下?