Joda Tempo LocalTime 24:00 fine giornata

Stiamo creando una applicazione di pianificazione e abbiamo bisogno di rappresentare qualcuno di pianificazione disponibili durante il giorno, indipendentemente dal fuso orario. Prendendo spunto da Joda Tempo dell’Intervallo, il che rappresenta un intervallo di tempo assoluto tra le due istanze (inizio inclusive, fine esclusivo), abbiamo creato un LocalInterval. Il LocalInterval si compone di due LocalTimes (inizio inclusive, fine esclusivo), e abbiamo anche fatto un utile di classe per mantenere questo in modalità di Ibernazione.

Per esempio, se qualcuno è disponibile dalle 1:00 alle 5:00, siamo in grado di creare:

new LocalInterval(new LocalTime(13, 0), new LocalTime(17, 0));

Finora tutto bene—fino a quando qualcuno non vuole essere disponibili dalle 11:00 fino a mezzanotte di un giorno. Dopo la fine di un intervallo è esclusivo, questo dovrebbe essere facilmente rappresentata come tale:

new LocalInterval(new LocalTime(23, 0), new LocalTime(24, 0));

Ack! No go. Questo genera un’eccezione, perché LocalTime non può tenere qualsiasi ora superiore a 23.

Questo mi sembra un difetto di progettazione a me—Joda non considerare che qualcuno potrebbe desiderare un LocalTime che rappresenta un non-inclusive endpoint.

Questo è davvero frustrante, come soffia un buco in quella che era altrimenti molto elegante modello che abbiamo creato.

Quali sono le mie opzioni—diverse da quelle che si biforcano Joda e di prendere il controllo per 24 ore? (No, non mi piace la possibilità di utilizzare un valore fittizio—dire 23:59:59—per rappresentare le 24:00.)

Aggiorna: Per quelli che continuano a dire che non c’è nessuna tale cosa come 24:00, ecco una citazione da ISO 8601-2004 4.2.3 Note 2,3: “La fine di un giorno di calendario [24:00] coincide con [00:00] all’inizio del giorno di calendario successivo …” e “Rappresentazioni dove [hh] ha il valore [24] sono solo preferito per rappresentare la fine di un intervallo di tempo ….”

  • Dal contesto della tua domanda, sto cercando di indovinare volevi dire “Il LocalInterval si compone di due LocalTimes (inizio inclusive, fine esclusivo), … “
  • Sì, era un errore di battitura. Ho aggiornato la domanda per indicare la fine esclusivo. Grazie.
  • Domanda molto interessante. Mi ha ispirato qualche tempo fa per introdurre la funzione di 24:00 nel mio data/ora-biblioteca Time4J. I suoi simili che con ciondolo a LocalTime, cioè PlainTime supporta il leggermente più ampia gamma 00:00/24:00. E ho trovato che questa caratteristica non fare alcun problema (dell’ordine di tempo è ancora abbastanza chiaro), ma può aiutare a risolvere alcuni altri problemi in un modo più elegante (per esempio la IANA-TZDB utilizza il valore 24:00, troppo).

 

9 Replies
  1. 5

    La soluzione, infine, è andato con la era quello di utilizzare 00:00 come uno stand-in per le 24:00, con la logica di tutta la classe e il resto dell’applicazione di interpretare questo valore locale. Questo è un vero kludge, ma è il meno invadente e più elegante cosa che ho potuto venire con.

    Primo, il LocalTimeInterval classe mantiene un flag interno di se l’intervallo endpoint è la fine-del-giorno della mezzanotte (24:00). Questa bandiera dovrà essere vero solo se l’ora di fine: 00:00 (pari a LocalTime.A MEZZANOTTE).

    /**
     * @return Whether the end of the day is {@link LocalTime#MIDNIGHT} and this should be considered midnight of the
     *         following day.
     */
    public boolean isEndOfDay()
    {
        return isEndOfDay;
    }

    Per impostazione predefinita, il costruttore ritiene 00:00 per essere a inizio giornata, ma c’è un alternativa al costruttore per la creazione manuale di un intervallo che va tutto il giorno:

    public LocalTimeInterval(final LocalTime start, final LocalTime end, final boolean considerMidnightEndOfDay)
    {
        ...
        this.isEndOfDay = considerMidnightEndOfDay && LocalTime.MIDNIGHT.equals(end);
    }

    C’è un motivo per cui questo costruttore non ha solo un tempo di inizio e di un “fine-del-giorno” di bandiera: quando utilizzato con una UI con una discesa di volte, non sappiamo se l’utente sceglie 00:00 (che è reso come 24:00), ma sappiamo che l’elenco a discesa per la fine dell’intervallo, nel nostro caso d’uso significa 24:00. (Anche se LocalTimeInterval permette vuoto intervalli, noi non consentire loro nel nostro programma.)

    Sovrapposizione di controllo richiede una particolare logica di prendersi cura del 24:00:

    public boolean overlaps(final LocalTimeInterval localInterval)
    {
        if (localInterval.isEndOfDay())
        {
            if (isEndOfDay())
            {
                return true;
            }
            return getEnd().isAfter(localInterval.getStart());
        }
        if (isEndOfDay())
        {
            return localInterval.getEnd().isAfter(getStart());
        }
        return localInterval.getEnd().isAfter(getStart()) && localInterval.getStart().isBefore(getEnd());
    }

    Allo stesso modo, la conversione di un assoluto Intervallo richiede l’aggiunta di un altro giorno per il risultato, se isEndOfDay() restituisce true. È importante che il codice dell’applicazione non costruisce un Intervallo manualmente da un LocalTimeInterval di inizio e di fine dei valori, come la fine del tempo potrebbe indicare la fine-del-giorno:

    public Interval toInterval(final ReadableInstant baseInstant)
    {
        final DateTime start = getStart().toDateTime(baseInstant);
        DateTime end = getEnd().toDateTime(baseInstant);
        if (isEndOfDay())
        {
            end = end.plusDays(1);
        }
        return new Interval(start, end);
    }

    Quando persistenti LocalTimeInterval nel database, siamo stati in grado di fare kludge totalmente trasparente, come Hibernate SQL e di avere n. 24:00 di restrizione (e infatti non hanno il concetto di “LocalTime” comunque). Se isEndOfDay() restituisce true, il nostro PersistentLocalTimeIntervalAsTime attuazione memorizza e recupera un vero valore di tempo di 24:00:

        ...
        final Time startTime = (Time) Hibernate.TIME.nullSafeGet(resultSet, names[0]);
        final Time endTime = (Time) Hibernate.TIME.nullSafeGet(resultSet, names[1]);
        ...
        final LocalTime start = new LocalTime(startTime, DateTimeZone.UTC);
        if (endTime.equals(TIME_2400))
        {
            return new LocalTimeInterval(start, LocalTime.MIDNIGHT, true);
        }
        return new LocalTimeInterval(start, new LocalTime(endTime, DateTimeZone.UTC));

    e

        final Time startTime = asTime(localTimeInterval.getStart());
        final Time endTime = localTimeInterval.isEndOfDay() ? TIME_2400 : asTime(localTimeInterval.getEnd());
        Hibernate.TIME.nullSafeSet(statement, startTime, index);
        Hibernate.TIME.nullSafeSet(statement, endTime, index + 1);

    È triste che abbiamo dovuto scrivere una soluzione, in primo luogo, questo è il meglio che ho potuto fare.

    • Per un’applicazione di pianificazione, credo che è è la soluzione più elegante. Quante volte ho avuto problemi con qualcuno in outlook cercando di rappresentare la loro intera giornata evento ” abbinando il tempo esatto: shift che nella nostra differenze di fuso orario, e tutto il nostro calendario è un casino. Questo è esattamente il motivo per cui in outlook si dispone di un separato passare per rappresentare un intero giorno.
  2. 5

    Dopo le 23:59:59 arriva 00:00:00 del giorno successivo. Così magari utilizzare un LocalTime di 0, 0 il giorno di calendario successivo?

    Anche se dal momento di inizio e di fine inclusive, 23:59:59 è davvero quello che vuoi comunque. Che include il 59 ° secondo del 59 ° minuto di 23 ore e termina la gamma esattamente 00:00:00.

    C’è nessuna tale cosa come 24:00 (se si utilizza LocalTime).

    • Scusate, la mia domanda originale era un errore di battitura. Volevo dire “fine esclusivo”, quindi quello che voglio è 24:00. C’è davvero una cosa come 24:00. Vedi ISO 8601-2004 Sezione 4.2.3 “Mezzanotte”. Perché il mio fine è esclusiva, è esattamente quello che voglio.
    • Dopo aver letto attraverso alcuni dei Joda docs ho un paio di suggerimenti. Primo, hai provato ad usare il LocalTime.MIDNIGHT costante? In secondo luogo, che la spec che dice e ciò che l’attuazione non sono necessariamente la stessa cosa. Si può provare qualcosa di simile System.out.println(new LocalTime(12,0).getChronology().hourOfDay().getMaximumValue())? Se il valore è inferiore a 24, quindi le 24:00 non esiste nell’attuazione. Che potrebbe essere qualcosa che si può alzare come un bug in Joda.
    • “Dopo le 23:59:59 arriva 00:00:00 del giorno successivo.” Non necessariamente. A volte dopo le 23:59:59 viene 23:59:60 – hpiers.obspm.fr/iers/bul/bulc/bulletinc.dat
    • “Non c’è nessuna tale cosa come 24:00.” è inesatto. LocalTime non supporta giorni > 23:59 ma questo non vuol dire che non esiste. GTFS opera su > 24 ore al giorno. developers.google.com/transit/gtfs/reference/#stop_timestxt
  3. 2

    Non è un difetto di progettazione. LocalDate non gestisce (24,0) perché non c’è nessuna tale cosa come 24:00.

    Inoltre, cosa succede quando si vuole rappresentare un intervallo tra, diciamo alle 9 di sera e le 3 del mattino?

    Cosa c’è di sbagliato con questo:

    new LocalInterval(new LocalTime(23, 0), new LocalTime(0, 0));

    Devi solo gestire la possibilità che la fine del tempo potrebbe essere “prima di” l’ora di inizio, e aggiungere un giorno, quando necessario, e spero solo che nessuno vuole rappresentare un intervallo più lungo di 24 ore.

    In alternativa, rappresentano l’intervallo come una combinazione di un LocalDate e un Duration o Period. Che elimina la “più di 24 ore” problema.

    • “[T]qui è nessuna tale cosa come 24:00”. In effetti c’è. Vedi ISO 8601-2004 Sezione 4.2.3 “Mezzanotte”. Perché il mio fine è esclusiva, è esattamente quello che voglio.
    • Cosa c’è di sbagliato con l’esempio che hai citato è che rappresenta un negativo intervallo, dalle 11:00 alle 00:00, che si è verificato 11 ore precedenti. Considerare l’intervallo dalle ore 00:00 alle 00:00—si tratta di un intervallo senza lunghezza, o un intervallo per l’intera giornata (ignorando le modifiche dell’ora legale)? Dovrebbe essere l’ex—un intervallo di tutta la giornata, ed esclusivo, sarebbe 00:00 alle 24:00. Questo sarebbe tutto il lavoro brillantemente e ISO 8601 conforme se non fosse per una singola riga di codice da qualche parte la generazione di un’eccezione, perché non gli piace il valore 24.
  4. 2

    Il tuo problema può essere inquadrato come la definizione di un intervallo su un dominio che avvolge. Il min 00:00, e il massimo è 24:00 (non incluso).

    Supponiamo che l’intervallo è definito come (inferiore, superiore). Se avete bisogno di abbassare < superiore, è possibile rappresentare (21:00, 24:00), ma si è ancora in grado di rappresentare (21:00, 02:00), un intervallo che avvolge tutto il min/max di confine.

    Non so se la tua applicazione di pianificazione comporterebbe avvolgente intervalli, ma se avete intenzione di andare a (21:00, 24:00) senza coinvolgere giorni, non vedo che cosa vi impedisce posto (21:00, 02:00) senza coinvolgere giorni (portando così a un avvolgente dimensione).

    Se il progetto è suscettibile di un avvolgente attuazione, l’intervallo gli operatori sono molto banali.

    Per esempio (in pseudo-codice):

    is x in (lower, upper)? :=
    if (lower <= upper) return (lower <= x && x <= upper)
    else return (lower <= x || x <= upper)

    In questo caso, ho scoperto che scrivere un wrapper di Joda-Tempo di attuazione operatori è abbastanza semplice, e riduce l’impedenza tra pensiero/matematica e API. Anche se è solo per l’inclusione delle 24:00 00:00.

    Io sono d’accordo che l’esclusione delle 24:00 mi dava fastidio all’inizio, e sarà bello se qualcuno ha offerto una soluzione. Fortunatamente per me, dato che il mio uso di intervalli di tempo è dominato da avvolgere intorno semantica, finisco sempre con un wrapper, che tra l’altro risolve il 24:00 di esclusione.

  5. 1

    Il tempo di 24:00 è difficile. Mentre noi umani possiamo capire che cosa si intende, codifica un’API per rappresentare che, senza compromettere tutto il resto mi sembra quasi impossibile.

    Il valore 24 non valido è profondamente codificato in Joda-Tempo – che cerca di rimuovere avrebbe implicazioni negative in un sacco di posti. Non vorrei raccomandare cercando di farlo.

    Per il tuo problema, il locale intervallo deve essere costituito da una (LocalTime, LocalTime, Days) o (LocalTime, Period). Quest’ultimo è leggermente più flessibile. Questo è necessario per supportare correttamente un intervallo dalle 23:00 alle 03:00.

    • Pertanto considerare come proposta di (LocalTime, Periodo) avrebbe lavorato su 13 Marzo 2011 in USA. Se mi segno la mia disponibilità la domenica dalle 00:00 alle 12:00, utilizzando il tuo approccio avrebbe usato (00:00, 12 ore). Perché abbiamo perso un’ora, il 13 Marzo, la vostra rappresentazione produrrebbe 00:00-13:00 di quel giorno, con l’aggiunta di 12 ore data / ora di mezzanotte darebbe 1:00. Se avessi invece mantenuto la fine del tempo come LocalTime, la risoluzione 12:00 data / ora di mezzanotte mi darebbe l’ora corretta—intervallo sul 2011-03-13 sarebbe più brevi a causa dell’ora legale.
  6. 1

    Trovo JodaStephenproposta di (LocalTime, LocalTime, Days) accettabile.

    Considerando il 13 Marzo 2011 e la tua disponibilità la domenica dalle 00:00 alle 12:00 si sarebbe (00:00, 12:00, 0) che erano in realtà 11 ore a causa dell’ora legale.

    Una disponibilità da dire 15:00-24:00 si potrebbe quindi codice (15:00, 00:00, 1) che avrebbe ampliato per 2011-03-13T15:00 – 2011-03-14T00:00 ne sarebbe desiderato 2011-03-13T24:00. Il che significa che si dovrebbe utilizzare una LocalTime (00:00 del giorno di calendario successivo, come già aroth proposto.

    Naturalmente, sarebbe bello utilizzare un 24:00 LocalTime direttamente e ISO 8601 conformi, ma questo non sembra possibile, senza modificare di un sacco all’interno di JodaTime in modo che questo approccio sembra il male minore.

    E, ultimo ma non da ultimo, si potrebbe anche estendere la barriera di un solo giorno con qualcosa di simile (16:00, 05:00, 1)

  7. 0

    questa è la nostra implementazione di TimeInterval, l’utilizzo di null come Data di fine per fine giornata. Supporta le sovrapposizioni() e() i metodi e si basa anche sulla joda tempo. Supporta intervalli si estendono per più giorni.

    /**
     * Description: Immutable time interval<br>
     * The start instant is inclusive but the end instant is exclusive.
     * The end is always greater than or equal to the start.
     * The interval is also restricted to just one chronology and time zone.
     * Start can be null (infinite).
     * End can be null and will stay null to let the interval last until end-of-day.
     * It supports intervals spanning multiple days.
     */
    public class TimeInterval {
    
        public static final ReadableInstant INSTANT = null; //null means today
    //   public static final ReadableInstant INSTANT = new Instant(0); //this means 1st jan 1970
    
        private final DateTime start;
        private final DateTime end;
    
        public TimeInterval() {
            this((LocalTime) null, null);
        }
    
        /**
         * @param from - null or a time  (null = left unbounded == LocalTime.MIDNIGHT)
         * @param to   - null or a time  (null = right unbounded)
         * @throws IllegalArgumentException if invalid (to is before from)
         */
        public TimeInterval(LocalTime from, LocalTime to) throws IllegalArgumentException {
            this(from == null ? null : from.toDateTime(INSTANT),
                    to == null ? null : to.toDateTime(INSTANT));
        }
    
        /**
         * create interval spanning multiple days possibly.
         *
         * @param start - start distinct time
         * @param end   - end distinct time
         * @throws IllegalArgumentException - if start > end. start must be <= end
         */
        public TimeInterval(DateTime start, DateTime end) throws IllegalArgumentException {
            this.start = start;
            this.end = end;
            if (start != null && end != null && start.isAfter(end))
                throw new IllegalArgumentException("start must be less or equal to end");
        }
    
        public DateTime getStart() {
            return start;
        }
    
        public DateTime getEnd() {
            return end;
        }
    
        public boolean isEndUndefined() {
            return end == null;
        }
    
        public boolean isStartUndefined() {
            return start == null;
        }
    
        public boolean isUndefined() {
            return isEndUndefined() && isStartUndefined();
        }
    
        public boolean overlaps(TimeInterval other) {
            return (start == null || (other.end == null || start.isBefore(other.end))) &&
                    (end == null || (other.start == null || other.start.isBefore(end)));
        }
    
        public boolean contains(TimeInterval other) {
            return ((start != null && other.start != null && !start.isAfter(other.start)) || (start == null)) &&
                    ((end != null && other.end != null && !other.end.isAfter(end)) || (end == null));
        }
    
        public boolean contains(LocalTime other) {
            return contains(other == null ? null : other.toDateTime(INSTANT));
        }
    
        public boolean containsEnd(DateTime other) {
            if (other == null) {
                return end == null;
            } else {
                return (start == null || !other.isBefore(start)) &&
                        (end == null || !other.isAfter(end));
            }
        }
    
        public boolean contains(DateTime other) {
            if (other == null) {
                return start == null;
            } else {
                return (start == null || !other.isBefore(start)) &&
                        (end == null || other.isBefore(end));
            }
        }
    
        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder();
            sb.append("TimeInterval");
            sb.append("{start=").append(start);
            sb.append(", end=").append(end);
            sb.append('}');
            return sb.toString();
        }
    }
  8. 0

    Per completezza questo test ha esito negativo:

    @Test()
    public void testJoda() throws DGConstraintViolatedException {
        DateTimeFormatter simpleTimeFormatter = DateTimeFormat.forPattern("HHmm");
        LocalTime t1 = LocalTime.parse("0000", simpleTimeFormatter);
        LocalTime t2 = LocalTime.MIDNIGHT;
        Assert.assertTrue(t1.isBefore(t2));
    }  

    Questo significa che la MEZZANOTTE costante non è molto utile per il problema, come qualcuno ha suggerito.

  9. 0

    Questa domanda è vecchia, ma molte di queste risposte, focus su Joda Tempo, e solo in parte affrontare il vero problema di fondo:

    Il modello in OP il codice non corrisponde la realtà è modellazione.

    Purtroppo, dal momento che non sembrano cura circa la condizione al contorno tra i giorni, il tuo “altrimenti elegante modello” non è una buona partita per il problema che si sta modellando. Hai usato una coppia di valori di tempo per rappresentare intervalli. Cercando di semplificare il modello in giù per un paio di volte è la semplificazione di seguito la complessità del mondo reale problema. Giorno in realtà non esistono, in realtà, e un paio di volte perde quel tipo di informazioni. Come sempre, l’eccessiva semplificazione risultati nelle successive complessità, al ripristino o al risarcimento per le informazioni mancanti. Reale complessità può solo essere spinto in giro da una parte del codice di un altro.

    La complessità della realtà può essere eliminato solo con la magia del non supportato “casi d’uso”.

    Modello avrebbe senso solo in un problema di spazio in cui non ci si cura quanti giorni potrebbe esistere tra l’inizio e la fine dei tempi. Il problema spazio non abbinare la maggior parte dei problemi del mondo reale. Pertanto, non è sorprendente che Joda Tempo non supporta bene. L’uso di 25 valori per le ore luogo (0-24) è un codice odore e di solito i punti di debolezza del progetto. Ci sono solo 24 ore in un giorno così 25 valori non dovrebbe essere necessario!

    Notare che, dato che non si cattura la data di LocalInterval, la classe, inoltre, non acquisire informazioni sufficienti per conto dell’ora legale. [00:30:00 TO 04:00:00) di solito è di 3,5 ore, ma potrebbe anche essere 2.5, o 4,5 ore.

    Si dovrebbe utilizzare un data/ora di inizio e la durata, o una data/ora di inizio e una data/ora di fine (inclusive di inizio, esclusivo fine è una buona scelta). Utilizzando una durata diventa difficile, se si desidera visualizzare l’ora di fine a causa di cose come l’ora legale, gli anni bisestili e secondi. D’altro canto l’utilizzo di una data di fine diventa difficile, se si prevede di visualizzare la durata. La memorizzazione del corso è pericoloso perché viola il LAVAGGIO principio. Se fossi iscritto ad una classe sarebbe memorizzare una data/ora di fine e incapsulare la logica per ottenere la durata attraverso un metodo sull’oggetto. Che modo i clienti di classe classe non sono arrivati tutti con il proprio codice a calcolare la durata.

    Mi piacerebbe codice di un esempio, ma c’è un’opzione ancora migliore. Utilizzare lo standard Intervallo di Classe da Joda tempo, che accetta già un inizio immediato e durata o la fine immediata. Sarà, inoltre, e felicemente calcolare la durata o la data di fine. Purtroppo JSR-310 non disporre di un intervallo o classe simile. (anche se uno può usare ThreeTenExtra a)

    Relativamente luminoso gente a Joda Tempo e Sun/Oracle (JSR-310) sia pensato molto attentamente su questi problemi. Si potrebbe essere più intelligente di loro. È possibile. Tuttavia, anche se sei un brillante lampadina, 1 ora probabilmente non sta per compiere ciò che hanno passato anni in su. A meno che non si trovano da qualche parte in un esoterico caso limite, è di solito spreco di tempo e denaro da spendere sforzo indovinare secondo loro. (ovviamente al momento dell’OP JSR-310 non è…)

    Speriamo che quanto sopra è aiutare persone che trovare questo problema, mentre la progettazione o la fissazione di problemi simili.

Lascia un commento