The problem with FormatStyle
's (AFAIK) is that they use predefined patterns. Although, it's possible to get them and manipulate/change the pattern to fit your needs.
I'm not using Android specific environment, so I'm not sure how well this code will work for you. I'm using Java JDK 1.7.0_79 and ThreeTen Backport 1.3.4. I'm also using America/New_York timezone for my tests - I guess it corresponds to EST.
I noticed some differences from your environment:
- using
FormatStyle.LONG
for japanese locale gives me 2017/01/23 13:28:37 EST
- I had to use
FormatStyle.FULL
to get 2017年1月23日 13時28分37秒 EST
But I think this doesn't invalidate my tests.
First I used java.text.DateFormat
class to get the localized pattern as a String
. Then I did some replacements to this String
, according to the configuration wanted:
- look for
HH
, hh
, H
or h
and change to use 12 or 24 hour format (I replaced keeping the same number of letters)
- remove or add
z
(or Z
): to remove or add a timezone
- avoid literals: some patterns (like in pt_BR locale) have a literal
h
after the hours (like HH'h'
which becomes 13h
), so I had to take care of this when replacing
The code to create the formatter is:
// creates a formatter with the specified style, locale and zone
// there are options to use 12 or 24 hour format and include or not a timezone
public DateTimeFormatter getFormatter(FormatStyle style, Locale locale, ZoneId zone,
boolean use24HourFormat, boolean useTimezone) {
// get the format correspondent to the style and locale
DateFormat dateFormat = DateFormat.getDateTimeInstance(style.ordinal(), style.ordinal(), locale);
// *** JDK 1.7.0_79 returns SimpleDateFormat ***
// If Android returns another type, check if it's possible to get the pattern from this type
if (dateFormat instanceof SimpleDateFormat) {
// get the pattern String for the locale
String pattern = ((SimpleDateFormat) dateFormat).toPattern();
if (use24HourFormat) {
if (pattern.contains("hh")) { // check the "hh" hour format
// hh not surrounded by ' (to avoid literals)
pattern = pattern.replaceAll("((?<!\')hh)|(hh(?!\'))", "HH");
} else { // check the "h" hour format
// h not surrounded by ' (to avoid literals)
pattern = pattern.replaceAll("((?<!\')h)|(h(?!\'))", "H");
}
} else {
if (pattern.contains("HH")) { // check the "HH" hour format
// HH not surrounded by ' (to avoid literals)
pattern = pattern.replaceAll("((?<!\')HH)|(HH(?!\'))", "hh");
} else { // check the "H" hour format
// H not surrounded by ' (to avoid literals)
pattern = pattern.replaceAll("((?<!\')H)|(H(?!\'))", "h");
}
}
if (useTimezone) {
// checking if already contains a timezone (the naive way)
if (!pattern.contains("z") && !pattern.contains("Z")) {
// I'm adding z in the end, but choose whatever pattern you want for the timezone (it can be Z, zzz, and so on)
pattern += " z";
}
} else {
// 1 or more (z or Z) not surrounded by ' (to avoid literals)
pattern = pattern.replaceAll("((?<!\')[zZ]+)|([zZ]+(?!\'))", "");
}
// create the formatter for the locale and zone, with the customized pattern
return DateTimeFormatter.ofPattern(pattern, locale).withZone(zone);
}
// can't get pattern string, return the default formatter for the specified style/locale/zone
return DateTimeFormatter.ofLocalizedDateTime(style).withLocale(locale).withZone(zone);
}
Some usage examples (my default Locale
is pt_BR - Brazilian Portuguese):
ZoneId zone = ZoneId.of("America/New_York");
Instant instant = ZonedDateTime.of(2017, 1, 23, 13, 28, 37, 0, zone).toInstant();
FormatStyle style = FormatStyle.FULL;
// US locale, 24-hour format, with timezone
DateTimeFormatter formatter = getFormatter(style, Locale.US, zone, true, true);
System.out.println(formatter.format(instant)); // Monday, January 23, 2017 13:28:37 PM EST
// US locale, 24-hour format, without timezone
formatter = getFormatter(style, Locale.US, zone, true, false);
System.out.println(formatter.format(instant)); // Monday, January 23, 2017 13:28:37 PM
// US locale, 12-hour format, with timezone
formatter = getFormatter(style, Locale.US, zone, false, true);
System.out.println(formatter.format(instant)); // Monday, January 23, 2017 1:28:37 PM EST
// US locale, 12-hour format, without timezone
formatter = getFormatter(style, Locale.US, zone, false, false);
System.out.println(formatter.format(instant)); // Monday, January 23, 2017 1:28:37 PM
// japanese locale, 24-hour format, with timezone
formatter = getFormatter(style, Locale.JAPAN, zone, true, true);
System.out.println(formatter.format(instant)); // 2017年1月23日 13時28分37秒 EST
// japanese locale, 24-hour format, without timezone
formatter = getFormatter(style, Locale.JAPAN, zone, true, false);
System.out.println(formatter.format(instant)); // 2017年1月23日 13時28分37秒
// japanese locale, 12-hour format, with timezone
formatter = getFormatter(style, Locale.JAPAN, zone, false, true);
System.out.println(formatter.format(instant)); // 2017年1月23日 1時28分37秒 EST
// japanese locale, 12-hour format, without timezone
formatter = getFormatter(style, Locale.JAPAN, zone, false, false);
System.out.println(formatter.format(instant)); // 2017年1月23日 1時28分37秒
// pt_BR locale, 24-hour format, with timezone
formatter = getFormatter(style, Locale.getDefault(), zone, true, true);
System.out.println(formatter.format(instant)); // Segunda-feira, 23 de Janeiro de 2017 13h28min37s EST
// pt_BR locale, 24-hour format, without timezone
formatter = getFormatter(style, Locale.getDefault(), zone, true, false);
System.out.println(formatter.format(instant)); // Segunda-feira, 23 de Janeiro de 2017 13h28min37s
// pt_BR locale, 12-hour format, with timezone
formatter = getFormatter(style, Locale.getDefault(), zone, false, true);
System.out.println(formatter.format(instant)); // Segunda-feira, 23 de Janeiro de 2017 01h28min37s EST
// pt_BR locale, 12-hour format, without timezone
formatter = getFormatter(style, Locale.getDefault(), zone, false, false);
System.out.println(formatter.format(instant)); // Segunda-feira, 23 de Janeiro de 2017 01h28min37s
Notes:
- The method
DateFormat.getDateTimeInstance
returns a SimpleDateFormat
, but I'm not sure if it works the same way in Android. You can check if it returns a different type and if it's possible to get the pattern String
from this class (if it's not, then I don't know another way of doing it).
- I created options to change the hour format (12 or 24) and include or remove a timezone. But you can create as many options as you need - once you have the pattern
String
, you can do anything with it
- My regular expressions are (IMO) a little bit ugly. But I'm not a regex expert and I'm not sure how they can be improved. I've tested them with some locales and they seem to be fine, but there might be some particular case where they fail (although I haven't tested enough to find it)
- If you want to add the offset (like
-05:00
) you can use the xxx
pattern (instead of z
). If you want the offset with GMT (like GMT-05:00
) you can use the ZZZZ
pattern.
- I'm not sure if all patterns have the
z
as the timezone (they can use Z
or x
), so you might change the code to look for the other patterns. In my tests I didn't find anything different from z
, but anyway I'd recommend a double check on this just to make sure.
- I'm checking if the pattern already has a timezone by doing
pattern.contains("z")
- a very naive/silly way because it doesn't handle a z
literal (inside quotes). Maybe this could be changed to use a regex as well (although I didn't find a locale with a pattern that has a z
literal).