Skip to content

Spring Boot 全局日期时间格式配置:让数据转换更优雅 🕐

为什么需要全局日期时间格式配置?

在 Spring Boot 应用开发中,我们经常遇到这样的场景:前端传递的日期字符串需要转换为后端的 Date 或 LocalDateTime 对象,或者后端的日期对象需要格式化为特定格式的字符串返回给前端。

IMPORTANT

默认情况下,Spring 使用 DateFormat.SHORT 样式来处理未标注 @DateTimeFormat 注解的日期时间字段,这可能不符合我们的业务需求。

传统方式的痛点 😵

kotlin
data class UserRequest(
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    val birthDate: LocalDate,
    
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    val createTime: LocalDateTime,
    
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    val updateDate: LocalDate
    // 每个日期字段都需要重复添加注解... 😰
)
kotlin
data class UserRequest(
    val birthDate: LocalDate,        // 自动使用全局格式
    val createTime: LocalDateTime,   // 自动使用全局格式  
    val updateDate: LocalDate        // 自动使用全局格式
    // 无需重复注解,代码更简洁 ✨
)

核心原理:自定义格式转换服务

Spring 的日期时间格式化基于 FormattingConversionService,它负责在不同数据类型之间进行转换。通过自定义这个服务,我们可以:

  1. 禁用默认格式化器:避免使用 Spring 的默认日期格式
  2. 注册自定义格式化器:定义我们想要的全局日期时间格式
  3. 保持其他功能:确保 @NumberFormat 等其他注解仍然有效

实战配置:打造你的全局日期格式

基础配置实现

kotlin
@Configuration
class DateTimeConfiguration {

    @Bean
    fun conversionService(): FormattingConversionService {
        // 创建格式化转换服务,但不注册默认格式化器
        return DefaultFormattingConversionService(false).apply {
            
            // 确保 @NumberFormat 注解仍然有效
            addFormatterForFieldAnnotation(NumberFormatAnnotationFormatterFactory())

            // 配置 JSR-310 日期时间格式 (LocalDate, LocalDateTime 等)
            val dateTimeRegistrar = DateTimeFormatterRegistrar()
            dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
            dateTimeRegistrar.registerFormatters(this)

            // 配置传统 Date 类型格式
            val dateRegistrar = DateFormatterRegistrar()
            dateRegistrar.setFormatter(DateFormatter("yyyyMMdd"))
            dateRegistrar.registerFormatters(this)
        }
    }
}

TIP

这里我们使用了 yyyyMMdd 格式作为示例,你可以根据实际业务需求调整为其他格式,如 yyyy-MM-ddyyyy/MM/dd HH:mm:ss

进阶配置:支持多种日期格式

在实际项目中,我们可能需要支持多种日期时间格式:

kotlin
@Configuration
class AdvancedDateTimeConfiguration {

    @Bean
    fun conversionService(): FormattingConversionService {
        return DefaultFormattingConversionService(false).apply {
            
            // 保持数字格式注解支持
            addFormatterForFieldAnnotation(NumberFormatAnnotationFormatterFactory())
            
            // 配置多种日期时间格式
            configureDateTime(this)
            configureLegacyDate(this)
        }
    }
    
    private fun configureDateTime(service: DefaultFormattingConversionService) {
        val registrar = DateTimeFormatterRegistrar()
        
        // 设置不同类型的默认格式
        registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd"))           // LocalDate
        registrar.setTimeFormatter(DateTimeFormatter.ofPattern("HH:mm:ss"))            // LocalTime  
        registrar.setDateTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) // LocalDateTime
        
        registrar.registerFormatters(service)
    }
    
    private fun configureLegacyDate(service: DefaultFormattingConversionService) {
        val registrar = DateFormatterRegistrar()
        registrar.setFormatter(DateFormatter("yyyy-MM-dd HH:mm:ss")) // 传统 Date 类型
        registrar.registerFormatters(service)
    }
}

实际应用场景

场景一:用户注册接口

kotlin
@RestController
@RequestMapping("/api/users")
class UserController {

    @PostMapping
    fun createUser(@RequestBody request: CreateUserRequest): ResponseEntity<User> {
        // 日期字符串会自动转换为 LocalDate 对象 ✨
        val user = User(
            name = request.name,
            birthDate = request.birthDate,  // 自动转换!
            createTime = LocalDateTime.now()
        )
        
        return ResponseEntity.ok(userService.save(user))
    }
}

data class CreateUserRequest(
    val name: String,
    val birthDate: LocalDate,        // 前端传 "1990-05-15",自动转换
    val email: String
)

场景二:数据查询接口

kotlin
@RestController
@RequestMapping("/api/reports")
class ReportController {

    @GetMapping
    fun getReport(
        @RequestParam startDate: LocalDate,    // 自动解析 "2024-01-01"
        @RequestParam endDate: LocalDate       // 自动解析 "2024-12-31"
    ): ReportData {
        return reportService.generateReport(startDate, endDate)
    }
}

NOTE

配置全局格式后,所有的日期时间字段都会使用统一的格式进行转换,大大减少了重复代码。

常见问题与解决方案

问题1:特殊字段需要不同格式怎么办?

WARNING

全局配置不会覆盖已有的 @DateTimeFormat 注解。如果某个字段需要特殊格式,仍然可以使用注解。

kotlin
data class SpecialRequest(
    val normalDate: LocalDate,                                    // 使用全局格式
    
    @DateTimeFormat(pattern = "dd/MM/yyyy")                      // 使用特殊格式
    val specialDate: LocalDate,
    
    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")       // JSON 序列化格式
    val timestamp: LocalDateTime
)

问题2:Web 应用中的额外考虑

IMPORTANT

在 Web 应用中配置日期时间格式时,还需要考虑 WebMVC 或 WebFlux 的转换和格式化配置。

Web 应用完整配置示例
kotlin
@Configuration
@EnableWebMvc
class WebMvcConfiguration : WebMvcConfigurer {
    
    override fun addFormatters(registry: FormatterRegistry) {
        // 添加自定义格式化器
        val dateTimeRegistrar = DateTimeFormatterRegistrar()
        dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
        dateTimeRegistrar.setDateTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
        dateTimeRegistrar.registerFormatters(registry)
    }
}

问题3:JSON 序列化格式不一致

kotlin
@Configuration
class JacksonConfiguration {
    
    @Bean
    @Primary
    fun objectMapper(): ObjectMapper {
        return ObjectMapper().apply {
            registerModule(JavaTimeModule())
            
            // 配置 JSON 序列化的日期格式
            dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
            
            // 配置 LocalDateTime 的序列化格式
            val module = SimpleModule()
            module.addSerializer(LocalDateTime::class.java, 
                LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
            registerModule(module)
        }
    }
}

最佳实践建议 🎯

推荐的配置策略

  1. 统一团队标准:在项目开始时就确定日期时间格式标准
  2. 前后端协调:确保前端和后端使用相同的日期格式约定
  3. 文档记录:在 API 文档中明确说明日期时间格式要求
  4. 测试覆盖:编写单元测试验证日期格式转换的正确性

推荐的格式选择

场景推荐格式示例说明
日期yyyy-MM-dd2024-03-15ISO 8601 标准,易读性好
时间HH:mm:ss14:30:2524小时制,精确到秒
日期时间yyyy-MM-dd HH:mm:ss2024-03-15 14:30:25完整的日期时间
紧凑格式yyyyMMdd20240315适合文件名或ID

总结

通过配置全局日期时间格式,我们可以:

简化代码:减少重复的 @DateTimeFormat 注解
统一标准:确保整个应用使用一致的日期格式
提高维护性:格式变更时只需修改一处配置
增强可读性:代码更加简洁,意图更加明确

CAUTION

记住,全局配置会影响整个应用的日期时间处理,在修改时要充分测试,确保不会破坏现有功能。

现在你已经掌握了 Spring Boot 全局日期时间格式配置的精髓,赶快在你的项目中应用这些技巧,让日期时间处理变得更加优雅吧! 🚀