XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEServerManager

【Java】我的世界XFE服务器管理器

公开
关注 0 Fork 0 Star 0
UTF-8
package com.xfestudio.xfeservermanager.core.trigger;

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.WeekFields;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;

/** Deterministic wall-clock values made available to every trigger execution. */
public final class TriggerTimeContext {
    public static final String DEFAULT_PATTERN = "uuuu-MM-dd HH:mm:ss";
    public static final String FORMAT_HINT = "推荐 uuuu-MM-dd HH:mm:ss;格式区分大小写:MM 是月、mm 是分、"
            + "HH 是 24 小时。yyyy 也可表示年号年;不要使用周历年 YYYY 或年内日 DD。 / "
            + "Use Java time patterns; uuuu-MM-dd HH:mm:ss is recommended. Patterns are case-sensitive: "
            + "MM is month, mm is minute, and HH is the 24-hour clock. yyyy is also accepted for the "
            + "year-of-era; YYYY is week-based, and DD is day-of-year.";
    private static final int MAX_PATTERN_LENGTH = 64;
    private static final int MAX_FORMATTED_LENGTH = 256;

    private TriggerTimeContext() { }

    /** Creates the immutable, server-local clock vocabulary for one captured instant. */
    public static Map<String, Object> values(Instant instant, ZoneId zone) {
        Objects.requireNonNull(instant, "instant");
        Objects.requireNonNull(zone, "zone");
        ZonedDateTime time = instant.atZone(zone);
        WeekFields weeks = WeekFields.ISO;
        Map<String, Object> values = new LinkedHashMap<>();
        values.put("server.time", time.format(DateTimeFormatter.ofPattern(DEFAULT_PATTERN, Locale.ROOT)));
        values.put("server.time.iso", time.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
        values.put("server.timezone", zone.getId());
        values.put("server.time.offset", time.getOffset().getId());
        values.put("server.time.epochSecond", instant.getEpochSecond());
        values.put("server.time.epochMilli", instant.toEpochMilli());
        values.put("server.time.year", time.getYear());
        values.put("server.time.month", time.getMonthValue());
        values.put("server.time.monthName", time.getMonth().name());
        values.put("server.time.day", time.getDayOfMonth());
        values.put("server.time.dayOfYear", time.getDayOfYear());
        values.put("server.time.dayOfWeek", time.getDayOfWeek().name());
        values.put("server.time.dayOfWeekNumber", time.getDayOfWeek().getValue());
        values.put("server.time.weekOfYear", time.get(weeks.weekOfWeekBasedYear()));
        values.put("server.time.hour24", time.getHour());
        values.put("server.time.hour12", time.getHour() % 12 == 0 ? 12 : time.getHour() % 12);
        values.put("server.time.minute", time.getMinute());
        values.put("server.time.second", time.getSecond());
        values.put("server.time.millisecond", time.getNano() / 1_000_000);
        values.put("server.time.amPm", time.getHour() < 12 ? "AM" : "PM");
        return Map.copyOf(values);
    }

    /** Formats an instant with the bounded pattern language accepted in message variables. */
    public static String format(Instant instant, ZoneId zone, String pattern) {
        validatePattern(pattern);
        String result = DateTimeFormatter.ofPattern(pattern, Locale.ROOT).format(instant.atZone(zone));
        if (result.length() > MAX_FORMATTED_LENGTH) {
            throw new IllegalArgumentException("formatted trigger time exceeds 256 characters");
        }
        return result;
    }

    /**
     * Validates a deliberately small DateTimeFormatter subset. It is broad enough for normal
     * dates, localized separators, weekday names and UTC offsets, while excluding expensive or
     * frequently-confused pattern fields.
     */
    public static void validatePattern(String pattern) {
        if (pattern == null || pattern.isBlank() || pattern.length() > MAX_PATTERN_LENGTH) {
            throw new IllegalArgumentException("time format must contain 1 to 64 characters");
        }
        boolean quoted = false;
        for (int index = 0; index < pattern.length(); index++) {
            char value = pattern.charAt(index);
            if (Character.isISOControl(value) || value == '{' || value == '}') {
                throw new IllegalArgumentException("time format contains a forbidden character");
            }
            if (value == '\'') {
                if (index + 1 < pattern.length() && pattern.charAt(index + 1) == '\'') {
                    index++;
                } else {
                    quoted = !quoted;
                }
                continue;
            }
            if (quoted || !isAsciiLetter(value)) continue;
            if (value == 'Y') {
                throw new IllegalArgumentException(
                        "use uuuu (recommended) or yyyy for a calendar year; YYYY is week-based");
            }
            if (value == 'D') {
                throw new IllegalArgumentException("use dd for the day of month; DD is day-of-year");
            }
            if ("uyMdHhmsSEeaXxZz".indexOf(value) < 0) {
                throw new IllegalArgumentException("unsupported time format letter: " + value);
            }
        }
        if (quoted) throw new IllegalArgumentException("time format has an unterminated quoted literal");
        try {
            DateTimeFormatter.ofPattern(pattern, Locale.ROOT);
        } catch (IllegalArgumentException exception) {
            throw new IllegalArgumentException("invalid time format: " + exception.getMessage(), exception);
        }
    }

    private static boolean isAsciiLetter(char value) {
        return value >= 'A' && value <= 'Z' || value >= 'a' && value <= 'z';
    }
}