001/* 002 * jPOS Project [http://jpos.org] 003 * Copyright (C) 2000-2026 jPOS Software SRL 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jpos.util; 020 021import java.text.DateFormat; 022import java.text.ParseException; 023import java.text.SimpleDateFormat; 024import java.util.Calendar; 025import java.util.Date; 026import java.util.GregorianCalendar; 027import java.util.Locale; 028import java.util.TimeZone; 029 030/** 031 * Convenience helpers for parsing and formatting dates and times in a small 032 * set of jPOS-specific patterns. Methods that take {@code null} return 033 * {@code null} so call sites do not need to guard themselves. 034 */ 035public class DateUtil { 036 /** Utility class; instances carry no state. */ 037 public DateUtil() {} 038 static SimpleDateFormat dfDate = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT, Locale.US); 039 static SimpleDateFormat dfDateTime = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, Locale.US); 040 041 static SimpleDateFormat dfDate_mmddyyyy = new SimpleDateFormat("MM/dd/yyyy"); 042 static SimpleDateFormat dfDate_yyyymmdd = new SimpleDateFormat("yyyyMMdd"); 043 static SimpleDateFormat dfDateTime_mmddyyyy = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss"); 044 045 static SimpleDateFormat dfDate_mmddyy = new SimpleDateFormat("MM/dd/yy"); 046 /** 047 * Parses {@code s} using the US short date format. 048 * 049 * @param s date string, or {@code null} 050 * @return parsed date, or {@code null} when {@code s} is {@code null} 051 * @throws ParseException if the string cannot be parsed 052 */ 053 public static Date parseDate (String s) throws ParseException { 054 if (s == null) 055 return null; 056 return dfDate.parse (s); 057 } 058 /** 059 * Parses {@code s} using the {@code MM/dd/yyyy} pattern. 060 * 061 * @param s date string, or {@code null} 062 * @return parsed date, or {@code null} when {@code s} is {@code null} 063 * @throws ParseException if the string cannot be parsed 064 */ 065 public static Date parseDate_mmddyyyy (String s) throws ParseException { 066 if (s == null) 067 return null; 068 return dfDate_mmddyyyy.parse (s); 069 } 070 /** 071 * Parses {@code s} using the {@code yyyyMMdd} pattern. 072 * 073 * @param s date string, or {@code null} 074 * @return parsed date, or {@code null} when {@code s} is {@code null} 075 * @throws ParseException if the string cannot be parsed 076 */ 077 public static Date parseDate_yyyymmdd (String s) throws ParseException { 078 if (s == null) 079 return null; 080 return dfDate_yyyymmdd.parse (s); 081 } 082 /** 083 * Parses {@code s} using the {@code MM/dd/yy} pattern. 084 * 085 * @param s date string, or {@code null} 086 * @return parsed date, or {@code null} when {@code s} is {@code null} 087 * @throws ParseException if the string cannot be parsed 088 */ 089 public static Date parseDate_mmddyy (String s) throws ParseException { 090 if (s == null) 091 return null; 092 return dfDate_mmddyy.parse (s); 093 } 094 095 /** 096 * Parses {@code s} using the US short date / medium time format. 097 * 098 * @param s date-time string, or {@code null} 099 * @return parsed date, or {@code null} when {@code s} is {@code null} 100 * @throws ParseException if the string cannot be parsed 101 */ 102 public static Date parseDateTime (String s) throws ParseException { 103 if (s == null) 104 return null; 105 return dfDateTime.parse (s); 106 } 107 /** 108 * Parses {@code s} using the {@code MM/dd/yyyy HH:mm:ss} pattern. 109 * 110 * @param s date-time string, or {@code null} 111 * @return parsed date, or {@code null} when {@code s} is {@code null} 112 * @throws ParseException if the string cannot be parsed 113 */ 114 public static Date parseDateTime_mmddyyyy (String s) throws ParseException { 115 if (s == null) 116 return null; 117 return dfDateTime_mmddyyyy.parse (s); 118 } 119 /** 120 * Parses {@code s} using the {@code MM/dd/yyyy HH:mm:ss} pattern. 121 * 122 * @param s timestamp string, or {@code null} 123 * @return parsed date, or {@code null} when {@code s} is {@code null} 124 * @throws ParseException if the string cannot be parsed 125 */ 126 public static Date parseTimestamp (String s) throws ParseException { 127 if (s == null) 128 return null; 129 return dfDateTime_mmddyyyy.parse (s); 130 } 131 132 /** 133 * Parses {@code s} using the {@code MM/dd/yyyy HH:mm:ss} pattern in the 134 * given time zone. 135 * 136 * @param s date-time string, or {@code null} 137 * @param tzString time-zone identifier accepted by {@link TimeZone#getTimeZone(String)}; 138 * {@code null} keeps the JVM default zone 139 * @return parsed date, or {@code null} when {@code s} is {@code null} 140 * @throws ParseException if the string cannot be parsed 141 */ 142 public static Date parseDateTime_mmddyyyy (String s, String tzString) 143 throws ParseException 144 { 145 if (s == null) 146 return null; 147 DateFormat df = (DateFormat) dfDateTime_mmddyyyy.clone(); 148 if (tzString != null) 149 df.setTimeZone (TimeZone.getTimeZone (tzString)); 150 return df.parse (s); 151 } 152 153 /** 154 * Formats {@code d} using the US short date format. 155 * 156 * @param d date, or {@code null} 157 * @return formatted string, or {@code null} when {@code d} is {@code null} 158 */ 159 public static String dateToString (Date d) { 160 if (d == null) 161 return null; 162 return dfDate.format (d); 163 } 164 /** 165 * Formats {@code d} using the {@code MM/dd/yyyy} pattern. 166 * 167 * @param d date, or {@code null} 168 * @return formatted string, or {@code null} when {@code d} is {@code null} 169 */ 170 public static String dateToString_mmddyyyy (Date d) { 171 if (d == null) 172 return null; 173 return dfDate_mmddyyyy.format (d); 174 } 175 176 /** 177 * Formats {@code d} using the US short date / medium time format. 178 * 179 * @param d date, or {@code null} 180 * @return formatted string, or {@code null} when {@code d} is {@code null} 181 */ 182 public static String dateTimeToString (Date d) { 183 if (d == null) 184 return null; 185 return dfDateTime.format (d); 186 } 187 /** 188 * Formats {@code d} using the US short date / medium time format in the 189 * given time zone. 190 * 191 * @param d date, or {@code null} 192 * @param tzString time-zone identifier; {@code null} keeps the JVM default zone 193 * @return formatted string, or {@code null} when {@code d} is {@code null} 194 */ 195 public static String dateTimeToString (Date d, String tzString) { 196 if (d == null) 197 return null; 198 DateFormat df = (DateFormat) dfDateTime.clone(); 199 if (tzString != null) 200 df.setTimeZone (TimeZone.getTimeZone (tzString)); 201 return df.format (d); 202 } 203 /** 204 * Formats {@code d} using the {@code MM/dd/yyyy HH:mm:ss} pattern. 205 * 206 * @param d date, or {@code null} 207 * @return formatted string, or {@code null} when {@code d} is {@code null} 208 */ 209 public static String dateTimeToString_mmddyyyy (Date d) { 210 if (d == null) 211 return null; 212 return dfDateTime_mmddyyyy.format (d); 213 } 214 /** 215 * Formats {@code d} using the {@code MM/dd/yyyy HH:mm:ss} pattern. 216 * 217 * @param d date, or {@code null} 218 * @return formatted string, or {@code null} when {@code d} is {@code null} 219 */ 220 public static String timestamp (Date d) { 221 if (d == null) 222 return null; 223 return dfDateTime_mmddyyyy.format (d); 224 } 225 /** 226 * Formats {@code d} using the {@code MM/dd/yyyy} pattern. 227 * 228 * @param d date, or {@code null} 229 * @return formatted string, or {@code null} when {@code d} is {@code null} 230 */ 231 public static String postdate (Date d) { 232 if (d == null) 233 return null; 234 return dfDate_mmddyyyy.format (d); 235 } 236 237 /** 238 * Parses an {@code MMDDYY} date and {@code HHMMSS} time, choosing the 239 * century closest to "now" so two-digit years near a century boundary 240 * round to the correct year. 241 * 242 * @param d date in {@code MMDDYY} 243 * @param t time in {@code HHMMSS} 244 * @return parsed date 245 */ 246 public static Date parseDateTime (String d, String t) { 247 Calendar cal = new GregorianCalendar(); 248 Date now = new Date (); 249 cal.setTime (now); 250 251 int YY = Integer.parseInt (d.substring (4)); 252 int MM = Integer.parseInt (d.substring (0, 2))-1; 253 int DD = Integer.parseInt (d.substring (2, 4)); 254 int hh = Integer.parseInt (t.substring (0, 2)); 255 int mm = Integer.parseInt (t.substring (2, 4)); 256 int ss = Integer.parseInt (t.substring (4)); 257 int century = cal.get (Calendar.YEAR) / 100; 258 259 cal.set (Calendar.YEAR, (century * 100) + YY); 260 cal.set (Calendar.MONTH, MM); 261 cal.set (Calendar.DATE, DD); 262 cal.set (Calendar.HOUR_OF_DAY, hh); 263 cal.set (Calendar.MINUTE, mm); 264 cal.set (Calendar.SECOND, ss); 265 266 // 267 // I expect this program to continue running by 2099 ... --apr@jpos.org 268 // 269 Date thisCentury = cal.getTime(); 270 cal.set (Calendar.YEAR, (--century * 100) + YY); 271 Date previousCentury = cal.getTime(); 272 273 if (Math.abs (now.getTime() - previousCentury.getTime()) < 274 Math.abs (now.getTime() - thisCentury.getTime()) ) 275 thisCentury = previousCentury; 276 return thisCentury; 277 } 278 /** 279 * Parses an {@code HHMM} or {@code HHMMSS} time using today as the date. 280 * 281 * @param t time string 282 * @return parsed date with today's year/month/day 283 */ 284 public static Date parseTime (String t) { 285 return parseTime (t, new Date()); 286 } 287 /** 288 * Parses an {@code HHMM} or {@code HHMMSS} time using {@code now} as the 289 * date portion. 290 * 291 * @param t time string 292 * @param now date supplying the year/month/day 293 * @return parsed date with the supplied date and the parsed time 294 */ 295 public static Date parseTime (String t, Date now) { 296 Calendar cal = new GregorianCalendar(); 297 cal.setTime (now); 298 299 int hh = Integer.parseInt (t.substring (0, 2)); 300 int mm = Integer.parseInt (t.substring (2, 4)); 301 int ss = t.length() > 4 ? Integer.parseInt (t.substring (4)) : 0; 302 303 cal.set (Calendar.HOUR_OF_DAY, hh); 304 cal.set (Calendar.MINUTE, mm); 305 cal.set (Calendar.SECOND, ss); 306 307 return cal.getTime(); 308 } 309 310 /** 311 * Formats {@code d} using the JVM default date format in the given time zone. 312 * 313 * @param d date 314 * @param tzString time-zone identifier; {@code null} keeps the JVM default zone 315 * @return formatted date string 316 */ 317 public static String dateToString (Date d, String tzString) { 318 DateFormat df = (DateFormat) DateFormat.getDateInstance().clone(); 319 if (tzString != null) 320 df.setTimeZone (TimeZone.getTimeZone (tzString)); 321 return df.format (d); 322 } 323 /** 324 * Formats the time portion of {@code d} using the JVM short time format 325 * in the given time zone. 326 * 327 * @param d date 328 * @param tzString time-zone identifier; {@code null} keeps the JVM default zone 329 * @return formatted time string 330 */ 331 public static String timeToString (Date d, String tzString) { 332 DateFormat df = (DateFormat) 333 DateFormat.getTimeInstance(DateFormat.SHORT).clone(); 334 if (tzString != null) 335 df.setTimeZone (TimeZone.getTimeZone (tzString)); 336 return df.format (d); 337 } 338 /** 339 * Formats the time portion of {@code d} using the JVM short time format. 340 * 341 * @param d date 342 * @return formatted time string 343 */ 344 public static String timeToString (Date d) { 345 return timeToString (d, null); 346 } 347 /** 348 * Renders a duration in milliseconds as a compact {@code 1h2m3s}-style string. 349 * 350 * @param period duration in milliseconds 351 * @return human-readable duration; always includes a seconds component, 352 * and includes hours/minutes only when non-zero 353 */ 354 public static String toDays (long period) { 355 StringBuffer sb = new StringBuffer(); 356 long hours = period / 3600000L; 357 if (hours > 0) { 358 sb.append (hours); 359 sb.append ("h"); 360 period -= (hours * 3600000L); 361 } 362 long mins = period / 60000L; 363 if (mins > 0) { 364 sb.append (mins); 365 sb.append ("m"); 366 period -= (mins * 60000L); 367 } 368 long secs = period / 1000L; 369 sb.append (secs); 370 sb.append ("s"); 371 return sb.toString(); 372 } 373} 374