Can't convert the date-like value to string because it isn't known if it's a date (no time part), ti...

博客主要围绕FreeMarker报错展开,指出报错原因是不能将Date类型直接转化为String类型,给出了解决办法,即在日期类型后添加特定内容,如将“生日:${student.studentBirthday}”修改为“生日:${(student.studentBirthday)?string(\yyyy-MM-dd\)}”。

报错

ERROR - 2021-05-17 15:35:22 [http-nio-8080-exec-8]freemarker.log._Log4jLoggerFactory$Log4jLogger.error(_Log4jLoggerFactory.java:59) -- Error executing FreeMarker template
FreeMarker template error:
Can't convert the date-like value to string because it isn't known if it's a date (no time part), time or date-time value.
The blamed expression:
==> brand.modifyDate  [in template "admin/twfw/brand/list.ftl" at line 297, column 43]

----
Tip: Use ?date, ?time, or ?datetime to tell FreeMarker the exact type.
----
Tip: If you need a particular format only once, use ?string(pattern), like ?string('dd.MM.yyyy HH:mm:ss'), to specify which fields to display. 
----

----
FTL stack trace ("~" means nesting-related):
    - Failed at: ${brand.modifyDate}  [in template "admin/twfw/brand/list.ftl" at line 297, column 41]
----

Java stack trace (for programmers):
----
freemarker.core._TemplateModelException: [... Exception message was already printed; see it above ...]
    at freemarker.core._MessageUtil.newCantFormatUnknownTypeDateException(_MessageUtil.java:301)
    at freemarker.core.Environment.getTemplateDateFormat(Environment.java:1660)
    at freemarker.core.Environment.getTemplateDateFormat(Environment.java:1645)
    at freemarker.core.EvalUtil.coerceModelToStringOrMarkup(EvalUtil.java:380)
    at freemarker.core.EvalUtil.coerceModelToStringOrMarkup(EvalUtil.java:358)
    at freemarker.core.DollarVariable.calculateInterpolatedStringOrMarkup(DollarVariable.java:100)
    at freemarker.core.DollarVariable.accept(DollarVariable.java:63)
    at freemarker.core.Environment.visit(Environment.java:366)
    at freemarker.core.IteratorBlock$IterationContext.executedNestedContentForCollOrSeqListing(IteratorBlock.java:291)
    at freemarker.core.IteratorBlock$IterationContext.executeNestedContent(IteratorBlock.java:271)
    at freemarker.core.IteratorBlock$IterationContext.accept(IteratorBlock.java:242)
    at freemarker.core.Environment.visitIteratorBlock(Environment.java:642)
    at freemarker.core.IteratorBlock.acceptWithResult(IteratorBlock.java:107)
    at freemarker.core.IteratorBlock.accept(IteratorBlock.java:93)
    at freemarker.core.Environment.visit(Environment.java:330)
    at freemarker.core.Environment.visit(Environment.java:336)
    at freemarker.core.Environment.process(Environment.java:309)
    at freemarker.template.Template.process(Template.java:384)
    at org.springframework.web.servlet.view.freemarker.FreeMarkerView.processTemplate(FreeMarkerView.java:368)
    at org.springframework.web.servlet.view.freemarker.FreeMarkerView.doRender(FreeMarkerView.java:285)
    at org.springframework.web.servlet.view.freemarker.FreeMarkerView.renderMergedTemplateModel(FreeMarkerView.java:235)
    at org.springframework.web.servlet.view.AbstractTemplateView.renderMergedOutputModel(AbstractTemplateView.java:167)
    at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:304)
    at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1286)
    at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1041)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:984)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:61)
    at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
    at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
    at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
    at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
    at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
    at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
    at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
    at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
    at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:450)
    at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365)
    at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
    at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
    at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:387)
    at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362)
    at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:347)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:263)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at net.shopxx.filter.SiteStatusFilter.doFilterInternal(SiteStatusFilter.java:50)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:347)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:263)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:650)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:800)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:806)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1498)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)
Caused by: freemarker.core.UnknownDateTypeFormattingUnsupportedException: Can't convert the date-like value to string because it isn't known if it's a date (no time part), time or date-time value.
    at freemarker.core.Environment.getTemplateDateFormat(Environment.java:1722)
    at freemarker.core.Environment.getTemplateDateFormat(Environment.java:1492)
    at freemarker.core.Environment.getTemplateDateFormat(Environment.java:1658)
    ... 82 more

原因

不能将Date类型直接转化为String类型

解决

将日期类型后面添加如下:

     原来:生日:${student.studentBirthday}

     修改后:生日:${(student.studentBirthday)?string("yyyy-MM-dd")}

作者:彼岸舞

时间:2021\05\17

内容关于:FreeMarker

本文属于作者原创,未经允许,禁止转发

<think>这个错误通常发生在FreeMarker中,当你尝试对一个日期类型(可能是`java.util.Date`、`java.time.LocalDate`、`java.time.LocalTime`等)进行格式化输出时,FreeMarker无法确定这个日期值是只包含日期部分、只包含时间部分还是同时包含日期和时间。 在FreeMarker中,日期类型有三种: - 日期(只有年、月、日,如`java.time.LocalDate`) - 时间(只有时、分、秒,如`java.time.LocalTime`) - 日期时间(包含日期和时间,如`java.time.LocalDateTime`) 当你使用`?string("format")`进行格式化时,FreeMarker需要知道这个值的确切类型(是日期、时间还是日期时间),因为不同的类型在格式化时使用的模式符号可能不同(虽然模式字符串相同,但FreeMarker内部需要知道如何解释这个值)。 错误信息指出,你尝试格式化的值是一个“date-like”值(即可能是日期、时间或日期时间中的一种),但FreeMarker无法确定它具体是哪种类型。这通常发生在使用`java.util.Date`类型时,因为`java.util.Date`实际上包含了日期和时间(精确到毫秒),但FreeMarker在早期版本中将其视为“未知”的日期时间类型(在2.3.23版本之前,FreeMarker对`java.util.Date`的处理是将其视为日期时间,但有时由于时区问题可能导致混淆)。另外,如果你使用的是Java8的日期时间类型,并且FreeMarker版本低于2.3.23,则可能无法正确识别这些类型。 解决方案: 1. **明确指定日期值的类型**(如果FreeMarker无法自动识别) 使用`?date`、`?time`或`?datetime`来明确告诉FreeMarker这个值的类型。 例如,如果你有一个`java.util.Date`对象(它包含日期和时间),你应该使用`?datetime`: ```ftl ${myDate?datetime?string("yyyy-MM-dd HH:mm:ss")} ``` 如果这个值是一个只有日期的值(比如`java.time.LocalDate`),则使用`?date`: ```ftl ${myLocalDate?date?string("yyyy-MM-dd")} ``` 如果只有时间(比如`java.time.LocalTime`),则使用`?time`: ```ftl ${myLocalTime?time?string("HH:mm:ss")} ``` 2. **检查FreeMarker版本** 如果你使用的是Java8的日期时间类型(`java.time`包),请确保FreeMarker版本在2.3.23及以上。因为2.3.23版本开始支持Java8的日期时间API。 3. **在模板中设置默认的日期格式** 你可以在模板开头设置默认的日期、时间和日期时间格式,这样在格式化时就不需要每次都指定类型(但注意,这只能解决输出格式,不能解决类型识别问题): ```ftl <#setting date_format="yyyy-MM-dd"> <#setting time_format="HH:mm:ss"> <#setting datetime_format="yyyy-MM-dd HH:mm:ss"> ``` 然后,你可以直接使用`?string`而不指定格式(使用默认格式): ```ftl ${myDate?string} <#-- 使用默认的日期时间格式 --> ``` 或者,如果你仍然想自定义格式,可以结合类型转换: ```ftl ${myDate?datetime?string("yyyy/MM/dd HH:mm")} ``` 4. **后端传递数据时确保类型明确** 如果可能,尽量传递具有明确类型的日期对象。例如,使用`java.time.LocalDateTime`代替`java.util.Date`,这样FreeMarker(2.3.23+)就能自动识别为日期时间类型。 5. **处理可能为null的情况** 为了避免null导致的错误,使用空值处理: ```ftl ${(myDate?datetime?string("yyyy-MM-dd HH:mm:ss"))!''} ``` 示例代码(假设后端传递了一个`java.util.Date`对象): FTL模板: ```ftl <#-- 明确指定为日期时间类型 --> The current datetime: ${myDate?datetime?string("yyyy-MM-dd HH:mm:ss")} ``` 如果错误仍然存在,请检查FreeMarker的版本,并确保传递的对象确实是日期类型。 注意:在FreeMarker 2.3.23及以上版本,对于`java.time`的日期类型,通常不需要使用`?date`、`?time`或`?datetime`转换,因为FreeMarker能自动识别。但`java.util.Date`仍然需要转换。 因此,对于`java.util.Date`,最安全的做法是始终使用`?datetime`进行转换。 如果问题中的日期值实际上是`java.sql.Date`(它继承自`java.util.Date`,但通常只表示日期部分),那么你应该将其视为日期(只有日期部分): ```ftl ${mySqlDate?date?string("yyyy-MM-dd")} ``` 总结:根据你的日期对象的实际内容,选择正确的类型转换(`?date`, `?time`, `?datetime`)来消除FreeMarker的困惑。 §§相关问题§§: 1. FreeMarker 中如何将字符串转换为日期对象? 2. 在 FreeMarker 中如何比较两个日期的大小? 3. FreeMarker 如何处理时区转换? 4. 如何避免 FreeMarker 日期格式化中的空指针异常?</think>### 问题分析 错误信息 `Can't convert the date-like value to string...` 通常发生在以下场景: 1. **日期类型不明确**:FreeMarker 无法确定传入的是纯日期(`LocalDate`)、纯时间(`LocalTime`)还是完整时间戳(`LocalDateTime`) 2. **旧版本兼容问题**:FreeMarker < 2.3.23 对 Java 8 时间类型支持不完善 3. **类型混淆**:后端传递的日期对象类型与模板预期不符 ### 解决方案 #### 1. 明确指定日期类型(推荐) 在格式化前使用 `?datetime` 明确类型: ```ftl <#-- 明确指定为日期时间类型 --> ${myDate?datetime?string("yyyy-MM-dd HH:mm:ss")} <#-- 安全处理 null 值 --> ${(myDate?datetime?string("yyyy-MM-dd HH:mm:ss"))!"N/A"} ``` #### 2. 后端统一使用 `java.time` 类型(Java 8+) ```java // Controller 中传递明确类型 model.addAttribute("orderTime", LocalDateTime.now()); // 完整时间戳 model.addAttribute("birthday", LocalDate.now()); // 纯日期 ``` #### 3. 全局配置日期类型(`freemarker.yml`) ```yaml freemarker: settings: datetime_format: "yyyy-MM-dd HH:mm:ss" date_format: "yyyy-MM-dd" time_format: "HH:mm:ss" ``` #### 4. 类型强制转换 ```ftl <#-- 将未知类型强制转为日期时间 --> ${myAmbiguousDate?string?datetime?string("yyyy-MM-dd HH:mm:ss")} ``` ### 根本原因解释 1. **类型歧义** FreeMarker 需要区分三种日期类型: - `date` (仅日期,如 `2023-01-01`) - `time` (仅时间,如 `14:30:00`) - `datetime` (完整时间戳) 当遇到 `java.util.Date` 等旧类型时,FreeMarker 无法自动判断类型归属 2. **版本兼容性** - FreeMarker ≥ 2.3.23 完美支持 `java.time.*` 类型 - 旧版本需要类型强制转换 3. **空值处理缺陷** 直接访问可能为 `null` 的日期变量会中断模板渲染 ### 完整示例 ```java // Spring Boot 控制器 @Controller public class ReportController { @GetMapping("/invoice") public String getInvoice(Model model) { // 明确传递类型化的日期对象 model.addAttribute("invoiceDate", LocalDateTime.now()); model.addAttribute("dueDate", LocalDate.now().plusDays(30)); return "invoice-template"; } } ``` ```ftl <!-- invoice-template.ftl --> <#setting datetime_format="yyyy-MM-dd HH:mm:ss"> 发票信息: - 开票时间: ${invoiceDate?datetime?string(datetime_format)} - 到期日期: ${dueDate?date?string("yyyy-MM-dd")} <#-- 处理可能不存在的支付时间 --> - 支付时间: ${(paymentTime?datetime?string(datetime_format))!"未支付"} ``` ### 错误预防技巧 1. **类型统一**:后端始终传递 `java.time` 类型对象 2. **模板防御**: ```ftl <#-- 安全操作链 --> ${(myDate?datetime?string("yyyy-MM-dd HH:mm:ss"))!''} ``` 3. **版本检查**:确保 FreeMarker ≥ 2.3.23 ```xml <!-- Maven 依赖 --> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.31</version> </dependency> ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值