I checked the SimpleDateFormat javadoc, but I am not able to find a way to parse the ordinal indicator in a date format like this:
Feb 13th 2015 9:00AM
I tried "MMM dd yyyy hh:mma", but the days have to be in number for it to be correct?
Is it possible to parse the "13th" date using a SimpleDateFormat without having to truncate the string?
Java's SimpleDateFormat doesn't support an ordinal suffix, but the ordinal suffix is just eye candy - it is redundant and can easily be removed to allow a straightforward parse:
Date date = new SimpleDateFormat("MMM dd yyyy hh:mma")
.parse(str.replaceAll("(?<=\\d)(st|nd|rd|th)", ""));
The replace regex is so simple because those sequences won't appear anywhere else in a valid date.
To handle any language that appends any length of ordinal indicator characters from any language as a suffix:
Date date = new SimpleDateFormat("MMM dd yyyy hh:mma")
.parse(str.replaceAll("(?<=\\d)(?=\\D* \\d+ )\\p{L}+", ""));
Some languages, eg Mandarin, prepend their ordinal indicator, but that could be handled too using an alternation - left as an exercise for the reader :)
Java 8 answer (and Java 6 and 7) (because when this question was asked in 2015, the replacement for SimpleDateFormat was already out):
DateTimeFormatter parseFormatter = DateTimeFormatter
.ofPattern("MMM d['st']['nd']['rd']['th'] uuuu h:mma", Locale.ENGLISH);
LocalDateTime dateTime = LocalDateTime.parse(dateTimeString, parseFormatter);
With the sample date from the question this yiedls:
2015-02-13T09:00
In the format pattern [] denotes optional parts and '' denotes literal parts. So the pattern says that the number may be followed by st, nd, rd or th.
To use this in Java 6 or 7 you need ThreeTen Backport. Or for Android ThreeTenABP.
Since those suffixes are special for English, and other languages/locales have completely other usages for writing dates and times (also they don’t use AM/PM), I believe that unless you have other requirements, you should try to implement this for English dates and times only. Also you should give an English speaking locale explicitly so it will work independently of the locale setting of your computer or JVM.
I have tried to combine the best parts of answers by Hugo and myself to a duplicate question. Under that duplicate question there are still more java 8 answers. One limitation of the above code is it doesn’t have very strict validation: you will get away with Feb 13rd and even Feb 13stndrdth.
Edit: My own favourite among my answers on ordinal indicators is this one. It’s about formatting, but the formatter I present there works fine for parsing too.
In case someone finds it useful: DateTimeFormatter builder. This formatter allows you to format and to parse UK dates with ordinal suffixes (eg. "1st January 2017"):
public class UkDateFormatterBuilder
{
/**
* The UK date formatter that formats a date without an offset, such as '14th September 2020' or '1st January 2017'.
* #return an immutable formatter which uses the {#link ResolverStyle#SMART SMART} resolver style. It has no override chronology or zone.
*/
public DateTimeFormatter build()
{
return new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.parseLenient()
.appendText(DAY_OF_MONTH, dayOfMonthMapping())
.appendLiteral(' ')
.appendText(MONTH_OF_YEAR, monthOfYearMapping())
.appendLiteral(' ')
.appendValue(YEAR, 4)
.toFormatter(Locale.UK);
}
private Map<Long, String> monthOfYearMapping()
{
Map<Long, String> monthOfYearMapping = new HashMap<>();
monthOfYearMapping.put(1L, "January");
monthOfYearMapping.put(2L, "February");
monthOfYearMapping.put(3L, "March");
monthOfYearMapping.put(4L, "April");
monthOfYearMapping.put(5L, "May");
monthOfYearMapping.put(6L, "June");
monthOfYearMapping.put(7L, "July");
monthOfYearMapping.put(8L, "August");
monthOfYearMapping.put(9L, "September");
monthOfYearMapping.put(10L, "October");
monthOfYearMapping.put(11L, "November");
monthOfYearMapping.put(12L, "December");
return monthOfYearMapping;
}
private Map<Long, String> dayOfMonthMapping()
{
Map<Long, String> suffixes = new HashMap<>();
for (int day=1; day<=31; day++)
{
suffixes.put((long)day, String.format("%s%s", (long) day, dayOfMonthSuffix(day)));
}
return suffixes;
}
private String dayOfMonthSuffix(final int day)
{
Preconditions.checkArgument(day >= 1 && day <= 31, "Illegal day of month: " + day);
if (day >= 11 && day <= 13)
{
return "th";
}
switch (day % 10)
{
case 1: return "st";
case 2: return "nd";
case 3: return "rd";
default: return "th";
}
}
}
Plus a fragment of the test class:
public class UkDateFormatterBuilderTest
{
DateTimeFormatter formatter = new UkDateFormatterBuilder().build();
#Test
public void shouldFormat1stJanuaryDate()
{
final LocalDate date = LocalDate.of(2017, 1, 1);
final String formattedDate = date.format(formatter);
Assert.assertEquals("1st January 2017", formattedDate);
}
#Test
public void shouldParse1stJanuaryDate()
{
final String formattedDate = "1st January 2017";
final LocalDate parsedDate = LocalDate.parse(formattedDate, formatter);
Assert.assertEquals(LocalDate.of(2017, 1, 1), parsedDate);
}
}
PS. I used Greg Mattes' solution for ordinal suffixes from here:
How do you format the day of the month to say "11th", "21st" or "23rd" in Java? (ordinal indicator)
Well, no need to replace the text. The DateTimeFormatterBuilder is able to parse this as well.
First, we need to create a Map which maps day-of-month against their day-of-month-with-ordinal-suffix. That is because unfortunately, there is no standard thing, as far as I know.
static final Map<Long, String> ORDINAL_SUFFIX_MAP;
static {
Map<Long, String> map = new HashMap<>();
for (int i = 1; i <= 31; i++) {
String suffix = switch (i) {
case 1, 21, 31 -> "st";
case 2, 22 -> "nd";
case 3, 23 -> "rd";
default -> "th";
};
map.put((long) i, i + suffix);
}
ORDINAL_SUFFIX_MAP = Map.copyOf(map);
}
Then we can utilize the DateTimeFormatterBuilder as follows:
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern(firstPartOfYourPattern)
.appendText(ChronoField.DAY_OF_MONTH, ORDINAL_SUFFIX_MAP)
.appendPattern(lastPartOfYourPattern)
.toFormatter(Locale.ROOT);
LocalDateTime result = LocalDateTime.parse(str, formatter);
You should be using RuleBasedNumberFormat. It works perfectly and it's respectful of the Locale.
Related
This question already has answers here:
Change date format in a Java string
(22 answers)
how to parse output of new Date().toString()
(4 answers)
Closed 3 years ago.
I have a string "Mon Jan 01 00:00:00 AEDT 1990" and I need to convert it into the format "yyyyMMdd" so in this case it would be "19900101".
I think it's possible to do this with Regular Expressions so that I could pull out the year, month(but would need to convert Jan to 01 and etc) and day from the string but I am not well versed in Regular Expressions. Anyone have any ideas?
tl;dr
Regex is overkill.
Here is a one-liner solution using java.time classes built into Java.
ZonedDateTime // Represent a moment as seen through the wall-clock time used by the people of a certain region (a time zone).
.parse( // Parse the input text.
"Mon Jan 01 00:00:00 AEDT 1990" ,
DateTimeFormatter.ofPattern(
"EEE MMM dd HH:mm:ss z uuuu" , // Specify a custom formatting pattern to match our input.
Locale.US // Specify a `Locale` for the human language to use in translating the name of month& day-of-week.
) // Returns a `DateTimeFormatter` object.
) // Returns a `ZonedDateTime` object.
.toLocalDate() // Extract the date, without time-of-day and without time zone.
.format( // Generate text to represent the value of our `LocalDate` object.
DateTimeFormatter.BASIC_ISO_DATE // Use the predefined formatting pattern YYYYMMDD.
) // Returns a String.
19900101
java.time
Regex is overkill for this.
The modern approach uses java.time classes.
Specify a custom formatting pattern to fit your input.
Specify a locale to facilitate translating the name of day-of-week and name of month.
ZonedDateTime
Parse as a ZonedDateTime, a moment as seen through the wall-clock time used by the people of a specific region (a time zone).
String input = "Mon Jan 01 00:00:00 AEDT 1990";
Locale locale = Locale.US;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "EEE MMM dd HH:mm:ss z uuuu" , locale );
ZonedDateTime zdt = ZonedDateTime.parse( input , f );
System.out.println( "zdt: " + zdt );
zdt: 1990-01-01T00:00+11:00[Australia/Sydney]
By the way, your input string is in a terrible format. It uses the 2-4 character pseudo-zones that are not actual time zones, not standardized, and are not unique! Another problem is depending on English. And it is difficult to parse. Educate the people publishing your data about the beauty of the ISO 8601 standard, created for exchanging date-time values as text.
LocalDate
You want only the date. So extract a LocalDate.
LocalDate ld = zdt.toLocalDate() ; // Extract only the date, leaving behind the time-of-day and the time zone.
Your desired output format has already been defined in the DateTimeFormatter class. The standard ISO 8601 format for a date is YYYY-MM-DD. A variation of that is known as "Basic" meaning it minimizes the use of delimiters: YYYYMMDD.
String output = ld.format( DateTimeFormatter.BASIC_ISO_DATE ) ;
19900101
Check if something like this helps
//date string
String soTime = "Mon Jan 04 12:30:23 AEDT 1990";
//Format
SimpleDateFormat so = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy");
SimpleDateFormat desiredFormat = new SimpleDateFormat("yyyyMMdd");
desiredFormat.setTimeZone(TimeZone.getTimeZone("Australia/Sydney"));
Date sodate = so.parse(soTime);
System.out.println("DAY : " + desiredFormat.format(sodate));
Parse date with AEDT and AEST time zone in java
Now assuming that each month and day names inside each passed string is matching one of enum name values (i.e "Mar" matches the value of field name in Month.MARCH, while "Marc" or "March" do not) and the format of the sample string you gave us is truly consistent, as in it is no subject to change during runtime and will always remain <day-name> <month> <day> <time> <zone> <year> where year is always a 4 digit number, the following code should answer give you exactly what you want:
Main Class
public static void main(String[] args) {
String time = "Mon Jul 05 00:00:00 AEDT 1990";
int result = CustomDateFormat.parseToInt(time);
System.out.println("Parsed in format [yyyyMMdd]: " + result);
}
CustomDateFormat Class
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CustomDateFormat {
private static final Pattern STANDARD_PATTERN =
Pattern.compile("^(?:[a-zA-Z]{3})\\s([a-zA-Z]{3})\\s([0-9]{2}).*([0-9]{4})");
/*
* This is just in case you want
* the name of the day as well
*/
public enum Day {
MONDAY("Mon", "Monday"),
TUESDAY("Tue", "Tuesday"),
WEDNESDAY("Wed", "Wednesday"),
THURSDAY("Thu", "Thursday"),
FRIDAY("Fri", "Friday"),
SATURDAY("Sat", "Saturday"),
SUNDAY("Sun", "Sunday");
final String shortName;
final String fullName;
Day(String name1, String name2) {
this.shortName = name1;
this.fullName = name2;
}
public static String getFullName(String alias) {
for (Day d : Day.values()) {
if (d.shortName.equals(alias))
return d.fullName;
}
return "";
}
}
public enum Month {
JANUARY("Jan", 1), FEBRUARY("Feb", 2),
MARCH("Mar", 3), APRIL("Apr", 4),
MAY("May", 5), JUNE("Jun", 6),
JULY("Jul", 7), AUGUST("Aug", 8),
SEPTEMBER("Sep", 9), OCTOBER("Oct", 10),
NOVEMBER("Nov", 11), DECEMBER("Dec", 12);
final String name;
final int value;
Month(String name, int value) {
this.name = name;
this.value = value;
}
public static int getMonth(String month) {
for (Month m : Month.values()) {
if (m.name.equals(month))
return m.value;
}
return 0;
}
}
public static int parseToInt(String date) {
System.out.println("Parsing date: " + date);
Matcher matcher = STANDARD_PATTERN.matcher(date);
if (matcher.find() && matcher.groupCount() == 3)
{
int month = Month.getMonth(matcher.group(1));
int day = Integer.valueOf(matcher.group(2));
int year = Integer.valueOf(matcher.group(3));
if (day == 0 || month == 0) {
throw new IllegalStateException("Unable to parse day or month from date " + date);
}
else return Integer.valueOf(year + "0" + month + "0" + day);
}
else throw new IllegalStateException("Unable to parse date " + date);
}
}
Output
Parsing date: Mon Jul 05 00:00:00 AEDT 1990
Parsed in format [yyyyMMdd]: 19900705
Let me know if this meets your requirements and if any other conditions need to be met or special case scenarios considered. It's a fairly simple implementation so it should take no time to adjust it to more specific needs.
EDIT: Fix some implementation mistakes, change sample string to a custom one and remove redundant output line.
I have the following string that represents a date like this "20190123" and I want to to convert it into this format "25 Dic 2019" the month name should be in Spanish and the first letter should be in uppercase so far I have this
String input = "12252013";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "MMddyyyy" );
LocalDate localDate = LocalDate.parse( input , formatter );
Locale spanishLocale=new Locale("es", "ES");
String dateInSpanish=localDate.format(DateTimeFormatter.ofPattern("dd MMM, yyyy",spanishLocale));
System.out.println("'2016-01-01' in Spanish: "+dateInSpanish);
this prints "25 dic, 2013" I want it to be like this "25 Dic 2019"
DateTimeFormatter uses the month names and capitalization according to the locale rules.
In English its mandatory to have the first letter of month Capitalized which is not the case in spanish. For spanish it is mandatory to have it as small letters as per RAE
You will have to use the class DateFormatSymbols to override the month names like below OR just have to convert the final string's in your case dateInspanish first letter after the first space to caps by writing some code.
Solution only applicable to Java7 or lower. For Java 8 and higher check out #Ole V.V. 's Answer
String input = "12252013";
Locale spanishLocale=new Locale("es", "ES");
DateFormatSymbols sym = DateFormatSymbols.getInstance(spanishLocale);
sym.setMonths(new String[]{"Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre" });
sym.setShortMonths(new String[]{"Ene","Feb","Mar","Abr","May","Jun","Jul","Ago","Sep","Oct","Nov","Dic" });
DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, spanishLocale);
SimpleDateFormat sdf = (SimpleDateFormat) df;
sdf.setDateFormatSymbols(sym);
Date inputDate = new SimpleDateFormat("MMddyyyy").parse(input);
String dateInSpanish = sdf.format(inputDate);
System.out.println(dateInSpanish);
Feature, not a bug
The proper localization for Spanish in Spain uses lowercase for month name.
Your desire for the first letter to be uppercase is incorrect. If you insist on that result, you will need to hard-code a solution rather than rely on the automatic localization feature.
This is the discouraged answer! I suppose that you speak and write Spanish better than I do, but my understanding is that Spanish — like possibly most of the languages of the world — uses a small first letter in month names. Of course you write “Diciembre es un mes frío” (December is a cold month; from Google Translate), but in the middle of “25 dic, 2013” you need a small d.
In any case, Java can of course produce incorrect output if you instruct it to produce incorrect output. So if your users insist:
Locale spanishLocale = new Locale("es", "ES");
Map<Long, String> monthAbbreviations = Stream.of(Month.values())
.collect(Collectors.toMap(m -> Long.valueOf(m.getValue()),
m -> {
String abbrev = m.getDisplayName(TextStyle.SHORT, spanishLocale);
// Capitalize first letter against Spanish usage
return abbrev.substring(0, 1).toUpperCase(spanishLocale)
+ abbrev.substring(1);
}));
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("dd ")
.appendText(ChronoField.MONTH_OF_YEAR, monthAbbreviations)
.appendPattern(", yyyy")
.toFormatter(spanishLocale);
LocalDate localDate = LocalDate.of(2013, Month.DECEMBER, 25);
String dateInSpanish=localDate.format(formatter);
System.out.println("'2013-12-25' in Spanish with non-current capitalization: " + dateInSpanish);
'2013-12-25' in Spanish with non-current capitalization: 25 Dic., 2013
If you don’t want the dot signifying the abbreviation, modify the code to remove that too.
Link: Months of the year in many different languages
dateString = "14/feb/2020";
dateString = UtilsDate.capitalizeDate(dateString);
...
public static String capitalizeDate(String dateString) {
StringBuilder dateStringBuilder = new StringBuilder(dateString.toLowerCase());
for (int i = 0; i < dateStringBuilder.length(); i++) {
char c = dateStringBuilder.charAt(i);
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
dateStringBuilder.setCharAt(i, Character.toUpperCase(dateStringBuilder.charAt(i)));
break;
}
}
return dateStringBuilder.toString();
}
How to convert a date having the following format
September 22nd 2015, 10:39:42 am
to
09/22/2015 10:39:42
in Java 8?
My current code:
String value = "September 22nd 2015, 10:39:42 am";
String format = "dd/MM/yyyy HH:mm:ss";
SimpleDateFormat sdf = new SimpleDateFormat(format);
try {
Date date = sdf.parse(value);
System.out.println(date);
System.out.println(sdf.format(date));
}
catch (ParseException ex) {
ex.printStackTrace();
}
The tricky part of the format is to handle ordinal numbers (like 22nd), i.e. handle the right suffix. There is not built-in pattern. For this, we have to build our own DateTimeFormatter with the help of DateTimeFormatterBuilder.
DateTimeFormatterBuilder has a method appendText(field, textLookup) whose goal is to look for the read text in the given map and replace it by the key associated to this value. This means we need to build a Map of all possibles days (1 to 31) with their corresponding suffix.
I took the conversion code from this answer.
We also need to make sure to parse the AM/PM identifier ignoring the case (by default, it looks for AM and PM in uppercase but yours are in lowercase). This is done by calling parseCaseInsensitive before appending the pattern for this.
private static final Map<Long, String> DAYS_LOOKUP =
IntStream.rangeClosed(1, 31).boxed().collect(toMap(Long::valueOf, i -> getOrdinal(i)));
public static void main(String[] args) throws Exception {
DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendPattern("MMMM")
.appendLiteral(" ")
.appendText(ChronoField.DAY_OF_MONTH, DAYS_LOOKUP)
.appendLiteral(" ")
.appendPattern("yyyy")
.appendLiteral(", ")
.appendPattern("hh")
.appendLiteral(":")
.appendPattern("mm")
.appendLiteral(":")
.appendPattern("ss")
.appendLiteral(" ")
.parseCaseInsensitive()
.appendPattern("a")
.toFormatter(Locale.ENGLISH);
LocalDateTime dateTime = formatter.parse("September 22nd 2015, 10:39:42 am", LocalDateTime::from);
String text = DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss").format(dateTime);
System.out.println(text);
}
private static String getOrdinal(int n) {
if (n >= 11 && n <= 13) {
return n + "th";
}
switch (n % 10) {
case 1: return n + "st";
case 2: return n + "nd";
case 3: return n + "rd";
default: return n + "th";
}
}
Here a shorter Java-8-only solution without an external library:
DateTimeFormatter formatter =
DateTimeFormatter.ofPattern(
"MMMM d['st']['nd']['rd']['th'] yyyy, hh:mm:ss a", Locale.ENGLISH);
formatter =
new DateTimeFormatterBuilder().parseCaseInsensitive().append(formatter).toFormatter();
LocalDateTime dateTime =
formatter.parse("September 22nd 2015, 10:39:42 am", LocalDateTime::from);
String text = DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss").format(dateTime);
System.out.println(text); // 09/22/2015 10:39:42
There is only one caveat: The suggested parser might also accept funny inputs like "...22ndst..." etc. but I think this can be neglected here.
You need 2 date formats (and essentially you need 2 steps to perform the task) :
Parsing the date September 22nd 2015, 10:39:42 am using a relevant date format string in order to get/convert it to date object
Formatting the date object for your desired dd/MM/yyyy HH:mm:ss format to get the date output.
I am leaving the implementation details for you to learn and explore.
Tutorial for parsing & formatting using the new Date & Time API
I checked the SimpleDateFormat javadoc, but I am not able to find a way to parse the ordinal indicator in a date format like this:
Feb 13th 2015 9:00AM
I tried "MMM dd yyyy hh:mma", but the days have to be in number for it to be correct?
Is it possible to parse the "13th" date using a SimpleDateFormat without having to truncate the string?
Java's SimpleDateFormat doesn't support an ordinal suffix, but the ordinal suffix is just eye candy - it is redundant and can easily be removed to allow a straightforward parse:
Date date = new SimpleDateFormat("MMM dd yyyy hh:mma")
.parse(str.replaceAll("(?<=\\d)(st|nd|rd|th)", ""));
The replace regex is so simple because those sequences won't appear anywhere else in a valid date.
To handle any language that appends any length of ordinal indicator characters from any language as a suffix:
Date date = new SimpleDateFormat("MMM dd yyyy hh:mma")
.parse(str.replaceAll("(?<=\\d)(?=\\D* \\d+ )\\p{L}+", ""));
Some languages, eg Mandarin, prepend their ordinal indicator, but that could be handled too using an alternation - left as an exercise for the reader :)
Java 8 answer (and Java 6 and 7) (because when this question was asked in 2015, the replacement for SimpleDateFormat was already out):
DateTimeFormatter parseFormatter = DateTimeFormatter
.ofPattern("MMM d['st']['nd']['rd']['th'] uuuu h:mma", Locale.ENGLISH);
LocalDateTime dateTime = LocalDateTime.parse(dateTimeString, parseFormatter);
With the sample date from the question this yiedls:
2015-02-13T09:00
In the format pattern [] denotes optional parts and '' denotes literal parts. So the pattern says that the number may be followed by st, nd, rd or th.
To use this in Java 6 or 7 you need ThreeTen Backport. Or for Android ThreeTenABP.
Since those suffixes are special for English, and other languages/locales have completely other usages for writing dates and times (also they don’t use AM/PM), I believe that unless you have other requirements, you should try to implement this for English dates and times only. Also you should give an English speaking locale explicitly so it will work independently of the locale setting of your computer or JVM.
I have tried to combine the best parts of answers by Hugo and myself to a duplicate question. Under that duplicate question there are still more java 8 answers. One limitation of the above code is it doesn’t have very strict validation: you will get away with Feb 13rd and even Feb 13stndrdth.
Edit: My own favourite among my answers on ordinal indicators is this one. It’s about formatting, but the formatter I present there works fine for parsing too.
In case someone finds it useful: DateTimeFormatter builder. This formatter allows you to format and to parse UK dates with ordinal suffixes (eg. "1st January 2017"):
public class UkDateFormatterBuilder
{
/**
* The UK date formatter that formats a date without an offset, such as '14th September 2020' or '1st January 2017'.
* #return an immutable formatter which uses the {#link ResolverStyle#SMART SMART} resolver style. It has no override chronology or zone.
*/
public DateTimeFormatter build()
{
return new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.parseLenient()
.appendText(DAY_OF_MONTH, dayOfMonthMapping())
.appendLiteral(' ')
.appendText(MONTH_OF_YEAR, monthOfYearMapping())
.appendLiteral(' ')
.appendValue(YEAR, 4)
.toFormatter(Locale.UK);
}
private Map<Long, String> monthOfYearMapping()
{
Map<Long, String> monthOfYearMapping = new HashMap<>();
monthOfYearMapping.put(1L, "January");
monthOfYearMapping.put(2L, "February");
monthOfYearMapping.put(3L, "March");
monthOfYearMapping.put(4L, "April");
monthOfYearMapping.put(5L, "May");
monthOfYearMapping.put(6L, "June");
monthOfYearMapping.put(7L, "July");
monthOfYearMapping.put(8L, "August");
monthOfYearMapping.put(9L, "September");
monthOfYearMapping.put(10L, "October");
monthOfYearMapping.put(11L, "November");
monthOfYearMapping.put(12L, "December");
return monthOfYearMapping;
}
private Map<Long, String> dayOfMonthMapping()
{
Map<Long, String> suffixes = new HashMap<>();
for (int day=1; day<=31; day++)
{
suffixes.put((long)day, String.format("%s%s", (long) day, dayOfMonthSuffix(day)));
}
return suffixes;
}
private String dayOfMonthSuffix(final int day)
{
Preconditions.checkArgument(day >= 1 && day <= 31, "Illegal day of month: " + day);
if (day >= 11 && day <= 13)
{
return "th";
}
switch (day % 10)
{
case 1: return "st";
case 2: return "nd";
case 3: return "rd";
default: return "th";
}
}
}
Plus a fragment of the test class:
public class UkDateFormatterBuilderTest
{
DateTimeFormatter formatter = new UkDateFormatterBuilder().build();
#Test
public void shouldFormat1stJanuaryDate()
{
final LocalDate date = LocalDate.of(2017, 1, 1);
final String formattedDate = date.format(formatter);
Assert.assertEquals("1st January 2017", formattedDate);
}
#Test
public void shouldParse1stJanuaryDate()
{
final String formattedDate = "1st January 2017";
final LocalDate parsedDate = LocalDate.parse(formattedDate, formatter);
Assert.assertEquals(LocalDate.of(2017, 1, 1), parsedDate);
}
}
PS. I used Greg Mattes' solution for ordinal suffixes from here:
How do you format the day of the month to say "11th", "21st" or "23rd" in Java? (ordinal indicator)
Well, no need to replace the text. The DateTimeFormatterBuilder is able to parse this as well.
First, we need to create a Map which maps day-of-month against their day-of-month-with-ordinal-suffix. That is because unfortunately, there is no standard thing, as far as I know.
static final Map<Long, String> ORDINAL_SUFFIX_MAP;
static {
Map<Long, String> map = new HashMap<>();
for (int i = 1; i <= 31; i++) {
String suffix = switch (i) {
case 1, 21, 31 -> "st";
case 2, 22 -> "nd";
case 3, 23 -> "rd";
default -> "th";
};
map.put((long) i, i + suffix);
}
ORDINAL_SUFFIX_MAP = Map.copyOf(map);
}
Then we can utilize the DateTimeFormatterBuilder as follows:
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern(firstPartOfYourPattern)
.appendText(ChronoField.DAY_OF_MONTH, ORDINAL_SUFFIX_MAP)
.appendPattern(lastPartOfYourPattern)
.toFormatter(Locale.ROOT);
LocalDateTime result = LocalDateTime.parse(str, formatter);
You should be using RuleBasedNumberFormat. It works perfectly and it's respectful of the Locale.
I need to print a DateTime in the form of, for example, Wednesday, January 9th, where the day of month automatically gets the proper suffix, e.g. January 2 would be January 2nd. How can I get a DateTimeFormatter that does this?
There is no support for this in Joda, but with some limitations, you can use the ICU library, since it includes localized rules for formatting ordinal numbers:
import com.ibm.icu.text.RuleBasedNumberFormat;
import com.ibm.icu.text.SimpleDateFormat;
...
SimpleDateFormat sdf =
new SimpleDateFormat("EEEE, MMMM d", Locale.ENGLISH);
sdf.setNumberFormat(
new RuleBasedNumberFormat(
Locale.ENGLISH, RuleBasedNumberFormat.ORDINAL));
System.out.println(sdf.format(new Date()));
Note that you can only specify one NumberFormat instance for the SimpleDateFormat instance, so that this approach only works if the "day of month" is the only number in the date pattern. Adding "yyyy" to the date pattern will e.g. format the year as "2,013th".
The ICU classes interface with the Date and Calendar classes from the standard API, so if you really have to use Joda in the first place, you would have to create a java.util.Date from your Joda DateTime instance.
In Joda, for simply getting the proper suffix for the day of month, something as simple as the following should be sufficient:
String dayOfMonth = now.dayOfMonth().getAsText();
String suffix = "";
if(dayOfMonth.endsWith("1")) suffix = "st";
if(dayOfMonth.endsWith("2")) suffix = "nd";
if(dayOfMonth.endsWith("3")) suffix= "rd";
if(dayOfMonth.endsWith("0") || dayOfMonth.endsWith("4") || dayOfMonth.endsWith("5") || dayOfMonth.endsWith("6")
|| dayOfMonth.endsWith("7") || dayOfMonth.endsWith("8") || dayOfMonth.endsWith("9")) suffix = "th";
I dont like the solution of using another library, so I solve this using a regular expression to preprocess the string and remove the ordinal suffix
val dateString1 = "6th December 2016"
dateString1.replaceFirst("^(\\d+).*? (\\w+ \\d+)", "$1 $2")
val dtf = DateTimeFormat.forPattern("dd MMMM yyyy").withLocale(Locale.ENGLISH)
val d1 = dtf.parseLocalDate(cured)
now d1 should be d1: org.joda.time.LocalDate = 2016-12-06