标题描述了我的问题.我现在觉得自己真的很蠢,因为这可能是一个很小的问题.

我已经被困在这件事上快两天了.我正在构建的应用程序要大得多,它使用docker compose来启动postgres容器,但这个问题可以通过创建一个新的(Java 21,Maven)Spring启动项目来重现,该项目只依赖于Spring web.在带有@SpringBootApplication的类中,添加以下内容:

@Bean
ApplicationRunner applicationRunner() {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("hh:mm a");
        return args -> {
            System.out.println("Parsed: 12:30 pm -> " + LocalTime.parse("12:30 p.m.", formatter));
        };
}

当我使用IntelliJ或./mvnw spring-boot:run运行它时,或者在普通的Java类中(没有注释),它就会运行.

但是,当我使用这个docker文件时:

FROM eclipse-temurin:21-jdk-alpine
COPY . /app/
WORKDIR /app
CMD ["./mvnw", "spring-boot:run", "-Dskiptests"]

docker build -t localtime:1.0 .构建,然后用:docker run -p 1234:8080 --name localtime-container localtime:1.0运行.以下是我在定期发布 spring 启动日志(log)消息后得到的信息:

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2024-02-20T06:08:00.097Z ERROR 71 --- [           main] o.s.boot.SpringApplication               : Application run failed

java.time.format.DateTimeParseException: Text '12:30 p.m.' could not be parsed at index 6
        at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2108) ~[na:na]
        at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:2010) ~[na:na]
        at java.base/java.time.LocalTime.parse(LocalTime.java:473) ~[na:na]
        at org.example.localtimedemo.LocaltimeDemoApplication.lambda$applicationRunner$0(LocaltimeDemoApplication.java:22) ~[classes/:na]
        at org.springframework.boot.SpringApplication.lambda$callRunner$4(SpringApplication.java:786) ~[spring-boot-3.2.2.jar:3.2.2]
        at org.springframework.util.function.ThrowingConsumer$1.acceptWithException(ThrowingConsumer.java:83) ~[spring-core-6.1.3.jar:6.1.3]
        at org.springframework.util.function.ThrowingConsumer.accept(ThrowingConsumer.java:60) ~[spring-core-6.1.3.jar:6.1.3]
        at org.springframework.util.function.ThrowingConsumer$1.accept(ThrowingConsumer.java:88) ~[spring-core-6.1.3.jar:6.1.3]
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:798) ~[spring-boot-3.2.2.jar:3.2.2]
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:786) ~[spring-boot-3.2.2.jar:3.2.2]
        at org.springframework.boot.SpringApplication.lambda$callRunners$3(SpringApplication.java:774) ~[spring-boot-3.2.2.jar:3.2.2]
        at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) ~[na:na]
        at java.base/java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:357) ~[na:na]
        at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:510) ~[na:na]
        at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
        at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) ~[na:na]
        at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) ~[na:na]
        at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
        at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) ~[na:na]
        at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:774) ~[spring-boot-3.2.2.jar:3.2.2]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:341) ~[spring-boot-3.2.2.jar:3.2.2]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1354) ~[spring-boot-3.2.2.jar:3.2.2]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) ~[spring-boot-3.2.2.jar:3.2.2]
        at org.example.localtimedemo.LocaltimeDemoApplication.main(LocaltimeDemoApplication.java:15) ~[classes/:na]

[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  26.863 s
[INFO] Finished at: 2024-02-20T06:08:00Z
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:3.2.2:run (default-cli) on project localtime-demo: Process terminated with exit code: 1 -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException

但是,当我取消对sout语句的注释时,它运行得很好.

我试过使用不同的Dockerfiles,同样的问题.

有人知道这是怎么回事吗?知道怎么解决吗?

谢谢

推荐答案

TL;DR

DateTimeFormatter
    .ofPattern( "hh:mm a" ) 
    .withLocale( Locale.CANADA ) 

本地指定本地指定

白天/夜晚的指标是localization的问题.有些地区使用AM/PM.有些人使用am/pm.有些人使用上午/还有一些人用"我不知道"

您的代码忽略了指定一个Locale对象.因此,您的DateTimeFormatter隐含地依赖于JVM的当前默认区域设置.

我敢打赌,你自己机器上JVM的当前默认语言环境与你的Docker环境不同.

最好的解决方案是设置为avoid needlessly relying on the JVM’s current default locale.👉🏽将明确规定.(顺便说一句,时区也是如此.)

要指定区域设置,请调用DateTimeFormatter#withLocale.请记住,java.time使用immutable objects.因此,withLocale方法生成并返回一个新的DateTimeFormatter对象,而不是改变("变异")原始对象.

Locale locale = Locale.US ;
DateTimeFormatter f = 
    DateTimeFormatter
        .ofPattern( … ) 
        .withLocale( locale ) ;
String output = myLocalTime.format( f ) ;

这只是一个例子.您需要自己确定符合您期望的适当区域设置.

地方勘探

如果您想了解哪些地区使用哪些昼夜指示器,可以编写一些代码来循环您的JDK中提供的所有地区.

final LocalTime time = LocalTime.of ( 12 , 30 );
final DateTimeFormatter f = DateTimeFormatter.ofPattern ( "hh:mm a" );
Locale
        .availableLocales ( )
        .forEach ( ( Locale locale ) ->
                System.out.println (
                        time.format ( f.withLocale ( locale ) )
                                .concat ( " | " )
                                .concat ( locale.getDisplayName ( Locale.US ) )
                                .concat ( " | " )
                                .concat ( locale.toString ( ) )
                )
        );

在Azul Systems的Java 21上运行时:

12:30 PM |  | 
12:30 אחה״צ | Hebrew | he
12:30 หลังเที่ยง | Thai (Thai, Thailand) | th_TH_#Thai
12:30 PM | Low German | nds
12:30 günortadan soň | Turkmen (Latin, Turkmenistan) | tk_TM_#Latn
12:30 ድ.ቀ. | Tigrinya (Ethiopia) | ti_ET
12:30 பிற்பகல் | Tamil (Singapore) | ta_SG
12:30 pēcpusdienā | Latvian | lv
12:30 pm | English (Niue) | en_NU
12:30 下午 | Chinese (Simplified, Singapore) | zh_SG_#Hans
12:30 𞤇𞤎 | Fula (Adlam, Liberia) | ff_LR_#Adlm
12:30 pm | English (Jamaica) | en_JM
12:30 PM | Kako | kkj
…
12:30 p. m. | Spanish (Nicaragua) | es_NI
12:30 ꁯꋒ | Sichuan Yi (Yi, China) | ii_CN_#Yiii
12:30 Ɛndámâ | Masai (Tanzania) | mas_TZ
12:30 منجهند، شام | Sindhi (Pakistan) | sd_PK
12:30 ድ.ቀ. | Tigrinya | ti
12:30 PTG | Malay (Brunei) | ms_BN
12:30 PM | Chakma (India) | ccp_IN
12:30 G.M. | Breton (France) | br_FR

区域设置更改

人类的文化在进化和变化.因此,Locale中的本地化规则可能会随着时间的推移而改变.

在像您这样的情况下,您希望从区域设置中获得特定的行为,您应该让👉🏽write tests to verify the locale实际以您所期望的方式运行.例如,今天使用a.m.的区域设置明天可能使用am.

仅供参考,现代Java的大多数实现默认依赖于Common Locale Data Repository (CLDR) by the Unicode Consortium的本地化规则.CLDR is periodically updated次,通常每年2-5次.较新的JDK版本通常包括CLDR的最新版本之一.

Java相关问答推荐

Spring boot:Bean和动态扩展器

SQlite for Android无法使用json_group_array/json_object

使用@MappdSuperClass扩展ParentClass&Won t继承ParentClass属性

如何获得执行人?

相同的Java SerializedLambda为implMethodKind返回不同的结果

使用UTC时区将startDatetime转换为本地时间

JOOQ中的子查询使用的是默认方言,而不是配置的方言

从LineChart<;字符串、字符串和gt;中删除数据时出现特殊的ClassCastException;

如何使用MapStrCut转换双向链接

Spring Framework6.1中引入的新RestClient是否有适合于测试的变体,就像RestTemplate和TestRestTemplate一样?

除0错误/抱歉我的句子是PT

具有多个模式的DateTimeForMatter的LocalDate.parse失败

如何在运行docker的应用程序中获取指定的配置文件

有没有办法知道在合并中执行了什么操作?

Win32函数的JNA绑定DwmGetColorizationColor返回E_INVALIDARG错误

字符串的Gzip压缩在java11和java17中给出了不同的结果

如何在Selenium上继续使用最新的WebDriver版本

使用DynamoDB增强客户端时未更新属性

什么是;u〃;平均值;jdku;在java开发工具包中?

为什么 Random() 的行为不符合预期?