javamultithreadingtimezonerssrss-reader

Java: Please provide an example on "How to set different timezone for multiple thread?"


I just ran into a situation where I am getting java.util.Date object and I am very much sure that it is wrong.

Scenario:

Currently, I am on a system having UTC as default time zone and trying to convert IST(Indian Standard Time) date to UTC, but it is printing me wrong value having a time difference of 2 hours in its output but it should be 5 hr 30 min of difference.

So, I decided to change the thread timezone, but unable to do so.

Please suggest something. Here is the code.

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);
    }

  }

}

I am getting 2 hours of difference here but it should be 5hr and 30 mins.


Solution

  • Edit: IST is ambiguous

    From your comment:

    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

    It took me a bit of investigation, but I have reproduced the behaviour. The problem is that IST is ambiguous: it may mean Irish Summer Time, Israel Standard Time, India Standard Time plus a few other possible time zone abbreviations. A similar ambiguity exists for very many time zone abbreviations, so we should not rely on them. Since you asked for a difference of 5 h 30, I take it that you intended India Standard Time. However, Java interprets IST as Israel Standard Time, which is at offset +02:00 from UTC/GMT. This explains your observed 2 hours difference. It does so even though Israel doesn’t use IST at this time of year, it uses Israel Daylight Time, IDT (at offset +03:00). However, when your default time zone is Asia/Kolkata (India Standard Time), Java does interpret IST to mean this. This explains why you got the expected and correct result in this case.

    While fixing this problem in one’s own code would not be too hard, how to persuade your library to do as you want is a different matter. A couple of options come to mind:

    1. The best solution is if you can persuade the publisher of your RSS feed not to use IST as time zone. According to the RSS 2.0 specification, the published date should be in RFC 822 format, and according to RFC 822, IST is not a valid time zone. So you have got arguments. From what I have seen, GMT is very often used here and is in agreement with the specs.

    2. I don’t know rometools, so there could be possibilities that I don’t know of and which you should investigate if you can. You may of course also file a bug report with the library developers.

    3. You may try setting your default time zone to Asia/Kolkata at the time when rometools instantiates the formatter that it uses for parsing. Whether it does this on initialization, on the first call or on every call — make some experiments, or if you want to be sure, inspect the source code on GitHub. If you need to, you can always set the default time zone back to UTC afterward. It’s a very bad hack and not without the risk of occasional incorrect results.

    4. If the published date from your RSS feed is always in IST in the sense India Standard Time, you may of course correct the wrong date that you get:

      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);
      

      Example output assuming that rometools started out from parsing 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
      

      Now you’ve got the difference of 5 h 30 min between the 13:42:38 in the RSS string and the 08:12:38 printed. Please note that if the published date in the RSS feeds comes in some other time zone, the Date will probably be correct and our “correction” make it faulty. So it’s a fragile approach too.

    Original answer

    The first answer is: Do not rely on the default time zone of your JVM. Specify explicit time zone for your date and time operations.

    There’s a tip that goes along: Use java.time, the modern Java date and time API. It is so much nicer to work with than the old classes Date, TimeZone and friends. These classes are not only old, they are generally poorly designed and they are long outdated. In addition, java.time generally makes it a lot more natural to provide an explicit time zone for your date and time operations. For example:

        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);
    

    Output is:

    IST time: 2020-03-27T12:34:56.123456+05:30[Asia/Kolkata]
    UTC time: 2020-03-27T07:04:56.123456Z
    

    The difference is 5 hours 30 minutes, as you said it should. The code will give the same output regardless of the default time zone of your JVM.

    Edit: If you’ve got a java.util.Date and you want it printed in UTC no matter the time zone setting of your JVM:

        OffsetDateTime utcTime = yourJavaUtilDate.toInstant().atOffset(ZoneOffset.UTC);
        System.out.println("UTC time: " + utcTime);
    

    UTC time: 2020-03-28T10:11:12.345Z

    A different time zone for each thread? ThreadLocal

    You can have a separate time zone for each thread. What you cannot have is a default time zone per thread. The default time zone of your JVM is exactly that: of your JVM. So if you change it, the change has effect for all threads in the JVM.

    If you want, each thread can keep a time zone as a ThreadLocal. It will be the resposibility of the thread to use the ThreadLocal and not the JVM’s default time zone.

    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));
            }
        }
        
    }
    

    Example output:

    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]
    

    Links