DateWrite.java

package com.alibaba.fastjson2.benchmark;

import com.alibaba.fastjson2.util.DateUtils;
import com.alibaba.fastjson2.util.JDKUtils;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import static com.alibaba.fastjson2.util.JDKUtils.*;

public class DateWrite {
    static final ZoneId ZONE_ID_SHANGHAI = ZoneId.of("Asia/Shanghai");
    static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZONE_ID_SHANGHAI);

    static final ThreadLocal<SimpleDateFormat> formatThreadLocal = ThreadLocal.withInitial(
            () -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
    );

    static Date[] dates = new Date[12];

    static {
        String[] strings = {
                "2001-07-01 01:02:03", "2011-06-02 09:02:03",
                "2021-11-03 02:02:03", "2001-11-14 07:02:03",
                "2002-10-07 16:02:03", "2003-09-12 15:02:03",
                "2006-08-16 02:02:03", "2002-01-30 22:02:03",
                "2009-02-27 01:02:03", "2011-04-26 11:02:03",
                "2012-06-23 01:02:03", "2022-02-18 09:02:03"
        };

        for (int i = 0; i < strings.length; i++) {
            LocalDateTime ldt = LocalDateTime.parse(strings[i], formatter);
            long millis = ldt.atZone(ZONE_ID_SHANGHAI).toInstant().toEpochMilli();
            dates[i] = new Date(millis);
        }
    }

    @Benchmark
    public void dateTimeFormatter(Blackhole bh) {
        for (int i = 0; i < dates.length; i++) {
            Date date = dates[i];
            bh.consume(
                    formatter.format(
                            date.toInstant()
                    )
            );
        }
    }

    @Benchmark
    public void simpleDateFormat(Blackhole bh) throws Exception {
        for (int i = 0; i < dates.length; i++) {
            Date date = dates[i];
            SimpleDateFormat format = formatThreadLocal.get();
            bh.consume(
                    format.format(date)
            );
        }
    }

    @Benchmark
    public void formatYYYYMMDDHHMMSS19(Blackhole bh) throws Throwable {
        for (int i = 0; i < dates.length; i++) {
            Date date = dates[i];
            formatYYYYMMDDHHMMSS19(ZONE_ID_SHANGHAI, date);
        }
    }

    static String formatYYYYMMDDHHMMSS19(ZoneId zoneId, Date date) throws Throwable {
        long millis = date.getTime();

        final long SECONDS_PER_DAY = 60 * 60 * 24;

        long epochSecond = Math.floorDiv(millis, 1000L);
        int offsetTotalSeconds;
        if (zoneId == DateUtils.SHANGHAI_ZONE_ID || zoneId.getRules() == DateUtils.SHANGHAI_ZONE_RULES) {
            offsetTotalSeconds = DateUtils.getShanghaiZoneOffsetTotalSeconds(epochSecond);
        } else {
            Instant instant = Instant.ofEpochMilli(millis);
            offsetTotalSeconds = zoneId.getRules().getOffset(instant).getTotalSeconds();
        }

        long localSecond = epochSecond + offsetTotalSeconds;
        long localEpochDay = Math.floorDiv(localSecond, SECONDS_PER_DAY);
        int secsOfDay = (int) Math.floorMod(localSecond, SECONDS_PER_DAY);
        int year, month, dayOfMonth;
        {
            final int DAYS_PER_CYCLE = 146097;
            final long DAYS_0000_TO_1970 = (DAYS_PER_CYCLE * 5L) - (30L * 365L + 7L);

            long zeroDay = localEpochDay + DAYS_0000_TO_1970;
            // find the march-based year
            zeroDay -= 60;  // adjust to 0000-03-01 so leap day is at end of four year cycle
            long adjust = 0;
            if (zeroDay < 0) {
                // adjust negative years to positive for calculation
                long adjustCycles = (zeroDay + 1) / DAYS_PER_CYCLE - 1;
                adjust = adjustCycles * 400;
                zeroDay += -adjustCycles * DAYS_PER_CYCLE;
            }
            long yearEst = (400 * zeroDay + 591) / DAYS_PER_CYCLE;
            long doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
            if (doyEst < 0) {
                // fix estimate
                yearEst--;
                doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
            }
            yearEst += adjust;  // reset any negative year
            int marchDoy0 = (int) doyEst;

            // convert march-based values back to january-based
            int marchMonth0 = (marchDoy0 * 5 + 2) / 153;
            month = (marchMonth0 + 2) % 12 + 1;
            dayOfMonth = marchDoy0 - (marchMonth0 * 306 + 5) / 10 + 1;
            yearEst += marchMonth0 / 10;

            // check year now we are certain it is correct
            if (yearEst < Year.MIN_VALUE || yearEst > Year.MAX_VALUE) {
                throw new DateTimeException("Invalid year " + yearEst);
            }

            year = (int) yearEst;
        }

        int hour, minute, second;
        {
            final int MINUTES_PER_HOUR = 60;
            final int SECONDS_PER_MINUTE = 60;
            final int SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR;

            long secondOfDay = secsOfDay;
            if (secondOfDay < 0 || secondOfDay > 86399) {
                throw new DateTimeException("Invalid secondOfDay " + secondOfDay);
            }
            int hours = (int) (secondOfDay / SECONDS_PER_HOUR);
            secondOfDay -= hours * SECONDS_PER_HOUR;
            int minutes = (int) (secondOfDay / SECONDS_PER_MINUTE);
            secondOfDay -= minutes * SECONDS_PER_MINUTE;

            hour = hours;
            minute = minutes;
            second = (int) secondOfDay;
        }

        if (JDKUtils.STRING_CREATOR_JDK8 != null) {
            char[] chars = new char[19];

            chars[0] = (char) (year / 1000 + '0');
            chars[1] = (char) ((year / 100) % 10 + '0');
            chars[2] = (char) ((year / 10) % 10 + '0');
            chars[3] = (char) (year % 10 + '0');
            chars[4] = '-';
            chars[5] = (char) (month / 10 + '0');
            chars[6] = (char) (month % 10 + '0');
            chars[7] = '-';
            chars[8] = (char) (dayOfMonth / 10 + '0');
            chars[9] = (char) (dayOfMonth % 10 + '0');
            chars[10] = ' ';
            chars[11] = (char) (hour / 10 + '0');
            chars[12] = (char) (hour % 10 + '0');
            chars[13] = ':';
            chars[14] = (char) (minute / 10 + '0');
            chars[15] = (char) (minute % 10 + '0');
            chars[16] = ':';
            chars[17] = (char) (second / 10 + '0');
            chars[18] = (char) (second % 10 + '0');

            return STRING_CREATOR_JDK8.apply(chars, Boolean.TRUE);
        } else {
            byte[] bytes = new byte[19];

            bytes[0] = (byte) (year / 1000 + '0');
            bytes[1] = (byte) ((year / 100) % 10 + '0');
            bytes[2] = (byte) ((year / 10) % 10 + '0');
            bytes[3] = (byte) (year % 10 + '0');
            bytes[4] = '-';
            bytes[5] = (byte) (month / 10 + '0');
            bytes[6] = (byte) (month % 10 + '0');
            bytes[7] = '-';
            bytes[8] = (byte) (dayOfMonth / 10 + '0');
            bytes[9] = (byte) (dayOfMonth % 10 + '0');
            bytes[10] = ' ';
            bytes[11] = (byte) (hour / 10 + '0');
            bytes[12] = (byte) (hour % 10 + '0');
            bytes[13] = ':';
            bytes[14] = (byte) (minute / 10 + '0');
            bytes[15] = (byte) (minute % 10 + '0');
            bytes[16] = ':';
            bytes[17] = (byte) (second / 10 + '0');
            bytes[18] = (byte) (second % 10 + '0');

            if (STRING_CREATOR_JDK11 != null) {
                return STRING_CREATOR_JDK11.apply(bytes, LATIN1);
            } else {
                return new String(bytes, 0, bytes.length);
            }
        }
    }

    public static void main(String[] args) throws Exception {
        Options options = new OptionsBuilder()
                .include(DateWrite.class.getName())
                .mode(Mode.Throughput)
                .timeUnit(TimeUnit.MILLISECONDS)
                .warmupIterations(3)
                .forks(1)
                .build();
        new Runner(options).run();
    }
}