0

In my Android app, I use the calendar in various time zones. This way I can adjust the app operation to local conditions.

runDate = Calendar.getInstance(); // will display tides for this date/time
useThisTZ = TimeZone.getTimeZone("US/Pacific");
runDate.setTimeZone(useThisTZ);

I need to adapt to nautical or military time zones. These are geographic rather than civil. I need to determine what identifiers I can pass into the getTimeZone function to do this. I have tried the time zone code "A" which identified the Alpha time zone (UTC + 1). However this placed the calendar into the GMT zone which would normally be called Zulu.

Does anybody know if these identifiers are available and what they might be.

user1644002
  • 3,211
  • 3
  • 19
  • 21
  • Yeah, like I said, I need the names that will be accepted by the setTimeZone call. I already have a list of military and nautical time zones. – user1644002 Sep 23 '20 at 18:49
  • If you can avoid using `Calendar` and `TimeZone`, do. Those classes are poorly designed and long outdated. Instead use `ZoneOffset` and `OffsetDateTime`, both from [java.time, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/). – Ole V.V. Sep 23 '20 at 19:15
  • There’s [a different and knowledgeable answer here](https://stackoverflow.com/a/23643847/5772882). You may find it helpful. – Ole V.V. Sep 24 '20 at 05:06
  • Also related: [Military time zones using JSR 310 (DateTime API)](https://stackoverflow.com/questions/28064988/military-time-zones-using-jsr-310-datetime-api) – Ole V.V. Sep 24 '20 at 05:09

3 Answers3

1

ZoneOffset and OffsetDateTime from java.time

Consider using java.time, the modern Java date and time API, for your date and time work. Nautical (or military) time zones are mere offsets in whole hours from UTC, so we don’t need any fancy time zone rules taking summer time (DST) and other anomalies into account, for there aren’t any. A plain ZoneOffset will do.

The time zone names are not built in. We need to code the conversion ourselves. One way to do it is through an array in which we can look up a time zone name and get the offset out:

private static final int[] offsetPerNauticalTimeZone = new int['Z' + 1];
private static final int invalid = -100;
static {
    Arrays.fill(offsetPerNauticalTimeZone, invalid);
    // The letter "Z" ("Zulu") indicates Coordinated Universal Time (UTC). 
    offsetPerNauticalTimeZone['Z'] = 0;
    // A through M denote positive offsets, but J is skipped
    for (int offset = 1; offset <= 9; offset++) {
        offsetPerNauticalTimeZone['A' - 1 + offset] = offset;
    }
    for (int offset = 10; offset <= 12; offset++) {
        offsetPerNauticalTimeZone['K' - 10 + offset] = offset;
    }
    // N through Y are the negative offsets
    for (int negatatedOffset = 1; negatatedOffset <= 12; negatatedOffset++) {
        offsetPerNauticalTimeZone['N' - 1 + negatatedOffset] = -negatatedOffset;
    }
}

Example use:

    char nauticalTimeZone = 'A'; // Set desired letter here
    
    int offsetHours = offsetPerNauticalTimeZone[nauticalTimeZone];
    if (offsetHours == invalid) {
        System.out.println("Invalid nautical time zone " + nauticalTimeZone);
    } else {
        ZoneOffset offset = ZoneOffset.ofHours(offsetHours);
        OffsetDateTime runDateTime = OffsetDateTime.now(offset);
        
        System.out.println(runDateTime);
    }

When I ran this snippet just now, the output was:

2020-09-23T20:05:52.451+01:00

You notice that the offset is +01:00 corresponding to nautical time zone A. Please try the other time zones too.

As the code stands it will throw anArrayIndexOutOfBounsException if the char is beyond upper case Z. I leave it to you to build in the necessary check.

Edit:

Well, the calendar code I am using does accept time zone names. I just need a complete list of what those are.

If that were me, I would take the opportunity to switch over from the outdated Calendar class to java.time. Also because, as I tried to indicate, when you use the old-fashioned TimeZone class, you are carrying with you everything that is needed for general time zones when all you need is an offset. If you insist on using Calendar and feeding it a TimeZone, the conversion is easy enough. Assuming your are using ThreeTenABP (see below):

        TimeZone oldfashionedTimeZone = DateTimeUtils.toTimeZone(offset);
        System.out.println(oldfashionedTimeZone.getID());

GMT+01:00

This is the time zone ID you were asking for. The others are similar, you can construct them yourself. If in doubt, run my code at get them from there. For feeding into a Calendar you don’t need the ID, of course, you have already got the TimeZone object.

Still a bit better (or not quite so bad), even if you need an old-fashioned Calendar for your legacy code, you don’t need to deal with the confusing and poorly designed TimeZone class. You may get your Calendar from this code:

        Calendar runDate = DateTimeUtils.toGregorianCalendar(
                runDateTime.atZoneSameInstant(offset));

If using Java 8 (also if using Java 8 through desugaring, I suppose), the conversions are a bit shorter still, TimeZone.getTimeZone(offset) and GregorianCalendar.from(runDateTime.atZoneSameInstant(offset)), respectively.

Question: Doesn’t java.time require Android API level 26?

java.time works nicely on both older and newer Android devices. It just requires at least Java 6.

  • In Java 8 and later and on newer Android devices (from API level 26) the modern API comes built-in.
  • In non-Android Java 6 and 7 get the ThreeTen Backport, the backport of the modern classes (ThreeTen for JSR 310; see the links at the bottom).
  • On older Android either use desugaring or the Android edition of ThreeTen Backport. It’s called ThreeTenABP. In the latter case make sure you import the date and time classes from org.threeten.bp with subpackages.

Links

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • Well, the calendar code I am using DOES accept time zone names. I just need a complete list of what those are. – user1644002 Sep 23 '20 at 19:12
  • And why such a long answer when `GMT+01:00`, `GMT+02:00`, … would suffice? Because rather than only answer the question as asked I prefer to write an answer that is more useful and helpful not only to yourself but also to other readers including non-Android Java programmers. – Ole V.V. Sep 23 '20 at 19:33
  • I have a perfectly working application that makes extensive use of the features of the Calendar and TimeZone. I need a little documentation of time zone names that will be accepted. So why would I rewrite it? – user1644002 Sep 23 '20 at 20:29
  • You can only decide that yourself, of course. In the working application that I am working on I am certainly taking opportunities for improving the code for better readability and maintainability, with getting rid of the old `Date`, `Calendar`, `TimeZone` and `SimpleDateFormat` being a priority. Because, as I said, they are poorly designed and long outdated and the code can sometimes be written *a lot* more elegantly and natiurally when we are using the modern API. The modern API is just so much nicer to work with. – Ole V.V. Sep 24 '20 at 04:11
0

I would use the modern date-time API as suggested by Ole V.V.. However, it looks like you want to do it using the legacy date-time API. A solution that comes to my mind is to build a Map of military time-zones mapped to the civil time-zones e.g. as mentioned here. After that, you can use the military time-zone letter (e.g. A, B, C etc.) to display date-time in the that time-zone.

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;

public class Main {
    public static void main(String[] args) {
        Map<String, String> tzMap = new HashMap<>();
        tzMap.put("A", "Europe/Paris");
        tzMap.put("B", "Europe/Athens");
        tzMap.put("C", "Europe/Moscow");
        // and so on...

        // Tests
        Calendar calendar = Calendar.getInstance();
        displayDateInMilitaryTZ(calendar, tzMap.get("A"));
        displayDateInMilitaryTZ(calendar, tzMap.get("B"));
        displayDateInMilitaryTZ(calendar, tzMap.get("C"));
    }

    static void displayDateInMilitaryTZ(Calendar runDate, String tz) {
        TimeZone useThisTZ = TimeZone.getTimeZone(tz);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        sdf.setTimeZone(useThisTZ);
        System.out.println(sdf.format(runDate.getTime()));
    }
}

Output:

2020-09-23 23:33:16
2020-09-24 00:33:16
2020-09-24 00:33:16
Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
  • Arvind, thank you I was considering using countries within the geographic areas to get the times there, as described in your incredibly helpful attachment. I am just a little apprehensive because there is more to time zones than time offsets, such as DST correctness. I am hoping the developers of the time coding I am using consciously built in the correct behavior into these zones. I would like to see an exhaustive list of supported time zones but I haven't found a way to see the list. – user1644002 Sep 23 '20 at 23:24
  • Using a `Map` is a fine idea. Even though your link mentions major cities, @user1644002 is correct that mapping to their time zones is incorrect, not only because you will often get summer time (DST) that the military time zone hasn’t got, you will also usually get incorrect times and offsets for historical dates. The table also has got errors. It gives New Delhi, India for GMT+5, but Delhi is at +05:30 and has used other offsets in history, but not +05:00 as far as I can recall. – Ole V.V. Sep 24 '20 at 04:28
  • If using Java 8 or later and you still need an old-fashioned `TimeZone`, I still think the best way is `TimeZone.getTimeZone(ZoneOffset.ofHours(1))`, etc. If neither using Java 8 nor desugaring nor ThreeTenABP (poor you :-) , use `TimeZone.getTimeZone("GMT+01:00")`, etc. `GMT+01:00` is [well documented](https://docs.oracle.com/javase/10/docs/api/java/util/TimeZone.html) as a *custom time zone ID*. – Ole V.V. Sep 24 '20 at 04:47
  • A different option being `Etc/GMT+1`, etc., [documented here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) and returned by `TimeZone.getAvailableIDs()`, as [suggested by Matt Johnson-Pint here](https://stackoverflow.com/a/23643847/5772882). Remeber to invert the sign. – Ole V.V. Sep 24 '20 at 05:13
  • @OleV.V. - Your answer and the comments, both are equally valuable. Probably it was that I was about to go to bed, I missed upvoting your answer last night...I've happily done it now . – Arvind Kumar Avinash Sep 24 '20 at 10:24
  • I am testing the Etc/GMT+1 approach described above. This list is from https://en.wikipedia.org/wiki/List_of_tz_database_time_zones which is what I was looking for. – user1644002 Sep 24 '20 at 12:41
  • @user1644002 - Great! Wish you success! – Arvind Kumar Avinash Sep 24 '20 at 12:59
0

Following Ole V.V. guidance this java code chooses and selects the correct time zone. This code is illustrative not efficient but it works.

            runDate = Calendar.getInstance(); 

            // select timezone geographically by longitude
            // degrees West = - longitude  0 to -180
            // degrees East = + longitude  0 to +180
            // the logic below proceeds eastward from Greenwich

            if ((longitude > -7.5) && (longitude < 7.5)) //UTC+0
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT+0"); tzName = "Zulu";       }

            else  if ((longitude > 7.5) && (longitude < 22.5)) //UTC+1
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT-1"); tzName = "Alpha";       }

            else  if ((longitude > 22.5) && (longitude < 37.5)) //UTC+2
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT-2"); tzName = "Bravo";       }

            else  if ((longitude > 37.5) && (longitude < 52.50)) //UTC+3
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT-3"); tzName = "Charlie";     }

            else  if ((longitude > 52.5) && (longitude < 67.5)) //UTC+4
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT-4"); tzName = "Delta";       }

            else  if ((longitude > 67.5) && (longitude  < 82.5)) //UTC+5
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT-5"); tzName = "Echo";        }

            else  if ((longitude > 82.5) && (longitude < 97.5)) //UTC+6
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT-6"); tzName = "Foxtrot";     }

            else  if ((longitude > 97.5) && (longitude < 112.5)) //UTC+7
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT-7"); tzName = "Golf";     }

            else  if ((longitude > 112.5) && (longitude < 127.5)) //UTC+8
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT-8"); tzName = "Hotel";     }

            else  if ((longitude > 127.5) && (longitude < 142.5)) //UTC+9
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT-9"); tzName = "India";     }

            else  if ((longitude > 142.5) && (longitude < 157.5)) //UTC+10
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT-10"); tzName = "Kilo";     }

            else  if ((longitude > 157.5) && (longitude < 172.5)) //UTC+11
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT-11"); tzName = "Lima";     }

            else  if ((longitude > 172.5) && (longitude < 180)) //UTC+12 7.5 degrees wide
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT-12"); tzName = "Mike";     }

            else  if ((longitude > -180) && (longitude < -172.5)) //UTC-12 7.5 degrees wide
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT+12"); tzName = "Yankee";     }

            else  if ((longitude > -172.5) && (longitude < -157.5)) //UTC-11
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT+11"); tzName = "X-Ray";     }

            else  if ((longitude > -157.5) && (longitude < -142.5)) //UTC-10
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT+10"); tzName = "Whiskey";     }

            else  if ((longitude > -142.5) && (longitude < -127.5)) //UTC-9
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT+9"); tzName = "Victor";     }

            else  if ((longitude > -127.5) && (longitude < -112.5)) //UTC-8
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT+8"); tzName = "Uniform";     }

            else  if ((longitude > -112.5) && (longitude < -97.5)) //UTC-7
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT+7"); tzName = "Tango";     }

            else  if ((longitude > -97.5) && (longitude < -82.5)) //UTC-6
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT+6"); tzName = "Sierra";     }

            else  if ((longitude > -82.5) && (longitude < -67.5)) //UTC-5
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT+5"); tzName = "Romeo";     }

            else  if ((longitude > -67.5) && (longitude < -52.5)) //UTC-4
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT+4"); tzName = "Quebec";     }

            else  if ((longitude > -52.5) && (longitude < -37.5)) //UTC-3
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT+3"); tzName = "Papa";     }

            else  if ((longitude > -37.5) && (longitude < -22.5)) //UTC-2
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT+2"); tzName = "Oscar";     }

            else  if ((longitude > -22.5) && (longitude < -7.5)) //UTC-1
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT+1"); tzName = "November";     }

            else
            {    useThisTZ = TimeZone.getTimeZone("Etc/GMT+0"); tzName = "Error";        }


            runDate.setTimeZone(useThisTZ);
user1644002
  • 3,211
  • 3
  • 19
  • 21
  • Thanks for sharing your solution. It may or may not be unlikely, but the longitude can fall exactly on a border, like 7.5 or 22.5. You should decode what you want to do in that corner case. I believe the offset in hours can be calculated as `Math.floor((longitude + 7.5) /15)`. After that you may find the letter (like `Alpha`, etc.) through some table lookup. – Ole V.V. Sep 26 '20 at 09:55
  • 1
    yes, I will change the tests to include the equal condition, somebody can input the exact values as their input and that would break the existing code. – user1644002 Sep 26 '20 at 10:02