Time in Java (time manipulation of chicken ribs)
For a long time, the Java date-time solution has been a controversial design, with many problems ranging from confusing concepts (e.g., Date versus Calendar) to unintuitive interface design (e.g., Date’s setMonth parameter is 0 to 11), and some implementations are problematic (for example, the SimpleDateFormat mentioned earlier requires multi-threaded concurrency and requires building a new object at a time).
This mess has been around for a long Time, and many people have tried to solve it (like Joda Time). Starting with Java 8, the official Java SDK borrowed from various libraries to introduce a new date-time solution. This solution is completely separate from the original solution, which means that all of our work can be handled with this new solution.
Date
The java.util package provides the Date class to encapsulate the current Date and time. The Date class provides two constructors to instantiate a Date object. The first constructor uses the current date and time to initialize the object. The second constructor takes an argument, the number of milliseconds from January 1, 1970.
Date( )
Date(long millisec)
Copy the code
Date provides the following common methods
The serial number | Methods and Description |
---|---|
1 | boolean after(Date date)The Date object when this method is called returns true after the specified Date, false otherwise. |
2 | boolean before(Date date)The Date object when this method is called returns true before the specified Date, false otherwise. |
3 | Object clone( )Returns a copy of this object. |
4 | int compareTo(Date date)Compares the Date object when this method is called with the specified Date. Return 0 if they are equal. The calling object returns a negative number until the specified date. The calling object returns a positive number after the specified date. |
5 | int compareTo(Object obj)If obj is of Date, the operation is the same as compareTo(Date). Otherwise it throws a ClassCastException. |
6 | boolean equals(Object date)Returns true if the Date object on which this method was called is equal to the specified Date, false otherwise. |
7 | long getTime( )Returns the number of milliseconds represented by this Date object since 00:00:00 GMT, January 1, 1970. |
8 | int hashCode( )Returns the hash code value of this object. |
9 | void setTime(long time)Set the time and date in milliseconds since 00:00:00 GMT, January 1, 1970. |
10 | String toString( )Convert the Date object to String of the following form: dow mon DD hh:mm: SS ZZZ YYYY Where: dow is a day in a week (Sun, MON, Tue, Wed, Thu, Fri, Sat). |
The most common thing we use is output time or return time, and that’s when we use toString. Let’s take a look at what toString outputs. Why do we always use SimpleDateFormat and Date together
public static void main(String[] args) {
Date date = new Date();
System.out.println(date);
}
Copy the code
The output
Thu Mar 25 17:23:23 CST 2021
Copy the code
We can see that when we output a date object, it is not very readable, so we often use the SimpleDateFormat and date types together
SimpleDateFormat
SimpleDateFormat serves two main purposes
- Convert the Date type to a more readable string
- Resolves a Date type from the string
SimpleDateFormat Format encoding. When formatting Date, select an appropriate encoding combination as required
The letter | describe | The sample |
---|---|---|
G | Era of tag | AD |
y | The four year | 2001 |
M | in | July or 07 |
d | A month’s date | 10 |
h | A.M./P.M. (1 to 12) In the hour format | 12 |
H | Hours in a day (0 to 23) | 22 |
m | minutes | 30 |
s | Number of seconds | 55 |
S | Number of milliseconds | 234 |
E | What day | Tuesday |
D | Days of the year | 360 |
F | The day of the week of a month | 2 (second Wed. in July) |
w | Week of the year | 40 |
W | Week of a month | 1 |
a | A.M. or P.M. tag | PM |
k | Hours in a day (1 to 24) | 24 |
K | A.M./P.M. (0 to 11) In the hour format | 10 |
z | The time zone | Eastern Standard Time |
‘ | Text delimiter | Delimiter |
“ | Single quotes | ` |
Format Friendly output
public static void main(String[] args) {
Date date = new Date();
System.out.println(date);
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String formatStr = format.format(date);
System.out.println(formatStr);
}
Copy the code
The output
Thu Mar 25 17:27:58 CST 2021
2021-03-25 17:27:58
Copy the code
You can see the readability is much better
Parse fast Conversion
In addition to the Date type, many times we may need to compare the time type (see the table above), which is inconvenient if the object is a string. We want to convert the string time to Date
public static void parse(a) throws ParseException {
Date date = new Date();
System.out.println(date);
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String formatStr = format.format(date);
System.out.println(formatStr);
date = format.parse(formatStr);
System.out.println(date);
}
Copy the code
The infamous SimpleDateFormat
The format method for thread unsafe causes
When we call the format method, the code implementation actually looks like this. The problem is in the calendar.settime (Date). I posted the source code below
// Called from Format after creating a FieldDelegate
private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date);
boolean useDateFormatSymbols = useDateFormatSymbols();
for (int i = 0; i < compiledPattern.length; ) {
int tag = compiledPattern[i] >>> 8;
int count = compiledPattern[i++] & 0xff;
if (count == 255) {
count = compiledPattern[i++] << 16;
count |= compiledPattern[i++];
}
switch (tag) {
case TAG_QUOTE_ASCII_CHAR:
toAppendTo.append((char)count);
break;
case TAG_QUOTE_CHARS:
toAppendTo.append(compiledPattern, i, count);
i += count;
break;
default:
subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
break; }}return toAppendTo;
}
Copy the code
Suppose that in a multithreaded environment, two threads hold the same instance of SimpleDateFormat and call the format method:
- Thread 1 calls the format method, changing the Calendar field.
- The thread is interrupted.
- Thread 2 starts executing, which also changes the calendar.
- Thread 2 is interrupted.
- Thread 1 returns, and Calendar is no longer the value it set, but the value set by thread 2.
- The result is that thread 1 prints the time of thread 2, causing a thread-safety issue
A parse approach to the cause of thread insecurity
Because of the long body of the parse method, we will use it directly and then try to locate the cause of the problem when an exception is thrown
Typically, we use SimpleDateFormat as a static variable to avoid frequently creating object instances of it, as shown in the following code
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void unSafeParse(a) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(100);
for (int i = 0; i < 20; i++) {
service.execute(()->{
for (int i1 = 0; i1 < 10; i1++) {
try {
System.out.println(parse("The 2021-03-25 18:14:22"));
} catch(ParseException e) { e.printStackTrace(); }}}); }// Wait for the thread to end
service.shutdown();
service.awaitTermination(1, TimeUnit.HOURS);
}
public synchronized static Date parse(String strDate) throws ParseException {
return sdf.parse(strDate);
}
Copy the code
The results
Exception in thread "pool-1-thread-4" java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
at java.text.DigitList.getDouble(DigitList.java:169)
at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
Thu Mar 25 18:14:22 CST 2021
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at datastructure.time.DateFormatDemo.parse(DateFormatDemo.java:41)
at datastructure.time.DateFormatDemo.lambda$unSafe$0(DateFormatDemo.java:27)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
Copy the code
Cause analysis,
The calendar object was called in a multi-threaded environment, which caused the other thread to get the time set by the other thread when formatting.
We see this section in the code implementation of Parse, which also uses calendar objects, and this is where we return parsedDate, which is the return value of the getTime method of the Calendar object
Date parsedDate;
try {
parsedDate = calb.establish(calendar).getTime();
// If the year value is ambiguous,
// then the two-digit year == the default start year
if (ambiguousYear[0]) {
if (parsedDate.before(defaultCenturyStart)) {
parsedDate = calb.addYear(100).establish(calendar).getTime(); }}}// An IllegalArgumentException will be thrown by Calendar.getTime()
// if any fields are out of range, e.g., MONTH == 17.
catch (IllegalArgumentException e) {
pos.errorIndex = start;
pos.index = oldStart;
return null;
}
return parsedDate;
Copy the code
So if we look at how the establish method builds calendar, we see that there’s a method that clears calendar and calls calendar set, right
Calendar establish(Calendar cal) {
boolean weekDate = isSet(WEEK_YEAR)
&& field[WEEK_YEAR] > field[YEAR];
if(weekDate && ! cal.isWeekDateSupported()) {// Use YEAR instead
if(! isSet(YEAR)) { set(YEAR, field[MAX_FIELD + WEEK_YEAR]); } weekDate =false;
}
cal.clear();
// Set the fields from the min stamp to the max stamp so that
// the field resolution works in the Calendar.
for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {
for (int index = 0; index <= maxFieldIndex; index++) {
if (field[index] == stamp) {
cal.set(index, field[MAX_FIELD + index]);
break; }}}}Copy the code
Now, we know that this is actually a bigger problem than the format problem, because there are so many thread unsafe method calls, and if one of them has a problem, it’s a problem, for example, another thread is just trying to get the time, Then a thread executes cal.clear(), and the thread can’t get the correct time.
How to solve the thread safety problem of SimpleDateFormat
Create local variables
This means that instead of creating the global variable, we create the SimpleDateFormat object in the method we use, since our methods are always executed in a thread and the local variables created are thread-private
public static void unSafeParse(a) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(100);
for (int i = 0; i < 20; i++) {
service.execute(() -> {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
for (int i1 = 0; i1 < 10; i1++) {
try {
Date date = sdf.parse("The 2021-03-25 18:14:22");
System.out.println(date);
} catch(ParseException e) { e.printStackTrace(); }}}); }// Wait for the thread to end
service.shutdown();
service.awaitTermination(1, TimeUnit.HOURS);
}
Copy the code
lock
We can introduce locks to achieve thread access security. In this example, we can have multiple places where locks can be placed. We will demonstrate two of these
Lock static methods
public static void unSafeParse(a) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(100);
for (int i = 0; i < 20; i++) {
service.execute(() -> {
for (int i1 = 0; i1 < 10; i1++) {
try {
System.out.println(parse("The 2021-03-25 18:14:22"));
} catch(ParseException e) { e.printStackTrace(); }}}); }// Wait for the thread to end
service.shutdown();
service.awaitTermination(1, TimeUnit.HOURS);
}
public synchronized static Date parse(String strDate) throws ParseException {
return sdf.parse(strDate);
}
Copy the code
Code block lock
public static void unSafeParse(a) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(100);
for (int i = 0; i < 20; i++) {
service.execute(() -> {
for (int i1 = 0; i1 < 10; i1++) {
try {
synchronized (sdf) {
System.out.println(parse("The 2021-03-25 18:14:22")); }}catch(ParseException e) { e.printStackTrace(); }}}); }// Wait for the thread to end
service.shutdown();
service.awaitTermination(1, TimeUnit.HOURS);
}
public static Date parse(String strDate) throws ParseException {
return sdf.parse(strDate);
}
Copy the code
Thread private
The scheme can solve the above SimpleDateFormat thread-safe, what is the difference between, thread locking the multi-threaded concurrent performance, a local variable is created a large number of objects, although the method over is destroyed, will not affect the GC, but comes at a cost of creating an object, between which there is no other solution, you don’t really have a, If we circumvent SimpleDateFormat’s thread-safety problems by using local variables in methods, our thread will create the object multiple times if our method is called multiple times in the thread.
We know thread-safety problems are ultimately caused by critical resources, so we can ensure thread-safety problems by ensuring that each thread holds its own SimpleDateFormat object, rather than creating local variables in the method, as in the following code. You can create a SimpleDateFormat object in position 2 to keep things thread-safe
public static void unSafeParse() throws InterruptedException { ExecutorService service = Executors.newFixedThreadPool(100); for (int i = 0; i < 20; i++) { service.execute(() -> { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); for (int i1 = 0; i1 < 10; I1 ++) {try {// position 2 Date Date = sdf.parse("2021-03-25 18:14:22"); System.out.println(date); } catch (ParseException e) { e.printStackTrace(); }}}); } // Wait for the thread to terminate service.shutdown(); service.awaitTermination(1, TimeUnit.HOURS); }Copy the code
As long as each thread holds its own SimpleDateFormat object, there are two ways to do this. The first is to give each thread a SimpleDateFormat object when creating a thread
A thread member variable
public static void unSafeParse(a) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(100);
for (int i = 0; i < 20; i++) {
service.submit(new ParseTask());
}
// Wait for the thread to end
service.shutdown();
service.awaitTermination(1, TimeUnit.HOURS);
}
static class ParseTask extends Thread {
public SimpleDateFormat sdf;
public ParseTask(SimpleDateFormat simpleDateFormat) {
sdf = simpleDateFormat;
}
public ParseTask(a) {
sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
}
@Override
public void run(a) {
for (int i1 = 0; i1 < 10; i1++) {
try {
Date date = sdf.parse("The 2021-03-25 18:14:22");
System.out.println(date);
} catch(ParseException e) { e.printStackTrace(); }}}}Copy the code
ThreadLocal
Of course, we can also use ThreadLocal to achieve thread privacy
public static void unSafeParse(a) throws InterruptedException {
final ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>();
ExecutorService service = Executors.newFixedThreadPool(100);
for (int i = 0; i < 20; i++) {
service.execute(() -> {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
threadLocal.set(sdf);
for (int i1 = 0; i1 < 10; i1++) {
try {
threadLocal.get();
Date date = sdf.parse("The 2021-03-25 18:14:22");
System.out.println(date);
} catch(ParseException e) { e.printStackTrace(); }}}); }// Wait for the thread to end
service.shutdown();
service.awaitTermination(1, TimeUnit.HOURS);
}
Copy the code
Calendar
To facilitate time manipulation, Java provides us with a tool class Calendar, which allows us to easily perform some operations on time, such as setting and obtaining the time, and Calendar class implements the Gregorian Calendar, you can determine whether this year is a leap year or a common year
Calendar and Date conversion to and from
Calendar cal = Calendar.getInstance();
// Calendar converts to Date
Date date = cal.getTime();
// Date converts to Calendar
date = new Date();
cal.setTime(date);
Copy the code
Gets a specific time
We often get a value for a specific time unit, such as year, month, and generally what we do is we convert Date to a string, and then we intercept the string to get the time we want
int year = cal.get(Calendar.YEAR);// Get the year
int month=cal.get(Calendar.MONTH);// Get the month
int day=cal.get(Calendar.DATE);/ / for days
int hour=cal.get(Calendar.HOUR);/ / hour
int minute=cal.get(Calendar.MINUTE);/ / points
int second=cal.get(Calendar.SECOND);/ / SEC.
int WeekOfYear = cal.get(Calendar.DAY_OF_WEEK);// The day of the week
Copy the code
If everything looks nice, it’s an illusion. For example, if you output the month, you’ll see that it goes from 0 to 11. If you want to get the normal month, you need to add one, and when you set it, you need to subtract one
Set and calculate the time
cal.set(Calendar.MONDAY, 2);
cal.set(Calendar.DATE, 30);
// 2003-8-23 => 2004-2-23
cal.add(Calendar.MONDAY, 5);
Copy the code
TimeZone
Geographically, the earth is divided into 24 time zones. Beijing Time, China, belongs to the East 8 zone, and the default implementation of time in the program is Greenwich Mean Time. This creates an eight-hour time difference. To make your program more generic, you can use TimeZone to set the TimeZone in your program, where TimeZone represents the TimeZone.
TimeZone is an abstract class. You cannot call its constructor to create an instance, but you can call its static methods getDefault() or getTimeZone() to get an instance of Tiinezone. The getDefault() method is used to obtain the default time zone of the running machine. The default time zone can be adjusted by modifying the configuration of the operating system. The getTimeZone() method obtains the corresponding time zone based on the time zone ID. The Timezone class provides some useful methods for getting information about time zones
- Static String[] getAvailablelDs() : Gets ids of all time zones supported by Java.
- Static Timezone getDefault() : gets the default Timezone on the running machine.
- String getDisplayName() : gets the TimeZone name of this TimeZone object.
- String getID() : obtains the ID of the time zone.
- Static Timezone getTimeZone(String ID) : obtains the Timezone object corresponding to the specified ID.
Basic operation of Timezone
Let’s demonstrate this in code
public static void main(String[] args) {
String[] ids = TimeZone.getAvailableIDs();
System.out.println(Arrays.toString(ids));
TimeZone my = TimeZone.getDefault();
System.out.println(my.getID());
System.out.println(my.getDisplayName());
System.out.println(TimeZone.getTimeZone("Africa/Addis_Ababa").getDisplayName());
}
Copy the code
TimeZone and Calendar
The earth is divided into 24 time zones, so the current time in each zone is different, so we generally speak for the current time zone.
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
int hour=calendar.get(Calendar.HOUR_OF_DAY);
System.out.println(hour);
calendar.setTimeZone(TimeZone.getTimeZone("Africa/Asmera"));
hour=calendar.get(Calendar.HOUR_OF_DAY);
System.out.println(hour);
}
Copy the code
The output
21
16
Copy the code
For example, for east 8, it is now 21, but for “Africa/Asmera” it is now 16
And calendar.getInstance () is the actual current time zone of the call by default, although we can pass in the time zone information at creation time
public static Calendar getInstance(a)
{
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}
public static Calendar getInstance(TimeZone zone)
{
return createCalendar(zone, Locale.getDefault(Locale.Category.FORMAT));
}
public static Calendar getInstance(Locale aLocale)
{
return createCalendar(TimeZone.getDefault(), aLocale);
}
Copy the code
conclusion
- Java provides Date, Calendar,SimpleDateFormat for us to operate time,Date we can recognize the time class, Calendar is the util of time calculation,SimpleDateFormat is the time formatting tool class, It looks so good together, but it’s not
- There are a lot of anti-human things about Calendar, and SimpleDateFormat is not thread-safe
- TimeZone provides TimeZone functionality, which is intended to support processing of data across time zones.
- Java8 gives us a new set of time-dependent solutions, which we’ll look at in the next article