Посмотрите, находится ли текущее время в определенный диапазон времени в текущий день в Java

Я уверен, что это было сделано 1000 раз в 1000 разных местах. Вопрос в том, хочу ли я знать, есть ли лучший/стандартный/более быстрый способ проверить, существует ли текущее "время" между двумя значениями времени, указанными в формате hh:mm:ss. Например, моя большая бизнес-логика не должна работать между 18:00:00 and 18:30:00. Итак, вот что я имел в виду:

 public static  boolean isCurrentTimeBetween(String starthhmmss, String endhhmmss) throws ParseException{
  DateFormat hhmmssFormat = new SimpleDateFormat("yyyyMMddhh:mm:ss");
  Date now = new Date();
  String yyyMMdd = hhmmssFormat.format(now).substring(0, 8);

  return(hhmmssFormat.parse(yyyMMdd+starthhmmss).before(now) &&
    hhmmssFormat.parse(yyyMMdd+endhhmmss).after(now));
 }

Пример тестового примера:

  String doNotRunBetween="18:00:00,18:30:00";//read from props file
  String[] hhmmss = downTime.split(",");
  if(isCurrentTimeBetween(hhmmss[0], hhmmss[1])){
   System.out.println("NOT OK TO RUN");
  }else{
   System.out.println("OK TO RUN");
  }

Что я ищу, это лучший код

  • в производительности
  • в образах
  • в правильности

То, что я не ищу

  • сторонние библиотеки
  • Обсуждение обработки исключений
  • соглашения об именах переменных
  • модификатор метода

Ответ 1

это все, что вам нужно сделать, этот метод слабо связан с входным и очень когерентным.

boolean isNowBetweenDateTime(final Date s, final Date e)
{
    final Date now = new Date();
    return now.after(s) && now.before(e);
}

как вы получаете объекты Date для начала и конца, не имеет значения для их сравнения. Вы делаете вещи более сложными, чем вам нужно, передавая представления String.

Вот лучший способ получить начальную и конечную даты, снова слабо связанную и очень связную.

private Date dateFromHourMinSec(final String hhmmss)
{
    if (hhmmss.matches("^[0-2][0-9]:[0-5][0-9]:[0-5][0-9]$"))
    {
        final String[] hms = hhmmss.split(":");
        final GregorianCalendar gc = new GregorianCalendar();
        gc.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hms[0]));
        gc.set(Calendar.MINUTE, Integer.parseInt(hms[1]));
        gc.set(Calendar.SECOND, Integer.parseInt(hms[2]));
        gc.set(Calendar.MILLISECOND, 0);
        return gc.getTime();
    }
    else
    {
        throw new IllegalArgumentException(hhmmss + " is not a valid time, expecting HH:MM:SS format");
    }
}

Теперь вы можете сделать два хорошо названных вызова метода, которые будут довольно самодокументироваться.

Ответ 2

Как указано Кевином, Fuzzy Lollipop Regex не будет брать время между 14:00 и 19:00.

Чтобы получить полный 24-часовой такт, вы можете использовать это:

if (hhmmss.matches("^([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$"))
{
    // Do stuff here
}

Ответ 3

Следующий класс - это то, что я только что создал из некоторого кода из других ответов. Он инкапсулирует поведение "периода времени", не относясь к конкретным дням. Наша система использует этот класс, чтобы проверить, находится ли текущее время в одном из наших назначенных окон обслуживания. т.е. 05:00:00 - 07:00:00

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

/**
*
* @author Adam Yocum
*/
public class ExclusionTimePeriod {
    private String timeStart;
    private String timeEnd;

    /**
    * @return the timeStart
    */
    public String getTimeStart() {
        return timeStart;
    }

    /**
    * @param timeStart the timeStart to set
    */
    public void setTimeStart(String timeStart) {
        if (timeStart.matches("^([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$"))
        {
            this.timeStart = timeStart;
        }
        else
        {
            throw new IllegalArgumentException(timeStart + " is not a valid time, expecting HH:MM:SS format");
        }

    }

    /**
    * @return the timeEnd
    */
    public String getTimeEnd() {
        return timeEnd;
    }

    /**
    * @param timeEnd the timeEnd to set
    */
    public void setTimeEnd(String timeEnd) {
        if (timeEnd.matches("^([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$"))
        {
            this.timeEnd = timeEnd;
        }
        else
        {
            throw new IllegalArgumentException(timeEnd + " is not a valid time, expecting HH:MM:SS format");
        }
    }

    private Date toDate(String hhmmss){
        final String[] hms = hhmmss.split(":");
        final GregorianCalendar gc = new GregorianCalendar();
        gc.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hms[0]));
        gc.set(Calendar.MINUTE, Integer.parseInt(hms[1]));
        gc.set(Calendar.SECOND, Integer.parseInt(hms[2]));
        gc.set(Calendar.MILLISECOND, 0);
        Date date = gc.getTime();
        return date;
    }

    public boolean isNowInPeriod()
    {
        final Date now = new Date();
        return now.after(toDate(getTimeStart())) && now.before(toDate(getTimeEnd()));
    }

    public static void main(String[] args){

        //Test All possible hours
        for(int hour=0;hour<=23;hour++){

            String hourStr = "";
            if(hour<=9){
                hourStr = "0"+hour;
            }else{
                hourStr = ""+hour;
            }

            for(int min=0;min<60;min++){
                String minStr = "";
                if(min<=9){
                    minStr = "0"+min;
                }else{
                    minStr = ""+min;
                }

                for(int sec=0;sec<60;sec++){
                    String secStr = "";
                    if(sec<=9){
                        secStr = "0"+sec;
                    }else{
                        secStr = ""+sec;
                    }

                    String hhmmss = hourStr+":"+minStr+":"+secStr;

                    ExclusionTimePeriod period = new ExclusionTimePeriod();
                    period.setTimeStart(hhmmss);
                    period.setTimeEnd(hhmmss);

                    System.out.println(hhmmss+" Ok");
                }
            }
        }


        //Test isInPeriod functionality
        ExclusionTimePeriod isInTest = new ExclusionTimePeriod();
        isInTest.setTimeStart("10:00:00");
        isInTest.setTimeEnd("10:43:00");

        System.out.println((new Date())+" is between "+isInTest.getTimeStart()+" and "+isInTest.getTimeEnd()+" = "+isInTest.isNowInPeriod());

    }
}

Ответ 4

Метод dateFromHourMinSec имеет недостатки, как указано. Это не позволит делать часы, когда вторая цифра больше 3, например. 18:00:00. Если вы измените его, чтобы разрешить [0-2] [0-9], это позволит использовать такие периоды, как 29:00:00. У вас есть исправление для этого?

Ответ 5

TL;DR

LocalTime now = ZonedDateTime.now( ZoneId.of( "America/Montreal" ) )
                             .toLocalTime() ;
Boolean isBetween = ( ! now.isBefore( LocalTime.of( 18 , 0 ) )  // "not before" means "is equal to OR after".
                    && 
                    now.isBefore( LocalTime.of( 18 , 30 ) ) ;  // Half-Open, beginning is *inclusive* while ending is *exclusive*.

Использование java.time

Вы используете старые классы времени, которые оказались плохо разработанными, запутанными и сложными. Теперь они legacy, вытесненные классами java.time.

LocalTime

Не передавать простые строки, представляющие значения времени дня. Теперь у нас есть тип, класс LocalTime.

LocalTime start = LocalTime.of( 18 , 0 );
LocalTime stop = LocalTime.of( 18 , 30 );

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

public static  boolean isCurrentTimeBetween( LocalTime start , LocalTime stop ) {
…

ZonedDateTime

Часовой пояс имеет решающее значение для определения текущей даты и времени суток. В любой данный момент дата изменяется по всему миру по зонам. Например, через несколько минут после полуночи в Париж Франция - это новый день, пока еще "вчера" в Монреаль Квебек.

Укажите имя часового пояса в формате continent/region, например America/Montreal, Africa/Casablanca или Pacific/Auckland. Никогда не используйте аббревиатуру 3-4 буквы, например EST или IST, поскольку они не являются настоящими часовыми поясами, а не стандартизированы и даже не уникальны (!).

ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.now( z );

Чтобы сравнить время суток, мы могли бы просто извлечь LocalTime из этого ZonedDateTime. Но у нас есть проблема аномалий, таких как переход на летнее время (DST) и политиков, переопределяющих часовые пояса. В определенный день может не быть часа 6 PM. Решение этой проблемы зависит от вашего бизнес-контекста и ваших бизнес-правил. Вы можете либо игнорировать головоломку, либо буквально спрашивать, находится ли текущее время между вашим целевым временем начала-остановки. Или вы можете применить часовой пояс к своему времени начала-остановки дня и позволить классу ZonedDateTime внести коррективы по своему усмотрению. Давайте посмотрим на оба подхода.

Игнорировать аномалии

Во-первых, игнорировать любые аномалии. Задавайте просто и буквально, если текущее время дня находится между целевым началом и остановкой времени суток.

Мы можем извлечь объект времени из объекта с зоной времени.

LocalTime localTimeNow = zdt.toLocalTime(); // Extract a time-of-day from the zoned date-time object.

Сравните это с нашими временными остановками. Обратите внимание, что мы используем здесь метод Half-Open для определения промежутка времени. В этом подходе начало включено, в то время как окончание является исключительным. Этот подход распространен в работе с датой и, как правило, является разумным способом.

Boolean isNowOnOrAfterStart = ( ! localTimeNow.isBefore( start ) ) ;  // A briefer way of asking "is equal to OR is after" is "is not before". 
Boolean isNowBeforeStop = localTimeNow.isBefore( stop );
Boolean isNowInTargetZone = ( isNowOnOrAfterStart && isNowBeforeStop ); // Half-Open: beginning is inclusive while ending is exclusive.

Рассмотрим аномалии

Далее рассмотрим любые аномалии. Мы применяем время начала и остановки к текущей дате в пределах того же часового пояса. Мы извлекаем дату только из объекта с зоной времени.

LocalDate localDateToday = zdt.toLocalDate();
ZonedDateTime zdtStart = ZonedDateTime.of( localDateToday , start , z );
ZonedDateTime zdtStop = ZonedDateTime.of( localDateToday , stop , z );

Изучите документацию класса, чтобы понять поведение ZonedDateTime.of в разрешении недопустимых значений времени дня. Не существует идеального способа разрешения несуществующих значений времени суток, поэтому вы должны решить, соответствует ли этот класс вашим бизнес-правилам.

ZonedDateTime.of

public static ZonedDateTime of(LocalDate date, LocalTime time, ZoneId zone)

Получает экземпляр ZonedDateTime из локальной даты и времени. Это создает зонированную дату-время, соответствующую местным датам и времени ввода, насколько это возможно. Правила часовых поясов, такие как переход на летнее время, означают, что не все локальные даты-даты действительны для указанной зоны, поэтому можно настроить локальное время-дату.

Локальное время и первое время объединяются для создания локального времени. Затем локальная дата-время разрешается к одному моменту времени. Это достигается путем нахождения допустимого смещения от UTC/Greenwich для локального дате-времени, определенного правилами ID зоны.

В большинстве случаев существует только одно допустимое смещение для локальной даты-времени. В случае перекрытия, когда часы установлены назад, существуют два допустимых смещения. Этот метод использует более раннее смещение, обычно соответствующее "лету".

В случае зазора, когда часы переходят вперед, нет действительного смещения. Вместо этого локальная дата-время настраивается на более позднюю длину пробела. Для типичного изменения дневного перехода на 1 час локальное время-время будет перемещено на один час позже в смещение, обычно соответствующее "летнему".

Примените ту же логику сравнения, что и мы видели выше.

Boolean isNowOnOrAfterStart = ( ! zdt.isBefore( zdtStart ) ) ;  // A briefer way of asking "is equal to OR is after" is "is not before". 
Boolean isNowBeforeStop = zdt.isBefore( zdtStop );
Boolean isNowInTargetZone = ( isNowOnOrAfterStart && isNowBeforeStop ); // Half-Open: beginning is inclusive while ending is exclusive.

Альтернативный способ сравнения - использовать удобный Interval класс из проекта ThreeTen-Extra. Этот класс принимает боль Instant объектов, которые вы можете извлечь из ваших объектов ZonedDateTime. Класс Instant представляет момент на временной шкале в UTC с разрешением наносекунды (до девяти (9) цифр десятичной дроби).

Interval interval = Interval.of( zdtStart.toInstant() , zdtStop.toInstant() );
Boolean isNowInTargetZone = interval.contains( zdt.toInstant() );

О java.time

Структура java.time встроена в Java 8 и более поздних версий. Эти классы вытесняют неприятные старые legacy классы времени, такие как java.util.Date, Calendar и SimpleDateFormat.

Проект Joda-Time, теперь режим обслуживания, советуем перейти к классам java.time.

Чтобы узнать больше, см. Учебник Oracle. И поиск Qaru для многих примеров и объяснений. Спецификация JSR 310.

Где получить классы java.time?

  • Java SE 8 и SE 9 и позже
    • Встроенный.
    • Часть стандартного Java API с объединенной реализацией.
    • Java 9 добавляет некоторые незначительные функции и исправления.
  • Java SE 6 и SE 7
    • Большая часть функциональных возможностей java.time обратно переносится на Java 6 и 7 в ThreeTen-Backport.
  • Android

Проект ThreeTen-Extra расширяет java.time с дополнительными классами. Этот проект является доказательством возможных будущих дополнений к java.time. Здесь вы можете найти полезные классы, такие как Interval, YearWeek, YearQuarter и больше.