001/*
002 * ============================================================================
003 * Copyright © 2002-2023 by Thomas Thrien.
004 * All Rights Reserved.
005 * ============================================================================
006 * Licensed to the public under the agreements of the GNU Lesser General Public
007 * License, version 3.0 (the "License"). You may obtain a copy of the License at
008 *
009 *      http://www.gnu.org/licenses/lgpl.html
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
013 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
014 * License for the specific language governing permissions and limitations
015 * under the License.
016 */
017
018package org.tquadrat.foundation.value;
019
020import static java.lang.String.format;
021import static java.math.BigDecimal.TEN;
022import static java.util.Arrays.stream;
023import static org.apiguardian.api.API.Status.STABLE;
024import static org.tquadrat.foundation.lang.CommonConstants.TROPICAL_YEAR;
025import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
026import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument;
027
028import java.math.BigDecimal;
029import java.time.Duration;
030import java.time.temporal.ChronoUnit;
031import java.time.temporal.Temporal;
032import java.time.temporal.TemporalUnit;
033import java.util.Optional;
034import java.util.concurrent.TimeUnit;
035
036import org.apiguardian.api.API;
037import org.tquadrat.foundation.annotation.ClassVersion;
038import org.tquadrat.foundation.lang.CommonConstants;
039import org.tquadrat.foundation.value.api.DimensionWithLinearConversion;
040
041/**
042 *  <p>{@summary The various instances of time periods &hellip;}</p>
043 *  <p>The instance of {@code Time} refers to the respective instances of
044 *  {@link java.util.concurrent.TimeUnit}
045 *  and
046 *  {@link java.time.temporal.ChronoUnit}
047 *  wherever possible.</p>
048 *
049 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
050 *  @version $Id: Time.java 1073 2023-10-01 11:08:51Z tquadrat $
051 *  @since 0.1.0
052 *
053 *  @UMLGraph.link
054 */
055@SuppressWarnings( "NewClassNamingConvention" )
056@ClassVersion( sourceVersion = "$Id: Time.java 1073 2023-10-01 11:08:51Z tquadrat $" )
057@API( status = STABLE, since = "0.1.0" )
058public enum Time implements DimensionWithLinearConversion
059{
060        /*------------------*\
061    ====** Enum Declaration **=================================================
062        \*------------------*/
063    /**
064     *  A nanosecond.
065     */
066    NANOSECOND( new BigDecimal( "1E-9" ), "ns", TimeUnit.NANOSECONDS, ChronoUnit.NANOS ),
067
068    /**
069     *  A microsecond.
070     */
071    MICROSECOND( new BigDecimal( "1E-6" ), "µs", TimeUnit.MICROSECONDS, ChronoUnit.MICROS ),
072
073    /**
074     *  A millisecond.
075     */
076    MILLISECOND( new BigDecimal( "0.001" ), "ms", TimeUnit.MILLISECONDS, ChronoUnit.MILLIS ),
077
078    /**
079     *  A second.
080     */
081    SECOND( BigDecimal.ONE, "s", TimeUnit.SECONDS, ChronoUnit.SECONDS ),
082
083    /**
084     *  A minute.
085     */
086    MINUTE( new BigDecimal( "60" ), "min", TimeUnit.MINUTES, ChronoUnit.MINUTES ),
087
088    /**
089     *  An hour.
090     */
091    HOUR( new BigDecimal( "3600.0" ), "h", TimeUnit.HOURS, ChronoUnit.HOURS ),
092
093    /**
094     *  Half a day.
095     */
096    HALF_DAY( new BigDecimal( "43200.0" ), "d/2", null, ChronoUnit.HALF_DAYS )
097    {
098        /**
099         *  {@inheritDoc}
100         */
101        @Override
102        public final String unitSymbolForPrinting() { return "½d"; }
103    },
104
105    /**
106     *  A day.
107     */
108    DAY( new BigDecimal( "86400.0" ), "d", TimeUnit.DAYS, ChronoUnit.DAYS ),
109
110    /**
111     *  A week (7 days).
112     */
113    WEEK( new BigDecimal( "604800.0" ), "w", null, ChronoUnit.WEEKS ),
114
115    /**
116     *  A fortnight (2 weeks or 14 days).
117     */
118    FORTNIGHT( new BigDecimal( "1209600.0" ), "fortnight", null, null )
119    {
120        /**
121         *  {@inheritDoc}
122         */
123        @Override
124        protected final TemporalUnit asTemporalUnit()
125        {
126            final var retValue = new TemporalUnitImpl( "Fortnights" );
127
128            //---* Done *------------------------------------------------------
129            //noinspection ReturnOfInnerClass
130            return retValue;
131        }   //  asTemporalUnit
132    },
133
134    /**
135     *  <p>{@summary A bank month.}</p>
136     *  <p>This has a fixed length of exact 30 days.</p>
137     */
138    BANK_MONTH( new BigDecimal( "2592000.0" ), "month30", null, null )
139    {
140        /**
141         *  {@inheritDoc}
142         */
143        @Override
144        protected final TemporalUnit asTemporalUnit()
145        {
146            final var retValue = new TemporalUnitImpl( "BankMonths" );
147
148            //---* Done *------------------------------------------------------
149            //noinspection ReturnOfInnerClass
150            return retValue;
151        }   //  asTemporalUnit
152    },
153
154    /**
155     *  <p>{@summary A month.}</p>
156     *  <p>This is the average month, calculated using the average year with
157     *  365.2425 days divided by 12.</p>
158     */
159    MONTH( new BigDecimal( "2629746.0" ), "month", null, ChronoUnit.MONTHS ),
160
161    /**
162     *  <p>{@summary A bank year.}</p>
163     *  <p>This has a fixed length of exact 360 days.</p>
164     */
165    BANK_YEAR( new BigDecimal( "31104000.0" ), "yr360", null, null )
166    {
167        /**
168         *  {@inheritDoc}
169         */
170        @Override
171        protected final TemporalUnit asTemporalUnit()
172        {
173            final var retValue = new TemporalUnitImpl( "BankYears" );
174
175            //---* Done *------------------------------------------------------
176            //noinspection ReturnOfInnerClass
177            return retValue;
178        }   //  asTemporalUnit
179    },
180
181    /**
182     *  A tropical year, according to SI.
183     *
184     *  @see CommonConstants#TROPICAL_YEAR
185     */
186    YEAR( new BigDecimal( "86400.0" ).multiply( TROPICAL_YEAR ), "yr", null, null )
187    {
188        /**
189         *  {@inheritDoc}
190         */
191        @Override
192        protected final TemporalUnit asTemporalUnit()
193        {
194            final var retValue = new TemporalUnitImpl( "TropicalYears" );
195
196            //---* Done *------------------------------------------------------
197            //noinspection ReturnOfInnerClass
198            return retValue;
199        }   //  asTemporalUnit
200    },
201
202    /**
203     *  <p>{@summary A year.}</p>
204     *  <p>This is calculated as the average year with 365.2425 days.</p>
205     *
206     * @see ChronoUnit#YEARS
207     */
208    SIMPLE_YEAR( new BigDecimal( ChronoUnit.YEARS.getDuration().get( ChronoUnit.SECONDS ) ), "yrs", null, ChronoUnit.YEARS ),
209
210    /**
211     *  <p>{@summary A decade.}</p>
212     *  <p>This is calculated as ten average years with 365.2425 days each.</p>
213     *
214     * @see ChronoUnit#DECADES
215     */
216    DECADE( new BigDecimal( ChronoUnit.DECADES.getDuration().get( ChronoUnit.SECONDS ) ), "decade", null, ChronoUnit.DECADES ),
217
218    /**
219     *  <p>{@summary A century.}</p>
220     *  <p>This is calculated as one hundred average years with 365.2425 days
221     *  each.</p>
222     *
223     * @see ChronoUnit#CENTURIES
224     */
225    CENTURY( new BigDecimal( ChronoUnit.CENTURIES.getDuration().get( ChronoUnit.SECONDS ) ).multiply( TEN ), "century", null, ChronoUnit.CENTURIES );
226
227        /*---------------*\
228    ====** Inner Classes **====================================================
229        \*---------------*/
230    /**
231     *  An implementation of
232     *  {@link TemporalUnit},
233     *  based on the settings for the {@code Time} instance.
234     *
235     *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
236     *  @version $Id: Time.java 1073 2023-10-01 11:08:51Z tquadrat $
237     *  @since 0.1.0
238     *
239     *  @UMLGraph.link
240     */
241    @ClassVersion( sourceVersion = "$Id: Time.java 1073 2023-10-01 11:08:51Z tquadrat $" )
242    @API( status = STABLE, since = "0.1.0" )
243    private class TemporalUnitImpl implements TemporalUnit
244    {
245            /*------------*\
246        ====** Attributes **===================================================
247            \*------------*/
248        /**
249         *  The duration for this temporal unit.
250         */
251        private final Duration m_Duration;
252
253        /**
254         *  The name of this temporal unit.
255         */
256        private final String m_Name;
257
258            /*--------------*\
259        ====** Constructors **=================================================
260            \*--------------*/
261        /**
262         *  Creates a new {@code TemporalUnitImpl} instance.
263         *
264         *  @param  name    The name of this temporal unit.
265         */
266        public TemporalUnitImpl( final String name )
267        {
268            m_Duration = Duration.ofSeconds( Time.this.factor().longValue() );
269            m_Name = name;
270        }   //  TemporalUnitImpl()
271
272            /*---------*\
273        ====** Methods **======================================================
274            \*---------*/
275        /**
276         *  {@inheritDoc}
277         */
278        @SuppressWarnings( "unchecked" )
279        @Override
280        public <R extends Temporal> R addTo( final R temporal, final long amount )
281        {
282            return (R) temporal.plus( amount, this );
283        }   //  addTo()
284
285        /**
286         *  {@inheritDoc}
287         */
288        @Override
289        public long between( final Temporal temporal1Inclusive, final Temporal temporal2Exclusive )
290        {
291            return temporal1Inclusive.until( temporal2Exclusive, this );
292        }   //  between()
293
294        /**
295         *  {@inheritDoc}
296         */
297        @Override
298        public final Duration getDuration() { return m_Duration; }
299
300        /**
301         *  {@inheritDoc}
302         */
303        @Override
304        public final boolean isDurationEstimated() { return true; }
305
306        /**
307         *  {@inheritDoc}
308         */
309        @Override
310        public final boolean isDateBased() { return false; }
311
312        /**
313         *  {@inheritDoc}
314         */
315        @Override
316        public final boolean isTimeBased() { return false; }
317
318        /**
319         *  {@inheritDoc}
320         */
321        @Override
322        public final String toString() { return m_Name; }
323    }
324    //  class TemporalUnitImpl()
325
326        /*------------*\
327    ====** Attributes **=======================================================
328        \*------------*/
329    /**
330     *  The factor.
331     */
332    private final BigDecimal m_Factor;
333
334    /**
335     *  The unit symbol.
336     */
337    private final String m_UnitSymbol;
338
339    /**
340     *  The time unit as used by the {@code java.time} package.
341     */
342    @SuppressWarnings( "OptionalUsedAsFieldOrParameterType" )
343    private final Optional<TemporalUnit> m_TemporalUnit;
344
345    /**
346     *  The time unit as used by the {@code java.util.concurrent} package.
347     */
348    private final TimeUnit m_TimeUnit;
349
350        /*--------------*\
351    ====** Constructors **=====================================================
352        \*--------------*/
353    /**
354     *  Creates a new {@code Time} instance.
355     *
356     *  @param  factor  The factor.
357     *  @param  unitSymbol  The unit symbol.
358     *  @param  timeUnit    The time unit as for the
359     *      {@code java.util.concurrent} package; may be {@code null}.
360     *  @param  temporalUnit    The time unit as for the {@code java.time}
361     *      package; may be {@code null}.
362     */
363    private Time( final BigDecimal factor, final String unitSymbol, final TimeUnit timeUnit, final TemporalUnit temporalUnit )
364    {
365        m_Factor = factor.stripTrailingZeros();
366        m_UnitSymbol = unitSymbol;
367        m_TimeUnit = timeUnit;
368        m_TemporalUnit = Optional.ofNullable( temporalUnit );
369    }   //  Time()
370
371        /*---------*\
372    ====** Methods **==========================================================
373        \*---------*/
374    /**
375     *  Returns this instance of {@code Time} as an instance of
376     *  {@link TemporalUnit}.
377     *
378     *  @return {@code this} as {@code TemporalUnit}.
379     */
380    @SuppressWarnings( "static-method" )
381    protected TemporalUnit asTemporalUnit() { return null; }
382
383    /**
384     *  {@inheritDoc}
385     */
386    @Override
387    @SuppressWarnings( "unchecked" )
388    public final Time baseUnit() { return SECOND; }
389
390    /**
391     *  {@inheritDoc}
392     */
393    @Override
394    public final BigDecimal factor() { return m_Factor; }
395
396    /**
397     *  Returns the {@code Time} instance for the given instance of
398     *  {@link ChronoUnit}.
399     *
400     *  @param  unit    The unit.
401     *  @return The respective instance.
402     *  @throws IllegalArgumentException    The given unit is unknown.
403     */
404    public static final Time forUnit( final ChronoUnit unit ) throws IllegalArgumentException
405    {
406        requireNonNullArgument( unit, "unit" );
407
408        final var retValue = stream( values() )
409            .filter( v -> v.getTemporalUnit() == unit )
410            .findFirst()
411            .orElseThrow( () -> new IllegalArgumentException( format( MSG_UnknownUnit, unit.name() ) ) );
412
413        //---* Done *----------------------------------------------------------
414        return retValue;
415    }   //  forUnit()
416
417    /**
418     *  Returns the {@code Time} instance for the given instance of
419     *  {@link TemporalUnit}.
420     *
421     *  @param  unit    The unit.
422     *  @return The respective instance.
423     *  @throws IllegalArgumentException    The given unit is unknown.
424     */
425    public static final Time forUnit( final TemporalUnit unit ) throws IllegalArgumentException
426    {
427        requireNonNullArgument( unit, "unit" );
428
429        final var retValue = stream( values() )
430            .filter( v -> v.getTemporalUnit() == unit )
431            .findFirst()
432            .orElseThrow( () -> new IllegalArgumentException( format( MSG_UnknownUnit, unit.toString() ) ) );
433
434        //---* Done *----------------------------------------------------------
435        return retValue;
436    }   //  forUnit()
437
438    /**
439     *  Returns the {@code Time} instance for the given instance of
440     *  {@link TimeUnit}.
441     *
442     *  @param  unit    The unit.
443     *  @return The respective instance.
444     *  @throws IllegalArgumentException    The given unit is unknown.
445     */
446    public static final Time forUnit( final TimeUnit unit ) throws IllegalArgumentException
447    {
448        requireNonNullArgument( unit, "unit" );
449
450        final var retValue = stream( values() )
451            .filter( v -> v.getTimeUnit() == unit )
452            .findFirst()
453            .orElseThrow( () -> new IllegalArgumentException( format( MSG_UnknownUnit, unit.name() ) ) );
454
455        //---* Done *----------------------------------------------------------
456        return retValue;
457    }   //  forUnit()
458
459    /**
460     *  Returns the {@code Time} instance for the given unit.
461     *
462     *  @param  unitSymbol  The unit symbol.
463     *  @return The respective instance.
464     *  @throws IllegalArgumentException    The given unit is unknown.
465     */
466    public static final Time forUnit( final String unitSymbol ) throws IllegalArgumentException
467    {
468        requireNotEmptyArgument( unitSymbol, "unit" );
469
470        final var retValue = stream( values() )
471            .filter( v -> v.unitSymbol().equals( unitSymbol ) )
472            .findFirst()
473            .orElseThrow( () -> new IllegalArgumentException( format( MSG_UnknownUnit, unitSymbol ) ) );
474
475        //---* Done *----------------------------------------------------------
476        return retValue;
477    }   //  forUnit()
478
479    /**
480     *  Returns the correspondent
481     *  {@link TemporalUnit}
482     *  for this instance, as used by the {@code java.time}
483     *  package. Because  we define here more units as in that package, the
484     *  return value may be {@code null}.
485     *
486     *  @return The corresponding temporal unit instance, or {@code null} if
487     *      there is no corresponding time unit.
488     */
489    @SuppressWarnings( "PublicMethodNotExposedInInterface" )
490    public final TemporalUnit getTemporalUnit() { return m_TemporalUnit.orElse( asTemporalUnit() ); }
491
492    /**
493     *  Returns the correspondent
494     *  {@link TimeUnit}
495     *  for this instance, as used by the {@code java.util.concurrent}
496     *  package. Because  we define here more units as in that package, the
497     *  return value may be {@code null}.
498     *
499     *  @return The corresponding time unit instance, or {@code null} if
500     *      there is no corresponding time unit.
501     */
502    @SuppressWarnings( "PublicMethodNotExposedInInterface" )
503    public final TimeUnit getTimeUnit() { return m_TimeUnit; }
504
505    /**
506     *  {@inheritDoc}
507     */
508    @Override
509    public final String unitSymbol() { return m_UnitSymbol; }
510}
511//  enum Time
512
513/*
514 *  End of File
515 */