Java:在OSGi应用程序中设置时区
我在Linux上运行的嵌入式Java应用程序应该允许用户通过GUI更改时区。我的应用程序运行在一个OSGI容器中(请参阅下面我为什么认为这是相关的),在使用新时区之前不需要重新启动 从Java/OSGi应用程序持续设置时区的推荐方法是什么? 我可以想到以下方法,为此我列出了一些优点和缺点。我错过什么了吗?推荐什么Java:在OSGi应用程序中设置时区,java,timezone,osgi,java-time,threetenbp,Java,Timezone,Osgi,Java Time,Threetenbp,我在Linux上运行的嵌入式Java应用程序应该允许用户通过GUI更改时区。我的应用程序运行在一个OSGI容器中(请参阅下面我为什么认为这是相关的),在使用新时区之前不需要重新启动 从Java/OSGi应用程序持续设置时区的推荐方法是什么? 我可以想到以下方法,为此我列出了一些优点和缺点。我错过什么了吗?推荐什么 从应用程序中,更改底层操作系统时区,另外为当前运行的JVM使用时区.setDefault(…),并更新所有持有旧TZ的时钟实例(因此需要某种事件)。缺点:这种方法依赖于操作系统,而且级
时区.setDefault(…)
,并更新所有持有旧TZ的时钟
实例(因此需要某种事件)。缺点:这种方法依赖于操作系统,而且级别很低,我也希望保持操作系统时钟UTC。优点:OS负责存储TZ,TZ在下次启动应用程序时立即正确-Duser.timezone=…
参数。缺点:非常难看,甚至更低级别,但允许将操作系统时钟保持在UTC,同时让应用程序以正确的TZ启动。还需要在更改时更新时钟
实例TimeZone.setDefault(…)
并在启动时尽早调用它。这将需要一个单独的持久性(首选项)来保存它。这里,在当前运行的JVM中,所有引用旧TZ的时钟实例都需要在更改时更新(需要事件)。当在OSGi容器中运行时,bundle的启动顺序不能得到保证,因此我无法确保在使用默认TZ之前设置好它。我怎么能保证这一点?此外,JSR310明确建议不要在时钟中使用“默认TZ”
Instant
和LocalXXX
值之间转换时,显式传递时区。这样就不需要事件来更新时钟
实例。但是我们需要注意不要使用LocalDate.now(clock)
,因为这使用了时钟的TZ(此时不再正确)。如何在OSGi中使用这个全局变量?使用ConfigAdmin
?如何使我无法控制的代码正确运行(例如记录时间戳)时钟
,我可以使用始终检查默认时区的时钟,但从性能角度来看,这似乎不是最理想的:
public class DefaultZoneClock extends Clock {
private final Clock ref = Clock.systemUTC();
@Override
public ZoneId getZone() {
return ZoneId.systemDefault(); // probed on each request
}
@Override
public Clock withZone(ZoneId zone) {
return ref.withZone(zone);
}
@Override
public Instant instant() {
return ref.instant();
}
}
这是个好主意吗
编辑2:
关于我上面提到的性能问题:它们显然是不合理的。当您调用LocalDate.now()
时,会在内部构建一个新的SytemClock
,它通过在地图中搜索来设置当前的ZoneID
——这与上面使用myDefaultZoneClock
完全相同,区别在于使用我的代码我可以插入任何其他时钟进行测试。(所有客户端代码都将使用LocalDate.now(时钟)
)
下面的答案建议不要更改JVM时区,而是在必要时根据用户定义的时区进行转换,这意味着我必须注意不要使用java.time方法,这些方法从时钟调用时区,例如,如果我需要位置时间
使用
// OK, TimeZone is set explicitely from user data
LocalTime t = clock.instant().atZone(myUserZoneID).toLocalTime();
// Not OK, uses the Clock's internal TimeZone which may not have been set or updated
LocalTime t2 = LocalTime.now(clock);
根据我的经验,日期/时间应始终使用UTC在内部表示。仅当向用户显示时才转换为本地时间。此时,您还需要根据用户的区域设置对其进行格式化
所以问题变成了,你如何知道用户的时区和地区?这取决于应用程序的性质。如果是单用户桌面应用程序,那么您应该在操作系统中查找这些应用程序,或者使用配置管理服务存储的配置。如果它是一个多用户web应用程序,那么在web会话中可能会有一些与用户相关的信息;或者使用Accept语言标题,甚至使用地理位置
更新
海报阐明了应用程序是嵌入式设备上的单用户。在这种情况下,每次需要显示时间时,查询当前系统时区不是更容易吗?这避免了令人讨厌的全局变量,还允许应用程序动态响应时区的变化,例如,如果用户将设备从一个地方带到另一个地方
我认为您不应该从Java应用程序中调用TimeZone.setDefault
,因为您将从哪里获得这些信息?可能是直接用户输入,但用户不愿意在操作系统中设置它,以便所有应用程序都能获得它吗?您似乎在小题大做,努力将一个简单的问题变成一个复杂的问题
将日期时间值视为本地化文本
日期时间本地化是一个本地化问题。如果一些用户说法语、意大利语和德语,您将以类似的方式处理它
用一种语言完成所有内部工作,例如查找和键值,比如说英语,即使您的用户都不会说英语
对于日期时间,等效的方法是在UTC时区中保留内部值。您的业务逻辑、文件存储和数据库记录都应该使用UTC。如果了解原始时区和/或原始输入非常重要,请将其作为UTC调整值之外的附加信息存储
当需要向用户显示或为用户导出数据时,您可以使用这些英语字符串和键来查找法语、意大利语或德语翻译。哪种语言?三种可能性:
- 用户的计算环境的默认语言(JVM默认值、http头、JavaScript查询等)
-
DateTimeZone zone = DateTimeZone.forID( "America/Montreal" );
DateTime alarmMontréal = DateTime.now( zone ).plusDays( 1 ).withTime( 7 , 30 , 0 , 0 );
DateTime alarmUtc = alarmMontréal.withZone( DateTimeZone.UTC );
DateTime alarmPortland = alarmUtc.withZone( DateTimeZone.forID( "America/Los_Angeles" ) ); // 3 hours behind Montréal, 04:30:00.000.
DateTime alarmKolkata = alarmUtc.withZone( DateTimeZone.forID( "Asia/Kolkata" ) );
Boolean alarmFired = Boolean.FALSE;
LocalTime alarm = new LocalTime( 7 , 30 );
// …
DateTimeZone zoneDefault = DateTimeZone.getDefault(); // Use the computer’s/device’s JVM’s current default time zone. May change at any moment.
if ( LocalTime.now( zoneDefault ).isAfter( alarm ) ) {
DateTime whenAlarmFired = DateTime.now( DateTimeZone.UTC );
alarmFired = Boolean.TRUE;
// fire the alarm.
}
LocalTime now = LocalTime.now(); // Implicit reliance on JVM’s current default time zone.
LocalTime now = LocalTime.now( DateTimeZone.getDefault() ); // Explicitly asking for JVM’s current default time zone.