I want to create a Java class with thread-safe static methods to parse dates. I understand that some of the Java 7 (and earlier) date time classes are not thread-safe. What is the best thread-safe implementation in Java 8 of this functionality:
String text = "5/16/2008";
long timestamp = DateUtil.getTimestamp(text);
In Java 7 and earlier, you would do this:
public class DateUtil {
public static long getTimestamp(String text) {
DateFormat df = new SimpleDateFormat("M/d/yyyy");
df.setTimeZone(TimeZone.getTimeZone("America/New_York"));
long timestamp = df.parse(text).getTime();
return timestamp;
}
}
But instead of creating a new instance of DateFormat for every call, I want to share a single static instance for all calls to this static getTimestamp method. My understanding is that this is not thread-safe.
One key requirement is that the text I want to parse has a short date like "5/16/2008" without HH:mm:ss resolution.
I also don't want to use a third party library like Joda-Time, but rather only standard Java 8 classes.
Here's a version of your code refactored to use the java.time.* package in Java 8. It uses a static final formatter instance, which is thread-safe and immutable, unlike java.text.SimpleDateFormat.
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
public class DateUtil {
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M/d/yyyy");
public static long getTimestamp(String text) {
LocalDate localDate = LocalDate.parse(text, formatter);
return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()).getTime();
}
}
You can use joda-time lib. DateTime is immutable - and once created the values do not change, so class can safely be passed around and used in multiple threads without synchronization.
A companion mutable class to DateTime is MutableDateTime, of which the class can be modified and are not thread-safe.
DateTimeFormatter formatter = DateTimeFormat.forPattern("M/d/yyyy'T'HH:mm:ss.SSSZZ")
.withLocale(Locale.ROOT).withChronology(ISOChronology.getInstanceUTC());
DateTime dt = formatter.parseDateTime(text);
Reference of DateTimeFormatt: DatetimeFormat api.
As stated in ck1's answer, usage of java.time API is a better approach than the legacy classes. DateTimeFormatter is immutable and thread-safe, and using a static final instance of it will solve your problem.
The only part where I differ from that answer is in the code , where the Date class is used to get the time. I would like to take the java.time approach here as well. Below is my version :
public class DateUtil {
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M/d/yyyy");
public static long getTimestamp(String text) {
LocalDate localDate = LocalDate.parse(text, formatter);
return Instant.from(localDate.atStartOfDay(ZoneId.systemDefault())).toEpochMilli();
}
public static void main(String[] args) {
String text = "5/16/2008";
long timestamp = DateUtil.getTimestamp(text);
System.out.println(timestamp);
}
}
Related
I wanted to create a class with a custom data type that returns the class object. Consider a class Custom:
public class Custom {
// Some fields.
public Custom(String custom) {
// Some Text.
}
// Some Methods.
public void customMethod() {
// Some Code.
}
}
Now, consider a second class TestCustom:
public class TestCustom {
public static void main(String[] args) {
Custom custom = new Custom("Custom");
System.out.println(custom); // This should print "Custom"
custom.customMethod(); // This should perform the action
}
}
So, the question how to get the value custom on instantiating an object instead of memory location. Like what I get is:
Custom#279f2327
The java.util.Date class returns the current date. This can be seen as the constructor for the class is
public Date() {
this(System.currentTimeMillis());
}
For example, the following code would print out the current date:
DateFormat format = new SimpleDateFormat("dd/MM/yyyy");
Date date = new Date();
System.out.println(format.format(date));
The Answer by ML72 is correct and should be accepted. The java.util.Date constructor captures the current moment in UTC.
java.time
The java.util.Date class is terrible, for many reasons. That class is now legacy, supplanted years ago but the java.time classes as of the adoption of JSR 310.
The java.time classes avoid constructors, instead using factory methods.
The replacement for java.util.Date is java.time.Instant. To capture the current moment in UTC, call the class method .now().
Instant instant = Instant.now() ;
If you want the current moment as seen through the wall-clock time used by the people of a particular region (a time zone), use ZoneId to get a ZonedDateTime object. Notice again the factory method rather than a constructor.
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = ZonedDateTime.now( z ) ;
Adjust to UTC by extracting an Instant.
Instant instant = zdt.toInstant() ;
Override the toString() method, as it is automatically invoked when you try to display an object:
Add a field. For example;
private String value;
In the constructor, add the following code:
value = custom;
this will assign a value passed to the constructor as a parameter, to the value field.
And finally override the toString() method as follows:
#Override
public String toString() {
return value;
}
Now, when you display the value of the custom object, the overridden toString() method will be invoked and the argument will be displayed instead of the memory address. Whereas methods of the object will work as they are programmed to work. There is nothing to be changed with them.
I have an Object MyTimes and in that object there are fields name ,start_date and configuration.
I have an array of this object, MyTimes [] mytimes
I am trying to sort the array by the start time but am struggling how to go about it.
The start_time field is a string, so this needs converting to a datetime.
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
for(int i=0; i<mytimes.length; i++) {
Date date = formatter.parse(mytimes[i].getStartTime());
}
I'd then put the date into an array list perhaps and then sort by datetime? But then I wouldnt know which start_time corresponds with which mytimes object...
What is the most efficient way of doing this?
Under the right circumstances this is a one-liner:
Arrays.sort(myTimes, Comparator.comparing(MyTimes::getStartDate));
Let’s see it in action:
MyTimes[] myTimes = {
new MyTimes("Polly", "2019-03-06T17:00:00Z"),
new MyTimes("Margaret", "2019-03-08T09:00:00Z"),
new MyTimes("Jane", "2019-03-01T06:00:00Z")
};
Arrays.sort(myTimes, Comparator.comparing(MyTimes::getStartDate));
Arrays.stream(myTimes).forEach(System.out::println);
Output:
Jane 2019-03-01T06:00:00Z
Polly 2019-03-06T17:00:00Z
Margaret 2019-03-08T09:00:00Z
I am assuming that getStartDate returns an Instant or another type the natural order of which agrees with the chronological order you want. For example:
public class MyTimes {
private String name;
private Instant startDate;
// Constructor, getters, toString, etc.
}
If you are receiving your start dates as strings somehow, you may write a convenient constructor that accepts a string for start date. I am already using such a constructor in the above snippet. One possibility is having two constructors:
public MyTimes(String name, Instant startDate) {
this.name = name;
this.startDate = startDate;
}
public MyTimes(String name, String startDate) {
this(name, Instant.parse(startDate));
}
The Instant class is part of java.time, the modern Java date and time API.
I am exploiting the fact that your strings are in the ISO 8601 format for an instant, the format that Instant.parse accepts and parses.
Avoid SimpleDateFormat and Date
I recommend you don’t use SimpleDateFormat and Date. Those classes are poorly designed and long outdated, the former in particular notoriously troublesome. There is also an error in your format pattern string for parsing: Z (pronounced “Zulu”) means UTC, and of you don’t parse it as such, you will get incorrect times (on most JVMs). Instant.parse efficiently avoids any problems here.
Don’t store date-tine as a string
It looks like you are are storing start time in a String field in your object? That would be poor modelling. Use a proper date-time type. Strings are for interfaces. Date-time classes like Instant offer much more functionality, for example define sort order.
You have two main approaches:
Make your class implement Comparable
Use a custom Comparator
Then, you can choose the field to compare from, and transform it.
IE (implementing comparable):
class Example implements Comparable<Example> {
private String stringDate;
public int compareTo(Example e) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
Date date1 = formatter.parse(this.stringDate);
Date date2 = formatter.parse(e.stringDate);
return date1.getTime() - date2.getTime();
}
}
And then using Arrays.sort would use your custom comparison.
Let your class implement Comparable and implement compareTo using modern formatting and date classes. Note that LocalDateTime also implements Comparable so once the string has been parsed you let LocalDateTime do the comparison
public class MyTimes implements Comparable<MyTimes> {
private final DateTimeFormatter dtf = DateTimeFormatter.ISO_INSTANT;
//other code
public int compareTo(MyTimes o) {
LocalDateTime thisDate = LocalDateTime.from(dtf.parse(this.getStartTime()));
LocalDateTime otherDate = LocalDateTime.from(dtf.parse(o.getStartTime()));
return thisDate.compareTo(otherDate);
}
}
You can also create a separate class as a comparator if this comparison is special and what you not always want to use
public class MyTimesComparator implements Comparator<MyTimes> {
#Override
public int compare(MyTimes arg0, MyTimes arg1) {
DateTimeFormatter dtf = DateTimeFormatter.ISO_INSTANT;
LocalDateTime thisDate = LocalDateTime.from(dtf.parse(this.getStartTime()));
LocalDateTime otherDate = LocalDateTime.from(dtf.parse(o.getStartTime()));
return thisDate.compareTo(otherDate);
}
}
and then use it like
someList.sort(new MyTimesComparator());
or use an inline function (I am using Instant here)
someList.sort( (m1, m2) -> {
DateTimeFormatter dtf = DateTimeFormatter.ISO_INSTANT;
Instant instant1 = Instant.from(dtf.parse(m1.getStartTime));
Instant instant2 = Instant.from(dtf.parse(m2.getStartTime));
return intant1.compareTo(instant2);
});
I noticed now that you have an array and not a list so you need to convert to a list or use Arrays.sort instead.
LocalDateTime is abstract class. So I cannot write:
LocalDateTime value = new LocalDateTime(); //error
If I want to get its instance, I have to write:
LocalDateTime value = LocalDateTime.now(); //not error
I have a question, Why can LocalDateTime return the instance? It's an abstract class.
I saw the overview, but I could not find it...
LocalDateTime is not an abstract class.
public final class LocalDateTime
implements Temporal, TemporalAdjuster, ChronoLocalDateTime<LocalDate>, Serializable {
It has private constructors, so direct instantiation is not possible. Factory method such now(), now(ZoneId) etc are used to create instances.
LocalDateTime is an immutable date-time object that represents a date-time.
This class does not store or represent a time-zone. Instead, it is a description of the date. It cannot represent an instant on the time-line without additional information such as an offset or time-zone.
Hence it has static methods e.g.
LocalDateTime desc = LocalDateTime.now();
SimpleDateFormat lets me do this:
SimpleDateFormat mySimpleDateFormatter = new SimpleDateFormat("dd-MMM-yyyy");
But FastDateFormat doesn't:
import org.apache.commons.lang3.time.FastDateFormat;
//...
FastDateFormat myFastDateFormatter = new FastDateFormat(str, tz, loc);
Now it complains that:
"The constructor FastDateFormat(String, TimeZone, Locale) is not visible"
The error message doesn't lead me anywhere... What did I do wrong?
Unlike, Java's SimpleDateFormat, you cannot simply declare an instance of Apache's FastDateFormat class.
Instead, FastDateFormat statically serves up instances of the class via the Factory Method Pattern - you must call the class' static method getInstance, as in the following:
String dateFormatPattern = "yyyy-mm-dd'T'HH:mm:ss.SSSZ";
FastDateFormat myFastDateFormatter = FastDateFormat.getInstance(dateFormatPattern);
Now you have a myFastDateFormatter loaded configured with that date format pattern.
You can use it to parse strings into real Dates, assuming those strings conform to your dateFormatPattern:
String dateString = "2014-04-03T14:02:57.182+0200";
Date myDate = myFastDateFormatter.parse(dateString);
Sonar is giving me the message:
Malicious code vulnerability - Field should be package protected for
static array FORMATS.
Why is this code considered malicious? I have a public class to store all the constants.
public class Constants
{
/*
all the public static final constants of primitive datatypes for which
there is no sonar warning.
*/
public static final String[] FORMATS = new String[] {
"yyyy-MM-dd HH:mm:ss.S z",
"yyyy-MM-dd HH:mm:ss.S"
}
Probably because another piece of code could execute:
Constants.FORMATS[0] = "SOME GARBAGE";
And break the rest of your code.
In other words your array is constant but not its content.
Examples of alternatives:
you can store each format as a separate String constant
you can use an immutable list instead: public static final List<String> FORMATS = Collections.unmodifiableList(Arrays.asList("yyyy-MM-dd HH:mm:ss.S z", "yyyy-MM-dd HH:mm:ss.S"));
make it a method:
public static String[] formats() {
return new String[] { "yyyy-MM-dd HH:mm:ss.S z", "yyyy-MM-dd HH:mm:ss.S" };
}
ignore the warning if you are confident that (i) only your own code will access that class and (ii) there is no way you/your colleagues would even think of reassigning one of the values.