package org.rr.commons.utils;
import static org.rr.commons.utils.StringUtil.EMPTY;
import java.text.DateFormatSymbols;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;
public class DateConversionUtils {
public static interface DateFormat {
public Date getDate(String dateString);
public boolean isMatching(String dateString);
public String getString(Date date);
}
public static enum DATE_FORMATS implements DateFormat {
DIN_5008 {
//11.2.2011 - Germany, Latvia, Armenia, Azerbaijan, Belarus, Bulgaria, Georgia, Greenland
//Iceland, Kyrgyzstan, Macedonia, Poland, Romania, Serbia, Switzerland, Tajikistan,
private final Pattern GERMAN_DATE_PATTERN = Pattern.compile("\\d{1,2}\\.\\d{1,2}\\.\\d{2,4}");
public Date getDate(String dateString) {
Calendar calendar = Calendar.getInstance(Locale.US);
String[] split = dateString.split("\\.");
calendar.set(formatToYear(CommonUtils.toNumber(split[2])), CommonUtils.toNumber(split[1]).intValue()-1, CommonUtils.toNumber(split[0]).intValue(), 0, 0, 0);
return calendar.getTime();
}
public boolean isMatching(String dateString) {
return GERMAN_DATE_PATTERN.matcher(dateString).matches();
}
public String getString(Date date) {
return new SimpleDateFormat("dd.MM.yyyy").format(date);
}
},
MSZ_ISO_8601_2003 {
//2011.11.2 - Hungary - MSZ ISO 8601:2003
private final Pattern HUNGARY_DATE_PATTERN = Pattern.compile("\\d{4}\\.\\d{1,2}\\.\\d{1,2}");
public Date getDate(String dateString) {
Calendar calendar = Calendar.getInstance(Locale.US);
String[] split = dateString.split("\\.");
calendar.set(formatToYear(CommonUtils.toNumber(split[0])), CommonUtils.toNumber(split[1]).intValue()-1, CommonUtils.toNumber(split[2]).intValue(), 0, 0, 0);
return calendar.getTime();
}
public boolean isMatching(String dateString) {
return HUNGARY_DATE_PATTERN.matcher(dateString).matches();
}
public String getString(Date date) {
return new SimpleDateFormat("yyyy.dd.MM").format(date);
}
},
ISO_8601_DATE {
//2011-11-2
private final Pattern EU_DATE_PATTERN = Pattern.compile("\\d{2,4}\\-\\d{1,2}\\-\\d{1,2}");
public Date getDate(String dateString) {
Calendar calendar = Calendar.getInstance(Locale.US);
String[] split = dateString.split("-");
calendar.set(formatToYear(CommonUtils.toNumber(split[0])), CommonUtils.toNumber(split[1]).intValue()-1, CommonUtils.toNumber(split[2]).intValue(), 0, 0, 0);
return calendar.getTime();
}
public boolean isMatching(String dateString) {
return EU_DATE_PATTERN.matcher(dateString).matches();
}
public String getString(Date date) {
return new SimpleDateFormat("yyyy-MM-dd").format(date);
}
},
ISO_8601_WEEK {
//2003-W14-2 (Date with week number)
private final Pattern ISO_8601_DATE_PATTERN = Pattern.compile("\\d{4}-[A-Z]??\\d{2}-\\d");
private final String FORMAT_PATTERN = "yyyy-'W'ww-d";
public Date getDate(String dateString) {
try {
return new SimpleDateFormat(FORMAT_PATTERN, Locale.US).parse(dateString);
} catch (ParseException e) {
throw new RuntimeException("could not parse date " + dateString);
}
}
public boolean isMatching(String dateString) {
return ISO_8601_DATE_PATTERN.matcher(dateString).matches();
}
public String getString(Date date) {
return new SimpleDateFormat(FORMAT_PATTERN).format(date);
}
},
ISO_8601_DATE_TIME {
//Separate date and time in UTC: 2011-05-07 14:39Z
//Combined date and time in UTC: 2011-05-07T14:39Z
private final Pattern ISO_8601_DATE_TIME_PATTERN_1 = Pattern.compile("\\d{4}-\\d{2}-\\d{2}[T\\s]\\d{2}:\\d{2}Z");
//2005-07-04T00:00:+0Z
//2005-07-04 00:00:+0Z
private final Pattern ISO_8601_DATE_TIME_PATTERN_2 = Pattern.compile("\\d{4}-\\d{2}-\\d{2}[\\sT]\\d{2}:\\d{2}:[+-]\\d{1,4}Z");
private final String FORMAT_PATTERN_1 = "yyyy-MM-dd hh:mm'Z'";
private final String FORMAT_PATTERN_2 = "yyyy-MM-dd hh:mm:Z";
public Date getDate(String dateString) {
try {
dateString = dateString.replace('T', ' ');
if(ISO_8601_DATE_TIME_PATTERN_1.matcher(dateString).matches()) {
return new SimpleDateFormat(FORMAT_PATTERN_1, Locale.US).parse(dateString);
} else if(ISO_8601_DATE_TIME_PATTERN_2.matcher(dateString).matches()) {
//format 2005-07-04 00:00:+0Z -> 2005-07-04 00:00:+0000Z
final int idx = dateString.indexOf(":+") != -1 ? dateString.indexOf(":+") : dateString.indexOf(":-");
final String timezone = dateString.substring(idx + 2, dateString.indexOf('Z', idx));
dateString = dateString.substring(0, idx + 2); //2005-07-04 00:00:+
if(timezone.length() == 1) {
dateString += "0" + timezone + "00";
} else if(timezone.length() == 2) {
dateString += "0" + timezone + "0";
} else if(timezone.length() == 3) {
dateString += "0" + timezone;
} else if(timezone.length() == 4) {
dateString += timezone;
}
return new SimpleDateFormat(FORMAT_PATTERN_2, Locale.US).parse(dateString);
} else {
throw new RuntimeException("unknown format for " + dateString);
}
} catch (ParseException e) {
throw new RuntimeException("could not parse date " + dateString);
}
}
public boolean isMatching(String dateString) {
return ISO_8601_DATE_TIME_PATTERN_1.matcher(dateString).matches() || ISO_8601_DATE_TIME_PATTERN_2.matcher(dateString).matches();
}
public String getString(Date date) {
return new SimpleDateFormat(FORMAT_PATTERN_1, Locale.US).format(date);
}
},
ISO_8601_ORDINAL {
//2011-127
private final Pattern ISO_8601_DATE_TIME_PATTERN = Pattern.compile("\\d{4}-\\d{3}");
public Date getDate(String dateString) {
List<String> split = ListUtils.split(dateString, "-", 2, UtilConstants.COMPARE_BINARY);
Calendar calendar = Calendar.getInstance(Locale.US);
calendar.set(formatToYear(CommonUtils.toNumber(split.get(0))), 0, 1, 0, 0, 0);
calendar.set(Calendar.DAY_OF_YEAR, CommonUtils.toNumber(split.get(1)).intValue());
return calendar.getTime();
}
public boolean isMatching(String dateString) {
return ISO_8601_DATE_TIME_PATTERN.matcher(dateString).matches();
}
public String getString(Date date) {
Calendar calendar = Calendar.getInstance(Locale.US);
calendar.setTime(date);
int dayOfYear = calendar.get(Calendar.DAY_OF_YEAR);
int year = calendar.get(Calendar.YEAR);
return formatToYear(Integer.valueOf(year)) + "-" + new DecimalFormat("##00").format(dayOfYear);
}
},
US_ANSI {
//11/28/2010 - ANSI INCITS 30-1997 (R2008) and NIST FIPS PUB 4-2
private final Pattern US_DATE_PATTERN = Pattern.compile("\\d{1,2}\\/\\d{1,2}\\/\\d{2,4}");
public Date getDate(String dateString) {
Calendar calendar = Calendar.getInstance(Locale.US);
String[] split = dateString.split("/");
calendar.set(formatToYear(CommonUtils.toNumber(split[2])), CommonUtils.toNumber(split[0]).intValue()-1, CommonUtils.toNumber(split[1]).intValue(), 0, 0, 0);
return calendar.getTime();
}
public boolean isMatching(String dateString) {
return US_DATE_PATTERN.matcher(dateString).matches();
}
public String getString(Date date) {
return new SimpleDateFormat("MM/dd/yyyy").format(date);
}
},
ISO_ARP_010_1989 {
//Iran, Korea, South Africa
//2010/11/28
private final Pattern ISO_ARP_010_1989_DATE_PATTERN = Pattern.compile("\\d{4}\\/\\d{1,2}\\/\\d{1,2}");
public Date getDate(String dateString) {
Calendar calendar = Calendar.getInstance(Locale.US);
String[] split = dateString.split("/");
calendar.set(formatToYear(CommonUtils.toNumber(split[0])), CommonUtils.toNumber(split[1]).intValue()-1, CommonUtils.toNumber(split[2]).intValue(), 0, 0, 0);
return calendar.getTime();
}
public boolean isMatching(String dateString) {
return ISO_ARP_010_1989_DATE_PATTERN.matcher(dateString).matches();
}
public String getString(Date date) {
return new SimpleDateFormat("yyyy/MM/dd").format(date);
}
},
W3C_MILLISECOND {
//2010-10-13T12:58:49.281000+00:00
//2010-09-22 11:15:52.216000+02:00
private final Pattern W3C_MILLISECOND_DATE_PATTERN_1 = Pattern.compile("\\d{4}-\\d{2}-\\d{2}[\\sT]\\d{2}:\\d{2}:\\d{2}\\.\\d{1,6}[+-]\\d{2}:??\\d{2}");
//2008-03-08T19:21:00.000Z
private final Pattern W3C_MILLISECOND_DATE_PATTERN_2 = Pattern.compile("\\d{4}-\\d{2}-\\d{2}[\\sT]\\d{2}:\\d{2}:\\d{2}\\.\\d{1,6}Z");
public Date getDate(String dateString) {
try {
if(dateString.indexOf(' ') != -1) {
dateString = StringUtil.replace(dateString, " ", "T");
}
if(dateString.endsWith("Z")) {
dateString = dateString.substring(0, dateString.length() -1) + "+00:00";
}
return new W3CDateFormat().parse(dateString);
} catch (ParseException e) {
throw new RuntimeException("could not parse date " + dateString);
}
}
public boolean isMatching(String dateString) {
return W3C_MILLISECOND_DATE_PATTERN_1.matcher(dateString).matches()
|| W3C_MILLISECOND_DATE_PATTERN_2.matcher(dateString).matches();
}
public String getString(Date date) {
return new W3CDateFormat(W3CDateFormat.Pattern.MILLISECOND).format(date);
}
},
W3C_SECOND {
//2010-11-27T23:00:00+00:00
//2010-11-27 23:00:00+00:00
private final Pattern W3C_SECOND_DATE_PATTERN = Pattern.compile("\\d{4}-\\d{2}-\\d{2}[\\sT]\\d{2}:\\d{2}:\\d{2}[+-]\\d{2}:??\\d{2}");
public Date getDate(String dateString) {
try {
if(dateString.indexOf(' ')!=-1) {
dateString = StringUtil.replace(dateString, " ", "T");
}
return new W3CDateFormat().parse(dateString);
} catch (ParseException e) {
throw new RuntimeException("could not parse date " + dateString);
}
}
public boolean isMatching(String dateString) {
return W3C_SECOND_DATE_PATTERN.matcher(dateString).matches();
}
public String getString(Date date) {
return new W3CDateFormat(W3CDateFormat.Pattern.SECOND).format(date);
}
},
W3C_MINUTE {
//2010-11-27T23:00+00:00
//2010-11-27 23:00+00:00
private final Pattern W3C_MINUTE_DATE_PATTERN_1 = Pattern.compile("\\d{4}-\\d{2}-\\d{2}[\\sT]\\d{2}:\\d{2}[+-]\\d{2}:??\\d{2}");
//2009-08-04T00:00:00
//2009-08-04 00:00:00
private final Pattern W3C_MINUTE_DATE_PATTERN_2 = Pattern.compile("\\d{4}-\\d{2}-\\d{2}[\\sT]\\d{2}:\\d{2}:\\d{2}");
public Date getDate(String dateString) {
try {
if(dateString.indexOf(' ')!=-1) {
dateString = StringUtil.replace(dateString, " ", "T");
}
return new W3CDateFormat().parse(dateString);
} catch (ParseException e) {
throw new RuntimeException("could not parse date " + dateString);
}
}
public boolean isMatching(String dateString) {
return W3C_MINUTE_DATE_PATTERN_1.matcher(dateString).matches() || W3C_MINUTE_DATE_PATTERN_2.matcher(dateString).matches();
}
public String getString(Date date) {
return new W3CDateFormat(W3CDateFormat.Pattern.MINUTE).format(date);
}
},
W3C_MINUTE2 {
private final Pattern W3C_MINUTE_DATE_PATTERN_1 = Pattern.compile("\\d{4}-\\d{2}-\\d{2}[\\sT]\\d{2}:\\d{2}:\\d{2}");
public Date getDate(String dateString) {
try {
if(dateString.indexOf(' ')!=-1) {
dateString = StringUtil.replace(dateString, " ", "T");
}
return new W3CDateFormat().parse(dateString);
} catch (ParseException e) {
throw new RuntimeException("could not parse date " + dateString);
}
}
public boolean isMatching(String dateString) {
return W3C_MINUTE_DATE_PATTERN_1.matcher(dateString).matches();
}
public String getString(Date date) {
return new W3CDateFormat(W3CDateFormat.Pattern.MINUTE).format(date);
}
},
W3C_MINUTE_MONTH {
//2010-11
//2010-11
private final Pattern W3C_MINUTE_MONTH_DATE_PATTERN = Pattern.compile("\\d{4}-\\d{2}");
public Date getDate(String dateString) {
try {
return new W3CDateFormat(W3CDateFormat.Pattern.MONTH).parse(dateString);
} catch (ParseException e) {
throw new RuntimeException("could not parse date " + dateString);
}
}
public boolean isMatching(String dateString) {
return W3C_MINUTE_MONTH_DATE_PATTERN.matcher(dateString).matches();
}
public String getString(Date date) {
return new W3CDateFormat(W3CDateFormat.Pattern.MONTH).format(date);
}
},
W3C_MINUTE_YEAR {
//2011
public Date getDate(String dateString) {
Calendar calendar = Calendar.getInstance(Locale.US);
calendar.set(CommonUtils.toNumber(dateString).intValue(), 0, 1, 0, 0, 0);
return calendar.getTime();
}
public boolean isMatching(String dateString) {
if(dateString.length()==4 && CommonUtils.isInteger(dateString)) {
return true;
}
return false;
}
public String getString(Date date) {
return new W3CDateFormat(W3CDateFormat.Pattern.YEAR).format(date);
}
},
RFC822_1 {
//Wed, 02 Oct 2002 15:00:00 +0200
//Wed, 04 Jul 2001 12:08:56 -0700
private final Pattern RFC822_1_DATE_PATTERN = Pattern.compile("[A-Z][a-z]{2},\\s\\d\\d\\s[A-Z][a-z]{2}\\s\\d{4}\\s\\d{2}:\\d{2}:\\d{2}\\s[+-]\\d{4}");
private final String FORMAT_PATTERN = "EEE, dd MMM yyyy HH:mm:ss z";
public Date getDate(String dateString) {
try {
return new SimpleDateFormat(FORMAT_PATTERN, Locale.US).parse(dateString);
} catch (ParseException e) {
throw new RuntimeException("could not parse date " + dateString);
}
}
public boolean isMatching(String dateString) {
return RFC822_1_DATE_PATTERN.matcher(dateString).matches();
}
@Override
public String getString(Date date) {
return new SimpleDateFormat(FORMAT_PATTERN).format(date);
}
},
RFC822_2 {
//Wed, 02 Oct 2002 15:00 +0200
//Wed, 04 Jul 2001 12:08 -0700
private final Pattern RFC822_1_DATE_PATTERN = Pattern.compile("[A-Z][a-z]{2},\\s\\d\\d\\s[A-Z][a-z]{2}\\s\\d{4}\\s\\d{2}:\\d{2}\\s[+-]\\d{4}");
private final String FORMAT_PATTERN = "EEE, dd MMM yyyy HH:mm z";
public Date getDate(String dateString) {
try {
return new SimpleDateFormat(FORMAT_PATTERN, Locale.US).parse(dateString);
} catch (ParseException e) {
throw new RuntimeException("could not parse date " + dateString);
}
}
public boolean isMatching(String dateString) {
return RFC822_1_DATE_PATTERN.matcher(dateString).matches();
}
public String getString(Date date) {
return new SimpleDateFormat(FORMAT_PATTERN).format(date);
}
},
RFC822_3 {
//02 Oct 2002 15:00:00 +0200
//04 Jul 2001 12:08:56 -0700
private final Pattern RFC822_1_DATE_PATTERN = Pattern.compile("\\d{2}\\s[A-Z][a-z]{2}\\s\\d{4}\\s\\d{2}:\\d{2}:\\d{2}\\s[+-]\\d{4}");
private final String FORMAT_PATTERN = "dd MMM yyyy HH:mm:ss z";
public Date getDate(String dateString) {
try {
return new SimpleDateFormat(FORMAT_PATTERN, Locale.US).parse(dateString);
} catch (ParseException e) {
throw new RuntimeException("could not parse date " + dateString);
}
}
public boolean isMatching(String dateString) {
return RFC822_1_DATE_PATTERN.matcher(dateString).matches();
}
public String getString(Date date) {
return new SimpleDateFormat(FORMAT_PATTERN).format(date);
}
},
JAVA {
//Thu Dec 13 00:00:00 CET 2012
//Fri Nov 11 00:00:00 CET 2011
//Sun Jan 23 00:00:00 CET 2011
//Thu Oct 23 00:00:00 CEST 2008
private final Pattern JAVA_DATE_PATTERN = Pattern.compile("[A-Z][a-z]{2}\\s[A-Z][a-z]{2}\\s\\d{2}\\s\\d{2}\\:\\d{2}:\\d{2}\\s[A-Z]{3,4}\\s\\d{4}");
public Date getDate(String dateString) {
SimpleDateFormat format = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.US);
format.setDateFormatSymbols(new DateFormatSymbols(Locale.US));
try {
return format.parse(dateString);
} catch (ParseException e) {
throw new RuntimeException("could not parse date " + dateString);
}
}
public boolean isMatching(String dateString) {
boolean match = JAVA_DATE_PATTERN.matcher(dateString).matches();
return match;
}
@Override
public String getString(Date date) {
return date.toString();
}
},
PDF {
//D:20001218180738+01'00'
//D:200012181807+01'00'
//D:200012181807Z
//D:200212291022+0100
//D:20110427120924Z00'00' Mac OS X 10.6.7 Quartz PDFContext
private final Pattern OO_PDF_DATE_PATTERN1 = Pattern.compile("D:\\d{12,14}[+-Z]\\d{2}\\'??\\d{2}\\'??Z??");
private final Pattern OO_PDF_DATE_PATTERN2 = Pattern.compile("D:\\d{12,14}Z??");
public Date getDate(String dateString) {
final String saved = dateString;
dateString = StringUtil.replace(dateString, "'", EMPTY).substring(2);
if(dateString.indexOf('Z')!=-1) {
//D:200012181807Z
//D:20110427120924Z00'00'
dateString = dateString.substring(0, dateString.indexOf('Z'));
}
try {
if(dateString.length()==19) {
//20001218180738+0100
return new SimpleDateFormat("yyyyMMddhhmmssZ").parse(dateString);
} else if(dateString.length()==17) {
//200012181807+0100
return new SimpleDateFormat("yyyyMMddhhmmZ").parse(dateString);
} else if(dateString.length()==14) {
//20001218180738
return new SimpleDateFormat("yyyyMMddhhmmss").parse(dateString);
} else if(dateString.length()==12) {
//200012181807
return new SimpleDateFormat("yyyyMMddhhmm").parse(dateString);
}
return new SimpleDateFormat("yyyyMMddhhmmZ").parse(dateString);
} catch (ParseException e) {
throw new RuntimeException("could not parse date " + saved);
}
}
public boolean isMatching(String dateString) {
return OO_PDF_DATE_PATTERN1.matcher(dateString).matches() || OO_PDF_DATE_PATTERN2.matcher(dateString).matches();
}
@Override
public String getString(Date date) {
//D:199812231952−08'00'
String dateString = new SimpleDateFormat("'D:'yyyyMMddHHmmZ").format(date);
dateString = dateString.substring(0, dateString.length()-2) + "'" + dateString.substring(dateString.length()-2) + "'";
return dateString;
}
},
}
/**
* Some pattern find in the free wildness. They will also be tried if no of the
* well specified ones will match.
*/
private static final SimpleDateFormat[] POTENTIAL_FORMATS = new SimpleDateFormat[] {
new SimpleDateFormat("EEEE, dd MMM yyyy hh:mm:ss a"),
new SimpleDateFormat("EEEE, MMM dd, yyyy hh:mm:ss a"),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz"),
new SimpleDateFormat("MM/dd/yyyy hh:mm:ss"),
new SimpleDateFormat("dd MMM yy HH:mm z"), //Two digit year RFC822
new SimpleDateFormat("EEEE, MMM dd, yyyy"), // Acrobat Distiller 1.0.2 for Macintosh
new SimpleDateFormat("EEEE MMM dd, yyyy HH:mm:ss"), // ECMP5
new SimpleDateFormat("EEEE MMM dd HH:mm:ss z yyyy"), // GNU Ghostscript 7.0.7
new SimpleDateFormat("EEEE, MMM dd, yyyy 'at' hh:mma"), // Acrobat Net Distiller 1.0 for Windows
new SimpleDateFormat("yyyyMMddhhmmZ"), //200908242027+0200
new SimpleDateFormat("E, dd. MMMMM yyyy hh:mm"), //Thursday, 20. March 2003 16:40
};
/**
* Tries to detect the dateformat for the given String.
* @param dateString The string with a date in an unknown format,.
* @return The fitting format for the given date string or <code>null</code> if no format could be found.
*/
public static DateFormat detectFromat(final String dateString) {
for (DateFormat dateFormat : DATE_FORMATS.class.getEnumConstants()) {
if(dateFormat.isMatching(dateString)) {
return dateFormat;
}
}
return null;
}
/**
* Formats the given date into a String with the specified format.
* @param date The date to be formatted.
* @param format The date format for the string result.
* @return The desired string or <code>null</code> if the date couldn't be formatted.
*/
public static String toString(final Date date, final DATE_FORMATS format) {
if(format==null || date==null) {
return null;
}
return format.getString(date);
}
/**
* Tries to create a date from the given date string but without the time part.
* @param dateString The date string to be parsed into a date.
* @return The desired date or <code>null</code> if the date format could not be detected.
*/
public static Date toDate(String dateString) {
final Date dateTime = toDateTime(dateString);
if(dateTime!=null) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(dateTime);
calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DATE), 0, 0, 0);
return calendar.getTime();
}
return dateTime;
}
/**
* Tries to create a date time from the given date string.
* @param dateString The date string to be parsed into a date.
* @return The desired date or <code>null</code> if the date format could not be detected.
*/
public static Date toDateTime(String dateString) {
if(dateString == null || dateString.isEmpty()) {
return null;
}
DateConversionUtils.DateFormat detectFromat = detectFromat(dateString);
if(detectFromat != null) {
return detectFromat.getDate(dateString);
} else if(CommonUtils.isInteger(dateString)) {
return new Date(CommonUtils.toNumber(dateString).longValue());
} else {
for (int i = 0; i < POTENTIAL_FORMATS.length; i++) {
try {
Date parse = POTENTIAL_FORMATS[i].parse(dateString);
return parse;
} catch (ParseException e) {
} catch (Exception e) {
e.printStackTrace();
}
}
}
return null;
}
/**
* Converts a nummer into a year value. If the number is a two digit one,
* the century will be added to it.
* @param number The year value.
* @return A four digit year value.
*/
private static int formatToYear(Number number) {
if(number!=null) {
int year = number.intValue();
if(year < 100) {
int currentTwoDigitYear = Integer.valueOf(String.valueOf(DateUtils.year(new Date())).substring(2));
if(year < currentTwoDigitYear+2) {
year += 2000;
} else {
year += 1900;
}
}
return year;
}
throw new RuntimeException("No year value specified.");
}
}