001/* 002 * Copyright (c) 2010-2024 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; 028import java.util.Optional; 029 030/** 031 * A collection of date-handling utility methods. 032 * 033 * @author <a href="http://restfb.com">Mark Allen</a> 034 * @since 1.6 035 */ 036public final class DateUtils { 037 /** 038 * Facebook "long" date format (IETF RFC 3339). Example: {@code 2010-02-28T16:11:08+0000} 039 */ 040 public static final String FACEBOOK_LONG_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ"; 041 042 /** 043 * Facebook "long" date format (IETF RFC 3339) without a timezone component. Example: {@code 2010-02-28T16:11:08} 044 */ 045 public static final String FACEBOOK_LONG_DATE_FORMAT_WITHOUT_TIMEZONE = "yyyy-MM-dd'T'HH:mm:ss"; 046 047 /** 048 * Facebook "long" date format (IETF RFC 3339) without a timezone or seconds component. Example: 049 * {@code 2010-02-28T16:11} 050 */ 051 public static final String FACEBOOK_LONG_DATE_FORMAT_WITHOUT_TIMEZONE_OR_SECONDS = "yyyy-MM-dd'T'HH:mm"; 052 053 /** 054 * Facebook short date format. Example: {@code 04/15/1984} 055 */ 056 public static final String FACEBOOK_SHORT_DATE_FORMAT = "MM/dd/yyyy"; 057 058 /** 059 * Facebook alternate short date format. Example: {@code 2012-09-15} 060 */ 061 public static final String FACEBOOK_ALTERNATE_SHORT_DATE_FORMAT = "yyyy-MM-dd"; 062 063 /** 064 * Facebook month-year only date format. Example: {@code Example: 2007-03} 065 */ 066 public static final String FACEBOOK_MONTH_YEAR_DATE_FORMAT = "yyyy-MM"; 067 068 /** 069 * DateFormatStrategy (default: SimpleDateFormat). 070 */ 071 private static DateFormatStrategy strategy = new SimpleDateFormatStrategy(); 072 073 /** 074 * Prevents instantiation. 075 */ 076 private DateUtils() { 077 throw new IllegalStateException("DateUtils must not be instantiated"); 078 } 079 080 /** 081 * Returns a Java representation of a Facebook "long" {@code date} string, or the number of seconds since the epoch. 082 * <p> 083 * Supports dates with or without timezone information. 084 * 085 * @param date 086 * Facebook {@code date} string. 087 * @return Java date representation of the given Facebook "long" {@code date} string or {@code null} if {@code date} 088 * is {@code null} or invalid. 089 */ 090 public static Date toDateFromLongFormat(String date) { 091 if (isNull(date)) { 092 return null; 093 } 094 095 // Is this an all-digit date? Then assume it's the "seconds since epoch" 096 // variant 097 if (date.trim().matches("\\d+")) { 098 return new Date(Long.parseLong(date) * 1000L); 099 } 100 101 Date parsedDate = toDateWithFormatString(date, FACEBOOK_LONG_DATE_FORMAT); 102 103 // Fall back to variant without timezone if the initial parse fails 104 if (isNull(parsedDate)) { 105 parsedDate = toDateWithFormatString(date, FACEBOOK_LONG_DATE_FORMAT_WITHOUT_TIMEZONE); 106 } 107 108 // Fall back to variant without seconds if secondary parse fails 109 if (isNull(parsedDate)) { 110 parsedDate = toDateWithFormatString(date, FACEBOOK_LONG_DATE_FORMAT_WITHOUT_TIMEZONE_OR_SECONDS); 111 } 112 113 // fallback if we assume long, but it is a short format 114 if (isNull(parsedDate)) { 115 parsedDate = toDateFromShortFormat(date); 116 } 117 118 return parsedDate; 119 } 120 121 private static boolean isNull(Object date) { 122 return date == null; 123 } 124 125 /** 126 * Returns a Java representation of a Facebook "short" {@code date} string. 127 * 128 * @param date 129 * Facebook {@code date} string. 130 * @return Java date representation of the given Facebook "short" {@code date} string or {@code null} if {@code date} 131 * is {@code null} or invalid. 132 */ 133 public static Date toDateFromShortFormat(String date) { 134 if (isNull(date)) { 135 return null; 136 } 137 138 Date parsedDate = toDateWithFormatString(date, FACEBOOK_SHORT_DATE_FORMAT); 139 140 // Fall back to variant if initial parse fails 141 if (isNull(parsedDate)) { 142 parsedDate = toDateWithFormatString(date, FACEBOOK_ALTERNATE_SHORT_DATE_FORMAT); 143 } 144 145 return parsedDate; 146 } 147 148 /** 149 * Returns a Java representation of a Facebook "month-year" {@code date} string. 150 * 151 * @param date 152 * Facebook {@code date} string. 153 * @return Java date representation of the given Facebook "month-year" {@code date} string or {@code null} if 154 * {@code date} is {@code null} or invalid. 155 */ 156 public static Date toDateFromMonthYearFormat(String date) { 157 if (isNull(date)) { 158 return null; 159 } 160 161 if ("0000-00".equals(date)) { 162 return null; 163 } 164 165 return toDateWithFormatString(date, FACEBOOK_MONTH_YEAR_DATE_FORMAT); 166 } 167 168 /** 169 * Returns a String representation of a {@code date} object 170 * 171 * @param date 172 * as Date 173 * @return String representation of a {@code date} object. The String is in the form {@code 2010-02-28T16:11:08} 174 */ 175 public static String toLongFormatFromDate(Date date) { 176 return Optional.ofNullable(date).map(strategy.formatFor(FACEBOOK_LONG_DATE_FORMAT_WITHOUT_TIMEZONE)::format).orElse(null); 177 } 178 179 /** 180 * Returns a <strong>short</strong> String representation of a {@code date} object 181 * 182 * @param date 183 * as Date 184 * @return String representation of a {@code date} object. The String is in the form {@code 2019-06-14} 185 */ 186 public static String toShortFormatFromDate(Date date) { 187 return Optional.ofNullable(date).map(strategy.formatFor(FACEBOOK_ALTERNATE_SHORT_DATE_FORMAT)::format).orElse(null); 188 } 189 190 /** 191 * Returns a Java representation of a {@code date} string. 192 * 193 * @param date 194 * Date in string format. 195 * @return Java date representation of the given {@code date} string or {@code null} if {@code date} is {@code null} 196 * or invalid. 197 */ 198 private static Date toDateWithFormatString(String date, String format) { 199 if (isNull(date)) { 200 return null; 201 } 202 203 try { 204 return strategy.formatFor(format).parse(date); 205 } catch (ParseException e) { 206 UTILS_LOGGER.trace("Unable to parse date '{}' using format string '{}': {}", date, format, e); 207 return null; 208 } 209 } 210 211 /** 212 * get the current DateFormatStrategy. 213 * 214 * @return the current DateFormatStrategy 215 */ 216 public static DateFormatStrategy getDateFormatStrategy() { 217 return strategy; 218 } 219 220 /** 221 * set the {@link DateFormatStrategy}. 222 * <p> 223 * default value: {@link SimpleDateFormatStrategy} 224 * 225 * @param dateFormatStrategy 226 * the used {@link DateFormatStrategy} 227 * 228 */ 229 public static void setDateFormatStrategy(DateFormatStrategy dateFormatStrategy) { 230 strategy = dateFormatStrategy; 231 } 232}