How do I set the Android Calendar to nautical or military timezones

389 views Asked by At

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.

3

There are 3 answers

4
Anonymous On

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

2
user1644002 On

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);
7
Arvind Kumar Avinash On

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