Как анализировать токены на основе времени в Java? (1м 1М 1д 1Y 1W 1S) - PullRequest
0 голосов
/ 25 февраля 2019

Я получаю следующие строки из FE:

1m
5M
3D
30m
2h
1Y
3W

Это соответствует 1 минуте, 5 месяцам, 3 дням, 30 минутам, 2 часам, 1 году, 3 неделям.

Isчто значит в java для его анализа?

Я хочу манипулировать (добавить / минус) с помощью Instant (или LocalDatetTime).Есть ли способ сделать это в Java?

Ответы [ 7 ]

0 голосов
/ 25 февраля 2019

Period & Duration

Я считаю следующее решение простым и довольно общим (не полностью общим).

public static TemporalAmount parse(String feString) {
    if (Character.isUpperCase(feString.charAt(feString.length() - 1))) {
        return Period.parse("P" + feString);
    } else {
        return Duration.parse("PT" + feString);
    }
}

Похоже, ваши основанные на дате единицы (год, месяц, неделя, день) обозначаются сокращениями в верхнем регистре (Y, M, W и D), а основанные на времени (часы и минуты) строчными (h и m)).Поэтому я проверяю регистр последнего символа строки, чтобы решить, нужно ли анализировать Period или Duration.Я использую тот факт, что оба из Period.parse и Duration.parse принимают буквы в любом случае.

Вы хотели добавить или вычесть длительности из Instant или LocalDateTime.Это работает в большинстве случаев.Давайте посмотрим:

    String[] timeAmountStrings = { "1m", "5M", "3D", "30m", "2h", "1Y", "3W" };
    LocalDateTime base = LocalDateTime.of(2019, Month.MARCH, 1, 0, 0);
    for (String tas : timeAmountStrings) {
        TemporalAmount amount = parse(tas);
        System.out.println("String: " + tas + " parsed: " + amount + " added: " + base.plus(amount));

        try {
            System.out.println("Added to Instant: " + Instant.EPOCH.plus(amount));
        } catch (DateTimeException dte) {
            System.out.println("Adding to Instant didn’t work: " + tas + ' ' + dte);
        }

        System.out.println();
    }

Вывод:

String: 1m parsed: PT1M added: 2019-03-01T00:01
Added to Instant: 1970-01-01T00:01:00Z

String: 5M parsed: P5M added: 2019-08-01T00:00
Adding to Instant didn’t work: 5M java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Months

String: 3D parsed: P3D added: 2019-03-04T00:00
Added to Instant: 1970-01-04T00:00:00Z

String: 30m parsed: PT30M added: 2019-03-01T00:30
Added to Instant: 1970-01-01T00:30:00Z

String: 2h parsed: PT2H added: 2019-03-01T02:00
Added to Instant: 1970-01-01T02:00:00Z

String: 1Y parsed: P1Y added: 2020-03-01T00:00
Adding to Instant didn’t work: 1Y java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Years

String: 3W parsed: P21D added: 2019-03-22T00:00
Added to Instant: 1970-01-22T00:00:00Z

Мы видим, что добавление к LocalDateTime работает во всех случаях.Добавление к Instant работает в большинстве случаев, только мы не можем добавить к нему период месяцев или лет.

0 голосов
/ 26 февраля 2019

Ответ Ole VV является правильным, умным и хорошо выполненным.Я бы сделал еще один шаг вперед.

PeriodDuration

Проект ThreeTen-Extra добавляет функциональность в java.time классы, встроенные в Java 8 и более поздние версии.Среди его предложений - класс PeriodDuration, объединяющий Period (годы-месяцы-дни) с Duration (часы-минуты-секунды-наночастицы).

Кстати, позвольте мне предостеречь вас от объединения двух понятий.Хотя это может показаться интуитивно понятным, но если немного поразмыслить, это может показаться проблематичным в зависимости от вашей бизнес-логики.

В любом случае, давайте адаптируем код, показанный в другом ответе.

  • Если ввод осуществляется в верхнем регистре, мы создаем строку в стандартном формате ISO 8601 длительность и анализируем ее как Period, добавляя этот результат к нашему PeriodDuration объекту.
  • Если ввод вводится строчными буквами, мы создаем строку в стандартном формате продолжительности ISO 8601, анализируем ее как Duration и добавляем результат в наш PeriodDuration объект.

Код:

List < String > inputs = List.of( "1m" , "5M" , "3D" , "30m" , "2h" , "1Y" , "3W" );
PeriodDuration pd = PeriodDuration.ZERO;
for ( String input : inputs )
{
    String s = input.trim();
    String lastLetter = s.substring( s.length() - 1 );
    int codepoint = Character.codePointAt( lastLetter , 0 );
    if ( Character.isUpperCase( codepoint ) )
    {
        String x = "P" + s;
        Period p = Period.parse( x );
        pd = pd.plus( p );
    } else
    { // Else, lowercase.
        String x = "PT".concat( s ).toUpperCase();
        Duration d = Duration.parse(x );
        pd = pd.plus( d );
    }
}
System.out.println( "pd.toString(): " + pd );

P1Y5M24DT2H31M

0 голосов
/ 25 февраля 2019

Нет прямого анализа, но вы можете сделать свой собственный:

// TODO: Proper variable/method names, comments, logging, code format, etc.
java.time.LocalDateTime changeDateTimeByString(java.time.LocalDateTime current, String part,
                                               boolean increaseOrDecrease){
  java.time.LocalDateTime newDateTime = null;

  Integer amount = Integer.parseInt(part.replaceAll("\\D",""));
  if(part.contains("S"))
    newDateTime = increaseOrDecrease ? current.plusSeconds(amount) : current.minusSeconds(amount);
  else if(part.contains("m"))
    newDateTime = increaseOrDecrease ? current.plusMinutes(amount) : current.minusMinutes(amount);
  else if(part.contains("h"))
    newDateTime = increaseOrDecrease ? current.plusHours(amount) : current.minusHours(amount);
  else if(part.contains("D"))
    newDateTime = increaseOrDecrease ? current.plusDays(amount) : current.minusDays(amount);
  else if(part.contains("W"))
    newDateTime = increaseOrDecrease ? current.plusDays(amount * 7) : current.minusDays(amount * 7);
  else if(part.contains("M"))
    newDateTime = increaseOrDecrease ? current.plusMonths(amount) : current.minusMonths(amount);
  else if(part.contains("Y"))
    newDateTime = increaseOrDecrease ? current.plusYears(amount) : current.minusYears(amount);
  else
    System.err.println("Unknown format: " + part);

  System.out.println("New dateTime after "+(increaseOrDecrease?"adding":"removing")+" "+part+": "+newDateTime);
  return newDateTime;
}

Параметры метода - это String, как указано вами в вопросе;логическое значение, хотите ли вы добавить или переместить это;и дата ввода, которую вы хотите изменить.Затем он вернет измененную дату (в данном случае используется Java 8+ java.time.LocalDateTime).Т.е.: changeDateTimeByString(dateTime, "1m", true); добавит 1 минуту;changeDateTimeByString(dateTime, "2Y", false); удалит 2 года.

Попробуйте онлайн.

0 голосов
/ 25 февраля 2019

Поскольку включены и годы, и минуты, я рекомендую вам использовать парсинг Duration и Period, как указано в комментарии.Используйте статические методы Period::parse(CharSequence text) и Duration.parse(CharSequence text) с их форматами по умолчанию (PnDTnHnMn.nS для Duration и PnYnMnD для Period), поскольку они не предоставляютспособ перевода пользовательского выражения таким образом.

Для этого варианта использования вы должны создать собственное отображение между вашим выражением и заданным форматом.

0 голосов
/ 25 февраля 2019

Spring уже делает это, но не со всеми теми же опциями, что и вы. Их реализация

private static Duration simpleParse(String rawTime) {
    if (rawTime == null || rawTime.isEmpty())
        return null;
    if (!Character.isDigit(rawTime.charAt(0)))
        return null;

    String time = rawTime.toLowerCase();
    return tryParse(time, "ns", Duration::ofNanos)
            .orElseGet(() -> tryParse(time, "ms", Duration::ofMillis).orElseGet(
                    () -> tryParse(time, "s", Duration::ofSeconds).orElseGet(
                            () -> tryParse(time, "m", Duration::ofMinutes).orElseGet(
                                    () -> tryParse(time, "h", Duration::ofHours)
                                            .orElseGet(() -> tryParse(time, "d",
                                                    Duration::ofDays)
                                                            .orElse(null))))));
}

private static Optional<Duration> tryParse(String time, String unit,
        Function<Long, Duration> toDuration) {
    if (time.endsWith(unit)) {
        String trim = time.substring(0, time.lastIndexOf(unit)).trim();
        try {
            return Optional.of(toDuration.apply(Long.parseLong(trim)));
        }
        catch (NumberFormatException ignore) {
            return Optional.empty();
        }
    }
    return Optional.empty();
}
0 голосов
/ 25 февраля 2019

Как уже предложено @Achinta Jha, вы можете реализовать простую логику синтаксического анализа с чем-то вроде

public static void main(String[] args) {
        final String[] inputs = {"1m", "5M", "3D"/*, "30m", "2h", "1Y", "3W"*/};

        Arrays.stream(inputs)
                .map(x -> Parse(x))
                .forEach(System.out::println);

    }

    private static final Map<Character, Function<String, Duration>> parsers = new HashMap<>();
    static {
        parsers.put('m', txt -> Duration.ofMinutes(Integer.parseInt(txt)));
        parsers.put('M', txt -> Duration.ofDays(30 * Integer.parseInt(txt)));
        parsers.put('D', txt -> Duration.ofDays(Integer.parseInt(txt)));
    }

    private static Duration Parse(String txt) {
        Character last = txt.charAt(txt.length() - 1);
        Function<String, Duration> parser = parsers.get(last);
        return parser.apply(txt.substring(0, txt.length() - 1));
    }

Это простая объяснительная реализация, не связанная ни с обработкой ошибок / null, ни сполная поддержка предлагаемых материалов.Обратите внимание, что Duration не поддерживает статический метод ofMonths: я предполагал, что вы намеревались 30 days, но вы можете настроить код в соответствии с желаемой семантикой.

Чтобы добавить / вычесть Duration s вы можете использовать нестатические методы Duration class .

0 голосов
/ 25 февраля 2019

Полагаю, вам следует создать свой собственный метод синтаксического анализатора, такой как этот, используя java.util.concurrent.TimeUnit, а затем вы можете использовать любой фреймворк для добавления секунд

private static long convertToSeconds(int value, char unit)
{
    long ret = value;
    switch (unit)
    {
    case 'd':
        ret = TimeUnit.DAYS.toSeconds(value);
        break;
    case 'h':
        ret = TimeUnit.HOURS.toSeconds(value);
        break;
    case 'm':
        ret = TimeUnit.MINUTES.toSeconds(value);
        break;
    case 's':
        break;
    default:
        // fail
        break;
    }
    return ret;
}
...