Java:请在 "How to set different timezone for multiple thread?" 上提供示例
Java: Please provide an example on "How to set different timezone for multiple thread?"
我只是 运行 遇到了 java.util.Date 对象的情况,我非常确定这是错误的。
场景:
目前,我在一个以 UTC 作为默认时区的系统上,并试图将 IST(印度标准时间)日期转换为 UTC,但它打印出错误的值,其输出时差为 2 小时,但是应该相差 5 小时 30 分钟。
所以,我决定更改线程时区,但无法这样做。
请提出一些建议。这是代码。
import com.rometools.rome.feed.rss.Item;
import com.rometools.rome.feed.synd.SyndEntry;
import com.rometools.rome.feed.synd.SyndFeed;
import com.rometools.rome.io.SyndFeedInput;
import com.rometools.rome.io.XmlReader;
import java.net.URL;
import java.util.Date;
public class NewsService {
public static void main(String[] args) {
NewsService newsService = new NewsService();
try {
newsService.printNews("https://timesofindia.indiatimes.com/rssfeeds/296589292.cms");
} catch (Exception e) {
e.printStackTrace();
}
}
public void printNews(String url) throws Exception {
// read RSS
SyndFeedInput in = new SyndFeedInput();
in.setPreserveWireFeed(true);
SyndFeed feed = in.build(new XmlReader(new URL(url)));
for (SyndEntry syndEntry : feed.getEntries()) {
Object obj = syndEntry.getWireEntry();
Item item = (Item) obj;
Date date = ((Item) obj).getPubDate();
System.out.println(item.getTitle() + " " + date);
}
}
}
我这里有 2 小时的差异,但应该是 5 小时 30 分钟。
编辑:IST 不明确
来自您的评论:
for example if the system timezone is in UTC and rss feed contains the
publishedDate as Sat, 28 Mar 2020 13:42:38 IST, the libary parses it to Sat Mar 28 11:42:38 GMT 2020
我进行了一些调查,但我已经重现了该行为。问题是 IST 含糊不清:它可能表示爱尔兰夏令时、以色列标准时间、印度标准时间以及其他一些可能的时区缩写。许多时区缩写都存在类似的歧义,因此我们不应依赖它们。由于您要求相差 5 小时 30 分,我认为您打算使用印度标准时间。但是,Java 将 IST 解释为以色列标准时间,与 UTC/GMT 的偏移量为 +02:00。这解释了您观察到的 2 小时差异。即使以色列在每年的这个时候不使用 IST,它也会这样做,它使用以色列夏令时 IDT(在偏移 +03:00)。但是,当您的默认时区是 Asia/Kolkata(印度标准时间)时,Java 会将 IST 解释为这个意思。这解释了为什么您在这种情况下得到了预期的正确结果。
虽然在自己的代码中解决这个问题并不太难,但如何说服你的图书馆按照你的意愿去做就是另一回事了。我想到了几个选项:
最好的解决办法是说服您的 RSS 提要发布者不要使用 IST 作为时区。根据 RSS 2.0 规范,发布日期应采用 RFC 822 格式,而根据 RFC 822,IST 不是有效时区。所以你有争论。据我所见,GMT
在这里经常使用,并且与规范一致。
我不知道 rometools,所以可能有一些我不知道的可能性,如果可以的话,你应该调查一下。您当然也可以向库开发人员提交错误报告。
当 rometools 实例化它用于解析的格式化程序时,您可以尝试将默认时区设置为 Asia/Kolkata。无论是在初始化、第一次调用还是每次调用时都这样做——做一些实验,或者如果你想确定的话,检查 GitHub 上的源代码。如果需要,您可以随时将默认时区设置回 UTC。这是一个非常糟糕的黑客攻击,而且并非没有偶尔出现错误结果的风险。
如果您的 RSS 提要中的发布日期始终是印度标准时间意义上的 IST,您当然可以更正您得到的错误日期:
System.out.println("Incorrect date from rometools: " + javaUtilDateFromRometools);
ZonedDateTime correctedDateTime = javaUtilDateFromRometools.toInstant()
.atZone(ZoneOffset.ofHours(2)) // Israel Standard Time, but +02:00 all year, so not Asia/Jerusalem
.withZoneSameLocal(ZoneId.of("Asia/Kolkata"))
.withZoneSameInstant(ZoneOffset.UTC);
System.out.println("UTC time: " + correctedDateTime);
假设 rometools 从解析开始的示例输出 Sat, 28 Mar 2020 13:42:38 IST
:
Incorrect date from rometools: Sat Mar 28 11:42:38 UTC 2020
UTC time: 2020-03-28T08:12:38Z
现在,RSS 字符串中的 13:42:38 与打印出来的 08:12:38 相差 5 小时 30 分钟。请注意,如果 RSS 提要中的发布日期位于其他时区,则 Date
可能是正确的,我们的“更正”使其有误。所以这也是一种脆弱的方法。
原回答
第一个答案是:不要依赖 JVM 的默认时区。为您的日期和时间操作指定明确的时区。
有一个提示:使用 java.time,现代 Java 日期和时间 API。与旧 类 Date
、TimeZone
和朋友一起工作要好得多。这些 类 不仅陈旧,而且通常设计不佳而且早已过时。此外,java.time 通常可以更自然地为您的日期和时间操作提供明确的时区。例如:
ZonedDateTime istTime = ZonedDateTime.of(
2020, 3, 27, 12, 34, 56, 123456000, ZoneId.of("Asia/Kolkata"));
System.out.println("IST time: " + istTime);
ZonedDateTime utcTIme = istTime.withZoneSameInstant(ZoneOffset.UTC);
System.out.println("UTC time: " + utcTIme);
输出为:
IST time: 2020-03-27T12:34:56.123456+05:30[Asia/Kolkata]
UTC time: 2020-03-27T07:04:56.123456Z
正如您所说,相差 5 小时 30 分钟。无论 JVM 的默认时区如何,代码都会给出相同的输出。
编辑:如果你有一个 java.util.Date
并且无论你的 JVM 的时区设置如何,你都希望它以 UTC 打印:
OffsetDateTime utcTime = yourJavaUtilDate.toInstant().atOffset(ZoneOffset.UTC);
System.out.println("UTC time: " + utcTime);
UTC time: 2020-03-28T10:11:12.345Z
每个线程的不同时区? ThreadLocal
您可以为每个线程设置单独的时区。你不能拥有的是每个线程的默认时区。您的 JVM 的默认时区正好是:您的 JVM。因此,如果您更改它,更改将对 JVM 中的所有线程产生影响。
如果你愿意,每个线程可以保留一个时区作为ThreadLocal
。线程有责任使用 ThreadLocal
而不是 JVM 的默认时区。
public class SetDifferentTimeZonesForThreads {
public static void main(String[] args) {
ZonedDateTime zdt = ZonedDateTime.of(
2020, 3, 27, 23, 30, 9, 0, ZoneId.of("Asia/Kolkata"));
Thread tUtc = new MyThread(zdt, "Etc/UTC");
Thread tIst = new MyThread(zdt, "Asia/Kolkata");
tUtc.start();
tIst.start();
}
}
class MyThread extends Thread {
private ZonedDateTime zdtToConvert;
private ThreadLocal<ZoneId> threadTimeZone;
public MyThread(ZonedDateTime zdt, String zoneIdString) {
zdtToConvert = zdt;
threadTimeZone = ThreadLocal.withInitial(() -> ZoneId.of(zoneIdString));
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
ZoneId zone = threadTimeZone.get();
System.out.format("In %-12s: %s%n", zone, zdtToConvert.withZoneSameInstant(zone));
}
}
}
示例输出:
In Asia/Kolkata: 2020-03-27T23:30:09+05:30[Asia/Kolkata]
In Asia/Kolkata: 2020-03-27T23:30:09+05:30[Asia/Kolkata]
In Etc/UTC : 2020-03-27T18:00:09Z[Etc/UTC]
In Asia/Kolkata: 2020-03-27T23:30:09+05:30[Asia/Kolkata]
In Etc/UTC : 2020-03-27T18:00:09Z[Etc/UTC]
In Asia/Kolkata: 2020-03-27T23:30:09+05:30[Asia/Kolkata]
In Asia/Kolkata: 2020-03-27T23:30:09+05:30[Asia/Kolkata]
In Etc/UTC : 2020-03-27T18:00:09Z[Etc/UTC]
In Etc/UTC : 2020-03-27T18:00:09Z[Etc/UTC]
In Etc/UTC : 2020-03-27T18:00:09Z[Etc/UTC]
链接
我只是 运行 遇到了 java.util.Date 对象的情况,我非常确定这是错误的。
场景:
目前,我在一个以 UTC 作为默认时区的系统上,并试图将 IST(印度标准时间)日期转换为 UTC,但它打印出错误的值,其输出时差为 2 小时,但是应该相差 5 小时 30 分钟。
所以,我决定更改线程时区,但无法这样做。
请提出一些建议。这是代码。
import com.rometools.rome.feed.rss.Item;
import com.rometools.rome.feed.synd.SyndEntry;
import com.rometools.rome.feed.synd.SyndFeed;
import com.rometools.rome.io.SyndFeedInput;
import com.rometools.rome.io.XmlReader;
import java.net.URL;
import java.util.Date;
public class NewsService {
public static void main(String[] args) {
NewsService newsService = new NewsService();
try {
newsService.printNews("https://timesofindia.indiatimes.com/rssfeeds/296589292.cms");
} catch (Exception e) {
e.printStackTrace();
}
}
public void printNews(String url) throws Exception {
// read RSS
SyndFeedInput in = new SyndFeedInput();
in.setPreserveWireFeed(true);
SyndFeed feed = in.build(new XmlReader(new URL(url)));
for (SyndEntry syndEntry : feed.getEntries()) {
Object obj = syndEntry.getWireEntry();
Item item = (Item) obj;
Date date = ((Item) obj).getPubDate();
System.out.println(item.getTitle() + " " + date);
}
}
}
我这里有 2 小时的差异,但应该是 5 小时 30 分钟。
编辑:IST 不明确
来自您的评论:
for example if the system timezone is in UTC and rss feed contains the publishedDate as Sat, 28 Mar 2020 13:42:38 IST, the libary parses it to Sat Mar 28 11:42:38 GMT 2020
我进行了一些调查,但我已经重现了该行为。问题是 IST 含糊不清:它可能表示爱尔兰夏令时、以色列标准时间、印度标准时间以及其他一些可能的时区缩写。许多时区缩写都存在类似的歧义,因此我们不应依赖它们。由于您要求相差 5 小时 30 分,我认为您打算使用印度标准时间。但是,Java 将 IST 解释为以色列标准时间,与 UTC/GMT 的偏移量为 +02:00。这解释了您观察到的 2 小时差异。即使以色列在每年的这个时候不使用 IST,它也会这样做,它使用以色列夏令时 IDT(在偏移 +03:00)。但是,当您的默认时区是 Asia/Kolkata(印度标准时间)时,Java 会将 IST 解释为这个意思。这解释了为什么您在这种情况下得到了预期的正确结果。
虽然在自己的代码中解决这个问题并不太难,但如何说服你的图书馆按照你的意愿去做就是另一回事了。我想到了几个选项:
最好的解决办法是说服您的 RSS 提要发布者不要使用 IST 作为时区。根据 RSS 2.0 规范,发布日期应采用 RFC 822 格式,而根据 RFC 822,IST 不是有效时区。所以你有争论。据我所见,
GMT
在这里经常使用,并且与规范一致。我不知道 rometools,所以可能有一些我不知道的可能性,如果可以的话,你应该调查一下。您当然也可以向库开发人员提交错误报告。
当 rometools 实例化它用于解析的格式化程序时,您可以尝试将默认时区设置为 Asia/Kolkata。无论是在初始化、第一次调用还是每次调用时都这样做——做一些实验,或者如果你想确定的话,检查 GitHub 上的源代码。如果需要,您可以随时将默认时区设置回 UTC。这是一个非常糟糕的黑客攻击,而且并非没有偶尔出现错误结果的风险。
如果您的 RSS 提要中的发布日期始终是印度标准时间意义上的 IST,您当然可以更正您得到的错误日期:
System.out.println("Incorrect date from rometools: " + javaUtilDateFromRometools); ZonedDateTime correctedDateTime = javaUtilDateFromRometools.toInstant() .atZone(ZoneOffset.ofHours(2)) // Israel Standard Time, but +02:00 all year, so not Asia/Jerusalem .withZoneSameLocal(ZoneId.of("Asia/Kolkata")) .withZoneSameInstant(ZoneOffset.UTC); System.out.println("UTC time: " + correctedDateTime);
假设 rometools 从解析开始的示例输出
Sat, 28 Mar 2020 13:42:38 IST
:Incorrect date from rometools: Sat Mar 28 11:42:38 UTC 2020 UTC time: 2020-03-28T08:12:38Z
现在,RSS 字符串中的 13:42:38 与打印出来的 08:12:38 相差 5 小时 30 分钟。请注意,如果 RSS 提要中的发布日期位于其他时区,则
Date
可能是正确的,我们的“更正”使其有误。所以这也是一种脆弱的方法。
原回答
第一个答案是:不要依赖 JVM 的默认时区。为您的日期和时间操作指定明确的时区。
有一个提示:使用 java.time,现代 Java 日期和时间 API。与旧 类 Date
、TimeZone
和朋友一起工作要好得多。这些 类 不仅陈旧,而且通常设计不佳而且早已过时。此外,java.time 通常可以更自然地为您的日期和时间操作提供明确的时区。例如:
ZonedDateTime istTime = ZonedDateTime.of(
2020, 3, 27, 12, 34, 56, 123456000, ZoneId.of("Asia/Kolkata"));
System.out.println("IST time: " + istTime);
ZonedDateTime utcTIme = istTime.withZoneSameInstant(ZoneOffset.UTC);
System.out.println("UTC time: " + utcTIme);
输出为:
IST time: 2020-03-27T12:34:56.123456+05:30[Asia/Kolkata] UTC time: 2020-03-27T07:04:56.123456Z
正如您所说,相差 5 小时 30 分钟。无论 JVM 的默认时区如何,代码都会给出相同的输出。
编辑:如果你有一个 java.util.Date
并且无论你的 JVM 的时区设置如何,你都希望它以 UTC 打印:
OffsetDateTime utcTime = yourJavaUtilDate.toInstant().atOffset(ZoneOffset.UTC);
System.out.println("UTC time: " + utcTime);
UTC time: 2020-03-28T10:11:12.345Z
每个线程的不同时区? ThreadLocal
您可以为每个线程设置单独的时区。你不能拥有的是每个线程的默认时区。您的 JVM 的默认时区正好是:您的 JVM。因此,如果您更改它,更改将对 JVM 中的所有线程产生影响。
如果你愿意,每个线程可以保留一个时区作为ThreadLocal
。线程有责任使用 ThreadLocal
而不是 JVM 的默认时区。
public class SetDifferentTimeZonesForThreads {
public static void main(String[] args) {
ZonedDateTime zdt = ZonedDateTime.of(
2020, 3, 27, 23, 30, 9, 0, ZoneId.of("Asia/Kolkata"));
Thread tUtc = new MyThread(zdt, "Etc/UTC");
Thread tIst = new MyThread(zdt, "Asia/Kolkata");
tUtc.start();
tIst.start();
}
}
class MyThread extends Thread {
private ZonedDateTime zdtToConvert;
private ThreadLocal<ZoneId> threadTimeZone;
public MyThread(ZonedDateTime zdt, String zoneIdString) {
zdtToConvert = zdt;
threadTimeZone = ThreadLocal.withInitial(() -> ZoneId.of(zoneIdString));
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
ZoneId zone = threadTimeZone.get();
System.out.format("In %-12s: %s%n", zone, zdtToConvert.withZoneSameInstant(zone));
}
}
}
示例输出:
In Asia/Kolkata: 2020-03-27T23:30:09+05:30[Asia/Kolkata] In Asia/Kolkata: 2020-03-27T23:30:09+05:30[Asia/Kolkata] In Etc/UTC : 2020-03-27T18:00:09Z[Etc/UTC] In Asia/Kolkata: 2020-03-27T23:30:09+05:30[Asia/Kolkata] In Etc/UTC : 2020-03-27T18:00:09Z[Etc/UTC] In Asia/Kolkata: 2020-03-27T23:30:09+05:30[Asia/Kolkata] In Asia/Kolkata: 2020-03-27T23:30:09+05:30[Asia/Kolkata] In Etc/UTC : 2020-03-27T18:00:09Z[Etc/UTC] In Etc/UTC : 2020-03-27T18:00:09Z[Etc/UTC] In Etc/UTC : 2020-03-27T18:00:09Z[Etc/UTC]