繁体   English   中英

Java使用TimeZone

[英]Java Using TimeZone

我在使用TimeZone时遇到麻烦,需要帮助。

背景:我有2个办事处,巴黎1个,纽约1个。 我都需要执行一些任务。 我唯一的服务器位于巴黎。 任务必须在当地时间9:00 AM执行。

因此,在巴黎时间9:00,我的流程将运行并完成巴黎办公室的所有工作。

在巴黎时间15:00(或3:00 PM),流程将再次运行以完成纽约任务,因为在巴黎的3:00 PM时,纽约的9:00 AM。

我尝试了以下类似的方法,但是成功减轻了:

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 (乔达时间)或其他任何东西),我应该怎么做才能得到相同的结果?


编辑

我尝试了在这里找到的东西

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

我认为这可能是解决我的问题的方法,因为实际上,我想要获得的是代表2个即时但在2个不同位置的2个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个不同的ZonedDateTime如何可以产生相同的InstantDate ……

避免遗留类

您使用的是可怕的日期时间类,而该类在几年前已被JSR 310中定义的现代java.time类所取代。

将服务器保留在UTC中

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

提示:最佳实践通常是将服务器保持在UTC作为其默认时区。 实际上,您的大多数业务逻辑,日志记录,数据交换和数据存储都应在UTC中完成。

java.time

LocalTime

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

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

Instant.now

在UTC中获取当前时刻。

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

ZoneId

今天那一刻几点 首先定义我们的时区。

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

Instant::atZone

然后,从Instant中的UTC调整到特定区域(时区)人们使用的挂钟时间所看到的同一时刻。 调整后的力矩由ZonedDateTime类表示。

请注意,这并没有改变。 我们在时间轴上都有相同的观点。 只有挂钟时间不同。 就像两个打长途电话的人可以同时抬头仰望各自墙上的时钟,并在同一时刻看到不同的一天中的时间(和日期!)。

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

ZonedDateTime::with

将时段更改为我们的目标。 ZonedDateTime::with方法将生成一个新的ZonedDateTime对象,该对象的值基于原始值,但传递的值除外。 在这里,我们传递一个LocalTime来更改小时,分钟,秒和小数秒。

这是我们将巴黎和纽约区分开的关键部分。 巴黎的早上九点与纽约的上午9点是一个非常不同的时刻,相隔几个小时。

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

isBefore比较

我们在任何一个区域都错过了上午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类计算经过的时间。

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

明智地检查持续时间是在将来,而不是过去。

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

同上为零。

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

在实际工作中,我将检查持续时间是否非常短。 如果太简短,以至于下面的代码可能要花费比时间跨度更长的时间,请添加一些任意时间。 我将其留给读者练习。

安排要完成的工作

准备要完成的工作。

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

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

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

我们将任务安排为在一定数量的分钟,秒或任何这样的粒度过去之后运行。 我们使用TimeUnit枚举指定数字和粒度。 我们可以从上面计算的Duration提取数字。

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool( 1 );

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

将来的对象可让您检查进度状态。 您可能根本不需要它。

重要说明:请确保在不再需要时(例如,应用程序结束时)正常关闭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 ) ;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM