001/*
002 *  Copyright 2019 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.core.util.date;
017
018import java.time.LocalDate;
019import java.time.LocalDateTime;
020import java.time.temporal.TemporalUnit;
021
022/**
023 * This represents an advanced {@link LocalDateTime}, allowing to be a static one, 
024 * or a relative one to the moment the {@link #resolveDateTime} method is called.
025 * <br>A single instance of {@link AdaptableDate} can be {@link #resolveDateTime() resolved} mutliple times, without being a problem at all, it just can lead to different results.
026 * <br>
027 * <br>You can get an {@link AdaptableDate} with:
028 * <ul>
029 * <li>{@link #fromDateTime} to have a static one, always resolved as the same given {@link LocalDateTime}</li>
030 * <li>{@link #fromDate} to have a static one, always resolved as the same given {@link LocalDate} (at the start of the day)</li>
031 * <li> {@link #now} to have one resolved as the current {@link LocalDateTime} when {@link #resolveDateTime} is called</li>
032 * <li> {@link #future} to have one resolved with the given amount of given {@link TemporalUnit} added to the current {@link LocalDateTime} when {@link #resolveDateTime} is called</li>
033 * <li> {@link #past} to have one resolved with the given amount of given {@link TemporalUnit} subtracted to the current {@link LocalDateTime} when {@link #resolveDateTime} is called</li>
034 * </ul>
035 */
036public final class AdaptableDate
037{
038    private static enum AdaptableDateType
039    {
040        STATIC,
041        RELATIVE_TO_NOW,
042    }
043    
044    private static enum OffsetType
045    {
046        NOW,
047        PAST,
048        FUTURE,
049    }
050    
051    private static final AdaptableDate __NOW = new AdaptableDate(OffsetType.NOW, 0, null);
052    
053    private final AdaptableDateType _type;
054    private final LocalDateTime _staticValue;
055    private final TemporalUnit _relativeUnit;
056    private final long _relativeAmount;
057    private final OffsetType _relativeOffsetType;
058    
059    private AdaptableDate(LocalDateTime date)
060    {
061        _type = AdaptableDateType.STATIC;
062        _staticValue = date;
063        _relativeUnit = null;
064        _relativeAmount = 0;
065        _relativeOffsetType = null;
066    }
067    
068    private AdaptableDate(OffsetType offsetType, long amount, TemporalUnit unit)
069    {
070        _type = AdaptableDateType.RELATIVE_TO_NOW;
071        _staticValue = null;
072        _relativeUnit = unit;
073        _relativeAmount = amount;
074        _relativeOffsetType = offsetType;
075    }
076    
077    /**
078     * Gets a static {@link AdaptableDate}, always resolved as the given {@link LocalDateTime}
079     * @param dateTime The {@link LocalDateTime}
080     * @return The {@link AdaptableDate}
081     */
082    public static AdaptableDate fromDateTime(LocalDateTime dateTime)
083    {
084        return new AdaptableDate(dateTime);
085    }
086    
087    /**
088     * Gets a static {@link AdaptableDate}, always resolved as the given {@link LocalDate}
089     * @param date The {@link LocalDate}
090     * @return The {@link AdaptableDate}
091     */
092    public static AdaptableDate fromDate(LocalDate date)
093    {
094        return new AdaptableDate(date.atStartOfDay());
095    }
096    
097    /**
098     * Gets an {@link AdaptableDate}, resolved as the current {@link LocalDateTime} at the moment of the {@link #resolveDateTime() call}
099     * @return The {@link AdaptableDate}
100     */
101    public static AdaptableDate now()
102    {
103        return __NOW;
104    }
105    
106    /**
107     * Gets an {@link AdaptableDate}, resolved as the current {@link LocalDateTime} at the moment of the {@link #resolveDateTime() call}, 
108     * minus the given amount of the given {@link TemporalUnit}, so as to get a {@link LocalDateTime} in the past
109     * @param amount The amount to subtract
110     * @param unit The {@link TemporalUnit} of the amount to subtract
111     * @return The {@link AdaptableDate}
112     */
113    public static AdaptableDate past(long amount, TemporalUnit unit)
114    {
115        return new AdaptableDate(OffsetType.PAST, amount, unit);
116    }
117    
118    /**
119     * Gets an {@link AdaptableDate}, resolved as the current {@link LocalDateTime} at the moment of the {@link #resolveDateTime() call}, 
120     * plus the given amount of the given {@link TemporalUnit}, so as to get a {@link LocalDateTime} in the future
121     * @param amount The amount to add
122     * @param unit The {@link TemporalUnit} of the amount to add
123     * @return The {@link AdaptableDate}
124     */
125    public static AdaptableDate future(long amount, TemporalUnit unit)
126    {
127        return new AdaptableDate(OffsetType.FUTURE, amount, unit);
128    }
129    
130    /**
131     * Resolves this {@link AdaptableDate}
132     * @return The resolved {@link LocalDateTime}
133     */
134    public LocalDateTime resolveDateTime()
135    {
136        switch (_type)
137        {
138            case RELATIVE_TO_NOW:
139                LocalDateTime now = LocalDateTime.now();
140                LocalDateTime computed;
141                switch (_relativeOffsetType)
142                {
143                    case NOW:
144                        computed = now;
145                        break;
146                    case PAST:
147                        computed = now.minus(_relativeAmount, _relativeUnit);
148                        break;
149                    case FUTURE:
150                    default:
151                        computed = now.plus(_relativeAmount, _relativeUnit);
152                        break;
153                }
154                return computed;
155            
156            case STATIC:
157            default:
158                return _staticValue;
159        }
160    }
161    
162    /**
163     * Resolves this {@link AdaptableDate}
164     * @return The resolved {@link LocalDate}
165     */
166    public LocalDate resolveDate()
167    {
168        return resolveDateTime()
169                .toLocalDate();
170    }
171    
172    @Override
173    public String toString()
174    {
175        switch (_type)
176        {
177            case STATIC:
178                return _staticValue.toString();
179            case RELATIVE_TO_NOW:
180            default:
181                switch (_relativeOffsetType)
182                {
183                    case NOW:
184                        return "Now";
185                    case PAST:
186                        return _relativeAmount + " " + _relativeUnit + " ago";
187                    case FUTURE:
188                    default:
189                        return "In " + _relativeAmount + " " + _relativeUnit;
190                }
191        }
192    }
193}
194