java时间工具类;获取当前,过去N个单位,前N个单位(不包含当前)年月日时分秒毫秒的开始与结束时间

time-counter.jpg

在项目开发中经常会遇到时间参数,比如开始时间结束时间;但是往往会出现一个问题那就是时间的格式问题,当前后端格式不一致的时候就导致参数无法接收。这就要写一个兼容很多格式的转换器。那有没有更简便的方式呢,例如我们通过间隔来计算开始与结束时间,只要传一个简单的代码码就可以呢。

  • 修饰符
代码(大写) 含义 描述
B before 前N个单位 (不包含当前)
P past 过去N个单位(包含当前)
C current 当前
  • 时间精度
单位 代码 大小写
毫秒 S 大写
s 小写
m 小写
H 大写
d 小写
M 大写
y 小写
W 大写
  • 传值方式

    修饰符和时间精度配合再加上单位间隔数量

描述 表达式
过去2个月 PM2
前7天 Bd7
当年 Cy

时间传参DTO类

import java.util.Date;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;

@Schema(name = "时间区段参数", description = "时间区段参数DTO")
@Getter
@Setter
@Accessors(chain = true)
public class DateSegmentDTO {

    @Schema(description = "开始时间")
    private Date start;

    @Schema(description = "结束时间")
    private Date end;

    @Schema(description = "时间区段代码")
    private String stepCode;
}

时间区间工具类

时间区间是[start,end) 包含开始不包含结束,条件应为 >= start , < end

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang3.Validate;
import org.springframework.util.Assert;

import com.qjwy.system.dto.DateSegmentDTO;

/**
 * 时间区间工具
 * 
 * 本工具旨在降低时间区间传参的复杂度,通过三个参数控制开始时间和结束时间的参数
 * 
 * Date start; Date end; String stepCode;
 * 
 * 
 * @author:三石
 * @since:1.0
 */
public class DurationUtils {

    private DurationUtils() {
    }

    // 所有修饰和精度的组合
    private static final List<String> COMBINATION = new ArrayList<String>();

    static {
        for (MODIFY_TYPE modifyType : MODIFY_TYPE.values()) {
            for (DURATION_TYPE durationType : DURATION_TYPE.values()) {
                COMBINATION.add(modifyType.name() + durationType.name());
            }
        }
    }

    /**
     * 注入时间
     * 
     * @param date    指定的时间
     * @param segment 时间参数封装
     */
    public static void injectDateTime(Date date, DateSegmentDTO segment) {

        Date start = segment.getStart();
        Date end = segment.getEnd();
        String stepCode = segment.getStepCode();

        if (start == null && end == null) {
            Assert.notNull(stepCode, "时间区间参数不能同时为null");
            String expression = stepCode.substring(0, 2);
            Assert.isTrue(COMBINATION.contains(expression), "不支持的组合:" + expression + ",目前可支持的组合" + COMBINATION);

            String[] codes = expression.split("");

            MODIFY_TYPE modifyType = MODIFY_TYPE.valueOf(codes[0]);
            DURATION_TYPE durationType = DURATION_TYPE.valueOf(codes[1]);

            Date[] se = modifyType.getRuleFunction().calc(//
                    date, //
                    durationType.getField(), //
                    stepCode.length() >= 3 ? Integer.parseInt(stepCode.substring(2)) : 0, //
                    durationType.getSmallerFields()//
            );

            segment.setStart(se[0]).setEnd(se[1]);
        }

    }

    /**
     * 时间区间类型
     */
    enum DURATION_TYPE {
        S("毫秒", Calendar.MILLISECOND, new int[] {}), //
        s("秒", Calendar.SECOND, new int[] { Calendar.MILLISECOND }), //
        m("分", Calendar.MINUTE, new int[] { Calendar.MILLISECOND, Calendar.SECOND }), //
        H("小时", Calendar.HOUR_OF_DAY, new int[] { Calendar.MILLISECOND, Calendar.SECOND, Calendar.MINUTE }), //
        d("日", Calendar.DAY_OF_MONTH, new int[] { Calendar.MILLISECOND, Calendar.SECOND, Calendar.MINUTE, Calendar.HOUR_OF_DAY }), //
        M("月", Calendar.MONTH, new int[] { Calendar.MILLISECOND, Calendar.SECOND, Calendar.MINUTE, Calendar.HOUR_OF_DAY, Calendar.DAY_OF_MONTH }), //
        y("年", Calendar.YEAR, new int[] { Calendar.MILLISECOND, Calendar.SECOND, Calendar.MINUTE, Calendar.HOUR_OF_DAY, Calendar.DAY_OF_MONTH, Calendar.MONTH }), //

        W("周", Calendar.WEEK_OF_YEAR, new int[] { Calendar.MILLISECOND, Calendar.SECOND, Calendar.MINUTE, Calendar.HOUR_OF_DAY, Calendar.DAY_OF_WEEK });

        // 精度
        private String accuracy;

        // 当前时间域
        private int field;

        // 更小的单位的时间域
        private int[] smallerFields;

        private DURATION_TYPE(String accuracy, int field, int[] smallerFields) {
            this.accuracy = accuracy;
            this.field = field;
            this.smallerFields = smallerFields;
        }

        public String getAccuracy() {
            return accuracy;
        }

        public int getField() {
            return field;
        }

        public int[] getSmallerFields() {
            return smallerFields;
        }

    }

    /**
     * 时间区间修饰类型
     */
    enum MODIFY_TYPE {

        B("before", "前N个,不包含当前", //
                (date, field, step, smallerunits) -> {
                    Date start = date;
                    Date end = date;

                    start = add(start, field, -step);

                    Calendar instance = Calendar.getInstance();
                    for (int unit : smallerunits) {
                        int minimum = instance.getMinimum(unit);

                        // 设置周一为每周中第一天
                        if (unit == Calendar.DAY_OF_WEEK) {
                            minimum = Calendar.MONDAY;
                        }

                        start = set(start, unit, minimum);
                        end = set(end, unit, minimum);
                    }

                    return new Date[] { start, end };
                }//
        ), //
        P("past", "过去N个,包含当前", //
                (date, field, step, smallerunits) -> {
                    Date start = date;
                    Date end = date;
                    start = add(start, field, -step);
                    return new Date[] { start, end };
                }//
        ), //

        C("current", "当前", //
                (date, field, step, smallerunits) -> {
                    Date start = date;
                    Date end = date;

                    Calendar instance = Calendar.getInstance();
                    for (int unit : smallerunits) {
                        int minimum = instance.getMinimum(unit);

                        // 设置周一为每周中第一天
                        if (unit == Calendar.DAY_OF_WEEK) {
                            minimum = Calendar.MONDAY;
                        }

                        start = set(start, unit, minimum);
                    }
                    return new Date[] { start, end };
                }//
        );

        // 含义
        private String meaning;
        // 描述
        private String desc;

        // 计算规则
        private RuleFunction ruleFunction;

        private MODIFY_TYPE(String meaning, String desc, RuleFunction ruleFunction) {
            this.meaning = meaning;
            this.desc = desc;
            this.ruleFunction = ruleFunction;
        }

        public String getMeaning() {
            return meaning;
        }

        public String getDesc() {
            return desc;
        }

        public RuleFunction getRuleFunction() {
            return ruleFunction;
        }

    }

    /**
     * 时间规则函数
     */

    @FunctionalInterface
    private interface RuleFunction {
        Date[] calc(Date date, int field, int step, int[] smallerunits);
    }

    /**
     * 验证date不为null
     * 
     * @param date 时间
     */
    private static void validateDateNotNull(final Date date) {
        Validate.notNull(date, "date");
    }

    /**
     * 将指定字段设置为返回新对象的日期。使用非宽松解释
     * 
     * 
     * <p>
     * 不会改变原来的 {@code Date}
     * </p>
     *
     * @param date          日期,不为空
     * @param calendarField 要添加到其中的日历字段
     * @param amount        要加的量
     * @return 新的{@code Date}与添加的时间量
     * @throws IllegalArgumentException 当date为null时
     */
    private static Date set(final Date date, final int calendarField, final int amount) {
        validateDateNotNull(date);
        // getInstance()返回一个新对象,因此这个方法是线程安全的
        final Calendar c = Calendar.getInstance();
        // 设置一周的开始为周一
        c.setFirstDayOfWeek(Calendar.MONDAY);
        c.setLenient(false);
        c.setTime(date);
        c.set(calendarField, amount);
        return c.getTime();
    }

    /**
     * 新创建一个日期对象,并设定日期
     * <p>
     * 不会改变原来的 {@code Date}
     * </p>
     * 
     * @param date          日期,不为空
     * @param calendarField 要添加到其中的日历字段
     * @param amount        要加的量,可以是负的
     * @return 新的{@code Date}与添加的时间量
     * @throws NullPointerException 当date为null时
     */
    private static Date add(final Date date, final int calendarField, final int amount) {
        validateDateNotNull(date);
        final Calendar c = Calendar.getInstance();
        // 设置一周的开始为周一
        c.setFirstDayOfWeek(Calendar.MONDAY);
        c.setTime(date);
        c.add(calendarField, amount);
        return c.getTime();
    }
}

测试

    public static void main(String[] args) throws ParseException {
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");

        Date date = format.parse("2022-07-16 09:30:25 321");

        DateSegmentDTO dto1 = new DateSegmentDTO().setStepCode("Py1");
        DurationUtils.injectDateTime(date, dto1);

        System.out.println("过去1年:Py1");
        System.out.println(format.format(dto1.getStart()));
        System.out.println(format.format(dto1.getEnd()));

        DateSegmentDTO dto2 = new DateSegmentDTO().setStepCode("By2");
        DurationUtils.injectDateTime(date, dto2);

        System.out.println("前两年:By2");
        System.out.println(format.format(dto2.getStart()));
        System.out.println(format.format(dto2.getEnd()));

        DateSegmentDTO dto3 = new DateSegmentDTO().setStepCode("Cy");
        DurationUtils.injectDateTime(date, dto3);
        System.out.println("当年:Cy");
        System.out.println(format.format(dto3.getStart()));
        System.out.println(format.format(dto3.getEnd()));
        
        DateSegmentDTO dto4 = new DateSegmentDTO().setStepCode("PW1");
        DurationUtils.injectDateTime(date, dto4);
        System.out.println("过去1周:PW1");
        System.out.println(format.format(dto4.getStart()));
        System.out.println(format.format(dto4.getEnd()));
        
        DateSegmentDTO dto5 = new DateSegmentDTO().setStepCode("Bd7");
        DurationUtils.injectDateTime(date, dto5);
        System.out.println("前7天:Bd7");
        System.out.println(format.format(dto5.getStart()));
        System.out.println(format.format(dto5.getEnd()));
    }

测试结果

过去1年:Py1
2021-07-16 09:30:25 321
2022-07-16 09:30:25 321
前两年:By2
2020-01-01 00:00:00 000
2022-01-01 00:00:00 000
当年:Cy
2022-01-01 00:00:00 000
2022-07-16 09:30:25 321
过去1周:PW1
2022-07-09 09:30:25 321
2022-07-16 09:30:25 321
前7天:Bd7
2022-07-09 00:00:00 000
2022-07-16 00:00:00 000
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容