diff --git a/java/Jsr303校验.md b/java/Jsr303校验.md
index ffd64ba..458c4e9 100644
--- a/java/Jsr303校验.md
+++ b/java/Jsr303校验.md
@@ -2,8 +2,6 @@
JSR303是Java为Bean数据合法性校验提供给的标准框架,已经包含在 JavaEE6.0 中、JSR303通过在Bean 属性中标注类似 @NotNull @Max 等标准的注解指定校验规则并通过标准的验证接口对 Bean进行验证
-
-
### 注解
### JSR303中含有的注解
@@ -36,142 +34,21 @@
注:HIbernate Validator是JSR303的一个参考实现,除了支持所有标准的校验注解外,另外HIbernate Validator还有JSR-380的实现
```
-### SpringMVC中使用JSR303进行服务器端验证
+### 静态工具 手动校验BEAN
+
+ ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+ Validator validator = factory.getValidator();
- a. 添加相关依赖
-
- org.hibernate
- hibernate-validator
- 6.0.7.Final
-
- b. 给校验对象的指定属性添加校验规则
- public class User {
- private Long userId;
-
- @NotBlank(message = "用户名不能为空")
- private String userName;
-
- @NotBlank(message = "密码不能为空")
- private String userPwd;
-
- ............
-
- }
- c. 在请求处理方法中,使用@Validated或@Valid注解要验证的对象,并根据BindingResult判断校验是否通过 另外,验证参数后必须紧跟BindingResult参数,否则spring会在校验不通过时直接抛出异常
-
- public String login(@Validated @ModelAttribute User user,
- BindingResult bindingResult){
- // 服务器端校验不通过
- if (bindingResult.hasErrors()) {
- return "login";
- }
- ....
- }
+ AirportDTO dto = new AirportDTO();
+ dto.setIataCode("PKX");
+ dto.setThroughput("-1"); // 符合条件
+ // data.setValue(1234567.789); // 不符合条件,整数部分超过6位
+ // data.setValue(123456.78901); // 不符合条件,小数部分超过4位
+ // data.setValue(-123.45); // 不符合条件,小于0
- 注1:@Valid和Validated的区别,随便使用哪个都行
- @Valid是使用hibernate validation的时候使用
- @Validated 是只用spring Validator校验机制使用
-
-
-
-### 在JSP页面上通过form标签显示消息
-
-
-
- 显示表单所有错误
- 显示特定表单对象属性的错误
-
- delimiter:如果一个属性有多个错误,错误信息的分隔符。默认是换行
- 注1:errors标签要放到form标签中才能显示错误消息
- 注2:如果使用form:errors标签不显示错误消息,请检查Model中是否已经添加了命令对象,没有是不会显示错误消息的
- 注3:注意命名规范,Book-->book
- @ModelAttribute
- public void init(Model model) {
- System.out.println("init...");
- model.addAttribute("user", new User());
- }
-
-### 通过BindingResult和form:errors标签在JSP页面显示非验证消息
-
-
-
- public String login(@Valid @ModelAttribute Yh yh, BindingResult bindingResult, Model model){
- bindingResult.rejectValue("yhzh", null, "帐号错误");
- ...
- }
- bindingResult.rejectValue("email", "validate.email.empty", "邮箱不能为空");//这个函数有好几个重载的变体
- 它们是可以支持国际化的。 比如,上面这个例子表示, 错误的字段(filed)是“email”, errorCode是“validate.email.empty”, 与资源文件对应, 第三个是defaultMessage
-
- 作业1:从BindingResult对象获得错误消息,生成基于json字符串返回到客户端
-
-如何实现分组校验
-2.1 通过接口定义若干个分组
- 2.2 给校验对象的指定属性添加校验规则,并指定校验组
- 2.3 在请求处理方法中,使用@Validated标记要验证的对象,并指定校验组
-
-还可以对验证进行分组(@Validated)
-public class Book {
- // 书本验证分组
- public static interface ValidateGroups {
- // 新增/修改
- public static interface AddEdit {
- }
-
-// 上架/下架
-public static interface DeployUndeploy {
-}
-
-// 更新封面
-public static interface UpdateBookImage {
-}
-
-}
-
-private Long bookId;
-
-@NotBlank(message = "书名不能为空", groups = { AddEdit.class })
-private String bookName;
-
-private String bookNamePinyin;
-
-@NotNull(message = "类别不能为空", groups = { AddEdit.class })
-private Long bookCategoryId;
-
-@NotBlank(message = "作者不能为空", groups = { AddEdit.class })
-private String bookAuthor;
-
-@NotNull(message = "价格不能为空", groups = { AddEdit.class })
-@Min(value = 1, message = "价格不能为负")
-private Double bookPrice;
-
-private Long bookImage;
-
-@NotBlank(message = "出版社不能为空", groups = { AddEdit.class })
-private String publishing;
-
-@NotBlank(message = "简介不能为空", groups = { AddEdit.class })
-private String bookDesc;
-
-private Integer bookState;
-
-private Date deployDatetime;
-
-private Integer salesVolume;
-
-}
-
-@RequestMapping("/add")
- public String add(
- @Validated(value = Book.BookGroups.Add.class) @ModelAttribute Book book,
- BindingResult bindingResult) {
- if (bindingResult.hasErrors()) {
- return "admin/addBook";
- }
- return "admin/addBook";
- }
-
-
-3. 通过分组来指定顺序
- @GroupSequence({First.class, Second.class, User.class})
+ Set> validate = validator.validate(dto,Update.class);
+ for (ConstraintViolation violation : validate) {
+ System.out.println(violation.getMessage());
+ }
+ }
diff --git a/java/在 Java 中优雅地操纵时间.md b/java/在 Java 中优雅地操纵时间.md
new file mode 100644
index 0000000..022e0eb
--- /dev/null
+++ b/java/在 Java 中优雅地操纵时间.md
@@ -0,0 +1,764 @@
+# 在 Java 中优雅地操纵时间
+
+2024-11-11
+
+[教程](https://springdoc.cn/categories/教程/)
+
+在开发时候,发现有很多需要用到时间的地方,例如记录操作的时间、比较时间判断产品是否有效等。总而言之,时间是我们业务开发必须关注、时刻注意的点。但目前工程的代码中使用了非常多时间的工具类,一会儿用 `java.util.Date` 记录时间,一会用 `java.time.LocalDateTime` 记录时间,怎么才能在 Java 中优雅的操纵时间呢,我整理了相关的概念和工具类,希望帮助大家在代码开发的过程中对对时间的使用更加优雅。
+
+这里先写一个结论:
+
+- 建议使用 java8 的时间 API,在安全性和易用性上都远高于 `java.util.Date`。
+- 目前比较流行的封装 java API 的时间工具类大都基于 `java.util.Date`,建议在开发过程中根据业务需要基于 `java.time.*` 的方法封装工具类(文末给出了一个简单的实现)。
+
+## 时间在计算机中的存储和展示
+
+时间以整数的方式进行存储:时间在计算机中存储的本质是一个整数,称为 Epoch Time(时间戳),计算从 1970 年 1 月 1 日零点(格林威治时间/GMT+00:00)到现在所经历的秒数。
+
+在 java 程序中,时间戳通常使用 `long` 表示毫秒数,通过 `System.currentTimeMillis()` 可以获取时间戳。时间戳对我们人来说是不易理解的,因此需要将其转换为易读的时间,例如,*2024-10-7 20:21:59*(实际上说的是本地时间),而同一时刻不同时区的人看到的本地时间是不一样,所以在时间展示的时候需要加上时区的信息,才能精准的找到对应的时刻。
+
+时区与世界时间标准相关:
+
+
+
+世界时间的标准在 1972 年发生了变化,但我们在开发程序的时候可以忽略 **GMT** 和 **UTC** 的差异, 因为计算机的时钟在联网的时候会自动与时间服务器同步时间。 本地时间等于我们所在(或者所使用)时区内的当地时间,它由与世界标准时间(UTC)之间的偏移量来定义。这个偏移量可以表示为 *UTC-* 或 *UTC+*,后面接上偏移的小时和分钟数。 例如:*GMT+08:00* 或者 *UTC+08:00* 表示东八区,*2024-10-7 20:21:59 UTC+08:00* 便可以精准的定位一个时刻。
+
+
+
+#### 时间容器
+
+- `LocalDate`:代表日期,不包含时间或时区。
+- `LocalTime`:代表时间,不包含日期或时区。
+- `LocalDateTime`:包括了日期和时间,但不包括时区。
+- `ZonedDateTime`:包括了日期和时间以及时区。
+- `Instant`:代表时间轴上的一个特定点,类似于时间戳。
+
+#### 时间操作
+
+- `Duration`:表示基于时间的时间量(例如 “5 小时” 或 “30 秒”)。
+- `Period`:代表基于日期的时间量(如 “2 年 3 个月”)。
+- `TemporalAdjusters`:提供调整日期的方法(如查找下一个星期一)。
+- `Clock`:使用时区提供当前日期时间,并可进行时间控制。
+
+#### 格式化和输出
+
+- `DateTimeFormatter`:用于格式化和解析日期时间对象。
+
+
+
+## 日期 API
+
+JDK 以版本 8 为界,有两套处理日期/时间的 API。
+
+
+
+简单的比较如下:
+
+| 特性 | java.util.Date | java.util.Date.Calendar | java.time.LocalDateTime |
+| -------- | --------------------------------------------- | ---------------------------- | -------------------------- |
+| 线程安全 | ❌ | ❌ | ✅ |
+| 时间运算 | ❌ | ✅ | ✅ |
+| 可读性 | Tue Oct 08 00:11:16 CST 2024 易读性较低 | ❌不易读 | ✅ yyyy-MM-dd’T’HH:mm:ss |
+| 常量设计 | 需要对获取的年份(+1900)月份(0-11)进行处理 | 需要对获月份(0-11)进行处理 | ✅ 不需要额外处理,符合常识 |
+| 时间精度 | 精确到毫秒 | 精确到毫秒 | 精确到纳秒 |
+| 时区 | 具体的时间调用 | 不 | - |
+
+| 特性 | java.text.SimpleDateFormat | java.time.DateTimeFormatter |
+| -------- | ------------------------------------------------------------ | -------------------------------------- |
+| 线程安全 | ❌ 在多线程环境下每个线程独立维护一份 SimpleDateFormat 对象实例,或者将 `SimpleDateFormat` 放到 `ThreadLocal` 中 | ✅ 不变对象,线程安全,可以使用单例存储 |
+| 使用场景 | `Date` | `LocalDateTime` |
+
+### java.util
+
+在 jdk8 之前,Java 使用 `java.util` 中的 API 对处理时间。 在获取年月日的时候,`Date` 和 `Calendar` 需要进行不同的转换 => 规则不统一。
+
+#### Date
+
+`java.util.Date` 用于表示一个日期和时间的对象,其实现很简单,实际上存储了一个 `long` 类型的以毫秒表示的时间戳,在通过 `new Date()` 获取当前时间的时候,实际上是通过 `System.currentTimeMillis()` 获取时间戳进行赋值。
+
+```java
+public class Date {
+ long fastTime;
+
+ public Date(long date) {
+ fastTime = date;
+ }
+
+ public long getTime() {
+ return fastTime;
+ }
+}
+```
+
+`java.util.Date` 承载的功能有限,且在利用 `Date` 类获取具体年/月/日的时候需要注意:`getYear()` 返回的年份必须加上 `1900`,`getMonth()` 返回的月份是 *0-11* 分别表示 *1-12* 月,所以要加 *1*,而 `getDate()` 返回的日期范围是 *1~31*,又不能加 *1*。
+
+#### Calendar
+
+`Calendar` 可以用于获取并设置年、月、日、时、分、秒,它和 `Date` 比,主要多了一个可以做简单的日期和时间运算的功能,但代码粗糙,API 不好用,性能也不好。
+
+`Calendar` 对象 `getTime()` 可以获得 `Date` 对象:
+
+```java
+import java.util.*;
+
+public class Main {
+ public static void main(String[] args) {
+ // 获取当前时间:
+ Calendar c = Calendar.getInstance();
+ int y = c.get(Calendar.YEAR);//返回年份不用转换
+ int m = 1 + c.get(Calendar.MONTH);//返回月份需要加1
+ int d = c.get(Calendar.DAY_OF_MONTH);
+ int w = c.get(Calendar.DAY_OF_WEEK);//返回的
+ int hh = c.get(Calendar.HOUR_OF_DAY);
+ int mm = c.get(Calendar.MINUTE);
+ int ss = c.get(Calendar.SECOND);
+ int ms = c.get(Calendar.MILLISECOND);
+ System.out.println(y + "-" + m + "-" + d + " " + w + " " + hh + ":" + mm + ":" + ss + "." + ms);
+ }
+}
+import java.text.*;
+import java.util.*;
+
+public class Main {
+ public static void main(String[] args) {
+ // 当前时间:
+ Calendar c = Calendar.getInstance();
+ // 清除所有:
+ c.clear();
+ // 设置年月日时分秒:
+ c.set(2019, 10 /* 11月 */, 20, 8, 15, 0);
+ // 加5天并减去2小时:
+ c.add(Calendar.DAY_OF_MONTH, 5);
+ c.add(Calendar.HOUR_OF_DAY, -2);
+ // 显示时间:
+ var sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ Date d = c.getTime();
+ System.out.println(sdf.format(d));
+ // 2019-11-25 6:15:00
+ }
+}
+```
+
+#### TimeZone
+
+`Calendar` 和 `Date` 相比,它提供了时区转换的功能。时区用 `TimeZone` 对象表示。
+
+时区的唯一标识是以字符串表示的 *ID*。获取指定 `TimeZone` 对象也是以这个 *ID* 为参数获取,*GMT+09:00*、*Asia/Shanghai* 都是有效的时区 *ID*。可以通过 `TimeZone.getAvailableIDs()` 获取系统支持的所有 *ID*。
+
+```java
+import java.text.*;
+import java.util.*;
+
+public class learnTime {
+ public static void main(String[] args) {
+ // 当前时间:
+ Calendar c = Calendar.getInstance();
+ // 清除所有字段:
+ c.clear();
+ // 设置为北京时区:
+ c.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
+ // 设置年月日时分秒:
+ c.set(2024, 9 /* 10月 */, 10, 8, 15, 0);
+ // 显示时间:
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
+ System.out.println(sdf.format(c.getTime()));
+ // 2024-10-09 20:15:00
+ }
+}
+```
+
+#### java.text.SimpleDateFormat
+
+`Date` 使用 `SimpleDateFormat` 解析和格式化时间:
+
+```java
+// SimpleDateFormat线程不安全,每次使用都要构造新的,在初始的时候定义解析的字符串格式
+SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+// 将指定字符串String解析为Date
+Date date = format.parse("2024-10-07 16:10:22");
+
+// 将Date格式化为String
+String str = format.format(date);
+```
+
+由于 `SimpleDateFormat` 线程不安全,为了提升性能,可以使用 `ThreadLocalCache`。
+
+如下:
+
+```java
+static final ThreadLocal SIMPLE_DATE_FORMAT_LOCAL
+ = ThreadLocal.withInitial(
+ () -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
+);
+```
+
+### Java.time.*
+
+> 开源社区开发了一个日期库 [Joda](https://www.joda.org/joda-time/),API 清晰,性能较好,提交了 *JSR-310*,在 java8 中称为 JDK 基础类库。
+
+- 本地日期和时间:`LocalDateTime`(日期和时间),`LocalDate`(日期),`LocalTime`(时间)(因为没有时区,所以无法与时间戳转换);
+- 带时区的日期和时间:`ZonedDateTime`;
+- 时刻:`Instant`;
+- 时区:`ZoneId`,`ZoneOffset`;
+- 时间间隔:`Duration`。
+
+以及一套新的用于取代 `SimpleDateFormat` 的格式化类型 `DateTimeFormatter`。
+
+#### LocalDate/LocalTime/LocalDateTime
+
+- 默认严格按照 *ISO 8601* 规定日期和时间格式进行打印(日期和时间的分隔符是 `T`)。
+
+ - 日期:`yyyy-MM-dd`; 时间 `HH:mm:ss`;
+ - 日期和时间:`yyyy-MM-dd'T'HH:mm:ss`;
+
+- 可以解析简单格式获取类型:
+
+ ```java
+ LocalDateTime localDayTime=LocalDateTime.of(2024, 10, 07, 8, 15, 0);
+ LocalDate localDay=LocalDate.of(2024, 10, 07);
+ LocalTime localTime=LocalTime.parse("08:15:07");
+ ```
+
+- 有对日期和时间进行加减的非常简单的链式调用,通过 `plusXxx()`/`minusXxx()` 对时间进行变换:
+
+ ```java
+ public class learnTime {
+ public static void main(String[] args) {
+ LocalDateTime dt = LocalDateTime.of(2024, 10, 10, 20, 30, 59);
+ System.out.println(dt);
+ // 加5天减3小时:2024-10-10T20:30:59
+ LocalDateTime dt2 = dt.plusDays(5).minusHours(3);
+ System.out.println(dt2); // 2024-10-15T17:30:59
+ // 减1月:
+ LocalDateTime dt3 = dt2.minusMonths(1); //2024-09-15T17:30:59
+ System.out.println(dt3); // 2019-09-30T17:30:59
+ }
+ }
+ ```
+
+- 对日期和时间进行调整使用 `withXxx()`,例如将月份调整为 9月: `dataLocalTime.withMonth(9)`;
+
+- 复杂的操作:获取特殊时间
+
+ - `with` 和 `TemporalAdjusters` 配合使用找到特殊时间(当月的第一天)。
+
+ ```java
+ public class Main {
+ public static void main(String[] args) {
+ LocalDateTime now = LocalDateTime.now();
+
+ // 获取本月第一天0:00时刻:
+ System.out.println("当月第一天0:00时刻"+now.withDayOfMonth(1).atStartOfDay());
+ //获取当月第一天
+ System.out.println("当月第一天:"+now.with(TemporalAdjusters.firstDayOfMonth()));
+ //获取下月第一天
+ System.out.println("下月第一天:"+now.with(TemporalAdjusters.firstDayOfNextMonth()));
+ //获取明年第一天
+ System.out.println("明年第一天:"+now.with(TemporalAdjusters.firstDayOfNextYear()));
+ //获取本年第一天
+ System.out.println("本年第一天:"+now.with(TemporalAdjusters.firstDayOfYear()));
+ //获取当月最后一天
+ System.out.println("当月最后一天:"+now.with(TemporalAdjusters.lastDayOfMonth()));
+ //获取本年最后一天
+ System.out.println("本年最后一天:"+now.with(TemporalAdjusters.lastDayOfYear()));
+ //获取当月第三周星期五
+ System.out.println("当月第三周星期五:"+now.with(TemporalAdjusters.dayOfWeekInMonth(3, DayOfWeek.FRIDAY)));
+ //获取上周一
+ System.out.println("上周一:"+now.with(TemporalAdjusters.previous(DayOfWeek.MONDAY)));
+ //获取下周日
+ System.out.println("下周日:"+now.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)));
+
+ }
+ }
+ ```
+
+- 比较可以使用 `isBefore()` 和 `isAfter()`。
+
+#### Duration 和 Period
+
+- Duration: 基于时间值(
+
+ ```
+ Instant
+ ```
+
+ /
+
+ ```
+ LocalDateTime
+ ```
+
+ ),表示两个时刻时间的时间间隔,适合处理较短的时间,需要更高的精确性。
+
+ - 使用 `between()` 方法比较两个瞬间的差;
+ - 使用 `getSeconds()` 或 `getNanosecends()` 方法获取时间单元的值;
+ - 获得具体的粒度的间隔:`ofDays()`、`ofHours()`、`ofMillis()`、`ofMinutes()`、`ofNanos()`、`ofSeconds()`;
+ - 通过文本创建 `Duration` 对象,格式为 “PnDTnHnMn.nS”,`Duration.parse("P1DT1H10M10.5S")`;
+ - 使用 `toDays()`、`toHours()`、`toMillis()`、`toMinutes()` 方法把 `Duration` 对象可以转成其他时间单元;
+ - 通过 `plusX()`、`minusX()` 方法增加或减少 `Duration` 对象,其中 X 表示 *days*, *hours*, *millis*, *minutes*, *nanos* 或 *seconds*。
+
+- ```
+ Period
+ ```
+
+
+
+ 基于日期值,表示一段时间的年、月、日:
+
+ - 使用 `between()` 方法比较两个日期的差;
+ - 使用 `getYears()`、`getMonhs()`、`getDays()` 方法获取具体粒度差距(返回的类型是 `int`);
+ - 通过文本创建 `Period` 对象,格式为 “PnYnMnD”:`Period.parse("P2Y3M5D")`;
+ - 可以通过 `plusX()`、`minusX()` 方法进行增加或减少,其中 `X` 表示日期单元;
+
+#### ZonedDateTime
+
+`ZonedDateTime` 是 `LocalDateTime` 加 `ZoneId`。
+
+- `ZonedDateTime` 带时区时间的常见方法:
+
+ - `now()`:获取当前时区的ZonedDateTime对象。
+ - `now(ZoneId zone)`:获取指定时区的 `ZonedDateTime` 对象。
+ - `getYear`、`getMonthValue`、`getDayOfMonth` 等:获取年月日、时分秒、纳秒等。
+ - `withXxx(时间)`:修改时间系列的方法。
+ - `minusXxx(时间)`:减少时间系列的方法。
+ - `plusXxx(时间)`:增加时间系列的方法。
+
+- 时区转换
+
+ ```java
+ import java.time.*;
+
+ public class Main {
+ public static void main(String[] args) {
+ // 以中国时区获取当前时间:
+ ZonedDateTime zbj = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
+ // 转换为纽约时间:
+ ZonedDateTime zny = zbj.withZoneSameInstant(ZoneId.of("America/New_York"));
+ System.out.println(zbj);
+ System.out.println(zny);
+ }
+ }
+ ```
+
+#### ZoneId
+
+时区类,功能和 `java.util.TimeZone` 类似。
+
+`ZoneId` 支持两种类型格式初始化,一种是时区偏移的格式(基于 UTC/Greenwich 时),一种是地域时区的格式(eg:*Europe/Paris*)。`ZoneId` 是抽象类,具体的逻辑实现由来子类完成,`ZoneOffset` 处理时区偏移类型的格式,`ZoneRegion` 处理基于地域时区的格式:
+
+- `getAvailableZoneIds()`:获取Java中支持的所有时区。
+- `systemDefault()`:获取系统默认时区。
+- `of(String zoneId)`:获取一个指定时区。
+
+| 格式 | 描述 | 示例 |
+| ------------------------------------------------------------ | ------------------------------------------------------------ | ----------------------------- |
+| Z, GMT, UTC, UT | 格林尼治标准时间,和中国相差8个小时 | `ZoneId.of("Z");` |
+| +h +hh +hh:mm -hh:mm +hhmm -hhmm +hh:mm:ss -hh:mm:ss +hhmmss -hhmmss | 表示从格林尼治标准时间偏移时间,中国用+8表示 | `ZoneId.of("+8");` |
+| 前缀:UTC+, UTC-, GMT+, GMT-, UT+ UT-, 后缀:-h +hh +hh:mm -hh:mm… | 表示从格林尼治标准时间偏移时间 | `ZoneId.of("UTC+8");` |
+| Asia/Aden, America/Cuiaba, Etc/GMT+9, Etc/GMT+8, Africa/Nairobi, America/Marigot… | 地区表示法,这些ID必须包含在getAvailableZoneIds集合中,否则会抛出异常 | `ZoneId.of("Asia/Shanghai");` |
+
+#### Instant
+
+> 时间线上的某个时刻/时间戳
+
+通过获取 `Instant` 的对象可以拿到此刻的时间,该时间由两部分组成:从 *1970-01-01 00:00:00* 开始走到此刻的总秒数+不够 1 秒的纳秒数。
+
+- 作用:可以用来记录代码的执行时间,或用于记录用户操作某个事件的时间点。
+- 传统的 `Date` 类,只能精确到毫秒,并且是可变对象。
+- 新增的 `Instant` 类,可以精确到纳秒,并且是不可变对象,推荐用 `Instant` 代替 `Date`。
+
+```java
+//1、创建Instant的对象,获取此刻时间信息
+Instant now = Instant.now(); //不可变对象
+//2、获取总秒数
+long second = now.getEpochSecond();
+system.out.println(second) ;
+//3、不够1秒的纳秒数
+int nano = now.getNano();
+system.out.println(nano) ;
+
+system.out.println(now);
+//可以进行加减法
+Instant instant = now.plusNanos(111);//将纳秒加111
+
+// Instant对象的作用:做代码的性能分析,或者记录用户的操作时间点
+Instant now1 = Instant.now();
+//代码执行...
+Instant now2 = Instant.now();
+//用这两个时间点相减就可以知道这段代码运行了多少时间
+```
+
+#### DateTimeFormatter
+
+使用方式,传入格式化字符串,可以指定 `local`。
+
+```java
+import java.time.*;
+import java.time.format.*;
+import java.util.Locale;
+
+public class Main {
+ public static void main(String[] args) {
+ ZonedDateTime zdt = ZonedDateTime.now();
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm ZZZZ");
+ System.out.println(formatter.format(zdt));
+
+ DateTimeFormatter zhFormatter = DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm", Locale.CHINA);
+ System.out.println(zhFormatter.format(zdt));
+
+ DateTimeFormatter usFormatter = DateTimeFormatter.ofPattern("E, MMMM/dd/yyyy HH:mm", Locale.US);
+ System.out.println(usFormatter.format(zdt));
+
+ //2024-10-08T00:25 GMT+08:00
+ //2024 十月 08 星期二 00:25
+ //Tue, October/08/2024 00:25
+ }
+}
+```
+
+### 转换
+
+#### `LocalTimeTime` 和 `Date` 的相互转换
+
+`LocalDateTime` 不包括时区,而 `Date` 代表一个具体的时间瞬间,精度为毫秒。
+
+为了从 `LocalDateTime` 转换到 `Date` 需要提供时区。
+
+```java
+// LocalDateTime 转换为 Date
+LocalDateTime localDateTime = LocalDateTime.now();
+ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.systemDefault());
+Date date = Date.from(zonedDateTime.toInstant());
+// Date 转换为 LocalDateTime
+Date date = new Date();
+Instant instant = date.toInstant();
+LocalDateTime localDateTime = instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
+```
+
+#### 数据库映射变化
+
+- `java.util.Date` 和数据库映射
+
+ ```xml
+
+ ```
+
+- `java.time.*` 和数据库映射
+
+ ```xml
+
+ ```
+
+ - *mybatis 3.5.0* 以后已经支持,有 `LocalDateTimeTypeHandler` 等类型处理器支持,不需要额外操作。
+
+ - 比较老的 *mybatis* 版本可能会报错,需要添加相关的依赖。
+
+ ```xml
+
+ org.mybatis
+ mybatis-typehandlers-jsr310
+ 1.0.2
+
+ ```
+
+*Mybatis* 中和时间相关的 `jdbcType` 和 j`avaType`、`typeHandler` 的对照关系如下:
+
+| TypeHandler | Java类型 | JDBC类型 |
+| ------------------------- | ----------------------------- | ---------------------- |
+| DateTypeHandler | java.util.Date | TIMESTAMP |
+| DateOnlyTypeHandler | java.util.Date | DATE |
+| TimeOnlyTypeHandler | java.util.Date | TIME |
+| InstantTypeHandler | java.time.Instant | TIMESTAMP |
+| LocalDateTimeTypeHandler | java.time.LocalDateTime | TIMESTAMP |
+| LocalDateTypeHandler | java.time.LocalDate | DATE |
+| LocalTimeTypeHandler | java.time.LocalTime | TIME |
+| OffsetDateTimeTypeHandler | java.time.OffsetDateTime | TIMESTAMP |
+| OffsetTimeTypeHandler | java.time.OffsetTime | TIME |
+| ZonedDateTimeTypeHandler | java.time.ZonedDateTime | TIMESTAMP |
+| YearTypeHandler | java.time.Year | INTEGER |
+| MonthTypeHandler | java.time.Month | INTEGER |
+| YearMonthTypeHandler | java.time.YearMonth | VARCHAR 或 LONGVARCHAR |
+| JapaneseDateTypeHandler | java.time.chrono.JapaneseDate | DATE |
+
+### 操作时间相关的工具
+
+有一些对基础的API进行了封装便于我们在开发中有效的处理时间。
+
+- 蚂蚁时间工具类:
+
+ ```
+ com.iwallet.biz.common.util.DateUtil
+ ```
+
+ - 基于 `java.Util.Date`,提供了广泛的日期/时间处理方法,可满足绝大部分需求。
+
+- ```
+ org.apache.commons.lang3.time
+ ```
+
+ - 包括多种基于 `java.util.Date` 封装的工具类,提供了很多方便操作日期和时间的算法。
+
+目前暂时没有发现基于 `java.time*` 封装的公共的时间工具类。
+
+在很多情况下,因为已有的工具类不能满足当下的业务需求,工程内部需要自己实现类似 `DateUtil` 的工具类,建议基于 `java.time*` 实现相关的工具类。
+
+```java
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+
+public class DateUtils {
+
+ // 获取当前日期
+ public static LocalDate getCurrentDate() {
+ return LocalDate.now();
+ }
+
+ // 获取当前时间
+ public static LocalTime getCurrentTime() {
+ return LocalTime.now();
+ }
+
+ // 获取当前日期时间
+ public static LocalDateTime getCurrentDateTime() {
+ return LocalDateTime.now();
+ }
+
+ // 格式化日期为字符串
+ public static String formatLocalDate(LocalDate date, String pattern) {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
+ return date.format(formatter);
+ }
+
+ // 解析字符串为LocalDate
+ public static LocalDate parseLocalDate(String dateStr, String pattern) {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
+ return LocalDate.parse(dateStr, formatter);
+ }
+
+ // 增加指定天数
+ public static LocalDate addDays(LocalDate date, long days) {
+ return date.plusDays(days);
+ }
+
+ // 减少指定天数
+ public static LocalDate minusDays(LocalDate date, long days) {
+ return date.minusDays(days);
+ }
+
+ // 计算两个日期之间的天数差
+ public static long getDaysBetween(LocalDate startDate, LocalDate endDate) {
+ return ChronoUnit.DAYS.between(startDate, endDate);
+ }
+
+ // 获取指定日期所在月份的第一天
+ public static LocalDate getFirstDayOfMonth(LocalDate date) {
+ return date.withDayOfMonth(1);
+ }
+
+ // 获取指定日期所在月份的最后一天
+ public static LocalDate getLastDayOfMonth(LocalDate date) {
+ return date.withDayOfMonth(date.lengthOfMonth());
+ }
+
+ // 判断两个日期是否相等
+ public static boolean isSameDate(LocalDate date1, LocalDate date2) {
+ return date1.isEqual(date2);
+ }
+
+ // 判断日期是否在指定范围内
+ public static boolean isDateInRange(LocalDate date, LocalDate startDate, LocalDate endDate) {
+ return date.isAfter(startDate) && date.isBefore(endDate);
+ }
+
+ // 获取指定日期的星期几
+ public static DayOfWeek getDayOfWeek(LocalDate date) {
+ return date.getDayOfWeek();
+ }
+
+ // 判断是否为闰年
+ public static boolean isLeapYear(int year) {
+ return Year.of(year).isLeap();
+ }
+
+ // 获取指定月份的天数
+ public static int getDaysInMonth(int year, int month) {
+ return YearMonth.of(year, month).lengthOfMonth();
+ }
+
+ // 获取指定日期的年份
+ public static int getYear(LocalDate date) {
+ return date.getYear();
+ }
+
+ // 获取指定日期的月份
+ public static int getMonth(LocalDate date) {
+ return date.getMonthValue();
+ }
+
+ // 获取指定日期的天数
+ public static int getDayOfMonth(LocalDate date) {
+ return date.getDayOfMonth();
+ }
+
+ // 获取指定日期的小时数
+ public static int getHour(LocalDateTime dateTime) {
+ return dateTime.getHour();
+ }
+
+ // 获取指定日期的分钟数
+ public static int getMinute(LocalDateTime dateTime) {
+ return dateTime.getMinute();
+ }
+
+ // 获取指定日期的秒数
+ public static int getSecond(LocalDateTime dateTime) {
+ return dateTime.getSecond();
+ }
+
+ // 判断指定日期是否在当前日期之前
+ public static boolean isBefore(LocalDate date) {
+ return date.isBefore(LocalDate.now());
+ }
+
+ // 判断指定日期是否在当前日期之后
+ public static boolean isAfter(LocalDate date) {
+ return date.isAfter(LocalDate.now());
+ }
+
+ // 判断指定日期是否在当前日期之前或相等
+ public static boolean isBeforeOrEqual(LocalDate date) {
+ return date.isBefore(LocalDate.now()) || date.isEqual(LocalDate.now());
+ }
+
+ // 判断指定日期是否在当前日期之后或相等
+ public static boolean isAfterOrEqual(LocalDate date) {
+ return date.isAfter(LocalDate.now()) || date.isEqual(LocalDate.now());
+ }
+
+ // 获取指定日期的年龄
+ public static int getAge(LocalDate birthDate) {
+ LocalDate currentDate = LocalDate.now();
+ return Period.between(birthDate, currentDate).getYears();
+ }
+
+ // 获取指定日期的季度
+ public static int getQuarter(LocalDate date) {
+ return (date.getMonthValue() - 1) / 3 + 1;
+ }
+
+ // 获取指定日期的下一个工作日
+ public static LocalDate getNextWorkingDay(LocalDate date) {
+ do {
+ date = date.plusDays(1);
+ } while (date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY);
+ return date;
+ }
+
+ // 获取指定日期的上一个工作日
+ public static LocalDate getPreviousWorkingDay(LocalDate date) {
+ do {
+ date = date.minusDays(1);
+ } while (date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY);
+ return date;
+ }
+
+ // 获取指定日期所在周的第一天(周一)
+ public static LocalDate getFirstDayOfWeek(LocalDate date) {
+ return date.with(DayOfWeek.MONDAY);
+ }
+
+ // 获取指定日期所在周的最后一天(周日)
+ public static LocalDate getLastDayOfWeek(LocalDate date) {
+ return date.with(DayOfWeek.SUNDAY);
+ }
+
+ // 获取指定日期所在年的第一天
+ public static LocalDate getFirstDayOfYear(LocalDate date) {
+ return date.withDayOfYear(1);
+ }
+
+ // 获取指定日期所在年的最后一天
+ public static LocalDate getLastDayOfYear(LocalDate date) {
+ return date.withDayOfYear(date.lengthOfYear());
+ }
+
+ // 获取指定日期所在季度的第一天
+ public static LocalDate getFirstDayOfQuarter(LocalDate date) {
+ int month = (date.getMonthValue() - 1) / 3 * 3 + 1;
+ return LocalDate.of(date.getYear(), month, 1);
+ }
+
+ // 获取指定日期所在季度的最后一天
+ public static LocalDate getLastDayOfQuarter(LocalDate date) {
+ int month = (date.getMonthValue() - 1) / 3 * 3 + 3;
+ return LocalDate.of(date.getYear(), month, Month.of(month).maxLength());
+ }
+
+ // 判断指定日期是否为工作日(周一至周五)
+ public static boolean isWeekday(LocalDate date) {
+ return date.getDayOfWeek() != DayOfWeek.SATURDAY && date.getDayOfWeek() != DayOfWeek.SUNDAY;
+ }
+
+ // 判断指定日期是否为周末(周六或周日)
+ public static boolean isWeekend(LocalDate date) {
+ return date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY;
+ }
+
+ // 获取指定日期所在月份的工作日天数
+ public static int getWeekdayCountOfMonth(LocalDate date) {
+ int weekdayCount = 0;
+ LocalDate firstDayOfMonth = getFirstDayOfMonth(date);
+ LocalDate lastDayOfMonth = getLastDayOfMonth(date);
+
+ while (!firstDayOfMonth.isAfter(lastDayOfMonth)) {
+ if (isWeekday(firstDayOfMonth)) {
+ weekdayCount++;
+ }
+ firstDayOfMonth = firstDayOfMonth.plusDays(1);
+ }
+
+ return weekdayCount;
+ }
+
+ // 获取指定日期所在月份的周末天数
+ public static int getWeekendCountOfMonth(LocalDate date) {
+ int weekendCount = 0;
+ LocalDate firstDayOfMonth = getFirstDayOfMonth(date);
+ LocalDate lastDayOfMonth = getLastDayOfMonth(date);
+
+ while (!firstDayOfMonth.isAfter(lastDayOfMonth)) {
+ if (isWeekend(firstDayOfMonth)) {
+ weekendCount++;
+ }
+ firstDayOfMonth = firstDayOfMonth.plusDays(1);
+ }
+
+ return weekendCount;
+ }
+
+ // 获取指定日期所在年份的工作日天数
+ public static int getWeekdayCountOfYear(LocalDate date) {
+ int weekdayCount = 0;
+ LocalDate firstDayOfYear = getFirstDayOfYear(date);
+ LocalDate lastDayOfYear = getLastDayOfYear(date);
+
+ while (!firstDayOfYear.isAfter(lastDayOfYear)) {
+ if (isWeekday(firstDayOfYear)) {
+ weekdayCount++;
+ }
+ firstDayOfYear = firstDayOfYear.plusDays(1);
+ }
+
+ return weekdayCount;
+ }
+
+}
+```
+
+------
+
+Ref:`https://mp.weixin.qq.com/s?__biz=MzIzOTU0NTQ0MA==&mid=2247542060&idx=1&sn=ebde870557f2f3002dacef8a43e04bfd`
+