Yan Khonski
Yan Khonski

Reputation: 13083

Get TimeZone by timezone offset

I need a single timezone, and I have timezone offset.

For example, I have offset = 14400000. I would like to have a timezone (any place, any city). I can use this timezone in SimpleDateFormat so it prints the correct time for this zone.

This is what I found (I refactored a bit):

private TimeZone getTimeZone(int offset) {
    final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("UTC");
    String[] ids = TimeZone.getAvailableIDs(offset);
    if (ids == null || ids.length == 0) {
        return UTC_TIME_ZONE;
    }

    String matchingZoneId = ids[0];
    return TimeZone.getTimeZone(matchingZoneId);
}

I get:

sun.util.calendar.ZoneInfo[id="Asia/Baku",offset=14400000,dstSavings=0,useDaylight=false,transitions=68,lastRule=null]

I do not like this approach, because TimeZone will call ZoneInfoFile getZoneIds(int var1) which is here. This is decompiled code.

 public static String[] getZoneIds(int var0) {
    ArrayList var1 = new ArrayList();
    String[] var2 = getZoneIds();
    int var3 = var2.length;

    for(int var4 = 0; var4 < var3; ++var4) {
        String var5 = var2[var4];
        ZoneInfo var6 = getZoneInfo(var5);
        if (var6.getRawOffset() == var0) {
            var1.add(var5);
        }
    }

    var2 = (String[])var1.toArray(new String[var1.size()]);
    Arrays.sort(var2);
    return var2;
}

You can see, that it loops over existing objects to build an array of Zone ids, while I just need one!

My question, is there a more effective way to do so? I just need ANY timezone id or a single timezone of given offset.

Upvotes: 0

Views: 4154

Answers (3)

Basil Bourque
Basil Bourque

Reputation: 338201

tl;dr

Do not do what you are attempting.

Does not make sense, as zones sharing an offset at one moment in time may not share that offset at other moments in the past or future.

Zone is a history of offsets

The Answer by Ole V.V. is correct. Here is a bit more discussion.

I need a single timezone, and I have timezone offset.

Not really possible.

An offset-from-UTC is nothing more than a number of hours, minutes, and seconds ahead of, or behind, UTC.

A time zone is much more. A zone is a history of past changes in offset used by the people of a specific region. A zone also has rules for present and future changes to the offset for that region. So a time zone is always preferable to an mere offset, but only when known with certainty. In your case, you do not know the zone, and should not guess; just stick with using the offset.

A given offset cannot lead you to a particular time zone without ambiguity and without error.

  • Ambiguity, because many zones may share the same offset by coincidence. For example, Europe/Gibralter & Europe/Oslo & Africa/Lagos all share the same offset today, one hour ahead of UTC. So with only an offset-from-UTC of +01:00 how would you know which is appropriate?
  • Error, because without a date you cannot know what offset applies to a time zone. For example, the three zones listed above share the same offset today in the winter of 2017-2018, but in the summer Gibralter and Oslo observe Daylight Saving Time (DST) meaning they shift an hour to be two hours ahead of UTC while Lagos remains one hour ahead of UTC. And historically, the offsets may have changed for other reasons than DST as politicians world-wide have a curious propensity for redefining zones. For example, twice in a decade Venezuela has shifted its offset by a half-hour. As another example in recent years, Russia and Turkey have been changing their minds about being on DST, reverting to their previous static offset, or changing to to stay permanently ahead of their previous static offset.

Note how the ZoneRules class can give you an offset for a particular zone only if you provide a moment (a date-time value) passed as an argument.

ZoneOffset offset = ZoneId.of( "Africa/Tunis" )        // Instantiate a `ZoneId`.
                        .getRules()                    // Obtain a `ZoneRules` object from that `ZoneId` object.
                        .getOffset( Instant.now() ) ;  // Interrogate that `ZoneRules` for an offset-from-UTC in use at a specific moment in history.

I just need ANY timezone id or a single timezone of given offset.

This in not advisable. As discussed above, the time zones sharing a particular offset on one date may not share that offset on another date. Each zone varies by its history of changes in offset. So picking a zone arbitrarily is not sensible.

java.time

As explained in the other Answer, the TimeZone & ZoneInfo classes are now legacy. They are part of the troublesome old date-time classes that are supplanted by the java.time classes.

Instead use ZoneOffset (offset-from-UTC) and ZoneId (time zone).

Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).

ZoneId z = ZoneId.of( "America/Montreal" );

About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Upvotes: 4

Anonymous
Anonymous

Reputation: 86173

The TimeZone class is long outdated together with the classes you would usually use it with; Calendar, GregorianCalendar, DateFormat and SimpleDateFormat.

I recommend you start using java.time, the modern Java date and time API instead. It is generally so much nicer to work with. Then you will need a ZoneId object for your time zone. For a ZoneId with constant offset, we create an instance of ZoneOffset, one of the two subclasses of ZoneId.

    int offsetMilliseconds = 14_400_000;
    int offsetSeconds = (int) TimeUnit.MILLISECONDS.toSeconds(offsetMilliseconds);
    // to check that we haven’t lost any precision, convert back to milliseconds and compare
    if (TimeUnit.SECONDS.toMillis(offsetSeconds) != offsetMilliseconds) {
        throw new IllegalArgumentException("Millisecond precision not supported: " + offsetMilliseconds);
    }
    ZoneId tzid = ZoneOffset.ofTotalSeconds(offsetMilliseconds / 1000);

    System.out.println(tzid);
    // test the time zone we got
    System.out.println(ZonedDateTime.now(tzid));

On my computer this just printed:

+04:00
2018-01-16T19:44:22.863760+04:00

A ZoneOffset only has precision of seconds, not milliseconds, which is fine for your case of 14 400 000 milliseconds. Should you have a number of milliseconds that don’t makeup a whole number of seconds, you may want to round to whole seconds rather than throw an exception, your decision.

Only if you need to pass an old-fashioned TimeZone object to some legacy API that you cannot change or don’t want to change just now, convert the ZoneId we got above:

    TimeZone oldfashionedTimeZone = TimeZone.getTimeZone(tzid);

This will give you a TimeZone with display name GMT+04:00 and (of course) an offset of 4 hours. Beware, however, that if TimeZone doesn’t recognize the time zone you are trying to convert to (a likely case if your offset was some odd number of seconds), it will tacitly give you GMT (just one of the problems with the old classes). So you should probably check the TimeZone object you got, for example like this:

    if (oldfashionedTimeZone.getRawOffset() != offsetMilliseconds) {
        throw new IllegalStateException("Conversion to legacy TimeZone object failed");
    }

Upvotes: 2

ᴇʟᴇvᴀтᴇ
ᴇʟᴇvᴀтᴇ

Reputation: 12751

You can construct a TimeZone instance for any offset, using SimpleTimeZone:

new SimpleTimeZone(offset, "My TimeZone");

Upvotes: 2

Related Questions