You can use DateTimeFormatterBuilder.appendText()
method that takes a map with custom values.
According to javadoc, the field AMPM_OF_DAY
can have the value 0
for AM
and 1
for PM
, so you just create a map with these values and pass it to the formatter:
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.time.LocalTime;
// create map with custom values (0=AM, 1=PM, mapping to values "A" and "P")
Map<Long, String> map = new HashMap<>();
map.put(0L, "A"); // AM mapped to "A"
map.put(1L, "P"); // PM mapped to "P"
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
// hour/minute/second (change to whatever pattern you need)
.appendPattern("hh:mm:ss ")
// use the mapped values
.appendText(ChronoField.AMPM_OF_DAY, map)
// create formatter
.toFormatter();
// testing
System.out.println(formatter.format(LocalTime.of(10, 30, 45))); // 10:30:45 A
System.out.println(formatter.format(LocalTime.of(22, 30, 45))); // 10:30:45 P
The output will be:
10:30:45 A
10:30:45 P
And it also works to parse a String
:
System.out.println(LocalTime.parse("10:20:30 A", formatter)); // 10:20:30
System.out.println(LocalTime.parse("10:20:30 P", formatter)); // 22:20:30
The output will be:
10:20:30
22:20:30
If you don't control the pattern, you can look for a letter a
(the pattern corresponding to the AM/PM field) and replace it by the custom version.
One alternative is to split the format String
, using a
as the delimiter, then you don't lose the rest of the pattern:
// format with day/month/year hour:minute:second am/pm timezone
String format = "dd/MM/yyyy hh:mm:ss a z";
// split the format ("a" is the AM/PM field)
String[] formats = format.split("a");
DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder()
// first part of the pattern (everything before "a")
.appendPattern(formats[0])
// use the AM/PM mapped values
.appendText(ChronoField.AMPM_OF_DAY, map);
// if there's something after the "a", add it
if (formats.length > 1) {
builder.appendPattern(formats[1]);
}
// create formatter
DateTimeFormatter formatter = builder.toFormatter();
//test
ZonedDateTime z = ZonedDateTime.of(2017, 5, 1, 10, 20, 30, 0, ZoneId.of("America/Sao_Paulo"));
// AM replaced by "A"
System.out.println(formatter.format(z)); // 01/05/2017 10:20:30 A BRT
This algorithm also works if the a
is in the beginning or end, or even if there's no a
(in this case, I'm adding in the end, I'm not sure if it must be added if not present).
// "a" in the end
String format = "hh:mm:ss a";
DateTimeFormatter formatter = // use same code above
System.out.println(formatter.format(LocalTime.of(15, 30))); // 03:30:00 P
// "a" in the beginning
String format = "a hh:mm:ss";
DateTimeFormatter formatter = // use same code above
System.out.println(formatter.format(LocalTime.of(15, 30))); // P 03:30:00
// no "a" (field is added in the end)
String format = "HH:mm:ss";
DateTimeFormatter formatter = // use same code above
System.out.println(formatter.format(LocalTime.of(15, 30))); // 03:30:00P
You can make some improvements, like if the format doesn't contain a
, then don't add it (or add with a space before it), or whatever you want.
PS: this code doesn't handle literals (text inside '
) like "dd/MM/yyyy 'at' hh:mm:ss a"
(the "at" is a literal and shouldn't be removed/replaced).
I'm not a regex expert (so I'm not 100% sure if it works for all cases), but you can do something like this:
String[] formats = format.split("(?<!\'[^\']{0,20})a|a(?![^\']{0,20}\')");
This regex will ignore a
inside quoted text. I have to use {0,20}
quantifier because lookahead and lookbehinds (the (?
patterns) don't accept *
, so this will work if the quoted text before or after the "a" has at most 20 characters (just change this value to what you think it fits best your use cases).
Some tests:
String format = "\'Time: at\' hh:mm:ss a";
DateTimeFormatter formatter = // use same code above, but with the modified regex split
System.out.println(formatter.format(LocalTime.of(15, 30))); // Time: at 03:30:00 P
String format = "dd/MM/yyyy \'at\' hh:mm:ss a z";
DateTimeFormatter formatter = // use same code above, but with the modified regex split
ZonedDateTime z = ZonedDateTime.of(2017, 5, 1, 10, 20, 30, 0, ZoneId.of("America/Sao_Paulo"));
System.out.println(formatter.format(z)); // 01/05/2017 at 10:20:30 A BRT