001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
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,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.lang3.time;
019
020import java.time.LocalDate;
021import java.time.LocalDateTime;
022import java.time.OffsetDateTime;
023import java.time.ZoneId;
024import java.time.ZonedDateTime;
025import java.util.Calendar;
026import java.util.Locale;
027import java.util.Locale.Category;
028import java.util.Map;
029import java.util.Objects;
030
031/**
032 * Helps use {@link Calendar}s.
033 *
034 * @since 3.10
035 */
036public class CalendarUtils {
037
038    /**
039     * The singleton instance for {@link Calendar#getInstance()}. The instance is created when the class is initialized and is based on the current time in the
040     * default time zone with the default {@link Category#FORMAT} locale.
041     *
042     * @see CalendarUtils#getInstance()
043     */
044    public static final CalendarUtils INSTANCE = getInstance();
045
046    /**
047     * Creates a new instance based on the current time in the default time zone with the default {@link Category#FORMAT} locale.
048     *
049     * @return a new instance.
050     * @since 3.14.0
051     */
052    public static CalendarUtils getInstance() {
053        return new CalendarUtils(Calendar.getInstance());
054    }
055
056    /**
057     * Gets a CalendarUtils using the default time zone and specified locale. The {@code CalendarUtils} returned is based on the current time in the
058     * default time zone with the given locale.
059     *
060     * @param locale the locale for the week data
061     * @return a Calendar.
062     */
063    static CalendarUtils getInstance(final Locale locale) {
064        return new CalendarUtils(Calendar.getInstance(locale), locale);
065    }
066
067    /**
068     * Converts a Calendar to a LocalDateTime.
069     *
070     * @param calendar the Calendar to convert.
071     * @return a LocalDateTime.
072     * @since 3.17.0
073     */
074    public static LocalDateTime toLocalDateTime(final Calendar calendar) {
075        return LocalDateTime.ofInstant(calendar.toInstant(), toZoneId(calendar));
076    }
077
078    /**
079     * Converts a Calendar to a OffsetDateTime.
080     *
081     * @param calendar the Calendar to convert.
082     * @return a OffsetDateTime.
083     * @since 3.17.0
084     */
085    public static OffsetDateTime toOffsetDateTime(final Calendar calendar) {
086        return OffsetDateTime.ofInstant(calendar.toInstant(), toZoneId(calendar));
087    }
088
089    /**
090     * Converts a Calendar to a ZonedDateTime.
091     *
092     * @param calendar the Calendar to convert.
093     * @return a ZonedDateTime.
094     * @since 3.17.0
095     */
096    public static ZonedDateTime toZonedDateTime(final Calendar calendar) {
097        return ZonedDateTime.ofInstant(calendar.toInstant(), toZoneId(calendar));
098    }
099
100    private static ZoneId toZoneId(final Calendar calendar) {
101        return calendar.getTimeZone().toZoneId();
102    }
103
104    private final Calendar calendar;
105
106    private final Locale locale;
107
108    /**
109     * Creates an instance for the given Calendar.
110     *
111     * @param calendar A Calendar.
112     */
113    public CalendarUtils(final Calendar calendar) {
114        this(calendar, Locale.getDefault());
115    }
116    /**
117     * Creates an instance for the given Calendar.
118     *
119     * @param calendar A Calendar.
120     * @param locale A Locale.
121     */
122    CalendarUtils(final Calendar calendar, final Locale locale) {
123        this.calendar = Objects.requireNonNull(calendar, "calendar");
124        this.locale = Objects.requireNonNull(locale, "locale");
125    }
126
127    /**
128     * Gets the current day of month.
129     *
130     * @return the current day of month.
131     */
132    public int getDayOfMonth() {
133        return calendar.get(Calendar.DAY_OF_MONTH);
134    }
135
136    /**
137     * Gets the current day of year.
138     *
139     * @return the current day of year.
140     * @since 3.13.0
141     */
142    public int getDayOfYear() {
143        return calendar.get(Calendar.DAY_OF_YEAR);
144    }
145
146    /**
147     * Gets the current month.
148     *
149     * @return the current month.
150     */
151    public int getMonth() {
152        return calendar.get(Calendar.MONTH);
153    }
154
155    /**
156     * Gets month names in the requested style.
157     * @param style Must be a valid {@link Calendar#getDisplayNames(int, int, Locale)} month style.
158     * @return Styled names of months
159     */
160    String[] getMonthDisplayNames(final int style) {
161        // Unfortunately standalone month names are not available in DateFormatSymbols,
162        // so we have to extract them.
163        final Map<String, Integer> displayNames = calendar.getDisplayNames(Calendar.MONTH, style, locale);
164        if (displayNames == null) {
165            return null;
166        }
167        final String[] monthNames = new String[displayNames.size()];
168        displayNames.forEach((k, v) -> monthNames[v] = k);
169        return monthNames;
170    }
171
172    /**
173     * Gets full standalone month names as used in "LLLL" date formatting.
174     * @return Long names of months
175     */
176    String[] getStandaloneLongMonthNames() {
177        return getMonthDisplayNames(Calendar.LONG_STANDALONE);
178    }
179
180    /**
181     * Gets short standalone month names as used in "LLLL" date formatting.
182     * @return Short names of months
183     */
184    String[] getStandaloneShortMonthNames() {
185        return getMonthDisplayNames(Calendar.SHORT_STANDALONE);
186    }
187
188    /**
189     * Gets the current year.
190     *
191     * @return the current year.
192     */
193    public int getYear() {
194        return calendar.get(Calendar.YEAR);
195    }
196
197    /**
198     * Converts this instance to a {@link LocalDate}.
199     *
200     * @return a LocalDateTime.
201     * @since 3.18.0
202     */
203    public LocalDate toLocalDate() {
204        return toLocalDateTime().toLocalDate();
205    }
206
207    /**
208     * Converts this instance to a {@link LocalDateTime}.
209     *
210     * @return a LocalDateTime.
211     * @since 3.17.0
212     */
213    public LocalDateTime toLocalDateTime() {
214        return toLocalDateTime(calendar);
215    }
216
217    /**
218     * Converts this instance to a {@link OffsetDateTime}.
219     *
220     * @return a OffsetDateTime.
221     * @since 3.17.0
222     */
223    public OffsetDateTime toOffsetDateTime() {
224        return toOffsetDateTime(calendar);
225    }
226
227    /**
228     * Converts this instance to a {@link ZonedDateTime}.
229     *
230     * @return a ZonedDateTime.
231     * @since 3.17.0
232     */
233    public ZonedDateTime toZonedDateTime() {
234        return toZonedDateTime(calendar);
235    }
236
237}