FieldReaderDateTimeCodec.java

package com.alibaba.fastjson2.reader;

import com.alibaba.fastjson2.JSONException;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.schema.JSONSchema;
import com.alibaba.fastjson2.util.DateUtils;
import com.alibaba.fastjson2.util.IOUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Date;
import java.util.Locale;

abstract class FieldReaderDateTimeCodec<T>
        extends FieldReader<T> {
    DateTimeFormatter formatter;

    ObjectReader dateReader;
    final boolean useSimpleFormatter;
    final boolean formatISO8601;
    final boolean formatUnixTime;
    final boolean formatMillis;
    final boolean formatHasDay;
    final boolean formatHasHour;
    final boolean yyyyMMddhhmmss19;

    public FieldReaderDateTimeCodec(
            String fieldName,
            Type fieldType,
            Class fieldClass,
            int ordinal,
            long features,
            String format,
            Locale locale,
            Object defaultValue,
            JSONSchema schema,
            Method method,
            Field field
    ) {
        super(fieldName, fieldType, fieldClass, ordinal, features, format, locale, defaultValue, schema, method, field);
        this.useSimpleFormatter = "yyyyMMddHHmmssSSSZ".equals(format);
        this.yyyyMMddhhmmss19 = "yyyy-MM-dd HH:mm:ss".equals(format);

        boolean formatUnixTime = false, formatISO8601 = false, formatMillis = false, hasDay = false, hasHour = false;
        if (format != null) {
            switch (format) {
                case "unixtime":
                    formatUnixTime = true;
                    break;
                case "iso8601":
                    formatISO8601 = true;
                    break;
                case "millis":
                    formatMillis = true;
                    break;
                default:
                    hasDay = format.indexOf('d') != -1;
                    hasHour = format.indexOf('H') != -1
                            || format.indexOf('h') != -1
                            || format.indexOf('K') != -1
                            || format.indexOf('k') != -1;
                    break;
            }
        }
        this.formatUnixTime = formatUnixTime;
        this.formatMillis = formatMillis;
        this.formatISO8601 = formatISO8601;

        this.formatHasDay = hasDay;
        this.formatHasHour = hasHour;
    }

    @Override
    public Object readFieldValue(JSONReader jsonReader) {
        Object fieldValue;
        if (jsonReader.isInt()) {
            long millis = jsonReader.readInt64Value();
            if (formatUnixTime) {
                millis *= 1000L;
            }
            fieldValue = apply(millis);
        } else if (jsonReader.isNull()) {
            jsonReader.readNull();
            return null;
        } else if (useSimpleFormatter) {
            String str = jsonReader.readString();
            try {
                Date date = new SimpleDateFormat(format).parse(str);
                fieldValue = apply(date);
            } catch (ParseException e) {
                throw new JSONException(jsonReader.info("parse error : " + str), e);
            }
        } else if (formatISO8601) {
            ZonedDateTime zdt = jsonReader.readZonedDateTime();
            fieldValue = apply(zdt);
        } else {
            long millis;
            if (yyyyMMddhhmmss19) {
                if ((jsonReader.features(features) & JSONReader.Feature.SupportSmartMatch.mask) != 0 && jsonReader.isString()) {
                    millis = jsonReader.readMillisFromString();
                } else {
                    millis = jsonReader.readMillis19();
                }
                return apply(millis);
            } else if (format != null) {
                String str = jsonReader.readString();
                if ((formatUnixTime || formatMillis) && IOUtils.isNumber(str)) {
                    millis = Long.parseLong(str);
                    if (formatUnixTime) {
                        millis *= 1000L;
                    }
                    return apply(millis);
                } else {
                    DateTimeFormatter formatter = getFormatter(jsonReader.getLocale());
                    LocalDateTime ldt;
                    if (!formatHasHour) {
                        ldt = LocalDateTime.of(LocalDate.parse(str, formatter), LocalTime.MIN);
                    } else {
                        ldt = LocalDateTime.parse(str, formatter);
                    }

                    ZonedDateTime zdt = ldt.atZone(jsonReader.getContext().getZoneId());
                    fieldValue = apply(zdt);
                }
            } else {
                millis = jsonReader.readMillisFromString();
                fieldValue = apply(millis);
            }
        }

        return fieldValue;
    }

    protected DateTimeFormatter getFormatter(Locale locale) {
        if (formatter != null && locale == null) {
            return formatter;
        }

        String format = this.format.replaceAll("aa", "a");

        if (locale != null && locale != Locale.getDefault()) {
            return DateTimeFormatter.ofPattern(format, locale);
        }

        if (this.locale != null) {
            return formatter = DateTimeFormatter.ofPattern(format, this.locale);
        }

        return formatter = DateTimeFormatter.ofPattern(format);
    }

    @Override
    public abstract ObjectReader getObjectReader(JSONReader jsonReader);

    public abstract ObjectReader getObjectReader(JSONReader.Context context);

    protected abstract void accept(T object, Date value);

    protected abstract void acceptNull(T object);

    protected abstract void accept(T object, Instant value);

    protected abstract void accept(T object, LocalDateTime ldt);

    protected abstract void accept(T object, ZonedDateTime zdt);

    protected abstract Object apply(Date value);

    protected abstract Object apply(Instant value);

    protected abstract Object apply(ZonedDateTime zdt);

    protected abstract Object apply(LocalDateTime zdt);

    protected abstract Object apply(long millis);

    @Override
    public void accept(T object, Object value) {
        if (value == null) {
            acceptNull(object);
            return;
        }

        if (value instanceof String) {
            String str = (String) value;
            if (str.isEmpty() || "null".equals(str)) {
                acceptNull(object);
                return;
            }

            if ((format == null || formatUnixTime || formatMillis) && IOUtils.isNumber(str)) {
                long millis = Long.parseLong(str);
                if (formatUnixTime) {
                    millis *= 1000L;
                }
                accept(object, millis);
                return;
            } else {
                value = DateUtils.parseDate(str, format, DateUtils.DEFAULT_ZONE_ID);
            }
        }

        if (value instanceof Date) {
            accept(object, (Date) value);
        } else if (value instanceof Instant) {
            accept(object, (Instant) value);
        } else if (value instanceof Long) {
            accept(object, ((Long) value).longValue());
        } else if (value instanceof LocalDateTime) {
            accept(object, (LocalDateTime) value);
        } else {
            throw new JSONException("not support value " + value.getClass());
        }
    }

    @Override
    public void readFieldValue(JSONReader jsonReader, T object) {
        java.util.Date fieldValue;
        try {
            if (jsonReader.isInt() && (format == null || formatUnixTime || formatMillis)) {
                long millis = jsonReader.readInt64Value();
                if (formatUnixTime) {
                    millis *= 1000L;
                }
                accept(object, millis);
                return;
            } else if (jsonReader.isNull()) {
                jsonReader.readNull();
                fieldValue = null;
            } else if (useSimpleFormatter) {
                String str = jsonReader.readString();
                try {
                    fieldValue = new SimpleDateFormat(format).parse(str);
                } catch (ParseException e) {
                    throw new JSONException(jsonReader.info("parse error : " + str), e);
                }
            } else {
                if (format != null) {
                    String str = jsonReader.readString();
                    if (str.isEmpty() || "null".equals(str)) {
                        fieldValue = null;
                    } else {
                        long millis;
                        if ((formatUnixTime || formatMillis) && IOUtils.isNumber(str)) {
                            millis = Long.parseLong(str);
                            if (formatUnixTime) {
                                millis *= 1000L;
                            }
                        } else {
                            Locale locale = jsonReader.getContext().getLocale();
                            DateTimeFormatter formatter = getFormatter(locale);

                            LocalDateTime ldt;
                            if (!formatHasHour) {
                                ldt = LocalDateTime.of(LocalDate.parse(str, formatter), LocalTime.MIN);
                            } else {
                                try {
                                    ldt = LocalDateTime.parse(str, formatter);
                                } catch (DateTimeParseException e) {
                                    if (jsonReader.isSupportSmartMatch(features)) {
                                        ldt = DateUtils.parseZonedDateTime(str)
                                                .toLocalDateTime();
                                    } else {
                                        throw e;
                                    }
                                }
                            }

                            ZonedDateTime zdt = ldt.atZone(jsonReader.getContext().getZoneId());
                            millis = zdt.toInstant().toEpochMilli();
                        }
                        fieldValue = new java.util.Date(millis);
                    }
                } else if (jsonReader.nextIfNullOrEmptyString()) {
                    fieldValue = null;
                } else {
                    long millis = jsonReader.readMillisFromString();
                    fieldValue = new java.util.Date(millis);
                }
            }
        } catch (Exception e) {
            if ((jsonReader.features(this.features) & JSONReader.Feature.NullOnError.mask) != 0) {
                fieldValue = null;
            } else {
                throw e;
            }
        }

        accept(object, fieldValue);
    }

    public boolean supportAcceptType(Class valueClass) {
        return valueClass == Date.class
                || valueClass == String.class;
    }
}