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    /**
118     * Creates an instance for the given Calendar.
119     *
120     * @param calendar A Calendar.
121     * @param locale A Locale.
122     */
123    CalendarUtils(final Calendar calendar, final Locale locale) {
124        this.calendar = Objects.requireNonNull(calendar, "calendar");
125        this.locale = Objects.requireNonNull(locale, "locale");
126    }
127
128    /**
129     * Gets the current day of month.
130     *
131     * @return the current day of month.
132     */
133    public int getDayOfMonth() {
134        return calendar.get(Calendar.DAY_OF_MONTH);
135    }
136
137    /**
138     * Gets the current day of year.
139     *
140     * @return the current day of year.
141     * @since 3.13.0
142     */
143    public int getDayOfYear() {
144        return calendar.get(Calendar.DAY_OF_YEAR);
145    }
146
147    /**
148     * Gets the current month.
149     *
150     * @return the current month.
151     */
152    public int getMonth() {
153        return calendar.get(Calendar.MONTH);
154    }
155
156    /**
157     * Gets month names in the requested style.
158     *
159     * @param style Must be a valid {@link Calendar#getDisplayNames(int, int, Locale)} month style.
160     * @return Styled names of months
161     */
162    String[] getMonthDisplayNames(final int style) {
163        // Unfortunately standalone month names are not available in DateFormatSymbols,
164        // so we have to extract them.
165        final Map<String, Integer> displayNames = calendar.getDisplayNames(Calendar.MONTH, style, locale);
166        if (displayNames == null) {
167            return null;
168        }
169        final String[] monthNames = new String[displayNames.size()];
170        displayNames.forEach((k, v) -> monthNames[v] = k);
171        return monthNames;
172    }
173
174    /**
175     * Gets full standalone month names as used in "LLLL" date formatting.
176     *
177     * @return Long names of months
178     */
179    String[] getStandaloneLongMonthNames() {
180        return getMonthDisplayNames(Calendar.LONG_STANDALONE);
181    }
182
183    /**
184     * Gets short standalone month names as used in "LLLL" date formatting.
185     *
186     * @return Short names of months
187     */
188    String[] getStandaloneShortMonthNames() {
189        return getMonthDisplayNames(Calendar.SHORT_STANDALONE);
190    }
191
192    /**
193     * Gets the current year.
194     *
195     * @return the current year.
196     */
197    public int getYear() {
198        return calendar.get(Calendar.YEAR);
199    }
200
201    /**
202     * Converts this instance to a {@link LocalDate}.
203     *
204     * @return a LocalDate.
205     * @since 3.18.0
206     */
207    public LocalDate toLocalDate() {
208        return toLocalDateTime().toLocalDate();
209    }
210
211    /**
212     * Converts this instance to a {@link LocalDateTime}.
213     *
214     * @return a LocalDateTime.
215     * @since 3.17.0
216     */
217    public LocalDateTime toLocalDateTime() {
218        return toLocalDateTime(calendar);
219    }
220
221    /**
222     * Converts this instance to a {@link OffsetDateTime}.
223     *
224     * @return a OffsetDateTime.
225     * @since 3.17.0
226     */
227    public OffsetDateTime toOffsetDateTime() {
228        return toOffsetDateTime(calendar);
229    }
230
231    /**
232     * Converts this instance to a {@link ZonedDateTime}.
233     *
234     * @return a ZonedDateTime.
235     * @since 3.17.0
236     */
237    public ZonedDateTime toZonedDateTime() {
238        return toZonedDateTime(calendar);
239    }
240
241}