【问题标题】:Switch Timezone for calculation切换时区进行计算
【发布时间】:2013-11-19 00:16:35
【问题描述】:

我们的应用程序将所有日期存储在 UTC 时区中。然而,我们的主要业务部门位于“欧洲/柏林”时区(+2,+1,具体取决于夏令时)。因此,当我们确定某个 Timespan 应该等于哪个“月”时,我们希望使用 THAT Timezone。

即由开始Thursday, 31 October 2013, 23:00:0 UTC 和结束Friday, 29 November 2013, 23:00:00 UTC 给出的时间段应该在全球范围内称为Nov 13,至于我们的主要业务单元,Times 将是Friday, 1 November 2013, 00:00:00Friday, 30 November 2013, 00:00:00

服务器本身以 UTC 运行,因此我们必须切换时区,同时计算周期名称。我们在后端使用 Joda Datetime,我总是可以这样做:

DateTime now = new DateTime();
now.withZone(DateTimeZone.forID("Europe/Berlin"));

但是有很多计算正在进行,我想知道是否有更好的方法,在某个时区内进行所有计算?

如果它是一个独立的应用程序,可以使用类似的东西:

DateTimeZone old = DateTimeZone.getDefault();
DateTimeZone.setDefault(DateTimeZone.forID("Europe/Berlin"));

//do all work

DateTimeZone.setDefault(old);

但是由于它是服务器环境,修改默认时区可能会导致其他线程上发生的其他计算结果错误。

所以,我想知道,在 java 中是否没有类似 c# using 指令的东西?除了错误的语法,我还在寻找这样的东西:

using(DateTimeZone.forID("Europe/Berlin")){
   //do all work, where JUST all DateTime usings inside this using
   //Statement are affected
}

编辑:一些小测试显示了问题:即使在 2 个线程上运行,两个线程都使用“默认”时区。因此,无论 LAST 设置的时区如何,都将用于两个线程。 (当然,这就是“默认”时区的用途。)

@Test
    public final void testTimeZoneThreaded() {

        Thread EuropeThread = new Thread(new Runnable() {
            @Override
            public void run() {
                DateTimeZone.setDefault(DateTimeZone.forID("Europe/Berlin"));

                int i = 0;
                while (i++ < 10) {
                    DateTime now = new DateTime();
                    System.out.println("It is now " + now + " in TimeZone " + DateTimeZone.getDefault().toString());

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        });

        Thread UTCThread = new Thread(new Runnable() {
            @Override
            public void run() {
                DateTimeZone.setDefault(DateTimeZone.forID("UTC"));

                int i = 0;
                while (i++ < 10) {
                    DateTime now = new DateTime();
                    System.out.println("It is now " + now + " in TimeZone " + DateTimeZone.getDefault().toString());

                    try {
                        Thread.sleep(800);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        });

        EuropeThread.start();
        UTCThread.start();

        while (UTCThread.isAlive()) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {

            }
        }

    }

能够在不同线程上使用不同的默认值也会有所帮助。

另一个想法是扩展 JodaDateTime 并使用其所有重载覆盖 to string() 方法:

class MyDateTime extends DateTime{
  @Override
  public String toString(){
    return super().withZone(DateTimeZone.for("Europe/Berlin")).toString();
  }

  @Override
  public String toString(String format){
    return super().withZone(DateTimeZone.for("Europe/Berlin")).toString(format);
  }
}
  • 日期时间是“最终的”... :-(

所以,目前我总是需要手动处理它,生成期间名称(年、夸特、月、周……)时

public String getMonthPeriodName() {
        DateTime firstOfMonth = this.getPeriodStart().withZone(DateTimeZone.forID("Europe/Berlin")).dayOfMonth().withMinimumValue().millisOfDay().withMinimumValue();
    DateTime lastOfMonth = this.getPeriodEnd().withZone(DateTimeZone.forID("Europe/Berlin")).dayOfMonth().withMaximumValue().millisOfDay().withMaximumValue();

        if (firstOfMonth.isEqual(getPeriodStart()) && lastOfMonth.isEqual(getPeriodEnd())) {
            // full month.
            return firstOfMonth.withZone(DateTimeZone.forID("Europe/Berlin")).toString("MMM yyyy", Locale.US);
        } else {
            // just partial month.
            String Basename = firstOfMonth.withZone(DateTimeZone.forID("Europe/Berlin")).toString("MMM yyyy", Locale.US);
            String f = getPeriodStart().withZone(DateTimeZone.forID("Europe/Berlin")).dayOfMonth().getAsShortText();
            String t = getPeriodEnd().withZone(DateTimeZone.forID("Europe/Berlin")).dayOfMonth().getAsShortText();
            return Basename + " (" + f + " to " + t + ")";
        }
    }

【问题讨论】:

  • 不确定在 java 中使用 USING 或它的等价物是否是这个问题的正确答案:stackoverflow.com/questions/2943542/using-keyword-in-java
  • @VladimirBozic 这就是为什么我写了搜索类似的东西——不确定,如果可能的话。在某个时区或任何具有相同效果的情况下启动线程也会很有帮助。
  • Joda 的全球时区是静态且易变的:正如您在示例中展示的那样,该值适用于所有线程。
  • 也许你应该看看类加载的东西,例如 OSGi,你可以在其中定义一堆独立于“经典”类加载器/应用程序上下文的类。但它确实看起来像是过度设计。

标签: java datetime timezone jodatime


【解决方案1】:

我可能不会考虑设置默认值,因为这样您就可以在代码中消除任何冗长,这将有助于您了解实际情况。

这是一个非常简单的建议,但也许您应该只使用局部变量。

...
DateTimeZone tz = DateTimeZone.forID("Europe/Berlin");
DateTime firstOfMonth = this.getPeriodStart().withZone(tz).dayOfMonth().withMinimumValue().millisOfDay().withMinimumValue();
DateTime lastOfMonth = this.getPeriodEnd().withZone(tz).dayOfMonth().withMaximumValue().millisOfDay().withMaximumValue();
... etc ...

查看您的代码,我看到了许多冗余,其中一些其他简单的局部变量可以更好地清理内容。

【讨论】:

  • 代码示例只是一个示例,我需要做的,没有设置默认值。您所描述的-仅设置一次-是我想要实现的-但不使用“默认”设置:)
  • 啊,我知道你在那里做了什么——好吧,像这样使用tz 只是我所做的另一个版本。不会改变这样一个事实,即我需要在需要时注意设置 tz。事实上,它保存在一个类属性中,只是将它移到方法体中,所以我不需要提供整个类。 (对于 c 的模式也是如此。)
  • 我同意马特·约翰逊的观点。您可以将该变量 (tz) 传递给新 DateTime 的构造函数:new org.joda.time.DateTime( tz )。当您明确想要使用 UTC 时,Joda-Time 有一个内置常量 .UTC,在这里可以看到传递给构造函数:new org.joda.time.DateTime( org.joda.time.DateTimeZone.UTC )。您可以轻松地在时区之间转换:myUtcDateTime.toDateTime( tz ) 或相反的myBerlinDateTime.toDateTime( org.joda.time.DateTimeZone.UTC )。提示:养成始终将所需时区传递给构造函数的习惯,因此即使服务器的时区未设置为 UTC,您的代码也会成功。
猜你喜欢
  • 1970-01-01
  • 2016-10-23
  • 1970-01-01
  • 2020-11-20
  • 2017-04-06
  • 1970-01-01
  • 2017-02-01
  • 1970-01-01
  • 2021-11-10
相关资源
最近更新 更多