/* * Copyright (C) 2012 Christian Halstrick and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.util; import java.text.MessageFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Locale; import java.util.Map; import org.eclipse.jgit.internal.JGitText; /** * Parses strings with time and date specifications into {@link java.util.Date}. * * When git needs to parse strings specified by the user this parser can be * used. One example is the parsing of the config parameter gc.pruneexpire. The * parser can handle only subset of what native gits approxidate parser * understands. * * @deprecated Use {@link GitTimeParser} instead. */ @Deprecated(since = "7.1") public class GitDateParser { /** * The Date representing never. Though this is a concrete value, most * callers are adviced to avoid depending on the actual value. */ public static final Date NEVER = new Date(Long.MAX_VALUE); // Since SimpleDateFormat instances are expensive to instantiate they should // be cached. Since they are also not threadsafe they are cached using // ThreadLocal. private static ThreadLocal>> formatCache = new ThreadLocal<>() { @Override protected Map> initialValue() { return new HashMap<>(); } }; // Gets an instance of a SimpleDateFormat for the specified locale. If there // is not already an appropriate instance in the (ThreadLocal) cache then // create one and put it into the cache. private static SimpleDateFormat getDateFormat(ParseableSimpleDateFormat f, Locale locale) { Map> cache = formatCache .get(); Map map = cache .get(locale); if (map == null) { map = new HashMap<>(); cache.put(locale, map); return getNewSimpleDateFormat(f, locale, map); } SimpleDateFormat dateFormat = map.get(f); if (dateFormat != null) return dateFormat; SimpleDateFormat df = getNewSimpleDateFormat(f, locale, map); return df; } private static SimpleDateFormat getNewSimpleDateFormat( ParseableSimpleDateFormat f, Locale locale, Map map) { SimpleDateFormat df = SystemReader.getInstance().getSimpleDateFormat( f.formatStr, locale); map.put(f, df); return df; } // An enum of all those formats which this parser can parse with the help of // a SimpleDateFormat. There are other formats (e.g. the relative formats // like "yesterday" or "1 week ago") which this parser can parse but which // are not listed here because they are parsed without the help of a // SimpleDateFormat. enum ParseableSimpleDateFormat { ISO("yyyy-MM-dd HH:mm:ss Z"), // //$NON-NLS-1$ RFC("EEE, dd MMM yyyy HH:mm:ss Z"), // //$NON-NLS-1$ SHORT("yyyy-MM-dd"), // //$NON-NLS-1$ SHORT_WITH_DOTS_REVERSE("dd.MM.yyyy"), // //$NON-NLS-1$ SHORT_WITH_DOTS("yyyy.MM.dd"), // //$NON-NLS-1$ SHORT_WITH_SLASH("MM/dd/yyyy"), // //$NON-NLS-1$ DEFAULT("EEE MMM dd HH:mm:ss yyyy Z"), // //$NON-NLS-1$ LOCAL("EEE MMM dd HH:mm:ss yyyy"); //$NON-NLS-1$ private final String formatStr; private ParseableSimpleDateFormat(String formatStr) { this.formatStr = formatStr; } } /** * Parses a string into a {@link java.util.Date} using the default locale. * Since this parser also supports relative formats (e.g. "yesterday") the * caller can specify the reference date. These types of strings can be * parsed: *
    *
  • "never"
  • *
  • "now"
  • *
  • "yesterday"
  • *
  • "(x) years|months|weeks|days|hours|minutes|seconds ago"
    * Multiple specs can be combined like in "2 weeks 3 days ago". Instead of ' * ' one can use '.' to separate the words
  • *
  • "yyyy-MM-dd HH:mm:ss Z" (ISO)
  • *
  • "EEE, dd MMM yyyy HH:mm:ss Z" (RFC)
  • *
  • "yyyy-MM-dd"
  • *
  • "yyyy.MM.dd"
  • *
  • "MM/dd/yyyy",
  • *
  • "dd.MM.yyyy"
  • *
  • "EEE MMM dd HH:mm:ss yyyy Z" (DEFAULT)
  • *
  • "EEE MMM dd HH:mm:ss yyyy" (LOCAL)
  • *
* * @param dateStr * the string to be parsed * @param now * the base date which is used for the calculation of relative * formats. E.g. if baseDate is "25.8.2012" then parsing of the * string "1 week ago" would result in a date corresponding to * "18.8.2012". This is used when a JGit command calls this * parser often but wants a consistent starting point for * calls.
* If set to null then the current time will be used * instead. * @return the parsed {@link java.util.Date} * @throws java.text.ParseException * if the given dateStr was not recognized */ public static Date parse(String dateStr, Calendar now) throws ParseException { return parse(dateStr, now, Locale.getDefault()); } /** * Parses a string into a {@link java.util.Date} using the given locale. * Since this parser also supports relative formats (e.g. "yesterday") the * caller can specify the reference date. These types of strings can be * parsed: *
    *
  • "never"
  • *
  • "now"
  • *
  • "yesterday"
  • *
  • "(x) years|months|weeks|days|hours|minutes|seconds ago"
    * Multiple specs can be combined like in "2 weeks 3 days ago". Instead of ' * ' one can use '.' to separate the words
  • *
  • "yyyy-MM-dd HH:mm:ss Z" (ISO)
  • *
  • "EEE, dd MMM yyyy HH:mm:ss Z" (RFC)
  • *
  • "yyyy-MM-dd"
  • *
  • "yyyy.MM.dd"
  • *
  • "MM/dd/yyyy",
  • *
  • "dd.MM.yyyy"
  • *
  • "EEE MMM dd HH:mm:ss yyyy Z" (DEFAULT)
  • *
  • "EEE MMM dd HH:mm:ss yyyy" (LOCAL)
  • *
* * @param dateStr * the string to be parsed * @param now * the base date which is used for the calculation of relative * formats. E.g. if baseDate is "25.8.2012" then parsing of the * string "1 week ago" would result in a date corresponding to * "18.8.2012". This is used when a JGit command calls this * parser often but wants a consistent starting point for * calls.
* If set to null then the current time will be used * instead. * @param locale * locale to be used to parse the date string * @return the parsed {@link java.util.Date} * @throws java.text.ParseException * if the given dateStr was not recognized * @since 3.2 */ public static Date parse(String dateStr, Calendar now, Locale locale) throws ParseException { dateStr = dateStr.trim(); Date ret; if ("never".equalsIgnoreCase(dateStr)) //$NON-NLS-1$ return NEVER; ret = parse_relative(dateStr, now); if (ret != null) return ret; for (ParseableSimpleDateFormat f : ParseableSimpleDateFormat.values()) { try { return parse_simple(dateStr, f, locale); } catch (ParseException e) { // simply proceed with the next parser } } ParseableSimpleDateFormat[] values = ParseableSimpleDateFormat.values(); StringBuilder allFormats = new StringBuilder("\"") //$NON-NLS-1$ .append(values[0].formatStr); for (int i = 1; i < values.length; i++) allFormats.append("\", \"").append(values[i].formatStr); //$NON-NLS-1$ allFormats.append("\""); //$NON-NLS-1$ throw new ParseException(MessageFormat.format( JGitText.get().cannotParseDate, dateStr, allFormats.toString()), 0); } // tries to parse a string with the formats supported by SimpleDateFormat private static Date parse_simple(String dateStr, ParseableSimpleDateFormat f, Locale locale) throws ParseException { SimpleDateFormat dateFormat = getDateFormat(f, locale); dateFormat.setLenient(false); return dateFormat.parse(dateStr); } // tries to parse a string with a relative time specification @SuppressWarnings("nls") private static Date parse_relative(String dateStr, Calendar now) { Calendar cal; SystemReader sysRead = SystemReader.getInstance(); // check for the static words "yesterday" or "now" if ("now".equals(dateStr)) { return ((now == null) ? new Date(sysRead.getCurrentTime()) : now .getTime()); } if (now == null) { cal = new GregorianCalendar(sysRead.getTimeZone(), sysRead.getLocale()); cal.setTimeInMillis(sysRead.getCurrentTime()); } else cal = (Calendar) now.clone(); if ("yesterday".equals(dateStr)) { cal.add(Calendar.DATE, -1); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); cal.set(Calendar.MILLISECOND, 0); return cal.getTime(); } // parse constructs like "3 days ago", "5.week.2.day.ago" String[] parts = dateStr.split("\\.| "); int partsLength = parts.length; // check we have an odd number of parts (at least 3) and that the last // part is "ago" if (partsLength < 3 || (partsLength & 1) == 0 || !"ago".equals(parts[parts.length - 1])) return null; int number; for (int i = 0; i < parts.length - 2; i += 2) { try { number = Integer.parseInt(parts[i]); } catch (NumberFormatException e) { return null; } if (parts[i + 1] == null){ return null; } switch (parts[i + 1]) { case "year": case "years": cal.add(Calendar.YEAR, -number); break; case "month": case "months": cal.add(Calendar.MONTH, -number); break; case "week": case "weeks": cal.add(Calendar.WEEK_OF_YEAR, -number); break; case "day": case "days": cal.add(Calendar.DATE, -number); break; case "hour": case "hours": cal.add(Calendar.HOUR_OF_DAY, -number); break; case "minute": case "minutes": cal.add(Calendar.MINUTE, -number); break; case "second": case "seconds": cal.add(Calendar.SECOND, -number); break; default: return null; } } return cal.getTime(); } }