Есть ли класс для кодирования местного времени недели в Java?

Я хочу создать расписание, которое охватывает неделю за неделей.

Поскольку расписание одно и то же от одной недели к другой, единственная информация, которую мне нужно хранить, — это день недели и время, когда оно происходит. например. Monday 2:30pm. Фактическая дата не важна, равно как и часовой пояс.

До сих пор я писал свой код с разделением дня и времени, используя перечисление DayOfWeek и тип LocalTime для работы со временем. Но я начал сталкиваться с проблемами при использовании двух переменных для управления временем вместо одного удобного типа, такого как DateTime. например. если я хочу получить время через 3 часа после 23:00 во вторник, я могу использовать метод LocalTime.plus(), чтобы получить 2:00, но это не учитывает перенос дня, и мне придется проверять и обновлять это отдельно.

Я также хочу убедиться, что любое решение, которое у меня есть, переносится с конца недели на начало: например. 5 часов после 22:00 воскресенья должны быть 3:00 понедельника.

Существует ли подобный класс в JDK или достаточно просто определить свои собственные типы времени? Или было бы лучше что-то решить, используя LocalDateTime, который предоставляет java, и каким-то образом игнорировать компонент даты?


person Mitchell Kehn    schedule 26.04.2020    source источник


Ответы (2)


В JDK нет такого встроенного класса, но создать такой класс самостоятельно не составит труда.

Я думаю, вы на правильном пути, используя LocalTime и DayOfWeek. Обязательно напишите класс, который обертывает эти два компонента, а затем добавьте методы для добавления единиц времени к объекту-оболочке.

Один из методов может выглядеть примерно так:

public WeekDayTime plusHours(int hours) {
    int plusDays = (hours / 24);
    int plusHours = hours % 24;
    if (this.time.getHours() + plusHours >= 24) {
        plusDays++;
        plusHours -= 24;
    }
    DayOfWeek newDayOfWeek = ...; // Calculate the day of week somehow
    LocalTime newTime = this.time.plusHours(plusHours);
    return new WeekDayTime(newDayOfWeek, newTime);
}

В качестве альтернативы вы также можете обернуть LocalDateTime и просто скрыть компонент даты. Это избавит вас от выполнения этих расчетов. Но затем убедитесь, что вы правильно реализуете, например, метод equals.

person MC Emperor    schedule 26.04.2020
comment
Неполная строка кода проста как DayOfWeek newDayOfWeek = this.dayOfWeek.plus(plusDays);. DayOfWeek.plus() имеет циклическое переполнение, поэтому, например, SATURDAY.plus(2) дает MONDAY. - person Ole V.V.; 26.04.2020
comment
@OleVV Я не был уверен, что у DayOfWeek есть метод plus. Хорошо знать. Другое дело, если пользователь хочет включить только определенные дни недели, например, только рабочие дни. - person MC Emperor; 26.04.2020
comment
Ваш хороший ответ вдохновил меня на написание такого класса. Мой занимает более общие Duration или Period, а не количество часов. См. мой ответ. - person Basil Bourque; 27.04.2020

правильный ответ MC Emperor вдохновил меня сделать следующий шаг, разработав класс DayOfWeekTime, который более гибко выполняет метод даты и времени. приняв более общий Period & Duration объекты класса, а не просто количество часов.

Я максимально использовал существующие классы java.time. Я следовал соглашениям об именах java.time. . И я эмулировал функциональность java.time.

Для строк я использовал стандарт ISO 8601, используемый java.time. К сожалению, стандарт не рассматривает день недели с концепцией времени суток. В стандарте есть представление о неделе года, отмеченной W, за которым следует число от 1 до 52 или 1-53. Для определенного дня конкретной недели стандартно добавляется дефис с номером 1-7 для обозначения понедельника-воскресенья. Поэтому я следовал этому шаблону для своих методов toString и parse. Я начинаю с W, опускаю любой номер недели, затем ставлю дефис и номер дня недели 1-7. Я добавляю T в качестве разделителя, следуя примеру стандарта. Затем добавьте время суток в 24-часовом формате с нулевым дополнением для часов и минут. Например, дата-время 2020-04-27T13:00-04:00[America/Montreal] дает W-7T19:46:40.937485.

Этот код был только что протестирован. Используйте на свой риск.

package work.basil.example;

import java.time.*;
import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAdjusters;
import java.util.Arrays;
import java.util.Locale;
import java.util.Objects;

// Revised 2020-04-27. Fixed bug where the plus & minus methods adjusted from baseline by time-of-day but not day-of-week. Fixed.

public class DayOfWeekTime
{
    // ----------|  Fields  |------------------------------------------

    private DayOfWeek dayOfWeek;
    private LocalTime localTime;

    // ----------|  Statics  |------------------------------------------

    // We do the date-time math by picking a date arbitrarily to use as a `LocalDateTime`.
    // For convenience, we might as well pick a year that starts on a Monday.
    // https://en.wikipedia.org/wiki/Common_year_starting_on_Monday
    // Let us go with 2001-01-01.
    static private LocalDateTime BASELINE = LocalDateTime.of( 2001 , 1 , 1 , 0 , 0 , 0 , 0 );

    // ----------|  Constructor  |------------------------------------------

    private DayOfWeekTime ( final DayOfWeek dayOfWeek , final LocalTime localTime )
    {
        Objects.requireNonNull( dayOfWeek , "Received a null argument. Message # e3e04fde-d96a-41d8-a4e7-c2b4f2f5634b." );
        Objects.requireNonNull( localTime , "Received a null argument. Message # 97ccf27a-6c05-402a-a4aa-0a48bcff62c2." );
        this.dayOfWeek = dayOfWeek;
        this.localTime = localTime;
    }

    // ----------|  Factory  |------------------------------------------

    static public DayOfWeekTime of ( final DayOfWeek dayOfWeek , final LocalTime localTime )
    {
        Objects.requireNonNull( dayOfWeek , "Received a null argument. Message # ecfe6bf6-de34-4f63-9a3e-d04cd70e721f." );
        Objects.requireNonNull( localTime , "Received a null argument. Message # 83020094-409d-40e1-8dc3-12592eea1b81." );
        return new DayOfWeekTime( dayOfWeek , localTime );
    }

    static public DayOfWeekTime now ( ZoneId zoneId )
    {
        Objects.requireNonNull( zoneId , "Received null argument for time zone. Message # 6044dd82-3616-40a6-8ac2-52581e12e60f." );
        ZonedDateTime now = ZonedDateTime.now( zoneId );
        DayOfWeek dow = now.getDayOfWeek();
        LocalTime lt = now.toLocalTime();
        DayOfWeekTime dayOfWeekTime = DayOfWeekTime.of( dow , lt );
        return dayOfWeekTime;
    }

    // ----------|  Duration  |------------------------------------------
    public DayOfWeekTime plus ( final Duration duration )
    {
        Objects.requireNonNull( duration , "Received a null argument. Message # cf60bd16-3992-4779-a621-a0a3fdb2d750." );
        LocalDateTime ldt = DayOfWeekTime.BASELINE.with( TemporalAdjusters.nextOrSame( this.dayOfWeek ) ).with( this.localTime );
        LocalDateTime ldtSum = ldt.plus( duration );
        DayOfWeekTime dayOfWeekTime = DayOfWeekTime.of( ldtSum.getDayOfWeek() , ldtSum.toLocalTime() );
        return dayOfWeekTime;
    }

    public DayOfWeekTime minus ( final Duration duration )
    {
        Objects.requireNonNull( duration , "Received a null argument. Message # 4e7bf8c9-6e4f-4e3f-a8b1-5a42dc23cd8a." );
        LocalDateTime ldt = DayOfWeekTime.BASELINE.with( TemporalAdjusters.nextOrSame( this.dayOfWeek ) ).with( this.localTime );
        LocalDateTime ldtSum = ldt.minus( duration );
        DayOfWeekTime dayOfWeekTime = DayOfWeekTime.of( ldtSum.getDayOfWeek() , ldtSum.toLocalTime() );
        return dayOfWeekTime;
    }

    // ----------|  Period  |------------------------------------------

    public DayOfWeekTime plus ( final Period period )
    {
        Objects.requireNonNull( period , "Received a null argument. Message # 3b1f65b0-5b2c-4e86-aaa3-527992356d32." );
        LocalDateTime ldt = DayOfWeekTime.BASELINE.with( TemporalAdjusters.nextOrSame( this.dayOfWeek ) ).with( this.localTime );
        LocalDateTime ldtSum = ldt.plus( period );
        DayOfWeekTime dayOfWeekTime = DayOfWeekTime.of( ldtSum.getDayOfWeek() , ldtSum.toLocalTime() );
        return dayOfWeekTime;
    }

    public DayOfWeekTime minus ( final Period period )
    {
        Objects.requireNonNull( period , "Received a null argument. Message # 045938fc-d4b2-4bd2-8803-91db54d92564." );
        LocalDateTime ldt = DayOfWeekTime.BASELINE.with( TemporalAdjusters.nextOrSame( this.dayOfWeek ) ).with( this.localTime );
        LocalDateTime ldtSum = ldt.minus( period );
        DayOfWeekTime dayOfWeekTime = DayOfWeekTime.of( ldtSum.getDayOfWeek() , ldtSum.toLocalTime() );
        return dayOfWeekTime;
    }

    // ----------|  Parsing  |------------------------------------------

    // This text output invented here in this method is styled to follow the designs of ISO 8601,
    // but is most certainly *not* defined in the standard.
    static public DayOfWeekTime parse ( final String input )
    {
        Objects.requireNonNull( input , "Received a null argument. Message # 7c519b65-a1ec-486b-a9e9-ff31ee1b8057." );
        if ( input.isEmpty() || input.isBlank() )
        {
            throw new IllegalArgumentException( "Received empty/blank string as argument. Message # 59993300-bdf9-4e69-82e0-823456715c60." );
        }

        DayOfWeek dayOfWeek = null;
        LocalTime localTime = null;

        // My regex powers are weak.
        // My half-baked scheme returns tokens = [, -1, 13:00] for an input of "W-1T13:00".
        String delimiters = "[WT]+";
        String[] tokens = input.toUpperCase( Locale.US ).split( delimiters );  // ISO 8601 requires the output of uppercase letters while mandating that we accept lowercase.
        System.out.println( "DEBUG tokens = " + Arrays.toString( tokens ) );
        if ( tokens.length != 3 )
        {
            throw new IllegalArgumentException( "Received invalid string as argument. Message # e521a4e3-1ee5-46e9-b351-a5edb7206b82." );
        }

        int dowNumber = Math.abs( Integer.parseInt( tokens[ 1 ] ) );
        dayOfWeek = DayOfWeek.of( dowNumber );

        String localTimeInput = Objects.requireNonNull( tokens[ 2 ] , "The time-of-day component of the input is null. Message # 1faed491-abaa-42bd-b767-d876f8ba07d9." );
        if ( localTimeInput.isEmpty() || localTimeInput.isBlank() )
        {
            throw new IllegalArgumentException( "The time-of-day component of the input is empty/blank. Message # 98208025-d7c2-4b59-bc5f-fed7bec09741." );
        }
        try
        {
            localTime = LocalTime.parse( localTimeInput );
        }
        catch ( DateTimeParseException e )
        {
            throw new IllegalArgumentException( "The time-of-day component of the input is invalid. Message # 8de7e8d8-f4a3-478d-96d8-911454aced14." );
        }

        DayOfWeekTime dayOfWeekTime = DayOfWeekTime.of( dayOfWeek , localTime );
        return dayOfWeekTime;
    }


    // ----------|  Moment  |------------------------------------------

    public ZonedDateTime atDateInZone ( LocalDate localDate , ZoneId zoneId )
    {
        Objects.requireNonNull( zoneId , "Received null argument. Message # b8f70601-2b1d-4321-a57f-96384759a960." );
        LocalDate ld = localDate.with( TemporalAdjusters.nextOrSame( this.dayOfWeek ) ); // Move to the next date with a matching day-of-week if not a match.
        LocalDateTime ldt = LocalDateTime.of( ld , this.localTime );
        ZonedDateTime zdt = ldt.atZone( zoneId );
        return zdt;
    }


    // ----------|  Accessors  |------------------------------------------

    public DayOfWeek getDayOfWeek ( ) { return this.dayOfWeek; }

    public LocalTime getLocalTime ( ) { return this.localTime; }


    // ----------|  Object  |------------------------------------------

    @Override
    public boolean equals ( Object o )
    {
        if ( this == o ) return true;
        if ( o == null || getClass() != o.getClass() ) return false;
        DayOfWeekTime that = ( DayOfWeekTime ) o;
        return dayOfWeek == that.dayOfWeek &&
                localTime.equals( that.localTime );
    }

    @Override
    public int hashCode ( )
    {
        return Objects.hash( dayOfWeek , localTime );
    }

    // This text output invented here in this method  is styled to follow the designs of ISO 8601,
    // but is most certainly *not* defined in the standard.
    @Override
    public String toString ( )
    {
        String output = "W" + "-" + this.dayOfWeek.getValue() + "T" + this.localTime.toString();
        return output;
    }
}

Пример использования для воскресенья.

DayOfWeekTime dayOfWeekTime = DayOfWeekTime.parse( "W-7T13:00" );

DayOfWeekTime nineDaysPrior = dayOfWeekTime.minus( Period.ofDays( 9 ) );
DayOfWeekTime oneDayPrior = dayOfWeekTime.minus( Period.ofDays( 1 ) );
DayOfWeekTime oneDayLater = dayOfWeekTime.plus( Period.ofDays( 1 ) );
DayOfWeekTime twoDaysLater = dayOfWeekTime.plus( Period.ofDays( 2 ) );
DayOfWeekTime weekLater = dayOfWeekTime.plus( Period.ofWeeks( 1 ) );
DayOfWeekTime nineDaysLater = dayOfWeekTime.plus( Period.ofDays( 9 ) );

DayOfWeekTime twoHoursLater = dayOfWeekTime.plus( Duration.ofHours( 2 ) );
DayOfWeekTime nineHoursLater = dayOfWeekTime.plus( Duration.ofHours( 9 ) );
DayOfWeekTime twentyFourHoursLater = dayOfWeekTime.plus( Duration.ofHours( 24 ) );

ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = dayOfWeekTime.atDateInZone( LocalDate.now( z ) , z );

DayOfWeekTime now = DayOfWeekTime.now( z );

Дамп на консоль.

System.out.println( "After parsing: dayOfWeekTime = " + dayOfWeekTime );

System.out.println( "nineDaysPrior = " + nineDaysPrior );
System.out.println( "oneDayPrior = " + oneDayPrior );
System.out.println( "oneDayLater = " + oneDayLater );
System.out.println( "twoDaysLater = " + twoDaysLater );
System.out.println( "weekLater = " + weekLater );
System.out.println( "nineDaysLater = " + nineDaysLater );

System.out.println( "twoHoursLater = " + twoHoursLater );
System.out.println( "nineHoursLater = " + nineHoursLater );
System.out.println( "twentyFourHoursLater = " + twentyFourHoursLater );

System.out.println( "zdt = " + zdt );
System.out.println( "now = " + now );
DEBUG tokens = [, -7, 13:00]
After parsing: dayOfWeekTime = W-7T13:00
nineDaysPrior = W-5T13:00
oneDayPrior = W-6T13:00
oneDayLater = W-1T13:00
twoDaysLater = W-2T13:00
weekLater = W-7T13:00
nineDaysLater = W-2T13:00
twoHoursLater = W-7T15:00
nineHoursLater = W-7T22:00
twentyFourHoursLater = W-1T13:00
zdt = 2020-05-03T13:00-04:00[America/Montreal]
now = W-1T16:10:33.248253

И на понедельник.

DayOfWeekTime dayOfWeekTime = DayOfWeekTime.parse( "W-1T13:00" );
…
DEBUG tokens = [, -1, 13:00]
After parsing: dayOfWeekTime = W-1T13:00
nineDaysPrior = W-6T13:00
oneDayPrior = W-7T13:00
oneDayLater = W-2T13:00
twoDaysLater = W-3T13:00
weekLater = W-1T13:00
nineDaysLater = W-3T13:00
twoHoursLater = W-1T15:00
nineHoursLater = W-1T22:00
twentyFourHoursLater = W-2T13:00
zdt = 2020-04-27T13:00-04:00[America/Montreal]
now = W-1T16:16:11.543665

Похоже, что эта функция действительно может быть полезна людям для управления рабочими сменами или времени открытия/закрытия магазина. Если это имеет смысл для других, возможно, кто-то захочет помочь с этой функцией запроса на вытягивание в проекте ThreeTen-Extra.

person Basil Bourque    schedule 26.04.2020
comment
хаха, это потрясающе! Я оставлю сообщение @MCEmperor в качестве отмеченного ответа, поскольку он концептуально дошел до вас, но на самом деле: хороший - person Mitchell Kehn; 27.04.2020
comment
@MitchellKehn Я думаю, вы должны принять ответ MC Emperor, потому что он короткий и приятный, прямо относящийся к вашему вопросу с правильным решением. Но, к вашему сведению, тот, кто пришел первым, не является причиной для принятия ответа о переполнении стека. См.: Какой ответ я должен принять: более быстрый или более документированный?. - person Basil Bourque; 27.04.2020
comment
Обновление: Исправлена ​​серьезная ошибка в классе, из-за которой методы плюс/минус отклонялись от базовой линии по времени суток, но не по дням недели. Я добавил этот вызов четырьмя методами: .with( TemporalAdjusters.nextOrSame( this.dayOfWeek ) ). - person Basil Bourque; 27.04.2020
comment
Похоже, что такой запрос на включение уже есть. - person MC Emperor; 01.06.2020