SimpleDateFormat と新しい日付の処理に関するスレッドの安全でない問題

目次

SimpleDateFormat の問題

I.はじめに

2. 概要

3. スレッドの安全性の問題をシミュレートする

4 番目に、スレッドのセキュリティが不安定になる理由

5. スレッドセーフを解決する方法

6. プロジェクト内で推奨する書き方

7. 最終的なまとめ

1.8 の新しい日付の紹介

LocalDate が使用するもの

LocalDate 時刻作成メソッド

localDate は、指定された日付/時刻に基づいてオブジェクトを作成するためのカスタム日付を設定します。

日付時刻の加算と減算

年、月、日などを指定された値に変更し、新しい日付(時刻)オブジェクトを返します

日付の年、月、日、週、時、分、秒を取得します

日時の前後の比較と判定

Java8クロック:クロック()

タイムスタンプ

時間、日付間隔を計算する

日付の書式設定

時刻オブジェクトを日付文字列に変換する

時刻文字列形式を日付オブジェクトに変換する

時刻と日付のオブジェクトをフォーマットされた時刻と日付のオブジェクトに変換する

変換

localDate 转日付

日付 转 localDate

localDate からタイムスタンプへ

localDate へのタイムスタンプ


SimpleDateFormat の問題

I.はじめに


日付の変換と書式設定はプロジェクトでより一般的に使用されるべきです

質問: プロジェクトで日付変換を使用するにはどうすればよいですか? SimpleDateFormat が使用されましたか? SimpleDateFormat のスレッド セーフティの問題とその解決方法について教えていただけますか?

回答: 通常、静的な SimpleDateFormat はグローバルに定義され、ビジネス処理メソッド (コントローラー) で直接使用されます。スレッド セーフについては... これは... スレッド セーフの問題に遭遇したことはありません。

説明しましょう。

2. 概要


SimpleDateFormat クラスは、主に日付の変換と書式設定の操作を担当します。SimpleDateFormat クラスはスレッドセーフではなく、シングルスレッドではないため、マルチスレッド環境でこのクラスを使用すると、誤ったデータ変換と処理が発生しやすくなります。スレッド環境に疑問があります。

SimpleDateFormat は、クラスのコメントでマルチスレッドのシナリオには適していないことも通知します。

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.

日期格式不同步。
建议为每个线程创建单独的格式实例。 
如果多个线程同时访问一种格式,则必须在外部同步该格式。

Alibaba Java 開発仕様で SimpleDateFormat がどのように記述されているかを見てみましょう。

3. スレッドの安全性の問題をシミュレートする

コードも真実もありません。次に、SimpleDateFormat のスレッド セーフティの問題をシミュレートするスレッドを作成します。

MyThread.java クラスを作成します。

public class MyThread extends Thread{
  
    private SimpleDateFormat simpleDateFormat;
    /* 要转换的日期字符串 */
    private String dateString;

    public MyThread(SimpleDateFormat simpleDateFormat, String dateString){
        this.simpleDateFormat = simpleDateFormat;
        this.dateString = dateString;
    }

    @Override
    public void run() {
        try {
            Date date = simpleDateFormat.parse(dateString);
            String newDate = simpleDateFormat.format(date).toString();
            if(!newDate.equals(dateString)){
                System.out.println("ThreadName=" + this.getName()
                    + " 报错了,日期字符串:" + dateString
                    + " 转换成的日期为:" + newDate);
            }
        }catch (ParseException e){
            e.printStackTrace();
        }
    }
}

 実行クラス Test.java クラスを作成します。

public class Test {

    // 一般我们使用SimpleDateFormat的时候会把它定义为一个静态变量,避免频繁创建它的对象实例
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("YYYY-MM-dd");

    public static void main(String[] args) {

        String[] dateStringArray = new String[] { "2020-09-10", "2020-09-11", "2020-09-12", "2020-09-13", "2020-09-14"};

        MyThread[] myThreads = new MyThread[5];

        // 创建线程
        for (int i = 0; i < 5; i++) {
            myThreads[i] = new MyThread(simpleDateFormat, dateStringArray[i]);
        }

        // 启动线程
        for (int i = 0; i < 5; i++) {
            myThreads[i].start();
        }
    }
}

実行時のスクリーンショットは次のとおりです。

コンソールに出力される結果からわかるように、シングルトンの SimpleDateFormat クラスを使用してマルチスレッド環境で日付変換を処理すると、変換例外 (java.lang.NumberFormatException: マルチポイント) や変換エラーが発生する傾向があります。

4 番目に、スレッドのセキュリティが不安定になる理由


このとき、ソースコードの format() 形式変換メソッドを確認する必要があります。

// 成员变量 Calendar
protected Calendar calendar;

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;
}

カレンダーに注目すると、このフォーマットメソッドの実行中に、メンバー変数calendarが操作され、時刻calendar.setTime(date)が保存されます。

しかし、SimpleDateFormat を宣言するときに静的定義を使用するため、この SimpleDateFormat は共有変数となり、SimpleDateFormat 内のカレンダーには複数のスレッドからアクセスできるため、次のような問題が発生します。

スレッド A が Calendar.setTime(date) ステートメントを実行して時刻を 2020-09-01 に設定したところですが、スレッドの実行がまだ終了しておらず、スレッド B が再度 Calendar.setTime(date) ステートメントを実行して時刻を 2020-09-01 に設定したとします。時刻が 2020-09-02 になると、この時点でファントム読み取りが発生し、スレッド A が実行を継続した場合、calendar.getTime から取得される時刻は、変更後のスレッド B が取得する時刻になります。

format() メソッドとは別に、SimpleDateFormat の parse メソッドにも同じ問題があります。

ここまでで、SimpleDateFormat の欠点を発見しました。この問題を解決するには、SimpleDateFormat を共有変数として使用しないことです。

5. スレッドセーフを解決する方法



1.グローバル ツール クラス DateUtils.java を作成するために使用するたびに、新しい SimpleDateFormat を作成します。

public class DateUtils {
    public static Date parse(String formatPattern, String dateString) throws ParseException {
        return new SimpleDateFormat(formatPattern).parse(dateString);
    }

    public static String  format(String formatPattern, Date date){
        return new SimpleDateFormat(formatPattern).format(date);
    }
}

このエラー処理エラーの原理は、SimpleDateFormat クラスの複数のインスタンスを作成し、それを使用する必要がある場所に新しいインスタンスを作成することであるため、スレッド セーフの問題はありませんが、オブジェクト作成の負担も増加します。オブジェクトの作成と破棄が頻繁に行われるため、効率が低下します。

2. シンクロロック

同期では導入されません

DateUtils.java を変更する

public class DateUtils {

    private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static Date parse(String formatPattern, String dateString) throws ParseException {
        synchronized (simpleDateFormat){
            return simpleDateFormat.parse(dateString);
        }
    }

    public static String format(String formatPattern, Date date) {
        synchronized (simpleDateFormat){
            return simpleDateFormat.format(date);
        }
    }
}

シンプルで失礼な同期は、スレッドの安全性の問題も解決できますが、欠点は、同期ロックを使用した後のマルチスレッドはシリアル、スレッドのブロック、実行効率が低い。

3. ThreadLocal (最高の MVP)
ThreadLocal は Java の特別な変数です。ThreadLocal はスレッド ローカル インスタンスを提供します。通常の変数との違いは、スレッド変数を使用する各スレッドが完全に独立したインスタンス コピーを初期化することです。

DateUtils.java の変換を続行します

public class DateUtils {

    private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(){
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd");
        }
    };

    public static Date parse(String formatPattern, String dateString) throws ParseException {
        return threadLocal.get().parse(dateString);
    }

    public static String format(String formatPattern, Date date) {
        return threadLocal.get().format(date);
    }
}

ThreadLocal を使用すると、各スレッドが単一の SimpleDateFormat オブジェクトを確実に取得できるため、競合の問題は発生しません。

プロジェクト内でSimpleDateFormatがまだ使われている場合はこの書き方が推奨されますが、これで終わりでしょうか?

明らかに違います…

6. プロジェクト内で推奨する書き方


上記の Alibaba Java 開発マニュアルには、「JDK8 のアプリケーションの場合、Date の代わりに Instant、Calendar の代わりに LocalDateTime、SimpleDateFormat の代わりに DateTimeFormatter を使用できます。公式の説明: シンプルで美しく、強力で不変で、スレッドセーフです。」と説明されています。

日付変換の SimpleDateFormat は使いやすいですが、Java 8 では新しい日付と時刻 API が導入され、スレッドセーフな日付クラスが導入されています。見てみましょう。

インスタント: 瞬間的なインスタンス。
LocalDate: 特定の時間を除く現地の日付。例: 2014-01-14 は、誕生日、記念日、入社日などを記録するために使用できます。
LocalTime: 日付を除く現地時間。
LocalDateTime: 日付と時刻を組み合わせますが、時差やタイムゾーンの情報は含まれません。
ZonedDateTime: タイムゾーンと UTC またはグリニッジとの時差を含む、最も完全な日付時刻。
新しい API では、ZoneOffSet クラスと ZoneId クラスも導入されており、タイム ゾーンの問題の解決が容易になります。

時刻を解析して書式設定する DateTimeFormatter クラスも完全に再設計されました。

たとえば、次のように、Date の代わりに LocalDate を使用し、SimpleDateFormat の代わりに DateTimeFormatter を使用します。

public class DateUtils {

    public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    public static LocalDate parse(String dateString){
        return LocalDate.parse(dateString, DATE_TIME_FORMATTER);
    }

    public static String format(LocalDate target) {
        return target.format(DATE_TIME_FORMATTER);
    }
}

7. 最終的なまとめ

SimpleDateFormart は DateFormart を継承しており、DataFormat クラス内に Calendar オブジェクト参照があります。SimpleDateFormat は、parse(String)、format(date) および同様のメソッドなど、この Calendar オブジェクトに基づいて日付を変換します。Calendar を使用する場合は、直接です。使用され、Calendar の値が変更されます。この場合、マルチスレッドではスレッド セーフティの問題が発生します。SimpleDateFormart が静的である場合、この SimpleDateFormart は複数のスレッド間で共有され、この Calendar 参照も共有されます。データ割り当ての範囲、つまりスレッドの安全性の問題が発生します。(プロジェクトで使用される日付変換は、Java 8 の LocalDate または LocalDateTime を使用するようになりました。本質的には、これらのクラスは不変クラスであり、不変性によってスレッドの安全性がある程度保証されます)。

1. 解決策:

ThreadLocal を使用すると、マルチスレッドで SimpleDateFormat を変更できます。ThreadLocal を使用すると、各スレッドが単一の SimpleDateFormat オブジェクトを確実に取得できるため、競合の問題は発生しません。

2. 推奨される方法:

Java 8 で導入された新しい日付クラス API であるこれらのクラスは不変であり、スレッドセーフです。

1.8 の新しい日付の紹介

LocalDate が使用するもの

LocalDate 時刻作成メソッド

/**
     * localDate时间创建方式
     * 获取当前时间
     *
     * @author hq
     * Created in 2020/10/16 9:40
     */
    @Test
    public void localDateCreate() {
        LocalDate yyyyMMdd = LocalDate.now();
        LocalTime HHmmssSSS = LocalTime.now();
        LocalDateTime yyyyMMddHHmmssSSS = LocalDateTime.now();
        System.out.println("年月日: " + yyyyMMdd);
        System.out.println("时分秒毫秒: " + HHmmssSSS);
        System.out.println("年月日时分秒毫秒: " + yyyyMMddHHmmssSSS);
        //输出:
        //        年月日: 2020-10-16
        //        时分秒毫秒: 09:55:49.448
        //        年月日时分秒毫秒: 2020-10-16T09:55:49.448
    }

localDate は、指定された日付/時刻に基づいてオブジェクトを作成するためのカスタム日付を設定します。

/**
     * localDate 设值自定义日期
     * 根据指定日期/时间创建对象
     *
     * @author hq
     * Created in 2020/10/16 9:40
     */
    @Test
    public void setDate() {
        LocalDate yyyyMMdd = LocalDate.of(2020, 10, 15);
        LocalTime HHmmssSSS = LocalTime.of(10, 10, 10);
        LocalDateTime yyyyMMddHHmmssSSS = LocalDateTime.of(2020, 10, 15, 10, 10);
        System.out.println("自定义的年月日: " + yyyyMMdd);
        System.out.println("自定义时分秒毫秒: " + HHmmssSSS);
        System.out.println("自定义年月日时分秒毫秒: " + yyyyMMddHHmmssSSS);

        //输出:
        //        自定义的年月日: 2020-10-15
        //        自定义时分秒毫秒: 10:10:10
        //        自定义年月日时分秒毫秒: 2020-10-15T10:10
    }

日付時刻の加算と減算

/**
     * 日期时间的加减
     *
     * @author hq
     * Created in 2020/10/16 9:58
     */
    @Test
    public void lessOrAddDate() {
        LocalDate yyyyMMdd = LocalDate.now();
        LocalDate addOneDay = yyyyMMdd.plusDays(1L); //添加一日
        LocalDate addOneMonths = yyyyMMdd.plusMonths(1L);//添加一月
        LocalDate addOneYears = yyyyMMdd.plusYears(1L);//添加一年
        LocalDate addOneWeeks = yyyyMMdd.plusWeeks(1L);//添加一周

        LocalDate delOneDay = yyyyMMdd.minusDays(1L); //减去一日
        LocalDate delOneMonths = yyyyMMdd.minusMonths(1L);//减去一月
        LocalDate delOneYears = yyyyMMdd.minusYears(1L);//减去一年
        LocalDate delOneWeeks = yyyyMMdd.minusWeeks(1L);//减去一周
        System.out.println("LocalDate yyyyMMdd 当前时间: " + yyyyMMdd);
        System.out.println("*********LocalDate yyyyMMdd 添加 操作*********");
        System.out.println("LocalDate yyyyMMdd 添加一日: " + addOneDay);
        System.out.println("LocalDate yyyyMMdd 添加一月: " + addOneMonths);
        System.out.println("LocalDate yyyyMMdd 添加一年: " + addOneYears);
        System.out.println("LocalDate yyyyMMdd 添加一周: " + addOneWeeks);
        System.out.println("*********LocalDate yyyyMMdd 减 操作*********");
        System.out.println("LocalDate yyyyMMdd 减去一日: " + delOneDay);
        System.out.println("LocalDate yyyyMMdd 减去一月: " + delOneMonths);
        System.out.println("LocalDate yyyyMMdd 减去一年: " + delOneYears);
        System.out.println("LocalDate yyyyMMdd 减去一周: " + delOneWeeks);
        System.out.println("####################################################################");
        LocalTime HHmmssSSS = LocalTime.now();
        LocalTime addOneHours = HHmmssSSS.plusHours(1L); //添加1小时
        LocalTime addOneMinutes = HHmmssSSS.plusMinutes(1L);//添加1分钟
        LocalTime addOneSeconds = HHmmssSSS.plusSeconds(1L);//添加1秒
        LocalTime addOneNanos = HHmmssSSS.plusNanos(1L);//添加1纳秒

        LocalTime delOneHours = HHmmssSSS.minusHours(1L); //减去1小时
        LocalTime delOneMinutes = HHmmssSSS.minusMinutes(1L);//减去1分钟
        LocalTime delOneSeconds = HHmmssSSS.minusSeconds(1L);//减去1秒
        LocalTime delOneNanos = HHmmssSSS.minusNanos(1L);//减去1纳秒

        System.out.println("LocalTime HHmmssSSS 当前时间: " + HHmmssSSS);
        System.out.println("*********LocalTime HHmmssSSS 添加 操作*********");
        System.out.println("LocalTime HHmmssSSS 添加1小时: " + addOneHours);
        System.out.println("LocalTime HHmmssSSS 添加1分钟: " + addOneMinutes);
        System.out.println("LocalTime HHmmssSSS 添加1秒: " + addOneSeconds);
        System.out.println("LocalTime HHmmssSSS 添加1纳秒: " + addOneNanos);
        System.out.println("*********LocalTime HHmmssSSS 减 操作*********");
        System.out.println("LocalTime HHmmssSSS 减去1小时: " + delOneHours);
        System.out.println("LocalTime HHmmssSSS 减去1分钟: " + delOneMinutes);
        System.out.println("LocalTime HHmmssSSS 减去1秒: " + delOneSeconds);
        System.out.println("LocalTime HHmmssSSS 减去1纳秒: " + delOneNanos);
        System.out.println("####################################################################");
        LocalDateTime yyyyMMddHHmmssSSS = LocalDateTime.now();
        LocalDateTime localDateTimeaddOneDay = yyyyMMddHHmmssSSS.plusDays(1L); //添加一日
        LocalDateTime localDateTimeaddOneMonths = yyyyMMddHHmmssSSS.plusMonths(1L);//添加一月
        LocalDateTime localDateTimeaddOneYears = yyyyMMddHHmmssSSS.plusYears(1L);//添加一年
        LocalDateTime localDateTimeaddOneWeeks = yyyyMMddHHmmssSSS.plusWeeks(1L);//添加一周
        LocalDateTime localDateTimeaddOneHours = yyyyMMddHHmmssSSS.plusHours(1L); //添加1小时
        LocalDateTime localDateTimeaddOneMinutes = yyyyMMddHHmmssSSS.plusMinutes(1L);//添加1分钟
        LocalDateTime localDateTimeaddOneSeconds = yyyyMMddHHmmssSSS.plusSeconds(1L);//添加1秒
        LocalDateTime localDateTimeaddOneNanos = yyyyMMddHHmmssSSS.plusNanos(1L);//添加1纳秒
        System.out.println("LocalDateTime yyyyMMddHHmmssSSS 当前时间: " + yyyyMMddHHmmssSSS);
        System.out.println("*********LocalDateTime yyyyMMddHHmmssSSS 添加 操作*********");
        System.out.println("LocalDateTime yyyyMMddHHmmssSSS 添加一日: " + localDateTimeaddOneDay);
        System.out.println("LocalDateTime yyyyMMddHHmmssSSS 添加一月: " + localDateTimeaddOneMonths);
        System.out.println("LocalDateTime yyyyMMddHHmmssSSS 添加一年: " + localDateTimeaddOneYears);
        System.out.println("LocalDateTime yyyyMMddHHmmssSSS 添加一周: " + localDateTimeaddOneWeeks);
        System.out.println("LocalDateTime yyyyMMddHHmmssSSS 添加1小时: " + localDateTimeaddOneHours);
        System.out.println("LocalDateTime yyyyMMddHHmmssSSS 添加1分钟: " + localDateTimeaddOneMinutes);
        System.out.println("LocalDateTime yyyyMMddHHmmssSSS 添加1秒: " + localDateTimeaddOneSeconds);
        System.out.println("LocalDateTime yyyyMMddHHmmssSSS 添加1纳秒: " + localDateTimeaddOneNanos);
        LocalDateTime localDateTimedelOneDay = yyyyMMddHHmmssSSS.minusDays(1L); //减去一日
        LocalDateTime localDateTimedelOneMonths = yyyyMMddHHmmssSSS.minusMonths(1L);//减去一月
        LocalDateTime localDateTimedelOneYears = yyyyMMddHHmmssSSS.minusYears(1L);//减去一年
        LocalDateTime localDateTimedelOneWeeks = yyyyMMddHHmmssSSS.minusWeeks(1L);//减去一周
        LocalDateTime localDateTimedelOneHours = yyyyMMddHHmmssSSS.minusHours(1L); //减去1小时
        LocalDateTime localDateTimedelOneMinutes = yyyyMMddHHmmssSSS.minusMinutes(1L);//减去1分钟
        LocalDateTime localDateTimedelOneSeconds = yyyyMMddHHmmssSSS.minusSeconds(1L);//减去1秒
        LocalDateTime localDateTimedelOneNanos = yyyyMMddHHmmssSSS.minusNanos(1L);//减去1纳秒
        System.out.println("*********LocalDateTime yyyyMMddHHmmssSSS 减 操作*********");
        System.out.println("LocalDateTime yyyyMMddHHmmssSSS 减去一日: " + localDateTimedelOneDay);
        System.out.println("LocalDateTime yyyyMMddHHmmssSSS 减去一月: " + localDateTimedelOneMonths);
        System.out.println("LocalDateTime yyyyMMddHHmmssSSS 减去一年: " + localDateTimedelOneYears);
        System.out.println("LocalDateTime yyyyMMddHHmmssSSS 减去一周: " + localDateTimedelOneWeeks);
        System.out.println("LocalDateTime yyyyMMddHHmmssSSS 减去1小时: " + localDateTimedelOneHours);
        System.out.println("LocalDateTime yyyyMMddHHmmssSSS 减去1分钟: " + localDateTimedelOneMinutes);
        System.out.println("LocalDateTime yyyyMMddHHmmssSSS 减去1秒: " + localDateTimedelOneSeconds);
        System.out.println("LocalDateTime yyyyMMddHHmmssSSS 减去1纳秒: " + localDateTimedelOneNanos);

        /*
        输出:
            LocalDate yyyyMMdd 当前时间: 2020-10-16

            *********LocalDate yyyyMMdd 添加 操作*********
            LocalDate yyyyMMdd 添加一日: 2020-10-17
            LocalDate yyyyMMdd 添加一月: 2020-11-16
            LocalDate yyyyMMdd 添加一年: 2021-10-16
            LocalDate yyyyMMdd 添加一周: 2020-10-23

            *********LocalDate yyyyMMdd 减 操作*********
            LocalDate yyyyMMdd 减去一日: 2020-10-15
            LocalDate yyyyMMdd 减去一月: 2020-09-16
            LocalDate yyyyMMdd 减去一年: 2019-10-16
            LocalDate yyyyMMdd 减去一周: 2020-10-09

            ####################################################################
            LocalTime HHmmssSSS 当前时间: 10:20:22.164

            *********LocalTime HHmmssSSS 添加 操作*********
            LocalTime HHmmssSSS 添加1小时: 11:20:22.164
            LocalTime HHmmssSSS 添加1分钟: 10:21:22.164
            LocalTime HHmmssSSS 添加1秒: 10:20:23.164
            LocalTime HHmmssSSS 添加1纳秒: 10:20:22.164000001

            *********LocalTime HHmmssSSS 减 操作*********
            LocalTime HHmmssSSS 减去1小时: 09:20:22.164
            LocalTime HHmmssSSS 减去1分钟: 10:19:22.164
            LocalTime HHmmssSSS 减去1秒: 10:20:21.164
            LocalTime HHmmssSSS 减去1纳秒: 10:20:22.163999999

            ####################################################################
            LocalDateTime yyyyMMddHHmmssSSS 当前时间: 2020-10-16T10:20:22.165

            *********LocalDateTime yyyyMMddHHmmssSSS 添加 操作*********
            LocalDateTime yyyyMMddHHmmssSSS 添加一日: 2020-10-17T10:20:22.165
            LocalDateTime yyyyMMddHHmmssSSS 添加一月: 2020-11-16T10:20:22.165
            LocalDateTime yyyyMMddHHmmssSSS 添加一年: 2021-10-16T10:20:22.165
            LocalDateTime yyyyMMddHHmmssSSS 添加一周: 2020-10-23T10:20:22.165
            LocalDateTime yyyyMMddHHmmssSSS 添加1小时: 2020-10-16T11:20:22.165
            LocalDateTime yyyyMMddHHmmssSSS 添加1分钟: 2020-10-16T10:21:22.165
            LocalDateTime yyyyMMddHHmmssSSS 添加1秒: 2020-10-16T10:20:23.165
            LocalDateTime yyyyMMddHHmmssSSS 添加1纳秒: 2020-10-16T10:20:22.165000001

            *********LocalDateTime yyyyMMddHHmmssSSS 减 操作*********
            LocalDateTime yyyyMMddHHmmssSSS 减去一日: 2020-10-15T10:20:22.165
            LocalDateTime yyyyMMddHHmmssSSS 减去一月: 2020-09-16T10:20:22.165
            LocalDateTime yyyyMMddHHmmssSSS 减去一年: 2019-10-16T10:20:22.165
            LocalDateTime yyyyMMddHHmmssSSS 减去一周: 2020-10-09T10:20:22.165
            LocalDateTime yyyyMMddHHmmssSSS 减去1小时: 2020-10-16T09:20:22.165
            LocalDateTime yyyyMMddHHmmssSSS 减去1分钟: 2020-10-16T10:19:22.165
            LocalDateTime yyyyMMddHHmmssSSS 减去1秒: 2020-10-16T10:20:21.165
            LocalDateTime yyyyMMddHHmmssSSS 减去1纳秒: 2020-10-16T10:20:22.164999999
         */
    }

年、月、日などを指定された値に変更し、新しい日付(時刻)オブジェクトを返します

/**
     * 将年、月、日等修改为指定的值,并返回新的日期(时间)对象
     * 析: 其效果与时间日期相加减差不多,如今天是2018-01-13,要想变为2018-01-20有两种方式
     * a. localDate.plusDays(20L) -> 相加指定的天数
     * b. localDate.withDayOfYear(20) -> 直接指定到哪一天
     * 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
     *
     * @author hq
     * Created in 2020/10/16 10:34
     */
    @Test
    public void directDesignationTime() {
        LocalDate yyyyMMdd = LocalDate.now();
        System.out.println("LocalDate yyyyMMdd 当前时间: " + yyyyMMdd);

        LocalDate addDay = yyyyMMdd.plusDays(4);
        System.out.println("LocalDate yyyyMMdd 添加4天后的日期: " + addDay);

        LocalDate directDesignationDate = yyyyMMdd.withDayOfMonth(20);
        System.out.println("LocalDate yyyyMMdd 直接指定到当月第几号: " + directDesignationDate);

        LocalDate directDesignationYearDate = yyyyMMdd.withDayOfYear(20);
        System.out.println("LocalDate yyyyMMdd 直接指定到当年第几天: " + directDesignationYearDate);

        LocalDate directDesignationYear = yyyyMMdd.withYear(2000);
        System.out.println("LocalDate yyyyMMdd 当前时间直接指定年份: " + directDesignationYear);

        LocalDate directDesignationMonth = yyyyMMdd.withMonth(6);
        System.out.println("LocalDate yyyyMMdd 当前时间直接指定月份: " + directDesignationMonth);
    }

日付の年、月、日、週、時、分、秒を取得します

 /**
     * 获取日期的年月日周时分秒
     *
     * @author hq
     * Created in 2020/10/16 10:48
     */
    @Test
    public void getDateInfo() {
        LocalDateTime yyyyMMddHHmmssSSS = LocalDateTime.now();
        //本年当中第多少天
        int dayOfYear = yyyyMMddHHmmssSSS.getDayOfYear();
        //本月当中第多少天
        int dayOfMonth = yyyyMMddHHmmssSSS.getDayOfMonth();
        DayOfWeek dayOfWeek = yyyyMMddHHmmssSSS.getDayOfWeek();
        //本周中星期几
        int value = dayOfWeek.getValue();
        System.out.println("今天是" + yyyyMMddHHmmssSSS + "\n"
                + "本年当中第" + dayOfYear + "天" + "\n"
                + "本月当中第" + dayOfMonth + "天" + "\n"
                + "本周中星期" + value + "-即" + dayOfWeek + "\n");

        //年
        int year = yyyyMMddHHmmssSSS.getYear();
        //月
        Month month = yyyyMMddHHmmssSSS.getMonth();
        //直接获取
        int monthValue = yyyyMMddHHmmssSSS.getMonthValue();
        //日
        int dayOfMonth1 = yyyyMMddHHmmssSSS.getDayOfMonth();
        //时
        int hour = yyyyMMddHHmmssSSS.getHour();
        //分
        int minute = yyyyMMddHHmmssSSS.getMinute();
        //秒
        int second = yyyyMMddHHmmssSSS.getSecond();
        //纳秒
        int nano = yyyyMMddHHmmssSSS.getNano();

        System.out.println("今天是" + yyyyMMddHHmmssSSS + "\n"
                + "年 : " + year + "\n"
                + "月 : " + monthValue + "-即 " + month + "\n"
                + "日 : " + dayOfMonth1 + "\n"
                + "时 : " + hour + "\n"
                + "分 : " + minute + "\n"
                + "秒 : " + second + "\n"
                + "纳秒 : " + nano + "\n"
        );
    }

日時の前後の比較と判定

/**
     * 时间日期前后的比较与判断
     *
     * @author hq
     * Created in 2020/10/16 11:08
     */
    @Test
    public void isBefore() {
        LocalDate now = LocalDate.now();
        LocalDate of = LocalDate.of(2020, 10, 15);
        //判断of 是否在 now 时间之前
        boolean before = of.isBefore(now);
        System.out.println("判断of 是否在 now 时间之前 " + before);
        //判断of 是否在 now 时间之后
        boolean after = of.isAfter(now);
        System.out.println("判断of 是否在 now 时间之后 " + after);
        //判断两个时间是否相等
        boolean equal = of.isEqual(now);
        System.out.println("判断两个时间是否相等 " + equal);
        //判断是否为闰年
        boolean leapYear = now.isLeapYear();
        System.out.println("判断是否为闰年 " + leapYear);

        /*
            判断of 是否在 now 时间之前true
            判断of 是否在 now 时间之后false
            判断两个时间是否相等false
            判断是否为闰年true
         */
    }

Java8クロック:クロック()

/**
     * java8时钟 : clock()
     *
     * @author hq
     * Created in 2020/10/16 11:16
     */
    @Test
    public void clock() {
        //返回当前时间,根据系统时间和UTC
        Clock clock = Clock.systemUTC();
        System.out.println(clock);
        // 运行结果: SystemClock[Z]
    }

タイムスタンプ

/**
     * 时间戳
     * 事实上Instant就是java8以前的Date,
     * 可以使用以下两个类中的方法在这两个类型之间进行转换,
     * 比如Date.from(Instant)就是用来把Instant转换成java.util.date的,
     * 而new Date().toInstant()就是将Date转换成Instant的
     *
     * @author hq
     * Created in 2020/10/16 11:17
     */
    @Test
    public void instant() {
        Instant instant = Instant.now();
        System.out.println(instant);

        Date date = Date.from(instant);

        Instant instant2 = date.toInstant();
        System.out.println(date);
        System.out.println(instant2);
    }

時間、日付間隔を計算する

/**
     * 计算时间、日期间隔
     * Duration:用于计算两个“时间”间隔
     * Period:用于计算两个“日期”间隔
     *
     * @author hq
     * Created in 2020/10/16 14:28
     */
    @Test
    public void durationOrPeriod() {
        LocalDateTime now = LocalDateTime.now();
        System.out.println("duration当前时间:" + now);
        LocalDateTime of = LocalDateTime.of(2020, 10, 15, 10, 24);
        System.out.println("duration自定义时间:" + of);
        //Duration:用于计算两个“时间”间隔
        Duration duration = Duration.between(now, of);
        System.out.println(now + " 与 " + of + " 间隔  " + "\n"
                + " 天 :" + duration.toDays() + "\n"
                + " 时 :" + duration.toHours() + "\n"
                + " 分 :" + duration.toMinutes() + "\n"
                + " 毫秒 :" + duration.toMillis() + "\n"
                + " 纳秒 :" + duration.toNanos() + "\n"
        );

        LocalDate nowDate = LocalDate.now();
        System.out.println("period当前时间:" + now);
        LocalDate OfDate = LocalDate.of(2020, 10, 15);
        System.out.println("period自定义时间:" + of);
        //Period:用于计算两个“日期”间隔
        Period period = Period.between(nowDate, OfDate);
        System.out.println("Period相差年数 : " + period.getYears());
        System.out.println("Period相差月数 : " + period.getMonths());
        System.out.println("Period相差日数 : " + period.getDays());
        //还可以这样获取相差的年月日
        System.out.println("-------------------------------");
        long years = period.get(ChronoUnit.YEARS);
        long months = period.get(ChronoUnit.MONTHS);
        long days = period.get(ChronoUnit.DAYS);
        System.out.println("Period相差的年月日分别为 : " + years + "," + months + "," + days);
        //注意,当获取两个日期的间隔时,并不是单纯的年月日对应的数字相加减,而是会先算出具体差多少天,在折算成相差几年几月几日的

        /*
        输出:
            duration当前时间:2020-10-16T14:41:40.235
            duration自定义时间:2020-10-15T10:24
            2020-10-16T14:41:40.235 与 2020-10-15T10:24 间隔
             天 :-1
             时 :-28
             分 :-1697
             毫秒 :-101860235
             纳秒 :-101860235000000

            period当前时间:2020-10-16T14:41:40.235
            period自定义时间:2020-10-15T10:24
            Period相差年数 : 0
            Period相差月数 : 0
            Period相差日数 : -1
            -------------------------------
            Period相差的年月日分别为 : 0,0,-1
         */
    }

日付の書式設定

時刻オブジェクトを日付文字列に変換する

 /**
     * 将时间对象转化为日期字符串
     * 时间日期的格式化(格式化后返回的类型是String) 自定格式 使用 DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS");
     * 注:自定义转化的格式一定要与日期类型对应
     * LocalDate只能设置仅含年月日的格式
     * LocalTime只能设置仅含时分秒的格式
     * LocalDateTime可以设置含年月日时分秒的格式
     *
     * @author hq
     * Created in 2020/10/16 14:42
     */
    @Test
    public void formatter1() {
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS");
        String format = dtf.format(now);
        System.out.println(format);
        //输出: 2020-10-16 14:41:01:086
    }

時刻文字列形式を日付オブジェクトに変換する

/**
     * 将时间字符串形式转化为日期对象
     * <p>
     * 注:格式的写法必须与字符串的形式一样
     * 2018-01-13 21:27:30 对应 yyyy-MM-dd HH:mm:ss
     * 20180113213328 对应 yyyyMMddHHmmss
     * 否则会报运行时异常!
     * <p>
     * 但要记住:得到的最终结果都是类似2018-01-13T21:27:30的格式
     * 因为在输出LocalDateTime对象时,会调用其重写的toString方法。
     *
     * @author hq
     * email [email protected]
     * Created in 2020/10/16 14:44
     */
    @Test
    public void formatter2() {
        String dateStr = "2020-11-12";
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        LocalDate formatterDate = LocalDate.parse(dateStr, dtf);
        System.out.println(formatterDate);
        //输出 2020-11-12
    }

時刻と日付のオブジェクトをフォーマットされた時刻と日付のオブジェクトに変換する

/**
     * 将时间日期对象转为格式化后的时间日期对象
     *
     * @author hq
     * Created in 2020/10/16 14:49
     */
    @Test
    public void formatter3() {
        //新的格式化API中,格式化后的结果都默认是String,有时我们也需要返回经过格式化的同类型对象
        LocalDateTime ldt1 = LocalDateTime.now();
        DateTimeFormatter dtf1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String temp = dtf1.format(ldt1);
        LocalDateTime formatedDateTime = LocalDateTime.parse(temp, dtf1);
        System.out.println(formatedDateTime);
    }

変換

localDate 转日付

/**
     * localDate 转 date
     * localDateTime 转 date
     *
     * @author hq
     * email [email protected]
     * Created in 2020/10/16 15:05
     */
    @Test
    public void localDateToDate() {
        LocalDate now = LocalDate.now();
        Date from = Date.from(now.atStartOfDay(ZoneOffset.systemDefault()).toInstant());
        LocalDateTime localDateTime = LocalDateTime.now();
        Date date = Date.from(localDateTime.atZone(ZoneOffset.ofHours(8)).toInstant());
        System.out.println(from);
        System.out.println(date);
    }

日付 转 localDate

/**
     * date 转 localDate
     * date 转 localDateTime
     *
     * @author hq
     * Created in 2020/10/16 15:05
     */
    @Test
    public void dateToLocalDate() {
        Date date = new Date();
        LocalDate localDate = date.toInstant().atZone(ZoneOffset.systemDefault()).toLocalDate();
        System.out.println(localDate);
        LocalDateTime localDateTime = date.toInstant().atZone(ZoneOffset.systemDefault()).toLocalDateTime();
        System.out.println(localDateTime);
    }

localDate からタイムスタンプへ

/**
     * LocalDate 转 时间戳
     * LocalDateTime 转 时间戳
     * Created in 2020/10/16 15:28
     */
    @Test
    public void localDateToInstant() {
        LocalDate localDate = LocalDate.now();
        long instant = localDate.atStartOfDay(ZoneOffset.systemDefault()).toInstant().toEpochMilli();
        System.out.println(instant);
        LocalDateTime now = LocalDateTime.now();
        long instant1 = now.toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
        System.out.println(instant1);
    }

localDate へのタイムスタンプ

/**
     * 时间戳 转 LocalDate
     * 时间戳 转 LocalDateTime
     * Created in 2020/10/16 15:28
     */
    @Test
    public void instantToLocalDate() {
        long time = new Date().getTime();
        LocalDateTime localDateTime = Instant.ofEpochMilli(time).atZone(ZoneOffset.systemDefault()).toLocalDateTime();
        System.out.println(localDateTime);
        LocalDate localDate = Instant.ofEpochMilli(time).atZone(ZoneOffset.systemDefault()).toLocalDate();
        System.out.println(localDate);

    }

おすすめ

転載: blog.csdn.net/lan861698789/article/details/131179201