当前位置: 首页 > 知识库问答 >
问题:

Java使用时区

暨曾笑
2023-03-14

我正在使用TimeZone,需要帮助。

背景:我有2个办公室,1个在巴黎,1个在纽约。我需要为两者执行一些任务。我唯一的服务器位于巴黎。任务必须在当地时间上午 9:00 执行

所以,在巴黎时间9点,我的流程将运行,并为巴黎办公室做所有事情。

巴黎时间15:00(或下午3:00),流程将再次运行以完成纽约任务,因为当巴黎下午3:00时,纽约上午9:00。

我尝试了以下方法,但成功率有所降低:

Entity e1 = Entity.getEntityInstance("Paris", TimeZone.getTimeZone("Europe/Paris"));
Entity e2 = Entity.getEntityInstance("NewYork", TimeZone.getTimeZone("America/New_York"));

    Date now = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("ddMMyyyy");
    sdf.setTimeZone(e1.getTimeZone());
    sdf.format(now);

    System.out.printf("Date à %s = %s", e1.getName(), sdf.getCalendar());

    sdf.setTimeZone(e2.getTimeZone());
    sdf.format(now);

    System.out.printf("Date à %s = %s", e2.getName(), sdf.getCalendar());

我得到的结果是这样的:

Date à Paris = java.util.GregorianCalendar[time=1563224081926,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Europe/Paris",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=184,lastRule=java.util.SimpleTimeZone[id=Europe/Paris,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2019,MONTH=6,WEEK_OF_YEAR=29,WEEK_OF_MONTH=3,DAY_OF_MONTH=15,DAY_OF_YEAR=196,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=3,AM_PM=1,HOUR=10,**HOUR_OF_DAY=22,MINUTE=54,SECOND=41**,MILLISECOND=926,ZONE_OFFSET=3600000,DST_OFFSET=3600000]
Date à NewYork = java.util.GregorianCalendar[time=1563224081926,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/New_York",offset=-18000000,dstSavings=3600000,useDaylight=true,transitions=235,lastRule=java.util.SimpleTimeZone[id=America/New_York,offset=-18000000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2019,MONTH=6,WEEK_OF_YEAR=29,WEEK_OF_MONTH=3,DAY_OF_MONTH=15,DAY_OF_YEAR=196,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=3,AM_PM=1,HOUR=4,**HOUR_OF_DAY=16,MINUTE=54,SECOND=41**,MILLISECOND=926,ZONE_OFFSET=-18000000,DST_OFFSET=3600000]

我可以有效地看到HOUR_OF_DAY的差异,但是我应该做些什么来使用非弃用对象(如DateTime(Joda time)或其他任何东西)获得相同的结果呢?

编辑

我尝试了我在这里发现的:

ZonedDateTime zdt = ZonedDateTime.now( ZoneId.of( "America/Montreal" ) );
…
Instant instant = zdt.toInstant();

我认为这可以解决我的问题,因为实际上,我想要的是获得两个< code>Date对象,它们代表相同的瞬间,但是在两个不同的地方。

我所做的是:

ZonedDateTime zdt1 = ZonedDateTime.now( ZoneId.of(e1.getTimeZone().getID()));
ZonedDateTime zdt2 = ZonedDateTime.now( ZoneId.of(e2.getTimeZone().getID()));
System.out.println("ZDT : " + zdt1);
System.out.println("ZDT : " + zdt2);

结果是引人入胜的:

ZDT : 2019-07-16T01:23:29.344+02:00[Europe/Paris]
ZDT : 2019-07-15T19:23:29.346-04:00[America/New_York]

但当我尝试将它们转换为Instant,然后转换为Date时,结果完全相同:

Instant i1 = zdt1.toInstant();
Instant i2 = zdt2.toInstant();
System.out.println(i1);
System.out.println(i2);
Date dd1 = Date.from(i1);
Date dd2 = Date.from(i2);
System.out.println(dd1);
System.out.println(dd2);

在控制台中:

Instant 1 = 2019-07-15T23:23:29.344Z
Instant 2 = 2019-07-15T23:23:29.346Z
Date 1 = Tue Jul 16 01:23:29 CEST 2019
Date 2 = Tue Jul 16 01:23:29 CEST 2019

我真的不明白 2 个不同的 ZonedDateTimes 如何产生相同的 Instants 和 Dates...

共有1个答案

闻人河
2023-03-14

您正在使用糟糕的日期时间类,这些类几年前被JSR 310中定义的现代java.time类所取代。

服务器的默认时区(和区域设置)不应影响代码。作为程序员,那里的设置是您无法控制的。因此,与其依赖这些默认值,不如始终通过将可选参数传递给各种方法来指定所需/预期的时区(和区域设置)。

提示:最佳做法是将服务器保留为 UTC 作为其默认时区。事实上,你的大部分业务逻辑、日志记录、数据交换和数据存储都应该用 UTC 完成。

每个位置都有一天的目标时间。

LocalTime targetTimeOfDayParis = LocalTime.of( 9 , 0 ) ;  // 09:00.
LocalTime targetTimeOfDayNewYork = LocalTime.of( 9 , 0 ) ;  // 09:00.

获取UTC时间的当前时刻。

Instant now = Instant.now() ;  // No need for time zone here. `Instant` is always in UTC, by definition.

今天是什么时候?首先定义我们的时区。

ZoneId zParis = ZoneId.of( "Europe/Paris" ) ;
ZoneId zNewYork = ZoneId.of( "America/New_York" ) ;

然后从Instant中的UTC调整到与特定地区(时区)的人们使用的挂钟时间相同的时刻。该调整后的时刻由ZonedDateTime类表示。

请注意,这不会改变这一刻。我们在两者的时间线上都有相同的点。只是挂钟时间不同。就像两个长途电话的人可以同时抬头看各自墙上的时钟,同时看到不同的时间(和日期!)。

ZonedDateTime zdtParis = now.atZone( zParis ) ;
ZonedDateTime zdtNewYork = now.atZone( zNewYork ) ;

将一天的时间更改为我们的目标。ZonedDateTime::with方法将生成一个新的ZonedDateTime对象,该对象的值基于原始值,您传递的值除外

这是我们在巴黎和纽约问题上分歧的关键部分。巴黎的上午九点与纽约的上午9点截然不同,相隔几个小时。

ZonedDateTime zdtParisTarget = zdtParis.with( targetTimeOfDayParis ) ;
ZonedDateTime zdtNewYorkTarget = zdtNewYork.with( targetTimeOfDayNewYork ) ;

我们错过了上午9点吗?

boolean pastParis = zdtParisTarget.isBefore( zdtParis ) ;
boolean pastNewYork = zdtNewYorkTarget.isBefore( zdtNewYork ) ;

如果过去,则添加一天。

if( pastParis ) {
    zdtParisTarget = zdtParisTarget.plusDays( 1 ) ;
}

if( pastNewYork ) {
    zdtNewYorkTarget = zdtNewYorkTarget.plusDays( 1 ) ;
}

使用Duration类计算运行时间。

Duration durationParis = Duration.between( now , zdtParisTarget.toInstant() ) ;
Duration durationNewYork = Duration.between( now , zdtNewYorkTarget.toInstant() ) ;

仔细检查持续时间是在未来,而不是过去。

if( durationParis.isNegative() ) { … handle error … } 
if( durationNewYork.isNegative() ) { … handle error … } 

0同上。

if( durationParis.isZero() ) { … handle error … } 
if( durationNewYork.isZero() ) { … handle error … } 

在实际工作中,我会检查持续时间是否非常小。如果简短到下面的代码可能需要比时间跨度更长的时间,请添加一些任意数量的时间。我将把它作为读者的练习。

准备要完成的工作。

Runnable runnableParis = () -> taskParis.work() ;
Runnable runnableNewYork = () -> taskNewYork.work() ;

或者,您可以编写一个实用程序,该实用程序接受ZoneId参数,查找要完成的工作,并返回可运行

安排要完成的工作。请参阅有关Exec的Oracle教程框架。该框架极大地简化了在后台线程上完成的调度工作。

我们安排任务在经过一定的分钟、秒或任何此类粒度后运行。我们使用<code>TimeUnit</code>枚举指定一个数字和粒度。我们可以从上面计算的持续时间中提取数字。

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool( 1 );

ScheduledFuture<?> futureParis = scheduler.schedule( runnableParis , durationParis.toSeconds() , TimeUnit.SECONDS ) ;
ScheduledFuture<?> futureNewYork = scheduler.schedule( runnableNewYork , durationNewYork.toSeconds() , TimeUnit.SECONDS ) ;

future对象允许您检查进度状态。你可能根本不需要它。

重要事项:当不再需要时,例如应用程序即将结束时,请确保正常关闭ScheduledExecutorService

注意:我还没有尝试过这些代码。但这会让你朝着正确的方向前进。

注意重复。无需在所有这些变量中对城市名称进行硬编码。您可以在将 ZoneId 作为参数的方法中编写一次此逻辑,如果有可能,也可以编写一天中的时间因城市而异。然后为任意数量的区域调用该方法。

List< ZoneId > zones = 
    List.of( 
            ZoneId.of( "Europe/Paris" ) , 
            ZoneId.of( "America/New_York" ) , 
            ZoneId.of( "Asia/Kolkata" ) 
    ) 
;
for( ZoneId z : zones ) {
    workManager.scheduleForZone( z ) ;
}
 类似资料:
  • 问题内容: 我一直想知道,在最佳实践中,是否允许不使用on方法,而是对from的结果进行空检查。 我的理由是,重复两次查找值似乎是多余的:首先对进行查找,然后对进行查找。 另一方面,可能是大多数标准实现都缓存了最后一次查找,或者编译器可以通过其他方式消除冗余,并且对于代码的可读性而言,最好保留该部分。 非常感谢您的评论。 问题答案: 一些Map实现被允许具有空值,例如HashMap,在这种情况下,

  • 问题内容: 因此,我在Google上搜索了几分钟如何使用计时器,并在此处找到了一些有用的线程。但是,当我想使用建议的代码时,Eclipse总是向我显示错误。 这是我现在要使用的代码。Eclipse在最后一行下划线,并在它们建议“删除参数以匹配’Timer()’”时加下划线。此外,它强调了start()并希望将其强制转换。:S 有人可以帮我吗?我已经安装了最新的Java版本^^ 多谢。 问题答案:

  • 我刚开始使用线程,一调用wait方法就会得到一个IllegalMonitorStateException,有人能帮我吗

  • 我有一个日期存储在DB字符串格式ddMMyyyy和hh: mm和时区。我想根据这些信息创建一个即时,但我不知道如何做到这一点。 像这样的东西

  • 我有一个Employee类,它有如下3个字段。 为此,我希望根据员工姓名(empName)排序,如果多个员工的姓名相同,则根据员工id(empId)排序。 为此,我编写了一个自定义比较器,使用java.util.比较器如下所示。 我已经创建了8个Employee对象并添加到ArrayList中,如下所示。 并使用上述比较器对列表进行如下排序。 它工作得非常好。但这可以使用类似的方法来完成,如下所示

  • 问题内容: StringBuilder在Java中,通常最好使用a 进行字符串连接。总是这样吗? 我的意思是:创建对象,调用方法并最终已经很小,然后将现有字符串与+运算符连接成两个字符串的开销是小的,还是建议仅用于两个以上的字符串? 如果有这样的阈值,它取决于什么(也许是字符串长度,但以哪种方式)? 最后,你是否会以+级联的可读性和简洁性为代价,以较小的情况(例如两个,三个或四个字符串)的性能为代