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的时钟实例(因此需要某种事件)。缺点:这种方法依赖于操作系统,而且级

我在Linux上运行的嵌入式Java应用程序应该允许用户通过GUI更改时区。我的应用程序运行在一个OSGI容器中(请参阅下面我为什么认为这是相关的),在使用新时区之前不需要重新启动

从Java/OSGi应用程序持续设置时区的推荐方法是什么?

我可以想到以下方法,为此我列出了一些优点和缺点。我错过什么了吗?推荐什么

  • 从应用程序中,更改底层操作系统时区,另外为当前运行的JVM使用
    时区.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
    ——这与上面使用my
    DefaultZoneClock
    完全相同,区别在于使用我的代码我可以插入任何其他
    时钟进行测试。(所有客户端代码都将使用
    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.