在监控系统中,触犯规则并进行告警时,需要根据特定时间点(如每天的00:00-06:00,或工作日的22:00-24:00等)进行特殊处理。该需求包含以下2点:

  • 设计一个存储格式来表示特定的时间段
  • 需要判断某个时间是否在这个时间范围内
  • 一般特定的时间段有以下特征:

  • 指定月份列表,如: 1月、6月、12月等
  • 指定每月的日期列表,如: 1日、10日、30日等
  • 指定周几的列表,如: 周一、周三、周五等
  • 具体某一天的时间段列表,如:09:00-18:00、22:00-24:00等
  • 因为我们关注的是具体某天的时间段,因此月份、日期、周几等表示都简单的使用数据来表示,并不需要支持范围(当然也可以根据需要来进行修改)

    我们使用Json格式来存储该时间范围的规则,这样就可以直接使用Json反序列化来转化为对象,然后调用方法判断某个时间是否在该范围内.
    以下为java对象的属性:

    private List < Integer > months; * 每月的日期列表, 如1号,5号可表示为: 1,5 private List < Integer > daysOfMonth; * 表示周几的列表 private List < Integer > daysOfWeek; * 时间段表达式列表 * 单个表达式如: 10:00-20:00 private List < String > timeRanges; 复制代码

    json格式的例子如下:

    {"daysOfWeek":[1,3,6],"timeRanges":["09:00-18:00","22:00-24:00"]}
    

    其中 months, daysOfMonth, daysOfWeek等属性值可以为空,表示忽略该属性值; 若这3个值都为空,则表示每天 这3个值的是and的关系,需要都满足,具体看下面的代码

    由于我们需要支持多个时间段,因此需要使用json数组表示多个TimeSection, json格式如下:

    [{"daysOfWeek":[1,2,3,4,5,6],"timeRanges":["20:00-24:00","00:00-09:00"]},{"daysOfWeek":[7],"timeRanges":["00:00-24:00"]}]
    

    以上的区间满足了表示非工作时间的时间范围

    通过如下代码转换成TimeSection对象(使用的是fastjson库)

            List<TimeSection> timeSectionList = JSON.parseArray(json, TimeSection.class);
    复制代码

    使用Java8日期API进行日期判断

    Java8表示日期时间的类有:

  • LocalDate: 表示日期(不含时间)
  • LocalTime: 表示时间(不含日期)
  • LocalDateTime: 同时包含日期和时间(LocalDateTime类中包含了2个属性LocalDate和LocalTime) 由于我们判断的时间段有日期和时间,因此使用LocalDateTime类
  • 生成LocalDateTime:

    // 当前时间
    LocalDateTime.now()
    // 以某个字符串形式的日期格式转换成LocalDateTime
    // 使用LocalDateTime.parse(String text, DateTimeFormatter formatter)
    // 如:
    LocalDateTime.parse("2020-06-13 10:10:10", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    复制代码

    使用LocalDateTime的方法来判断月份、日期、周几等的判断

    // 获取月份
    Month month = localDateTime.getMonth();
    // 获取日期
    int dayOfMonth = localDateTime.getDayOfMonth();
    // 获取周几
    DayOfWeek dayOfWeek = localDateTime.getDayOfWeek()
    复制代码

    其中Month,DayOfWeek为枚举类,通过.getValue()方法来获取int类型

    // 枚举转换为数字: FEBRUARY -> 2, APRIL -> 4 
    month.getValue()
    // 枚举转换为数据: MONDAY -> 1, SUNDAY -> 7
    dayOfWeek.getValue()
    复制代码

    把具体的时间转换成它在一天中的毫秒数(long)

    // 这里的timePat为: HH:mm, timeValue可为:11:00...
    TemporalAccessor temporalAccessor = DateTimeFormatter.ofPattern(timePat).parse(timeValue);
    // 获取一天已经过去的毫秒数
    temporalAccessor.getLong(ChronoField.MILLI_OF_DAY);
    复制代码

    其中:

  • Temporal: 是所有时间的超级接口
  • TemporalAccessor : 只提供只读版本的接口
  • 通过temporalAccessor.getLong(ChronoField.MILLI_OF_DAY)获取某个时间点在一天中过去的毫秒数来做时间判断非常合适

     startTime的毫秒数 < 当前时间的毫秒数 < endTime的毫秒数 
    复制代码

    时间范围抽象

    我们存储的时间范围为类似: 11:00-20:00 这样,而实际判断过程中我们需要将开始时间和结束时间提取出来, 因此需要抽象成如下的对象:

        public static class TimeRange {
             * 开始时间段
             * HH:mm格式
            private String timeStart;
             * 结束时间段
             * HH:mm格式
            private String timeEnd;
        	// ...
    复制代码

    在做匹配的时候通过字符串处理进行timeStart和timeEnd的赋值

    * 表示时间区间 @Data public class TimeSection { * 时间格式 private final transient static String TIME_PATTERN = "HH:mm"; private List<Integer> months; * 每月的日期列表, 如1号,5号可表示为: 1,5 private List<Integer> daysOfMonth; * 表示周几的列表 private List<Integer> daysOfWeek; * 时间段表达式列表 * 单个表达式如: 10:00-20:00 private List<String> timeRanges; @Data @AllArgsConstructor public static class TimeRange { * 开始时间段 * HH:mm格式 private String timeStart; * 结束时间段 * HH:mm格式 private String timeEnd; * 某个时间是否在该时间范围内 * @param localDateTime * @return public boolean isIn(LocalDateTime localDateTime) { String comparedCurrentHourMin = localDateTime.format(DateTimeFormatter.ofPattern(TIME_PATTERN)); long comparedMilliSeconds = getMillisOfDay(comparedCurrentHourMin, TIME_PATTERN); long startMilliSeconds = getMillisOfDay(timeStart, TIME_PATTERN); long endMilliSeconds = getMillisOfDay(timeEnd, TIME_PATTERN); return comparedMilliSeconds >= startMilliSeconds && comparedMilliSeconds < endMilliSeconds; * 获取某一个时间在一天中的毫秒数 * @param timeValue * @param timePat * @return public static long getMillisOfDay(String timeValue, String timePat) { return DateTimeFormatter.ofPattern(timePat).parse(timeValue).getLong(ChronoField.MILLI_OF_DAY); * 获取时间范围对象列表 * @return @JSONField(serialize = false) public List<TimeRange> getRanges() { List<TimeRange> resultList = new ArrayList<>(4); if (CollectionUtils.isNotEmpty(timeRanges)) { for (String timeSection : timeRanges) { String[] array = timeSection.split("-"); if (array.length != 2) { continue; resultList.add(new TimeRange(array[0], array[1])); return resultList; * 某一个时间是否在范围内 * @param localDateTime * @return public boolean isCurrentIn(LocalDateTime localDateTime) { // check month if (CollectionUtils.isNotEmpty(months)) { if (!months.contains(localDateTime.getMonth().getValue())) { return false; // check day of month if (CollectionUtils.isNotEmpty(daysOfMonth)) { if (!daysOfMonth.contains(localDateTime.getDayOfMonth())) { return false; // check day of week if (CollectionUtils.isNotEmpty(daysOfWeek)) { // localDateTime.getDayOfWeek() is Enum And it's getValue() is the dayofweek int value. if (!daysOfWeek.contains(localDateTime.getDayOfWeek().getValue())) { return false; // check time range List<TimeRange> ranges = getRanges(); if (CollectionUtils.isNotEmpty(ranges)) { for (TimeRange timeRange : ranges) { if (timeRange.isIn(localDateTime)) { // satisfy one range is true return true; return false; 复制代码

    如何使用

            String json = "{\"daysOfWeek\":[1,3,6],\"timeRanges\":[\"09:00-18:00\",\"22:00-24:00\"]}";
            TimeSection timeSection = JSON.parseObject(json, TimeSection.class);
            timeSection.isCurrentIn(LocalDateTime.now())
    复制代码
    • 2.9w
  • 私信