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

  1. Convert the Date type to a more readable string
  2. 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:

  1. Thread 1 calls the format method, changing the Calendar field.
  2. The thread is interrupted.
  3. Thread 2 starts executing, which also changes the calendar.
  4. Thread 2 is interrupted.
  5. Thread 1 returns, and Calendar is no longer the value it set, but the value set by thread 2.
  6. 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

  1. Static String[] getAvailablelDs() : Gets ids of all time zones supported by Java.
  2. Static Timezone getDefault() : gets the default Timezone on the running machine.
  3. String getDisplayName() : gets the TimeZone name of this TimeZone object.
  4. String getID() : obtains the ID of the time zone.
  5. 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

  1. 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
  2. There are a lot of anti-human things about Calendar, and SimpleDateFormat is not thread-safe
  3. TimeZone provides TimeZone functionality, which is intended to support processing of data across time zones.
  4. Java8 gives us a new set of time-dependent solutions, which we’ll look at in the next article