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
Related
I need a different format of strings to convert to "DD.MM.YYYY".
"Thu, 3 Nov 2022 06:00:00 +0100" has to be changed to "03.11.2022"
and
"01.11.2022 20:00:00" to "01.11.2022".
All the formats are in String.
I tried doing
String pattern="DD.MM.YYYY";
DateTimeFormatter formatter=DateTimeFormatter.ofPattern(pattern);
new SimpleDateFormat(pattern).parse("01.11.2022 20:00:00")
I have also tried doing the following
java.time.LocalDateTime.parse(
item.getStartdatum(),
DateTimeFormatter.ofPattern( "DDMMYYYY" )
).format(
DateTimeFormatter.ofPattern("DD.MM.YYYY")
)
But got the error :
Exception in thread "main" java.time.format.DateTimeParseException:
Text 'Sun, 30 Oct 2022 00:30:00 +0200' could not be parsed at index 0
I tried doing the following as well
String pattern="DD.MM.YYYY";
DateFormat format = new SimpleDateFormat(pattern);
Date date = format.parse(01.11.2022 20:00:00);
However, I am not getting the correct output. How can I get my desired result?
Several things…
if you can use java.time, use it exclusively if possible (no SimpleDateFormat or similar legacy stuff)
a DateTimeFormatter can be used to parse and format Strings representing a datetime, if input and output format are different, you will need two different DateTimeFormatters
the Text 'Sun, 30 Oct 2022 00:30:00 +0200' could not be parsed at index 0 due to your try to parse it with the pattern "DD.MM.YYYY", which is wrong on several levels:
the pattern seems to expect the String to start with a numerical representation of the day of month, but it starts with Thu, an abbreviation of the name of a day of week
the symbol D means day of year, a number between 1 and 366 (in leap years, 365 otherwise)
the symbol Y means week-based year
Read more about those symbols in the JavaDocs of DateTimeFormatter
You could do the following instead:
public static void main(String[] args) {
// two example inputs
String first = "Thu, 3 Nov 2022 06:00:00 +0100";
String second = "01.11.2022 20:00:00";
// prepare a formatter for each pattern in order to parse the Strings
DateTimeFormatter dtfInFirst = DateTimeFormatter.ofPattern(
"EEE, d MMM uuuu HH:mm:ss x",
Locale.ENGLISH
);
// (second one does not have an offset from UTC, so the resulting class is different)
DateTimeFormatter dtfInSecond = DateTimeFormatter.ofPattern("dd.MM.uuuu HH:mm:ss");
// parse the Strings using the formatters
OffsetDateTime odt = OffsetDateTime.parse(first, dtfInFirst);
LocalDateTime ldt = LocalDateTime.parse(second, dtfInSecond);
// prepare a formatter, this time for output formatting
DateTimeFormatter dtfDateOnlySeparatedByDots = DateTimeFormatter.ofPattern("dd.MM.uuuu");
// extract the date part of each result of the parsing
LocalDate firstResult = odt.toLocalDate();
LocalDate secondResult = ldt.toLocalDate();
// and print it formatted using the output formatter
System.out.println(first + " ---> "
+ firstResult.format(dtfDateOnlySeparatedByDots));
System.out.println(second + " ---> "
+ secondResult.format(dtfDateOnlySeparatedByDots));
}
Which will output the conversion results as follows:
Thu, 3 Nov 2022 06:00:00 +0100 ---> 03.11.2022
01.11.2022 20:00:00 ---> 01.11.2022
The first formatter will need a Locale because of the presence of names (day of week & month). You cannot parse that using any exclusively numerical parser and the language / culture must match.<
short version
public static void main(String[] args) {
// two example inputs
String first = "Thu, 3 Nov 2022 06:00:00 +0100";
String second = "01.11.2022 20:00:00";
// prepare a custom formatter for the second pattern
DateTimeFormatter dtfInSecond = DateTimeFormatter
.ofPattern("dd.MM.uuuu HH:mm:ss");
// parse the first String by means of a built-in RFC formatter
OffsetDateTime odt = OffsetDateTime.parse(
first,
DateTimeFormatter.RFC_1123_DATE_TIME);
// parse the second String using the custom formatter
LocalDateTime ldt = LocalDateTime.parse(second, dtfInSecond);
// prepare a formatter, this time for output formatting
DateTimeFormatter dtfDateOnlySeparatedByDots = DateTimeFormatter
.ofPattern("dd.MM.uuuu");
// and print it formatted using the output formatter
System.out.println(first + " ---> "
+ odt.format(dtfDateOnlySeparatedByDots));
System.out.println(second + " ---> "
+ ldt.format(dtfDateOnlySeparatedByDots));
}
HINT:
For dates like the one mentioned in your comment…
Text '9.28.2022 6:30:00' could not be parsed at index 0
you will have to use a pattern with single-digit day of month and hour of day, probably even month of year if anything like 9.8.2022 is possible. However, you will definitely need to switch day of month and month of year because there is just no month no. 28 in a year.
Short example:
String third = "9.28.2022 6:30:00";
DateTimeFormatter dtfInThird = DateTimeFormatter
.ofPattern("M.d.uuuu H:mm:ss");
LocalDateTime ldtThird = LocalDateTime.parse(third, dtfInThird);
System.out.println(third + " ---> "
+ ldtThird.format(dtfDateOnlySeparatedByDots));
Executed in a main, this will output
9.28.2022 6:30:00 ---> 28.09.2022
The java.util date-time API and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern date-time API.
deHaar has already written a good answer. However, if you want to use a single DateTimeFormatter, you can check this answer.
For parsing, you can build a DateTimeFormatter with optional patterns and default time-zone offset value (since you do not have time-zone offset in the second date-time string) as follows:
DateTimeFormatter parser = new DateTimeFormatterBuilder()
.appendPattern("[d.M.uuuu H:m:s][EEE, d MMM uuuu H:m:s X]")
.parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
.toFormatter(Locale.ENGLISH);
where the optional patterns are in square brackets. Alternatively, you can use DateTimeFormatterBuilder#optionalStart and DateTimeFormatterBuilder#optionalEnd to specify optional patterns.
With this parser, you can parse the given date-time strings to OffsetDateTime and format it to the desired string using the following DateTimeFormatter:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.uuuu", Locale.ENGLISH);
Demo:
public class Main {
public static void main(String[] args) {
DateTimeFormatter parser = new DateTimeFormatterBuilder()
.appendPattern("[d.M.uuuu H:m:s][EEE, d MMM uuuu H:m:s X]")
.parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
.toFormatter(Locale.ENGLISH);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.uuuu", Locale.ENGLISH);
// Test
Stream.of(
"Thu, 3 Nov 2022 06:00:00 +0100",
"01.11.2022 20:00:00"
)
.map(s -> OffsetDateTime.parse(s, parser).format(formatter).toString())
.forEach(System.out::println);
;
}
}
Output:
03.11.2022
01.11.2022
Note
Make sure to check the DateTimeFormatter documentation to understand the difference between Y and y and between D and d.
You can use y instead of u but I prefer u to y with DateTimeFormatter.
Learn more about the modern Date-Time API from Trail: Date Time.
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.
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 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 have a string in the format of:
3:00 pm on Aug 28
What would be the best way to verify that a valid time and valid date is contained within this string? My first thought was to split the string and use two regexs to match a time and the other one to match that specfic date format (abbreviate month day). However I'm having a little bit of trouble with the second regex (the one for the specfic date format). How else could one go about verifying the string is in the correct format?
You can try this:
public boolean isValid( String dateStr ) {
// K: hour of the day in am/pm
// m: minute of a hour
// 'on': static text
// MMM: name of the month with tree letters
// dd: day of the month (you can use just d too)
DateFormat df = new SimpleDateFormat( "K:m a 'on' MMM dd", Locale.US );
try {
df.parse( dateStr );
return true;
} catch ( ParseException exc ) {
}
return false;
}
More about the format string here: http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html
Use java.text.SimpleDateFormat. Use a format string something like HH:mm aa 'on' MMM dd.
You may have to add yyyy to the format string and 2012 to your input.
Use SimpleDateFormat and make sure it doesn't use lenient parsing:
try {
DateFormat df = new SimpleDateFormat("h:mm a 'on' MMM dd", Locale.US);
df.setLenient(false);
Date dt = df.parse(s);
} catch (ParseException pe) {
// Wrong format
}