专栏首页王磊的博客SpringBoot中时间格式化的5种方法!

SpringBoot中时间格式化的5种方法!

在我们日常工作中,时间格式化是一件经常遇到的事儿,所以本文我们就来盘点一下 Spring Boot 中时间格式化的几种方法。 ​

时间问题演示

为了方便演示,我写了一个简单 Spring Boot 项目,其中数据库中包含了一张 userinfo 表,它的组成结构和数据信息如下:

项目目录是这样的:

UserController 实现代码如下:

@RestController
@RequestMapping("/user")
public class UserController {
    @Resource
    private UserMapper userMapper;

    @RequestMapping("/list")
    public List<UserInfo> getList() {
        return userMapper.getList();
    }
}

UserMapper 实现代码如下:

@Mapper
public interface UserMapper {
    public List<UserInfo> getList();
}

UserInfo 实现代码如下:

@Data
public class UserInfo {
    private int id;
    private String username;
    private Date createtime;
    private Date updatetime;
}

UserMapper.xml 实现代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
    <select id="getList" resultType="com.example.demo.model.UserInfo">
        select * from userinfo
    </select>
</mapper>

经过以上内容的编写,我们就制作出了一个简单的 Spring Boot 项目了。接下来,我们使用 PostMan 来模拟调用 UserController 接口,执行结果如下:

从上述结果可以看出,时间字段 createtimeupdatetime 的显示方式是很“凌乱”的,并不符合我们的阅读习惯,也不能直接展示给前端的用户使用,这时候,我们就需要对时间进行格式化处理了。 ​

时间格式化的方法总共包含以下 5 种。 ​

1.前端时间格式化

如果后端在公司中拥有绝对的话语权,或者是后端比较强势的情况下,我们可以将时间格式化的这个“锅”强行甩给前端来处理。 ​

为了让这个“锅”甩的更平顺一些(磊哥不做厨师都可惜了),咱们可以给前端工程师提供切实可行的时间格式化方法,实现代码如下。

JS 版时间格式化

function dateFormat(fmt, date) {
    let ret;
    const opt = {
        "Y+": date.getFullYear().toString(),        // 年
        "m+": (date.getMonth() + 1).toString(),     // 月
        "d+": date.getDate().toString(),            // 日
        "H+": date.getHours().toString(),           // 时
        "M+": date.getMinutes().toString(),         // 分
        "S+": date.getSeconds().toString()          // 秒
        // 有其他格式化字符需求可以继续添加,必须转化成字符串
    };
    for (let k in opt) {
        ret = new RegExp("(" + k + ")").exec(fmt);
        if (ret) {
            fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
        };
    };
    return fmt;
}

方法调用:

let date = new Date();
dateFormat("YYYY-mm-dd HH:MM:SS", date);

>>> 2021-07-25 21:45:12

2.SimpleDateFormat格式化

大多数情况下,我们还是需要自力更生,各扫门前雪的,这个时候我们后端程序员就需要发挥自己的特长了,我们提供的第 1 个时间格式化的方法是使用 SimpleDateFormat 来进行时间格式化,它也是 JDK 8 之前重要的时间格式化方法,它的核心实现代码如下:

// 定义时间格式化对象和定义格式化样式
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 格式化时间对象
String date = dateFormat.format(new Date())

接下来我们使用 SimpleDateFormat 来实现一下本项目中的时间格式化,它的实现代码如下:

@RequestMapping("/list")
public List<UserInfo> getList() {
    // 定义时间格式化对象
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    List<UserInfo> list = userMapper.getList();
    // 循环执行时间格式化
    list.forEach(item -> {
        // 使用预留字段 ctime 接收 createtime 格式化的时间(Date->String)
        item.setCtime(dateFormat.format(item.getCreatetime()));
        item.setUtime(dateFormat.format(item.getUpdatetime()));
    });
    return list;
}

程序执行结果如下:

从上述结果可以看出,时间格式化没有任何问题,以及到底我们预想的目的了。但细心的读者会发现,为什么接口的返回字段咋变了呢?(之前的字段是 createtime 现在却是 ctime...) ​

这是因为使用 #SimpleDateFormat.format 方法之后,它返回的是一个 String 类型的结果,而我们之前的 createtimeupdatetime 字段都是 Date 类型的,因此它们是不能接收时间格式化得结果的。 ​

所以此时我们就需要在实体类 UserInfo 新增两个字符串类型的“时间”字段,再将之前 Data 类型的时间字段进行隐藏,最终实体类 UserInfo 的实现代码如下:

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;

import java.util.Date;

@Data
public class UserInfo {
    private int id;
    private String username;
    @JsonIgnore // 输出结果时隐藏此字段
    private Date createtime;
    // 时间格式化后的字段
    private String ctime;
    @JsonIgnore // 输出结果时隐藏此字段
    private Date updatetime;
    // 时间格式化后的字段
    private String utime;
}

我们可以使用 @JsonIgnore 注解将字段进行隐藏,隐藏之后的执行结果如下:

3.DateTimeFormatter格式化

JDK 8 之后,我们可以使用 DateTimeFormatter 来替代 SimpleDateFormat,因为 SimpleDateFormat 是非线程安全的,而 DateTimeFormatter 是线程安全的,所以如果是 JDK 8 以上的项目,尽量使用 DateTimeFormatter 来进行时间格式化。 ​

DateTimeFormatter 格式化的代码和 SimpleDateFormat 类似,具体实现如下:

@RequestMapping("/list")
public List<UserInfo> getList() {
    // 定义时间格式化对象
    DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    List<UserInfo> list = userMapper.getList();
    // 循环执行时间格式化
    list.forEach(item -> {
        // 使用预留字段 ctime 接收 createtime 格式化的时间(Date->String)
        item.setCtime(dateFormat.format(item.getCreatetime()));
        item.setUtime(dateFormat.format(item.getUpdatetime()));
    });
    return list;
}

执行结果如下所示:

DateTimeFormatterSimpleDateFormat 在使用上的区别是 DateTimeFormatter 是用来格式化 JDK 8 提供的时间类型得,如 LocalDateTime,而 SimpleDateFormat 是用来格式化 Date 类型的,所以我们需要对 UserInfoer 实体类做如下的修改:

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;

import java.time.LocalDateTime;

@Data
public class UserInfo {
    private int id;
    private String username;
    @JsonIgnore
    private LocalDateTime createtime;
    private String ctime;
    @JsonIgnore
    private LocalDateTime updatetime;
    private String utime;
}

我们可以使用 LocalDateTime 来接收 MySQL 中的 datetime 类型。 ​

4.全局时间格式化

以上两种后端格式化的实现都有一个致命的缺点,它们在进行时间格式化的时候,都需要对核心业务类做一定的修改,这就相当为了解决一个问题,又引入了一个新的问题,那有没有简单一点、优雅一点的解决方案呢? ​

答案是:有的。我们可以不改任何代码,只需要在配置文件中设置一下就可以实现时间格式化的功能了。 ​

首先,我们找到 Spring Boot 的配置文件 application.properties(或 application.yml),只需要在 application.properties 配置文件中添加以下两行配置:

# 格式化全局时间字段
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
# 指定时间区域类型
spring.jackson.time-zone=GMT+8

这样设置之后,我们将原始的 UserInfo 和 UserController 进行还原。 ​

UserInfo 实现代码如下:

import lombok.Data;
import java.util.Date;

@Data
public class UserInfo {
    private int id;
    private String username;
    private Date createtime;
    private Date updatetime;
}

UserController 实现代码:

@RequestMapping("/list")
public List<UserInfo> getList() {
    return userMapper.getList();
}

然后我们运行程序,看到的执行结果如下:

从以上结果和代码可以看出,我们只需要在程序中简单配置一下,就可以实现所有时间字段的格式化了。 ​

实现原理分析

为什么在配置文件中设置一下,就可以实现所有时间字段的格式化了呢?

# 格式化全局时间字段
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
# 指定时间区域类型
spring.jackson.time-zone=GMT+8

这是因为 Controller 在返回数据时,会自动调用 Spring Boot 框架中内置的 JSON 框架 Jackson,对返回的数据进行统一的 JSON 格式化处理,在处理的过程中它会判断配置文件中是否设置了“spring.jackson.date-format=yyyy-MM-dd HH:mm:ss”,如果设置了,那么 Jackson 框架在对时间类型的字段输出时就会执行时间格式化的处理,这样我们就通过配置来实现全局时间字段的格式化功能了。 ​

为什么要指定时间区域类型“spring.jackson.time-zone=GMT+8”呢? ​

最现实的原因是,如果我们不指定时间区域类型,那么查询出来的时间就会比预期的时间少 8 个小时,这因为我们(中国)所处的时间区域比世界时间少 8 个小时导致的,而当我们设置了时区之后,我们的时间查询才会和预期时间保持一致。

GMT 是什么?

时间区域设置中的“GMT” 是什么意思? ​

Greenwich Mean Time (GMT) 格林尼治时间,也叫做世界时间。 ​

格林尼治时间

格林尼治是英国伦敦南郊原皇家格林尼治天文台所在地,地球本初子午线的标界处,世界计算时间和经度的起点。以其海事历史、作为本初子午线的标准点、以及格林尼治时间以其命名而闻名于世。这里地势险要,风景秀丽,兼具历史和地方风情,也是伦敦在泰晤士河的东方门户。 ​

不光是天文学家使用格林尼治时间,就是在新闻报刊上也经常出现这个名词。我们知道各地都有各地的地方时间。如果对国际上某一重大事情,用地方时间来记录,就会感到复杂不便.而且将来日子一长容易搞错。因此,天文学家就提出一个大家都能接受且又方便的记录方法,那就是以格林尼治的地方时间为标准。 ​

以本初子午线的平子夜起算的平太阳时。又称格林尼治平时或格林尼治时间。各地的地方平时与世界时之差等于该地的地理经度。1960年以前曾作为基本时间计量系统被广泛应用。由于地球自转速率曾被认为是均匀的,因此在1960年以前,世界时被认为是一种均匀时。由于地球自转速度变化的影响,它不是一种均匀的时间系统,它与原子时或力学时都没有任何理论上的关系,只有通过观测才能对它们进行比较。后来世界时先后被历书时和原子时所取代,但在日常生活、天文导航、大地测量和宇宙飞行等方面仍属必需;同时,世界时反映地球自转速率的变化,是地球自转参数之一,仍为天文学和地球物理学的基本资料。

5.部分时间格式化

某些场景下,我们不需要对全局的时间都进行统一的处理,这种情况我们可以使用注解的方式来实现部分时间字段的格式化。 ​

我们需要在实体类 UserInfo 中添加 @JsonFormat 注解,这样就可以实现时间的格式化功能了,实现代码如下:

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.util.Date;

@Data
public class UserInfo {
    private int id;
    private String username;
    // 对 createtime 字段进行格式化处理
    @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss", timezone = "GMT+8")
    private Date createtime;
    private Date updatetime;
}

修改完代码之后,我们运行项目执行结果如下:

​ 从上述结果可以看出,使用注解的方式也可以实现时间的格式化。它的实现原理和第 4 种时间格式化的实现原理类似,都是在返回数据之前,对相应的字段进行时间格式化的处理。

总结

本文我们介绍了 5 种时间格式化的实现方法,其中第 1 种为前端时间格式化的方法,后 4 种为后端格式化的方法,SimpleDateFormatDateTimeFormatter 格式化的方法更适用普通的 Java 项目,其中 SimpleDateFormat 是非线程安全的,而 DateTimeFormatter 是线程安全的,但它们都不是 Spring Boot 项目中最优的时间格式化方案。 ​

如果是 Spring Boot 的项目,推荐使用第 4 种全局时间格式化或第 5 种局部时间格式化的方式,这两种实现方式都无需修改核心业务代码,只需要简单的配置一下,就可以完成时间的格式化功能了。

参考 & 鸣谢

www.jianshu.com/p/49fb78bca621

baike.baidu.com/item/世界时/692237 ​

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • js时间格式化通用方法

    /** * 时间工具类 */ /** * 获取现在的时间戳(精确到s) * 使用:new Date().nowTimestamp * @type {...

    Java中文社群-磊哥
  • moment格式化时间使用方法

    越陌度阡
  • 3种 Springboot 全局时间格式化方式,别再写重复代码了

    时间格式化在项目中使用频率是非常高的,当我们的 API 接口返回结果,需要对其中某一个 date 字段属性进行特殊的格式化处理,通常会用到 SimpleDate...

    程序员内点事
  • 3种 Springboot 全局时间格式化方式,别再写重复代码了

    时间格式化在项目中使用频率是非常高的,当我们的 API 接口返回结果,需要对其中某一个 date 字段属性进行特殊的格式化处理,通常会用到 SimpleDate...

    程序员内点事
  • Python 中日期和时间格式化输出的方法

    python 格式化日期时间的函数为 datetime.datetime.strftime(); 由字符串转为日期型的函数为:datetime.datetim...

    王图思睿
  • SpringBoot 中实现跨域的5种方式

    出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能...

    程序员白楠楠
  • liunx 时间函数与时间格式与字符串之间的转化方法

    time_t类型,这本质上是一个长整数( long ),表示从1970-01-01 00:00:00到目前计时时间的秒数,timeval则精确到毫秒

    砸漏
  • Python 中格式化字符串 % 和 format 两种方法之间的区别

      Python2.6引入了 format 格式化字符串的方法,现在格式化字符串有两种方法,就是 % 和 format ,具体这两种方法有什么区别呢?请看以下解...

    新人小试
  • Golang中的格式化时间输出 顶

    通常我们在程序处理过程需要对时间进行一定格式化的输出,以便让用户或者其他程序能够更加友好的识别时间,本篇记录下在使用golang语言过程中如何格式化时间。 通常...

    BGBiao
  • 格式化字符串的几种方法

    TalkPython
  • java中各种时间格式的转化(包含java多数情况下时间的转换)

    gfu
  • django rest framework serializer返回时间自动格式化方法

    补充知识:Django 解决不修改models的情况下,通过修改serializers, 返回自己想要的字段

    砸漏
  • javascript中日期格式与时间戳之间的转化

    日期格式与时间戳之间的转化 一:日期格式转化为时间戳 function timeTodate(date) { var new_str = date.re...

    猿人谷
  • python的三种字符串格式化方法

            刚入门python的同学,特别是,没有系统的学习过python,而是学过别的语言,直接上手python的同学,怕是还不是很了解python强大的...

    钱塘小甲子
  • Jsp中格式化时间戳的常用标签

      首先关于时间格式化的问题,前不久在公司开发一套数据录入系统,这其中就无时不刻不从数据库拿数据,然后进过一系列处理最后渲染页面,刚开始我习惯用jstl的标签库...

    赵小忠
  • 一日一技:推特时间的格式化方案

    实际上,我们有更简单的办法来处理。Tue Dec 24 10:51:15 +0000 2019对应的时间格式为%a %b %d %H:%M:%S %z %Y;对...

    青南
  • jdk8获取当前时间|时间加减|java8时间格式化|时间处理工具|时间比较|线程安全的时间处理方法

    在很久之前,我总结了一些jdk7版本之前的关于时间处理的一些公共方法,日期转换成字符串、指定时间加上指定天数后的日期、获取上周周一时间 等等;具体的可以戳链接查...

    小小鱼儿小小林
  • 7种方式玩转信息可视化中的时间线设计

    大数据文摘
  • 在云中应用自动化的5种方法

    利用自动化工具可以确保组织的云计算资源立即扩展以适应需求和服务器负载。无论组织是使用单个云平台还是使用混合云或多云环境,自动扩展也可以通过只在实际需要时添加资源...

    静一

扫码关注云+社区

领取腾讯云代金券