001/**
002 * Copyright (c) 2010-2019 Mark Allen, Norbert Bartels.
003 *
004 * Permission is hereby granted, free of charge, to any person obtaining a copy
005 * of this software and associated documentation files (the "Software"), to deal
006 * in the Software without restriction, including without limitation the rights
007 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
008 * copies of the Software, and to permit persons to whom the Software is
009 * furnished to do so, subject to the following conditions:
010 *
011 * The above copyright notice and this permission notice shall be included in
012 * all copies or substantial portions of the Software.
013 *
014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
017 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
019 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
020 * THE SOFTWARE.
021 */
022package com.restfb.util;
023
024import static com.restfb.logging.RestFBLogger.UTILS_LOGGER;
025
026import java.text.ParseException;
027import java.util.Date;
028
029/**
030 * A collection of date-handling utility methods.
031 * 
032 * @author <a href="http://restfb.com">Mark Allen</a>
033 * @since 1.6
034 */
035public final class DateUtils {
036  /**
037   * Facebook "long" date format (IETF RFC 3339). Example: {@code 2010-02-28T16:11:08+0000}
038   */
039  public static final String FACEBOOK_LONG_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ";
040
041  /**
042   * Facebook "long" date format (IETF RFC 3339) without a timezone component. Example: {@code 2010-02-28T16:11:08}
043   */
044  public static final String FACEBOOK_LONG_DATE_FORMAT_WITHOUT_TIMEZONE = "yyyy-MM-dd'T'HH:mm:ss";
045
046  /**
047   * Facebook "long" date format (IETF RFC 3339) without a timezone or seconds component. Example:
048   * {@code 2010-02-28T16:11}
049   */
050  public static final String FACEBOOK_LONG_DATE_FORMAT_WITHOUT_TIMEZONE_OR_SECONDS = "yyyy-MM-dd'T'HH:mm";
051
052  /**
053   * Facebook short date format. Example: {@code 04/15/1984}
054   */
055  public static final String FACEBOOK_SHORT_DATE_FORMAT = "MM/dd/yyyy";
056
057  /**
058   * Facebook alternate short date format. Example: {@code 2012-09-15}
059   */
060  public static final String FACEBOOK_ALTERNATE_SHORT_DATE_FORMAT = "yyyy-MM-dd";
061
062  /**
063   * Facebook month-year only date format. Example: {@code Example: 2007-03}
064   */
065  public static final String FACEBOOK_MONTH_YEAR_DATE_FORMAT = "yyyy-MM";
066
067  /**
068   * DateFormatStrategy (default: SimpleDateFormat).
069   */
070  private static DateFormatStrategy strategy = new SimpleDateFormatStrategy();
071
072  /**
073   * Prevents instantiation.
074   */
075  private DateUtils() {
076    // Prevents instantiation
077  }
078
079  /**
080   * Returns a Java representation of a Facebook "long" {@code date} string, or the number of seconds since the epoch.
081   * <p>
082   * Supports dates with or without timezone information.
083   * 
084   * @param date
085   *          Facebook {@code date} string.
086   * @return Java date representation of the given Facebook "long" {@code date} string or {@code null} if {@code date}
087   *         is {@code null} or invalid.
088   */
089  public static Date toDateFromLongFormat(String date) {
090    if (date == null) {
091      return null;
092    }
093
094    // Is this an all-digit date? Then assume it's the "seconds since epoch"
095    // variant
096    if (date.trim().matches("\\d+")) {
097      return new Date(Long.parseLong(date) * 1000L);
098    }
099
100    Date parsedDate = toDateWithFormatString(date, FACEBOOK_LONG_DATE_FORMAT);
101
102    // Fall back to variant without timezone if the initial parse fails
103    if (parsedDate == null) {
104      parsedDate = toDateWithFormatString(date, FACEBOOK_LONG_DATE_FORMAT_WITHOUT_TIMEZONE);
105    }
106
107    // Fall back to variant without seconds if secondary parse fails
108    if (parsedDate == null) {
109      parsedDate = toDateWithFormatString(date, FACEBOOK_LONG_DATE_FORMAT_WITHOUT_TIMEZONE_OR_SECONDS);
110    }
111
112    return parsedDate;
113  }
114
115  /**
116   * Returns a Java representation of a Facebook "short" {@code date} string.
117   * 
118   * @param date
119   *          Facebook {@code date} string.
120   * @return Java date representation of the given Facebook "short" {@code date} string or {@code null} if {@code date}
121   *         is {@code null} or invalid.
122   */
123  public static Date toDateFromShortFormat(String date) {
124    if (date == null) {
125      return null;
126    }
127
128    Date parsedDate = toDateWithFormatString(date, FACEBOOK_SHORT_DATE_FORMAT);
129
130    // Fall back to variant if initial parse fails
131    if (parsedDate == null) {
132      parsedDate = toDateWithFormatString(date, FACEBOOK_ALTERNATE_SHORT_DATE_FORMAT);
133    }
134
135    return parsedDate;
136  }
137
138  /**
139   * Returns a Java representation of a Facebook "month-year" {@code date} string.
140   * 
141   * @param date
142   *          Facebook {@code date} string.
143   * @return Java date representation of the given Facebook "month-year" {@code date} string or {@code null} if
144   *         {@code date} is {@code null} or invalid.
145   */
146  public static Date toDateFromMonthYearFormat(String date) {
147    if (date == null) {
148      return null;
149    }
150
151    if ("0000-00".equals(date)) {
152      return null;
153    }
154
155    return toDateWithFormatString(date, FACEBOOK_MONTH_YEAR_DATE_FORMAT);
156  }
157
158  /**
159   * Returns a String representation of a {@code date} object
160   * 
161   * @param date
162   *          as Date
163   * @return String representation of a {@code date} object. The String is in the form {@code 2010-02-28T16:11:08}
164   */
165  public static String toLongFormatFromDate(Date date) {
166    if (date == null) {
167      return null;
168    }
169
170    return strategy.formatFor(FACEBOOK_LONG_DATE_FORMAT_WITHOUT_TIMEZONE).format(date);
171  }
172
173  /**
174   * Returns a <strong>short</strong> String representation of a {@code date} object
175   *
176   * @param date
177   *          as Date
178   * @return String representation of a {@code date} object. The String is in the form {@code 2019-06-14}
179   */
180  public static String toShortFormatFromDate(Date date) {
181    if (date == null) {
182      return null;
183    }
184
185    return strategy.formatFor(FACEBOOK_ALTERNATE_SHORT_DATE_FORMAT).format(date);
186  }
187
188  /**
189   * Returns a Java representation of a {@code date} string.
190   * 
191   * @param date
192   *          Date in string format.
193   * @return Java date representation of the given {@code date} string or {@code null} if {@code date} is {@code null}
194   *         or invalid.
195   */
196  private static Date toDateWithFormatString(String date, String format) {
197    if (date == null) {
198      return null;
199    }
200
201    try {
202      return strategy.formatFor(format).parse(date);
203    } catch (ParseException e) {
204      UTILS_LOGGER.trace("Unable to parse date '{}' using format string '{}': {}", date, format, e);
205
206      return null;
207    }
208  }
209
210  /**
211   * get the current DateFormatStrategy.
212   * 
213   * @return the current DateFormatStrategy
214   */
215  public static DateFormatStrategy getDateFormatStrategy() {
216    return strategy;
217  }
218
219  /**
220   * set the {@link DateFormatStrategy}.
221   * 
222   * default value: {@link SimpleDateFormatStrategy}
223   * 
224   * @param dateFormatStrategy
225   *          the used {@link DateFormatStrategy}
226   * 
227   */
228  public static void setDateFormatStrategy(DateFormatStrategy dateFormatStrategy) {
229    strategy = dateFormatStrategy;
230  }
231}