Determine if a String is a valid date before parsing - java

I have this situation where I am reading about 130K records containing dates stored as String fields. Some records contain blanks (nulls), some contain strings like this: 'dd-MMM-yy' and some contain this 'dd/MM/yyyy'.
I have written a method like this:
public Date parsedate(String date){
if(date !== null){
try{
1. create a SimpleDateFormat object using 'dd-MMM-yy' as the pattern
2. parse the date
3. return the parsed date
}catch(ParseException e){
try{
1. create a SimpleDateFormat object using 'dd/MM/yyy' as the pattern
2. parse the date
3. return parsed date
}catch(ParseException e){
return null
}
}
}else{
return null
}
}
So you may have already spotted the problem. I am using the try .. catch as part of my logic. It would be better is I can determine before hand that the String actually contains a parseable date in some format then attempt to parse it.
So, is there some API or library that can help with this? I do not mind writing several different Parse classes to handle the different formats and then creating a factory to select the correct6 one, but, how do I determine which one?
Thanks.

See Lazy Error Handling in Java for an overview of how to eliminate try/catch blocks using an Option type.
Functional Java is your friend.
In essence, what you want to do is to wrap the date parsing in a function that doesn't throw anything, but indicates in its return type whether parsing was successful or not. For example:
import fj.F; import fj.F2;
import fj.data.Option;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import static fj.Function.curry;
import static fj.Option.some;
import static fj.Option.none;
...
F<String, F<String, Option<Date>>> parseDate =
curry(new F2<String, String, Option<Date>>() {
public Option<Date> f(String pattern, String s) {
try {
return some(new SimpleDateFormat(pattern).parse(s));
}
catch (ParseException e) {
return none();
}
}
});
OK, now you've a reusable date parser that doesn't throw anything, but indicates failure by returning a value of type Option.None. Here's how you use it:
import fj.data.List;
import static fj.data.Stream.stream;
import static fj.data.Option.isSome_;
....
public Option<Date> parseWithPatterns(String s, Stream<String> patterns) {
return stream(s).apply(patterns.map(parseDate)).find(isSome_());
}
That will give you the date parsed with the first pattern that matches, or a value of type Option.None, which is type-safe whereas null isn't.
If you're wondering what Stream is... it's a lazy list. This ensures that you ignore patterns after the first successful one. No need to do too much work.
Call your function like this:
for (Date d: parseWithPatterns(someString, stream("dd/MM/yyyy", "dd-MM-yyyy")) {
// Do something with the date here.
}
Or...
Option<Date> d = parseWithPatterns(someString,
stream("dd/MM/yyyy", "dd-MM-yyyy"));
if (d.isNone()) {
// Handle the case where neither pattern matches.
}
else {
// Do something with d.some()
}

Don't be too hard on yourself about using try-catch in logic: this is one of those situations where Java forces you to so there's not a lot you can do about it.
But in this case you could instead use DateFormat.parse(String, ParsePosition).

You can take advantage of regular expressions to determine which format the string is in, and whether it matches any valid format. Something like this (not tested):
(Oops, I wrote this in C# before checking to see what language you were using.)
Regex test = new Regex(#"^(?:(?<formatA>\d{2}-[a-zA-Z]{3}-\d{2})|(?<formatB>\d{2}/\d{2}/\d{3}))$", RegexOption.Compiled);
Match match = test.Match(yourString);
if (match.Success)
{
if (!string.IsNullOrEmpty(match.Groups["formatA"]))
{
// Use format A.
}
else if (!string.IsNullOrEmpty(match.Groups["formatB"]))
{
// Use format B.
}
...
}

If you formats are exact (June 7th 1999 would be either 07-Jun-99 or 07/06/1999: you are sure that you have leading zeros), then you could just check for the length of the string before trying to parse.
Be careful with the short month name in the first version, because Jun may not be June in another language.
But if your data is coming from one database, then I would just convert all dates to the common format (it is one-off, but then you control the data and its format).

In this limited situation, the best (and fastest method) is certinally to parse out the day, then based on the next char either '/' or '-' try to parse out the rest. and if at any point there is unexpected data, return NULL then.

Assuming the patterns you gave are the only likely choices, I would look at the String passed in to see which format to apply.
public Date parseDate(final String date) {
if (date == null) {
return null;
}
SimpleDateFormat format = (date.charAt(2) == '/') ? new SimpleDateFormat("dd/MMM/yyyy")
: new SimpleDateFormat("dd-MMM-yy");
try {
return format.parse(date);
} catch (ParseException e) {
// Log a complaint and include date in the complaint
}
return null;
}
As others have mentioned, if you can guarantee that you will never access the DateFormats in a multi-threaded manner, you can make class-level or static instances.

Looks like three options if you only have two, known formats:
check for the presence of - or / first and start with that parsing for that format.
check the length since "dd-MMM-yy" and "dd/MM/yyyy" are different
use precompiled regular expressions
The latter seems unnecessary.

Use regular expressions to parse your string. Make sure that you keep both regex's pre-compiled (not create new on every method call, but store them as constants), and compare if it actually is faster then the try-catch you use.
I still find it strange that your method returns null if both versions fail rather then throwing an exception.

you could use split to determine which format to use
String[] parts = date.split("-");
df = (parts.length==3 ? format1 : format2);
That assumes they are all in one or the other format, you could improve the checking if need be

An alternative to creating a SimpleDateFormat (or two) per iteration would be to lazily populate a ThreadLocal container for these formats. This will solve both Thread safety concerns and concerns around object creation performance.

A simple utility class I have written for my project. Hope this helps someone.
Usage examples:
DateUtils.multiParse("1-12-12");
DateUtils.multiParse("2-24-2012");
DateUtils.multiParse("3/5/2012");
DateUtils.multiParse("2/16/12");
public class DateUtils {
private static List<SimpleDateFormat> dateFormats = new ArrayList<SimpleDateFormat>();
private Utils() {
dateFormats.add(new SimpleDateFormat("MM/dd/yy")); // must precede yyyy
dateFormats.add(new SimpleDateFormat("MM/dd/yyyy"));
dateFormats.add(new SimpleDateFormat("MM-dd-yy"));
dateFormats.add(new SimpleDateFormat("MM-dd-yyyy"));
}
private static Date tryToParse(String input, SimpleDateFormat format) {
Date date = null;
try {
date = format.parse(input);
} catch (ParseException e) {
}
return date;
}
public static Date multiParse(String input) {
Date date = null;
for (SimpleDateFormat format : dateFormats) {
date = tryToParse(input, format);
if (date != null) break;
}
return date;
}
}

On one hand I see nothing wrong with your use of try/catch for the purpose, it’s the option I would use. On the other hand there are alternatives:
Take a taste from the string before deciding how to parse it.
Use optional parts of the format pattern string.
For my demonstrations I am using java.time, the modern Java date and time API, because the Date class used in the question was always poorly designed and is now long outdated. For a date without time of day we need a java.time.LocalDate.
try-catch
Using try-catch with java.time looks like this:
DateTimeFormatter ddmmmuuFormatter = DateTimeFormatter.ofPattern("dd-MMM-uu", Locale.ENGLISH);
DateTimeFormatter ddmmuuuuFormatter = DateTimeFormatter.ofPattern("dd/MM/uuuu");
String dateString = "07-Jun-09";
LocalDate result;
try {
result = LocalDate.parse(dateString, ddmmmuuFormatter);
} catch (DateTimeParseException dtpe) {
result = LocalDate.parse(dateString, ddmmuuuuFormatter);
}
System.out.println("Date: " + result);
Output is:
Date: 2009-06-07
Suppose instead we defined the string as:
String dateString = "07/06/2009";
Then output is still the same.
Take a taste
If you prefer to avoid the try-catch construct, it’s easy to make a simple check to decide which of the formats your string conforms to. For example:
if (dateString.contains("-")) {
result = LocalDate.parse(dateString, ddmmmuuFormatter);
} else {
result = LocalDate.parse(dateString, ddmmuuuuFormatter);
}
The result is the same as before.
Use optional parts in the format pattern string
This is the option I like the least, but it’s short and presented for some measure of completeness.
DateTimeFormatter dateFormatter
= DateTimeFormatter.ofPattern("[dd-MMM-uu][dd/MM/uuuu]", Locale.ENGLISH);
LocalDate result = LocalDate.parse(dateString, dateFormatter);
The square brackets denote optional parts of the format. So Java first tries to parse using dd-MMM-uu. No matter if successful or not it then tries to parse the remainder of the string using dd/MM/uuuu. Given your two formats one of the attempts will succeed, and you have parsed the date. The result is still the same as above.
Link
Oracle tutorial: Date Time explaining how to use java.time.

Related

Custom Comparator not sorting time

I am working on a project where I am comparing the date and time in a custom Comparator. I actually concatenated the date with date and time. When I debugged the issue, I realized that time is not getting sorted. Here is the snippet of my code from my Comparator.
Date dateObject1= new Date();
Date dateObject2 = new Date();
try {
dateObject1 = sdf.parse(date1 + "T" + time1);
dateObject2 = sdf.parse(date2 + "T" + time2);
} catch (Exception e) { }
if (dateObject1.compareTo(dateObject2) > 0)
return 1;
else if (dateObject1.compareTo(dateObject2) < 0)
return -1;
else
return 0;
Test cases:
1. date1 - 2019-12-13 , date2 - 2019-12-13
time1 - 08:00:00, time2 - 12:00:00
When i debugged the issue I found it's returning 0 for the above test case. I am not sure why it's happening but I intent to return -1 such that it's sorted in ascending order.
Please advice.
Your problem is here
} catch (Exception e) { }
You initialize your 2 dates, both of which get initialized to the current time (System.currentTimeMillis()).
Date dateObject1= new Date();
Date dateObject2 = new Date();
Your parsing then fails, but you swallow the exception so you never noticed it.
Then you try to sort two dates which are either exactly same, or separated by a couple of milliseconds, but are certainly unrelated to the actual timestamps that you're trying to sort.
Check the exception, fix the parsing, and then it will work.
java.time and Comparator.comparing … thenComparing
I don’t know what your Java version is. The following snippet works on Java 8 and above. The most important ideas can be applied on Java 6 and 7 too.
List<MyObject> listToBeSorted = Arrays.asList(
new MyObject("2019-12-12", "11:53:50"),
new MyObject("2019-12-11", "13:07:05"),
new MyObject("2019-12-13", "05:02:16"),
new MyObject("2019-12-11", "09:54:57"),
new MyObject("2019-12-12", "05:53:52"),
new MyObject("2019-12-13", "06:56:08"),
new MyObject("2019-12-12", "02:31:55"),
new MyObject("2019-12-11", "09:28:16"),
new MyObject("2019-12-11", "20:58:55"));
Comparator<MyObject> cmpr = Comparator.comparing(MyObject::getDate)
.thenComparing(MyObject::getTime);
listToBeSorted.sort(cmpr);
listToBeSorted.forEach(System.out::println);
Output is:
MyObject [date=2019-12-11, time=09:28:16]
MyObject [date=2019-12-11, time=09:54:57]
MyObject [date=2019-12-11, time=13:07:05]
MyObject [date=2019-12-11, time=20:58:55]
MyObject [date=2019-12-12, time=02:31:55]
MyObject [date=2019-12-12, time=05:53:52]
MyObject [date=2019-12-12, time=11:53:50]
MyObject [date=2019-12-13, time=05:02:16]
MyObject [date=2019-12-13, time=06:56:08]
You will observe that the objects have been sorted by date and objects with the same date also by time. Here is the MyObject class that I used:
public class MyObject {
LocalDate date;
LocalTime time;
public MyObject(String dateString, String timeString) {
date = LocalDate.parse(dateString);
time = LocalTime.parse(timeString);
}
public LocalDate getDate() {
return date;
}
public LocalTime getTime() {
return time;
}
#Override
public String toString() {
return "MyObject [date=" + date + ", time=" + time + "]";
}
}
The two key messages are:
Don’t keep your dates and times as strings in your objects. Keep proper date and time objects. It may require parsing strings when you build your objects, but everything else gets noticeably easier.
Don’t use Date and SimpleDateFormat at all. Use classes from java.time, the modern Java date and time API. In this case LocalDate and LocalTime. The SimpleDateFormat and Date classes are poorly designed and long outdated, the former in particular notoriously troublesome. The modern API is so much nicer to work with.
The advantage of the Comparator methods comparing and thenComparing is not so much that code gets considerably shorter. The really important gain is that writing comparators in this style is much less error prone, and the code reads more naturally.
What went wrong in your code?
The problem is in the line that you posted in a comment:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
This formatter only parses the date from each string and ignores the time. It’s one of many confusing traits of SimpleDateFormat that it is happy to parse only a part of the string and doesn’t draw our attention to the fact that some of the text is ignored — in this case the T and the entire time.
Link
Oracle tutorial: Date Time explaining how to use java.time.
Just return the value of the comparison since that is what you return anyway.
Date dateObject1= new Date();
Date dateObject2 = new Date();
try {
dateObject1 = sdf.parse(date1 + "T" + time1);
dateObject2 = sdf.parse(date2 + "T" + time2);
} catch (Exception e) {
e.printStackTrace(); // always print these. They are there to help you.
}
return dateObject1.compareTo(dateObject2);

How to check if string matches date pattern using time API?

My program is parsing an input string to a LocalDate object. For most of the time the string looks like 30.03.2014, but occasionally it looks like 3/30/2014. Depending on which, I need to use a different pattern to call DateTimeFormatter.ofPattern(String pattern) with. Basically, I need to check if the string matches the pattern dd.MM.yyyy or M/dd/yyyy before doing the parsing.
The regex approach would be something like:
LocalDate date;
if (dateString.matches("^\\d?\\d/\\d{2}/\\d{4}$")) {
date = LocalDate.parse(dateString, DateTimeFormatter.ofPattern("M/dd/yyyy"));
} else {
date = LocalDate.parse(dateString, DateTimeFormatter.ofPattern("dd.MM.yyyy"));
}
This works, but it would be nice to use the date pattern string when matching the string also.
Are there any standard ways to do this with the new Java 8 time API, without resorting to regex matching? I have looked in the docs for DateTimeFormatter but I couldn't find anything.
Okay I'm going ahead and posting it as an answer. One way is to create the class that will holds the patterns.
public class Test {
public static void main(String[] args){
MyFormatter format = new MyFormatter("dd.MM.yyyy", "M/dd/yyyy");
LocalDate date = format.parse("3/30/2014"); //2014-03-30
LocalDate date2 = format.parse("30.03.2014"); //2014-03-30
}
}
class MyFormatter {
private final String[] patterns;
public MyFormatter(String... patterns){
this.patterns = patterns;
}
public LocalDate parse(String text){
for(int i = 0; i < patterns.length; i++){
try{
return LocalDate.parse(text, DateTimeFormatter.ofPattern(patterns[i]));
}catch(DateTimeParseException excep){}
}
throw new IllegalArgumentException("Not able to parse the date for all patterns given");
}
}
You could improve this as #MenoHochschild did by directly creating an array of DateTimeFormatter from the array of String you pass in the constructor.
Another way would be to use a DateTimeFormatterBuilder, appending the formats you want. There may exists some other ways to do it, I didn't go deeply through the documentation :-)
DateTimeFormatter dfs = new DateTimeFormatterBuilder()
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
.appendOptional(DateTimeFormatter.ofPattern("dd.MM.yyyy"))
.toFormatter();
LocalDate d = LocalDate.parse("2014-05-14", dfs); //2014-05-14
LocalDate d2 = LocalDate.parse("14.05.2014", dfs); //2014-05-14
With DateTimeFormatter, optional patterns can be specified using square brackets.
Demo:
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("[d.M.u][M/d/u][u-M-d]", Locale.ENGLISH);
Stream.of(
"3/30/2014",
"30.03.2014",
"2014-05-14",
"14.05.2014"
).forEach(s -> System.out.println(LocalDate.parse(s, dtf)));
}
}
Output:
2014-03-30
2014-03-30
2014-05-14
2014-05-14
Learn more about the the modern date-time API* from Trail: Date Time.
* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.
The approach of #ZouZou is a possible solution.
In order to avoid using exceptions for program logic as much as possible (also not so nice performancewise) following alternative might be considered:
static final String[] PATTERNS = {"dd.MM.yyyy", "M/dd/yyyy"};
static final DateTimeFormatter[] FORMATTERS = new DateTimeFormatter[PATTERNS.length];
static {
for (int i = 0; i < PATTERNS.length; i++) {
FORMATTERS[i] = DateTimeFormatter.ofPattern(PATTERNS[i]);
}
}
public static LocalDate parse(String input) {
ParsePosition pos = new ParsePosition();
for (int i = 0; i < patterns.length; i++) {
try {
TemporalAccessor tacc = FORMATTERS[i].parseUnresolved(input, pos);
if (pos.getErrorIndex < 0) {
return LocalDate.from(tacc); // possibly throwing DateTimeException => validation failure
}
} catch (DateTimeException ex) { // catches also possible DateTimeParseException
// go to next pattern
}
pos.setIndex(0);
pos.setErrorIndex(-1);
}
throw new IllegalArgumentException("Input does not match any pattern: " + input);
}
More explanation about the method parseUnresolved():
This method does only the first phase of parsing, so there is no second phase containing preliminary validation or combining effort of parsed fields. However, LocalDate.from() does validate every input, so I think this is still sufficient. And the advantage is that parseUnresolved() uses the error index of ParsePosition. This is in agreement with traditional java.text.Format-behaviour.
Unfortunately the alternative and more intuitive method DateTimeFormater.parse() first creates a DateTimeParseException and then store the error index in this exception. So I decided not to use this method in order to avoid the creation of an unnecessary exception. For me, this API-detail is a questionable design decision.

Java system time

I have this code copied from one of questions from SO:
public static String getCurrentTimeStamp() {
SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date now = new Date();
String strDate = sdfDate.format(now);
return strDate;
}
I want to get only the system time and NOT the date. Then I must change second line of code to:
SimpleDateFormat sdfDate = new SimpleDateFormat(" HH:mm:ss") ;
Then, DATE() must get the current time. Clear upto this point but I can't understand the format() function used.
I mean cant we simply output variable now instead of strdate?
Is it just because that the return type of function getCurrentTimeStamp() is String?
Please clarify and if there is any other simpler and one line code for getting system time alone, do share.
I mean cant we simply output variable now instead of strdate.
Well you could return now.toString() - but that will use the format that Date.toString() happens to choose, whereas you want a specific format. The point of the SimpleDateFormat object in this case is to convert a Date (which is a point in time, without reference to any particular calendar or time zone) into a String, applying an appropriate time zone, calendar system, and text format (in your case HH:mm:ss).
You can still simplify your method somewhat though, by removing the local variables (which are each only used once):
public static String getCurrentTimeStamp() {
return new SimpleDateFormat("HH:mm:ss").format(new Date());
}
Or maybe you'd find it more readable to keep the variable for the date format, but not the date and the return value:
public static String getCurrentTimeStamp() {
DateFormat format = new SimpleDateFormat("HH:mm:ss");
return format.format(new Date());
}
Personally I'd recommend using Joda Time instead, mind you - it's a much nicer date/time API, and its formatted are thread-safe so you could easily keep a reference to a single formatting object.
public static String getCurrentTimeStampwithTimeOnly() {
return new SimpleDateFormat("HH:mm:ss").format(new Date());
}
Helps you to do this.
you can call this line any time
Date now = new Date();
The now variable will contain the current timestamp
The format function just generates a String from this timestamp
also take a look at the Calendar class ( Calendar.getInstance())

using the date type in java

I am trying to get two dates from a SQL query, and compare them. So to compare them, I believe I will need to use the "Date" type. Here is what I am trying, I know I am getting the date from the resultSet incorrectly, but I am not sure how to do it.
Date validDate = new Date(0);
Date currentDate = new Date(0);
// query
if (result.next()) {
validDate = (result.getObject("validDate")!=null)?result.getObject("validDate").toDate():"";
currentDate = (result.getObject("currentDate")!=null)?result.getObject("currentDate").toDate():"";
}
if (currentDate > validDate) {
//do something
}
So again, this was my attempt, but I cant seem to get it to run. Thanks in advance.
EDIT: the query has TO_CHAR(column, 'MM-DD-YYYY') on the two dates that I am getting.
EDIT: Now you've mentioned that your query converts the date to a string, stop doing that. You'll end up reparsing it on the calling side - so why perform two conversions pointlessly? Keep string conversions to the absolute minimum - stay in the most appropriate data type wherever possible.
Original answer
You haven't shown what result is, but you probably want something like ResultSet.getDate() to fetch the date values.
Note that your comparison code won't work either because there's no > for Date - you'd need something like:
if (currentDate.after(validDate))
Or fetch the underlying number of millis:
if (currentDate.getTime() > validDate.getTime())
Additionally:
You can't assign "" to a Date variable - a string isn't a Date.
You can just call ResultSet.getDate() and check whether the returned value is null, rather than calling getObject first and then getDate()
Try currentDate.after(validDate)
To compdare dates I always use the before and after methodes of Date.
Some nasty things can happen when accessing dates via the getObject method. You should try to use the rs.getTimestamp (with timeinfo) or the rs.getDate (without timeinfo) methods.
Also, because of the rather complex hierarchy of Date-objects you should compare Dates only using the date1.compareTo(date2) > 0 method.
if your result object is ResultSet, then
Date validDate = result.getTimestamp("validDate");
Date currentDate= result.getTimestamp("currentDate");
// you can add null checks here too....
// you can also use if (currentDate.getTime() > validDate.getTime()){}
if (currentDate.before(validDate)) {
//some code inhere...
}
There are at least three things wrong with your code:
"" is a String literal, so you cannot use it int your ternary expressions to be assigned to a variable of type Date - use null instead so you don't need a ternary
ResultSet.getObject() returns an Object, which does not have a toDate() method. Instead, simply use ResultSet.getDate()
You cannot compare Date instances using a > operator. You have to use the before() and after() methods of the Date class
Taking all this together, the following code might work:
Date validDate = new Date(0);
Date currentDate = new Date(0);
if (result.next()) {
validDate = result.getDate("validDate");
currentDate = result.getDate("currentDate");
}
if (currentDate.after(validDate)) {
//do something
}
The if clause may have to include some extra logic to deal with null values though. It's better to do that than to leave that to implicit conversions, too.

Synchronizing access to SimpleDateFormat

The javadoc for SimpleDateFormat states that SimpleDateFormat is not synchronized.
"Date formats are not synchronized. It
is recommended to create separate
format instances for each thread. If
multiple threads access a format
concurrently, it must be synchronized
externally."
But what is the best approach to using an instance of SimpleDateFormat in a multi threaded environment. Here are a few options I have thought of, I have used options 1 and 2 in the past but I am curious to know if there are any better alternatives or which of these options would offer the best performance and concurrency.
Option 1: Create local instances when required
public String formatDate(Date d) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(d);
}
Option 2: Create an instance of SimpleDateFormat as a class variable but synchronize access to it.
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public String formatDate(Date d) {
synchronized(sdf) {
return sdf.format(d);
}
}
Option 3: Create a ThreadLocal to store a different instance of SimpleDateFormat for each thread.
private ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>();
public String formatDate(Date d) {
SimpleDateFormat sdf = tl.get();
if(sdf == null) {
sdf = new SimpleDateFormat("yyyy-MM-hh");
tl.set(sdf);
}
return sdf.format(d);
}
Creating SimpleDateFormat is expensive. Don't use this unless it's done seldom.
OK if you can live with a bit of blocking. Use if formatDate() is not used much.
Fastest option IF you reuse threads (thread pool). Uses more memory than 2. and has higher startup overhead.
For applications both 2. and 3. are viable options. Which is best for your case depends on your use case. Beware of premature optimization. Only do it if you believe this is an issue.
For libraries that would be used by 3rd party I'd use option 3.
The other option is Commons Lang FastDateFormat but you can only use it for date formatting and not parsing.
Unlike Joda, it can function as a drop-in replacement for formatting.
(Update: Since v3.3.2, FastDateFormat can produce a FastDateParser, which is a drop-in thread-safe replacement for SimpleDateFormat)
If you are using Java 8, you may want to use java.time.format.DateTimeFormatter:
This class is immutable and thread-safe.
e.g.:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String str = new java.util.Date().toInstant()
.atZone(ZoneId.systemDefault())
.format(formatter);
Commons Lang 3.x now has FastDateParser as well as FastDateFormat. It is thread safe and faster than SimpleDateFormat. It also uses the same format/parse pattern specifications as SimpleDateFormat.
Don't use SimpleDateFormat, use joda-time's DateTimeFormatter instead. It is a bit stricter in the parsing side and so isn't quite a drop in replacement for SimpleDateFormat, but joda-time is much more concurrent friendly in terms of safety and performance.
I would say, create a simple wrapper-class for SimpleDateFormat that synchronizes access to parse() and format() and can be used as a drop-in replacement. More foolproof than your option #2, less cumbersome than your option #3.
Seems like making SimpleDateFormat unsynchronized was a poor design decision on the part of the Java API designers; I doubt anyone expects format() and parse() to need to be synchronized.
Another option is to keep instances in a thread-safe queue:
import java.util.concurrent.ArrayBlockingQueue;
private static final int DATE_FORMAT_QUEUE_LEN = 4;
private static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";
private ArrayBlockingQueue<SimpleDateFormat> dateFormatQueue = new ArrayBlockingQueue<SimpleDateFormat>(DATE_FORMAT_QUEUE_LEN);
// thread-safe date time formatting
public String format(Date date) {
SimpleDateFormat fmt = dateFormatQueue.poll();
if (fmt == null) {
fmt = new SimpleDateFormat(DATE_PATTERN);
}
String text = fmt.format(date);
dateFormatQueue.offer(fmt);
return text;
}
public Date parse(String text) throws ParseException {
SimpleDateFormat fmt = dateFormatQueue.poll();
if (fmt == null) {
fmt = new SimpleDateFormat(DATE_PATTERN);
}
Date date = null;
try {
date = fmt.parse(text);
} finally {
dateFormatQueue.offer(fmt);
}
return date;
}
The size of dateFormatQueue should be something close to the estimated number of threads which can routinely call this function at the same time.
In the worst case where more threads than this number do actually use all the instances concurrently, some SimpleDateFormat instances will be created which cannot be returned to dateFormatQueue because it is full. This will not generate an error, it will just incur the penalty of creating some SimpleDateFormat which are used only once.
I just implemented this with Option 3, but made a few code changes:
ThreadLocal should usually be static
Seems cleaner to override initialValue() rather than test if (get() == null)
You may want to set locale and time zone unless you really want the default settings (defaults are very error prone with Java)
private static final ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>() {
#Override
protected SimpleDateFormat initialValue() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-hh", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
return sdf;
}
};
public String formatDate(Date d) {
return tl.get().format(d);
}
Imagine your application has one thread. Why would you synchronize access to SimpleDataFormat variable then?

Categories