如何使 LocalDateTime.parse 拒绝 24:00 时间

How to make LocalDateTime.parse reject 24:00 time

提问人:Algimantas 提问时间:8/28/2023 最后编辑:Algimantas 更新时间:9/3/2023 访问量:126

问:

我有一个表单,用户可以从键盘输入日期和时间,当输入的时间部分为 24:00 时,LocalDateTime 接受输入,将其转换为第二天。

我的代码

import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

val INPUT_DATETIME_PATTERN = "yyyyMMddHHmm"
val formatter = DateTimeFormatter.ofPattern(INPUT_DATETIME_PATTERN)
    
val datetime = "202308102400"

val localdatetime = LocalDateTime.parse(datetime, formatter)
println(datetime)
println(localdatetime.format(formatter))

预期结果:
LocalDateTime.parse 引发异常。
根据文档,HH 代表一天中的小时数 (0-23),因此 2400 无效。输入字符串202308102401也会引发异常。

实际结果:
打印202308110000。在 Android Studio 和 Kotlin playground 中签入了划痕(链接
)

Kotlin java-time datetimeformatter

评论

1赞 Marco F. 8/28/2023
这能以某种方式帮助吗?docs.oracle.com/javase/8/docs/api/java/time/format/......
0赞 Algimantas 8/29/2023
天哪,我感觉这是可以通过仔细阅读文档来解决的问题之一。谢谢!

答:

-2赞 firapinch 8/29/2023 #1
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

val INPUT_DATETIME_PATTERN = "yyyyMMddHHmm"
val formatter = DateTimeFormatter.ofPattern(INPUT_DATETIME_PATTERN)

val datetime = "202308102400"
val adjustedDatetime = datetime.replaceRange(8..9, "00")

val localdatetime = LocalDateTime.parse(adjustedDatetime, formatter)
println(datetime)
println(localdatetime.format(formatter))

此代码在分析日期时间字符串之前将时间部分从“2400”调整为“0000”,并生成预期的输出

评论

0赞 Algimantas 8/29/2023
我通过检查符号 8-9 是否等于 24 来获得我的预期行为,但这仍然是一个额外的检查,似乎应该由 parse() 在内部完成。
0赞 Algimantas 8/29/2023
尤其是当接受与我的格式化程序相同的范围的 LocalDateTime.of() 拒绝时间 24:00:00 时。
0赞 firapinch 8/29/2023
你能更详细地解释一下吗?
0赞 Algimantas 8/29/2023
我想出了一个解决方案并将其作为答案发布:stackoverflow.com/a/76998745/7119522
3赞 Basil Bourque 8/29/2023 #2

(在 Java 语法中,而不是 Kotlin)

TL的;博士

若要拒绝此类输入,请指定严格的解析器样式。

DateTimeFormatter
    .ofPattern( "uuuuMMddHHmm" )
    .withResolverStyle( ResolverStyle.STRICT )

当输入的时间部分为 24:00 时,LocalDateTime 接受输入,并将其转换为第二天

是的,它应该这样做。您看到的是一个功能,而不是一个错误

在 00-23 时钟上使用 24:00 表示“今天结束”,也是“明天开始”。所以从逻辑上讲:

( 202308102400 = 202308110000 ) -> true

此特定行为已记录在案

24:00 确实意味着第二天

24:00 表示第二天的第一刻。相反,00:00 表示指定日期的第一个时刻。

从概念上讲,每天有两个午夜,一个在一天的开始,一个在一天的结束。为了避免这种歧义,请注意 java.time 类避免了“午夜”这个术语和概念。相反,java.time 类关注的是一天中的第一个时刻

一些行业通过使用 & 来区分两个午夜。他们使用符号作为“在今天一天结束时”的一种方式。00:0024:0024:00

因此,我们看到这与 .今天一天的结束也是明天一天的开始。202308102400202308110000

因此,的行为在语义上是正确的,将第二天的日期以及一天中的时间 00:00 返回给您。LocalDateTime

请参阅以下代码,在 Ideone.com

    final DateTimeFormatter BASIC_ISO_LOCAL_DATE_TIME = DateTimeFormatter.ofPattern( "uuuuMMddHHmm" ) ;
    String input = "202308102400" ; 
    try {
        LocalDateTime ldt = LocalDateTime.parse ( input , BASIC_ISO_LOCAL_DATE_TIME ) ;
        System.out.println( "ldt.toString() = " + ldt ) ;
    } catch ( DateTimeParseException e ) {
        System.out.println( "Yuck. Your input is distasteful. " ) ;
    }

ldt.toString() = 2023-08-11T00:00

检查输入String#endsWith

如果您确定应该拒绝时间,您可以简单地检查字符串输入是否存在 at end24:002400

if ( input.endsWith( "2400" ) { … }
else { LocalDateTime ldt = LocalDateTime.parse( input ) ; }

Resolver.STRICT

正如您最终发现的那样,若要使 DateTimeFormatter 对象拒绝时间为 24:00 的输入,请将 ResolverStyle 设置为枚举对象 STRICT。严格模式只能容忍 00-23 小时。

    final DateTimeFormatter STRICT_BASIC_ISO_LOCAL_DATE_TIME = 
        DateTimeFormatter
            .ofPattern( "uuuuMMddHHmm" )
            .withResolverStyle( ResolverStyle.STRICT ) ;  // <— Reject 2400 as time of day. 

捕获 DateTimeParseException 以处理此类不受欢迎的输入。

    String input = "202308102400" ; 
    try {
        LocalDateTime ldt = LocalDateTime.parse ( input , STRICT_BASIC_ISO_LOCAL_DATE_TIME ) ;
        System.out.println( "ldt.toString() = " + ldt ) ;
    } catch ( DateTimeParseException e ) {
        System.out.println( "Yuck. Your input is distasteful. " ) ;
    }

评论

1赞 Algimantas 8/29/2023
是的,我认为这可能是我不想要的功能,并且想要一种方法来关闭它。默认情况下,DateTimeFormater 是使用 ResolverStyle.SMART 创建的,而我需要 ResolverStyle.STRICT。
0赞 Arvind Kumar Avinash 8/30/2023
一个经过充分研究的答案!答案中的所有链接都很有价值。
4赞 Algimantas 8/29/2023 #3

事实证明,没有足够注意阅读文档是我的错......感谢Marco F.指出我错过了什么。

DateTimeResolver默认情况下,它对每个字段执行合理的默认值,例如将 24:00 解释为“第二天的第一个时刻”,或者允许当月值最多 31,静默转换为实际有效日期值。ResolverStyle.SMART

正确的代码以获取预期行为:

val INPUT_DATETIME_PATTERN = "uuuuMMddHHmm"
val formatter = DateTimeFormatter
    .ofPattern(INPUT_DATETIME_PATTERN)
    .withResolverStyle(ResolverStyle.STRICT)

val datetime = "202308102400"

val localdatetime = LocalDateTime.parse(datetime, formatter)
println(datetime)
println(localdatetime.format(formatter))

另请注意,年份说明符从 yyyy(纪元年)更改为 uuuu(年),因为严格解析器需要一个纪元与 YearOfEra()一起使用。

1赞 Michael Gantman 8/29/2023 #4

将您的行

更改为
“查看此问题以获取更多信息”:使用新的 Java 8 DateTimeFormatter 执行严格的日期解析 此外,在您的格式中,将年份部分从“”更改为“”。没有这个,它就行不通。
val formatter = DateTimeFormatter.ofPattern(INPUT_DATETIME_PATTERN)val formatter = DateTimeFormatter.ofPattern(INPUT_DATETIME_PATTERN).withResolverStyle(ResolverStyle.STRICT)yyyyuuuu

评论

0赞 Michael Gantman 8/29/2023
@OleV.V.你是对的,一旦我将“yyyy”部分切换到“uuuu”,它就起作用了。更新答案