I have many threads processing Trade objects where I use a RowMapper to map database columns to Trade object.
I understand SimpleDateFormat is not thread-safe in any Java. As a result, I get some unpredictable result in startDate. For example, I see date which is endDate also in startDate.
Here is my code:
public class ExampleTradeMapper {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd-MMM-yyyy");
public void map(Trade trade, ResultSet rs, int rowNum) throws SQLException {
trade.setStartDate(getFormattedDate(rs.getDate("START_DATE")));
trade.setEndDate(getFormattedDate(rs.getDate("END_DATE")));
trade.setDescription(rs.getString("DESCRIPTION"));
}
private String getFormattedDate(Date date) {
try {
if (date != null)
return DATE_FORMAT.format(date).toUpperCase();
else
return null;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
public class SomeRowMapper extends TradeMapper implements RowMapper<Trade> {
#Override
public Trade mapRow(ResultSet rs, int rowNum) throws SQLException {
Trade trade = new Trade();
map(trade, rs, rowNum);
return trade;
}
}
My core pool size is about 20 for this application with maximum about 50. These threads can be processing about 100s of trade records from the database at some time.
What would be the best way to make this date formatting thread safe? Should I be using a direct replacement using FastDateFormat?
Is there a better alternative way of doing making this thread safe?
tl;dr
Rather than using strings, use java.time objects (LocalDate specifically) exchanged with your database via JDBC 4.2 or later.
myResultSet.getObject( // Exchange modern java.time objects with your database.
"START_DATE" ,
LocalDate.class
) // Returns a `LocalDate` object.
.format( // Generate a `String` representing textually the content of this `LocalDate`.
DateTimeFormatter.ofPattern( "dd-MMM-uuuu" , Locale.US )
)
23-Jan-2018
Being immutable objects, the java.time objects are thread-safe by design. You can cache the java.time objects for use across threads.
java.time
Making SimpleDateFormat thread safe
Don’t.
Use the modern java.time classes that years ago supplanted the troublesome old legacy date-time classes such as SimpleDateFormat, java.util.Date, java.sql.Date, and Calendar.
The java.time classes are designed to be thread-safe. They use immutable objects pattern, to return fresh objects based on the values of an original rather than “mutating” (altering) the original.
Use smart objects, not dumb strings
I see no reason for using strings in your example code: Not in your database access code, not in your business object (Trade).
JDBC
As of JDBC 4.2, we can exchange java.time objects with the database. For a database column of a type akin to the SQL-standard DATE, use the class LocalDate. The LocalDate class represents a date-only value without time-of-day and without time zone.
myPreparedStatement.setObject( … , myLocalDate ) ;
Retrieval.
LocalDate myLocalDate = myResultSet.getObject( … , LocalDate.class ) ;
Business object
Your Trade class should be holding member variables startDate & endDate as LocalDate objects, not strings.
public class Trade {
private LocalDate startDate ;
private LocalDate endDate ;
…
// Getters
public LocalDate getStartDate() {
return this.startDate ;
}
public LocalDate getEndDate() {
return this.endDate;
}
public Period getPeriod() { // Number of years-months-days elapsed.
return Period.between( this.startDate , this.endDate ) ;
}
// Setters
public void setStartDate( LocalDate startDateArg ) {
this.startDate = startDateArg ;
}
public void setEndDate( LocalDate endDateArg ) {
this.endDate = endDateArg ;
}
#Override
public toString() {
"Trade={ " + "startDate=" + this.startDate.toString() …
}
…
}
No need for strings, no need for formatting patterns.
Strings
To exchange or store date-time values as text, use the standard ISO 8601 formats rather than a custom format as seen in your Question.
The java.time classes use the ISO 8601 formats by default when parsing/generating strings. So no need to specify a formatting pattern.
LocalDate ld = LocalDate.parse( "2018-01-23" ) ; // January 23, 2018.
String s = ld.toString() ; // Outputs 2018-01-23.
For presentation in a user-interface, let java.time automatically localize. To localize, specify:
FormatStyle to determine how long or abbreviated should the string be.
Locale to determine:
The human language for translation of name of day, name of month, and such.
The cultural norms deciding issues of abbreviation, capitalization, punctuation, separators, and such.
Example:
Locale l = Locale.CANADA_FRENCH ;
DateTimeFormatter f =
DateTimeFormatter.ofLocalizedDate( FormatStyle.FULL )
.withLocale( l ) ;
String output = ld.format( f ) ;
mardi 23 janvier 2018
The DateTimeFormatter class is thread-safe, by design, as an immutable object. You could hold one instance to be used across threads.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, Java SE 10, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
You can make it ThreadLocal. Every thread in the pool will hold their own formattor.
private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT = new ThreadLocal<SimpleDateFormat>() {
#Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("dd-MMM-yyyy");
}
};
Here you can see the fastest way to use date format in thread safe way. Because you have 3 ways to do it :
With DateFormat.getDateInstance()
With synchronized
And the local thread way which from far offers the best performance
Full code sample :
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimpleDateFormatThreadExample {
private static String FORMAT = "dd-M-yyyy hh:mm:ss";
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(FORMAT);
public static void main(String[] args) {
final String dateStr = "02-1-2018 06:07:59";
ExecutorService executorService = Executors.newFixedThreadPool(10);
Runnable task = new Runnable() {
#Override
public void run() {
parseDate(dateStr);
}
};
Runnable taskInThread = new Runnable() {
#Override
public void run() {
try {
ConcurrentDateFormatAccess concurrentDateFormatAccess = new ConcurrentDateFormatAccess();
System.out.println("Successfully Parsed Date " + concurrentDateFormatAccess.convertStringToDate(dateStr));
// don't forget to use CLEAN because the classloader with keep date format !
concurrentDateFormatAccess.clean();
} catch (ParseException e) {
e.printStackTrace();
}
}
};
for (int i = 0; i < 100; i++) {
executorService.submit(task);
// remove this comment to use thread safe way !
// executorService.submit(taskInThread);
}
executorService.shutdown();
}
private static void parseDate(String dateStr) {
try {
Date date = simpleDateFormat.parse(dateStr);
System.out.println("Successfully Parsed Date " + date);
} catch (ParseException e) {
System.out.println("ParseError " + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
}
public static class ConcurrentDateFormatAccess {
private ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
#Override
public DateFormat get() {
return super.get();
}
#Override
protected DateFormat initialValue() {
return new SimpleDateFormat(FORMAT);
}
#Override
public void remove() {
super.remove();
}
#Override
public void set(DateFormat value) {
super.set(value);
}
};
public void clean() {
df.remove();
}
public Date convertStringToDate(String dateString) throws ParseException {
return df.get().parse(dateString);
}
}
}
Related
Date format: "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
Input date: "2017-09-18T03:08:20.888+0200"
Problem: I need retrieve timezone offset from the input String and print the parsed date in this timezone. In other words, I need output to be the same as the input.
SimpleDateFormat parses input date successfully and returns java.util.Date object. As we know, Date does not have timezone field. SimpleDateFormat converts parsed date to its timezone, which is, by default, system timezone. When I print this date, it is printed in System timezone.
Simple demo
private static void runDemoTask() throws ParseException {
final String dateTimeTimezoneFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
final SimpleDateFormat inputSdf = new SimpleDateFormat(dateTimeTimezoneFormat);
final String inputDate = "2017-09-18T01:08:20.888+0200";
Date parsedDate = inputSdf.parse(inputDate);
final SimpleDateFormat outputSdf = new SimpleDateFormat(dateTimeTimezoneFormat);
//outputSdf.setTimeZone("X_TIMEZONE_WHICH_I_NEED");
String output = outputSdf.format(parsedDate);
System.out.println(output);
}
Output
Mon Sep 18 00:08:20 GMT+01:00 2017
Note, output date has system timezone, which is different from input string.
Note, I will not use java.time, Joda Time and other libraries because I need to support existing code.
Possible unpleasant solution
I tried to use regular expression to retrieve sign and offset.
private static String parseTimeZone(String input) {
final int singGroup = 1;
final int offsetGroup = 2;
final String timezonePatternStr = "([+-])(\\d{4})$";
final Pattern timezonePattern = Pattern.compile(timezonePatternStr);
Matcher matcher = timezonePattern.matcher(input);
if (matcher.find()) {
String sign = matcher.group(singGroup);
String offset = matcher.group(offsetGroup);
System.out.println(sign + " " + offset);
}
return "";
}
It prints
+ 0200
Thank you, guys: #Thomas, #ole-v-v
final DateTimeFormatter inputSdf1 = DateTimeFormatter.ofPattern(dateTimeTimezoneFormat);
OffsetDateTime d = OffsetDateTime.parse(inputDate, inputSdf1);
ZoneOffset zo = d.getOffset(); //Offset from the input.
TimeZone tz = TimeZone.getTimeZone(zo.normalized());
outputSdf.setTimeZone(tz);
System.out.println(outputSdf.format(parsedDate));
tl;dr
TimeZone.getTimeZone( // Convert from modern java.time type (`ZoneOffset`/`ZoneId`) to legacy type (`TimeZone`)
OffsetDateTime.parse ( // Instantiate a `OffsetDateTime`, a moment on the timeline.
"2017-09-18T03:08:20.888+0200" ,
DateTimeFormatter.ofPattern( "uuuu-MM-dd'T'HH:mm:ss.SSSX" )
).getOffset() // Extract a `ZoneOffset`, which is a subclass of `ZoneId`.
)
Convert directly from modern ZoneOffset to legacy TimeZone
The code seen here is similar to Answers by Yan Khonski, but using the variation of TimeZone.getTimeZone that directly converts from the modern java.time classes (ZoneOffset & ZoneID) to the legacy TimeZone class.
While there is no difference in the end result, this approach uses a an explicit conversion method. This is one of many new methods added to the old date-time classes for converting to/from java.time objects.
Using such a conversion method makes your code more self-documenting. Also makes more clear your awareness that you are consciously moving between the modern & legacy classes.
String input = "2017-09-18T03:08:20.888+0200";
DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuu-MM-dd'T'HH:mm:ss.SSSX" );
OffsetDateTime odt = OffsetDateTime.parse( input , f ); // Parse input.
ZoneOffset offset = odt.getOffset( ); // Interrogate for the `ZoneOffset` object representing this moment’s offset-from-UTC (a number of hours/minutes/seconds).
TimeZone tz = TimeZone.getTimeZone( offset ); // Convert from modern java.time object (a `ZoneOffset`/`ZoneId`) to the legacy class `TimeZone`.
Dump to console.
System.out.println( "odt.toString(): " + odt );
System.out.println( "offset.toString(): " + offset );
System.out.println( "tz.toString(): " + tz );
odt.toString(): 2017-09-18T03:08:20.888+02:00
offset.toString(): +02:00
tz.toString(): sun.util.calendar.ZoneInfo[id="GMT+02:00",offset=7200000,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android, the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
SimpleDateFormat extends DateFormat and thus internally uses a Calendar. When parsing the date that calendar is being updated so you can get the timezone from it after parsing:
//use the timezone of the internally stored calendar
outputSdf.setTimeZone( inputSdf.getTimezone() );
That also shows why DateFormat is not threadsafe.
EDIT:
It seems the internal calendar's timezone isn't updated but the ZONE_OFFSET field is. Hence you could do something like this:
int zoneOffset = inputSdf.getCalendar().get( Calendar.ZONE_OFFSET );
//length check etc. left for you
String matchingZoneId = TimeZone.getAvailableIDs( zoneOffset )[0];
outputSdf.setTimeZone( TimeZone.getTimeZone( matchingZoneId ) );
Note that you can't just set the zone offset of the output format since that won't update the timezone reference which is used when formatting.
As you can see doing it this way looks a little "hacky" and thus you should think hard on whether you really need the timezone. In most cases you'd define the output timezone in a different way anyways, e.g. by getting the user's location, input, etc.
Hardcore solution!
Parse timezone and retrieve hours and minutes and sign and build a timezone!
public class ParseTimeZone {
private static final String TIME_ZONE_REGEX = "([+-])(\\d{2})[:]?(\\d{2})$";
private static final Pattern TIME_ZONE_PATTERN = Pattern.compile(TIME_ZONE_REGEX);
private static final int SING_GROUP = 1;
private static final int HOURS_GROUP = 2;
private static final int MINUTES_GROUP = 3;
private static final String PLUS = "+";
private static final String MINUS = "-";
private static final int MINUTES_IN_HOUR = 60;
private static final int SECONDS_IN_MINUTE = 60;
public static void main(String[] args) {
final String inputDate = "2017-09-18T01:08:20.888Z";
parseTimeZone(inputDate);
}
private static String parseTimeZone(String input) {
input = fixDateStringWithZeroZoneOffset(input);
Matcher matcher = TIME_ZONE_PATTERN.matcher(input);
if (!matcher.find()) {
return "";
}
String sign = matcher.group(SING_GROUP);
String hours = matcher.group(HOURS_GROUP);
String minutes = matcher.group(MINUTES_GROUP);
int offsetSeconds = calculateOffsetSeconds(sign, hours, minutes);
ZoneOffset zoneOffset = ZoneOffset.ofTotalSeconds(offsetSeconds);
System.out.println(zoneOffset);
TimeZone timeZone = TimeZone.getTimeZone(zoneOffset);
System.out.println(timeZone);
return "";
}
private static int calculateOffsetSeconds(String signStr, String hoursStr, String minutesStr) {
try {
int hours = Integer.parseInt(hoursStr);
int minutes = Integer.parseInt(minutesStr);
int sign = parseSign(signStr);
int seconds = sign * ((hours * MINUTES_IN_HOUR + minutes) * SECONDS_IN_MINUTE);
return seconds;
} catch (NumberFormatException e) {
throw new RuntimeException("It should not happen because it matches the regular expression. There should be numbers.", e);
}
}
private static int parseSign(String sign) {
if (sign.equals(PLUS)) {
return 1;
} else if (sign.equals(MINUS)) {
return -1;
} else {
throw new RuntimeException("Offset sign should be + or -.");
}
}
private static String fixDateStringWithZeroZoneOffset(String dateString) {
if (dateString.endsWith("Z")) {
return dateString.replaceAll("Z$", "+0000");
}
return dateString;
}
}
I have a date in the following format: 2010-03-01T00:00:00-08:00
I have thrown the following SimpleDateFormats at it to parse it:
private static final SimpleDateFormat[] FORMATS = {
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"), //ISO8601 long RFC822 zone
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz"), //ISO8601 long long form zone
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"), //ignore timezone
new SimpleDateFormat("yyyyMMddHHmmssZ"), //ISO8601 short
new SimpleDateFormat("yyyyMMddHHmm"),
new SimpleDateFormat("yyyyMMdd"), //birthdate from NIST IHE C32 sample
new SimpleDateFormat("yyyyMM"),
new SimpleDateFormat("yyyy") //just the year
};
I have a convenience method that uses those formats like so:
public static Date figureOutTheDamnDate(String wtf) {
if (wtf == null) {
return null;
}
Date retval = null;
for (SimpleDateFormat sdf : FORMATS) {
try {
sdf.setLenient(false)
retval = sdf.parse(wtf);
System.out.println("Date:" + wtf + " hit on pattern:" + sdf.toPattern());
break;
} catch (ParseException ex) {
retval = null;
continue;
}
}
return retval;
}
It seems to hit on the pattern yyyyMMddHHmm but returns the date as Thu Dec 03 00:01:00 PST 2009.
What is the correct pattern to parse this date?
UPDATE: I don't NEED the time zone parsing. I don't anticipate having time sensitive issues moving between zones, but how would I get the "-08:00" zone format to parse????
Unit test:
#Test
public void test_date_parser() {
System.out.println("\ntest_date_parser");
//month is zero based, are you effing kidding me
Calendar d = new GregorianCalendar(2000, 3, 6, 13, 00, 00);
assertEquals(d.getTime(), MyClass.figureOutTheDamnDate("200004061300"));
assertEquals(new GregorianCalendar(1950, 0, 1).getTime(), MyClass.figureOutTheDamnDate("1950"));
assertEquals(new GregorianCalendar(1997, 0, 1).getTime(), MyClass.figureOutTheDamnDate("199701"));
assertEquals(new GregorianCalendar(2010, 1, 25, 15, 19, 44).getTime(), MyClass.figureOutTheDamnDate("20100225151944-0800"));
//my machine happens to be in GMT-0800
assertEquals(new GregorianCalendar(2010, 1, 15, 13, 15, 00).getTime(),MyClass.figureOutTheDamnDate("2010-02-15T13:15:00-05:00"));
assertEquals(new GregorianCalendar(2010, 1, 15, 18, 15, 00).getTime(), MyClass.figureOutTheDamnDate("2010-02-15T18:15:00-05:00"));
assertEquals(new GregorianCalendar(2010, 2, 1).getTime(), MyClass.figureOutTheDamnDate("2010-03-01T00:00:00-08:00"));
assertEquals(new GregorianCalendar(2010, 2, 1, 17, 0, 0).getTime(), MyClass.figureOutTheDamnDate("2010-03-01T17:00:00-05:00"));
}
Output from unit test:
test_date_parser
Date:200004061300 hit on pattern:yyyyMMddHHmm
Date:1950 hit on pattern:yyyy
Date:199701 hit on pattern:yyyyMM
Date:20100225151944-0800 hit on pattern:yyyyMMddHHmmssZ
Date:2010-02-15T13:15:00-05:00 hit on pattern:yyyy-MM-dd'T'HH:mm:ss
Date:2010-02-15T18:15:00-05:00 hit on pattern:yyyy-MM-dd'T'HH:mm:ss
Date:2010-03-01T00:00:00-08:00 hit on pattern:yyyy-MM-dd'T'HH:mm:ss
Date:2010-03-01T17:00:00-05:00 hit on pattern:yyyy-MM-dd'T'HH:mm:ss
JodaTime's DateTimeFormat to rescue:
String dateString = "2010-03-01T00:00:00-08:00";
String pattern = "yyyy-MM-dd'T'HH:mm:ssZ";
DateTimeFormatter dtf = DateTimeFormat.forPattern(pattern);
DateTime dateTime = dtf.parseDateTime(dateString);
System.out.println(dateTime); // 2010-03-01T04:00:00.000-04:00
(time and timezone difference in toString() is just because I'm at GMT-4 and didn't set locale explicitly)
If you want to end up with java.util.Date just use DateTime#toDate():
Date date = dateTime.toDate();
Wait for JDK7 (JSR-310) JSR-310, the referrence implementation is called ThreeTen (hopefully it will make it into Java 8) if you want a better formatter in the standard Java SE API. The current SimpleDateFormat indeed doesn't eat the colon in the timezone notation.
Update: as per the update, you apparently don't need the timezone. This should work with SimpleDateFormat. Just omit it (the Z) in the pattern.
String dateString = "2010-03-01T00:00:00-08:00";
String pattern = "yyyy-MM-dd'T'HH:mm:ss";
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
Date date = sdf.parse(dateString);
System.out.println(date); // Mon Mar 01 00:00:00 BOT 2010
(which is correct as per my timezone)
if you used the java 7, you could have used the following Date Time Pattern. Seems like this pattern is not supported in the Earlier version of java.
String dateTimeString = "2010-03-01T00:00:00-08:00";
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
Date date = df.parse(dateTimeString);
For More information refer to the SimpleDateFormat documentation.
Here's a snippet I used - with plain SimpleDateFormat. Hope somebody else may benefit from it:
public static void main(String[] args) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") {
public StringBuffer format(Date date, StringBuffer toAppendTo, java.text.FieldPosition pos) {
StringBuffer toFix = super.format(date, toAppendTo, pos);
return toFix.insert(toFix.length()-2, ':');
};
};
// Usage:
System.out.println(dateFormat.format(new Date()));
}
Output:
- Usual Output.........: 2013-06-14T10:54:07-0200
- This snippet's Output: 2013-06-14T10:54:07-02:00
Or... better, use a simpler, different, pattern:
SimpleDateFormat dateFormat2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
// Usage:
System.out.println(dateFormat2.format(new Date()));
Output:
- This pattern's output: 2013-06-14T10:54:07-02:00
See the docs for that.
Try this, its work for me:
Date date = javax.xml.bind.DatatypeConverter.parseDateTime("2013-06-01T12:45:01+04:00").getTime();
In Java 8:
OffsetDateTime dt = OffsetDateTime.parse("2010-03-01T00:00:00-08:00");
If you can use JDK 1.7 or higher, try this:
public class DateUtil {
private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
public static String format(Date date) {
return dateFormat.format(date);
}
public static Date parse(String dateString) throws AquariusException {
try {
return dateFormat.parse(dateString);
} catch (ParseException e) {
throw new AquariusException(e);
}
}
}
document: https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html
which supports a new Time Zone format "XXX" (e.g. -3:00)
While JDK 1.6 only support other formats for Time Zone, which are "z" (e.g. NZST), "zzzz" (e.g. New Zealand Standard Time), "Z" (e.g. +1200), etc.
tl;dr
OffsetDateTime.parse( "2010-03-01T00:00:00-08:00" )
Details
The answer by BalusC is correct, but now outdated as of Java 8.
java.time
The java.time framework is the successor to both Joda-Time library and the old troublesome date-time classes bundled with the earliest versions of Java (java.util.Date/.Calendar & java.text.SimpleDateFormat).
ISO 8601
Your input data string happens to comply with the ISO 8601 standard.
The java.time classes use ISO 8601 formats by default when parsing/generating textual representations of date-time values. So no need to define a formatting pattern.
OffsetDateTime
The OffsetDateTime class represents a moment on the time line adjusted to some particular offset-from-UTC. In your input, the offset is 8 hours behind UTC, commonly used on much of the west coast of North America.
OffsetDateTime odt = OffsetDateTime.parse( "2010-03-01T00:00:00-08:00" );
You seem to want the date-only, in which case use the LocalDate class. But keep in mind you are discarding data, (a) time-of-day, and (b) the time zone. Really, a date has no meaning without the context of a time zone. For any given moment the date varies around the world. For example, just after midnight in Paris is still “yesterday” in Montréal. So while I suggest sticking with date-time values, you can easily convert to a LocalDate if you insist.
LocalDate localDate = odt.toLocalDate();
Time Zone
If you know the intended time zone, apply it. A time zone is an offset plus the rules to use for handling anomalies such as Daylight Saving Time (DST). Applying a ZoneId gets us a ZonedDateTime object.
ZoneId zoneId = ZoneId.of( "America/Los_Angeles" );
ZonedDateTime zdt = odt.atZoneSameInstant( zoneId );
Generating strings
To generate a string in ISO 8601 format, call toString.
String output = odt.toString();
If you need strings in other formats, search Stack Overflow for use of the java.util.format package.
Converting to java.util.Date
Best to avoid java.util.Date, but if you must, you can convert. Call the new methods added to the old classes such as java.util.Date.from where you pass an Instant. An Instant is a moment on the timeline in UTC. We can extract an Instant from our OffsetDateTime.
java.util.Date utilDate = java.util.Date( odt.toInstant() );
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, Java SE 10, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
Thanks acdcjunior for your solution. Here's a little optimized version for formatting and parsing :
public static final SimpleDateFormat XML_SDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.FRANCE)
{
private static final long serialVersionUID = -8275126788734707527L;
public StringBuffer format(Date date, StringBuffer toAppendTo, java.text.FieldPosition pos)
{
final StringBuffer buf = super.format(date, toAppendTo, pos);
buf.insert(buf.length() - 2, ':');
return buf;
};
public Date parse(String source) throws java.text.ParseException {
final int split = source.length() - 2;
return super.parse(source.substring(0, split - 1) + source.substring(split)); // replace ":" du TimeZone
};
};
You can use X in Java 7.
https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html
static final SimpleDateFormat DATE_TIME_FORMAT =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
static final SimpleDateFormat JSON_DATE_TIME_FORMAT =
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
private String stringDate = "2016-12-01 22:05:30";
private String requiredDate = "2016-12-01T22:05:30+03:00";
#Test
public void parseDateToBinBankFormat() throws ParseException {
Date date = DATE_TIME_FORMAT.parse(stringDate);
String jsonDate = JSON_DATE_TIME_FORMAT.format(date);
System.out.println(jsonDate);
Assert.assertEquals(jsonDate, requiredDate);
}
Try setLenient(false).
Addendum: It looks like you're recognizing variously formatted Date strings. If you have to do entry, you might like looking at this example that extends InputVerifier.
Since an example of Apache FastDateFormat(click for the documentations of versions:2.6and3.5) is missing here, I am adding one for those who may need it. The key here is the pattern ZZ(2 capital Zs).
import java.text.ParseException
import java.util.Date;
import org.apache.commons.lang3.time.FastDateFormat;
public class DateFormatTest throws ParseException {
public static void main(String[] args) {
String stringDateFormat = "yyyy-MM-dd'T'HH:mm:ssZZ";
FastDateFormat fastDateFormat = FastDateFormat.getInstance(stringDateFormat);
System.out.println("Date formatted into String:");
System.out.println(fastDateFormat.format(new Date()));
String stringFormattedDate = "2016-11-22T14:30:14+05:30";
System.out.println("String parsed into Date:");
System.out.println(fastDateFormat.parse(stringFormattedDate));
}
}
Here is the output of the code:
Date formatted into String:
2016-11-22T14:52:17+05:30
String parsed into Date:
Tue Nov 22 14:30:14 IST 2016
Note: The above code is of Apache Commons' lang3. The class org.apache.commons.lang.time.FastDateFormat does not support parsing, and it supports only formatting. For example, the output of the following code:
import java.text.ParseException;
import java.util.Date;
import org.apache.commons.lang.time.FastDateFormat;
public class DateFormatTest {
public static void main(String[] args) throws ParseException {
String stringDateFormat = "yyyy-MM-dd'T'HH:mm:ssZZ";
FastDateFormat fastDateFormat = FastDateFormat.getInstance(stringDateFormat);
System.out.println("Date formatted into String:");
System.out.println(fastDateFormat.format(new Date()));
String stringFormattedDate = "2016-11-22T14:30:14+05:30";
System.out.println("String parsed into Date:");
System.out.println(fastDateFormat.parseObject(stringFormattedDate));
}
}
will be this:
Date formatted into String:
2016-11-22T14:55:56+05:30
String parsed into Date:
Exception in thread "main" java.text.ParseException: Format.parseObject(String) failed
at java.text.Format.parseObject(Format.java:228)
at DateFormatTest.main(DateFormatTest.java:12)
If date string is like 2018-07-20T12:18:29.802Z
Use this
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
i'm trying to allow a customer to set the date and time they would want to make a reservation. The code which i have already completed creates lots of text in the text file when saved and crashes when you try to load it again.
This is my code for adding a reservation:
public static List<Reservation> addReservations(List<Reservation> reservations, List<Customer> customers) {
int newReservationId = Reservation.getNumberOfReservations() + 1;
String startString = readString("Enter Reservation date");
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
Date date = null;
try {
date = sdf.parse(startString);
} catch (ParseException ex) {
Logger.getLogger(ProjectIncrement5.class.getName()).log(Level.SEVERE, null, ex);
}
Calendar dateTime = Calendar.getInstance();
dateTime.setTime(date);
listCustomers(customers, reservations);
int reservationCustomerId = readInt("Enter Customer Id", Customer.getNumberOfCustomers(), 1);
Customer reservationCustomer = customers.get(reservationCustomerId - 1);
Reservation res = new Reservation(newReservationId, dateTime, reservationCustomer);
reservations.add(res);
return reservations;
}
Reservation Class:
public class Reservation {
private int reservationId;
private Calendar dateTime;
private Customer customer;
private static int numberOfReservations = 0;
public Reservation() {
this.reservationId = 0;
this.dateTime = null;
this.customer = null;
numberOfReservations++;
}
public Reservation(int reservationId, Calendar dateTime, Customer customer) {
this.reservationId = reservationId;
this.dateTime = dateTime;
this.customer = customer;
numberOfReservations++;
}
public static int getNumberOfReservations() {
return numberOfReservations;
}
public int getReservationId() {
return reservationId;
}
public void setreservationId(int reservationId) {
this.reservationId = reservationId;
}
public Calendar getDateTime() {
return dateTime;
}
public Customer getCustomer()
{
return customer;
}
public void setCustomer(Customer customer)
{
this.customer = customer;
}
public String setDateTime() {
SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
String dateString = formatter.format(this.dateTime.getTime());
return dateString;
}
#Override
public String toString() {
return "Reservation id: " + getReservationId() + ", "
+ "Date/Time " + setDateTime() +
"customer: " + getCustomer();
}
}
This is what saves into the text file causing the error:
1:java.util.GregorianCalendar[time=851385600000,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Europe/London",offset=0,dstSavings=3600000,useDaylight=true,transitions=242,lastRule=java.util.SimpleTimeZone[id=Europe/London,offset=0,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=1996,MONTH=11,WEEK_OF_YEAR=52,WEEK_OF_MONTH=4,DAY_OF_MONTH=24,DAY_OF_YEAR=359,DAY_OF_WEEK=3,DAY_OF_WEEK_IN_MONTH=4,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=0,DST_OFFSET=0]:4:<
I think the error occurs when getting the instance of the calendar but I'm am unsure of how to do it an easier way and fix this problem. Can anyone help?
The problem is you store the Calendar.toString() and not the real date into the text file.
Look at what you have posted:
firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=1996,MONTH=11,WEEK_OF_YEAR=52,WEEK_OF_MONTH=4,DAY_OF_MONTH=24,DAY_OF_YEAR=359,DAY_OF_WEEK=3,DAY_OF_WEEK_IN_MONTH=4,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=0,DST_OFFSET=0]
That seems a correct date. I will say more, this seems a really complete date, with a lot of information doesn't??? ;)
I don't know how this date is supposed to be stored, but, for example, if you want to store date in format dd/MM/yyyy use this lines of code to check if it's a valid date (you already do this in your code).
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
date = sdf.parse(startString);
After checking this, if the startString is valid, you don't need to create any Calendar instance just write startString to the text file.
UPDATE:
So in my Reservation class, instead of having: private Calendar dateTime I will take that out and just have: private String startString?
NOPE. The idea is:
In a Java class, store in a specific Object (Calendar is ok, but Date will be also)
In text file, send a human readable date via String.
Sorry but I can't be more specific, I can't execute your code because a lot of parts are missing (Customer, readInt, listCustomers etc...).
I accept Jordi Castilla's Answer, and also want to tell other option to do the same.
here you are saving the Date & Time to file using Calendar.toString() method, instead you could use following code
DateFormat df = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
String stringToFile = df.format(date);//you can send this string to the file which you want to store the Date & Time
Avoid legacy date-time classes
The modern approach uses java.time classes. Avoid the troublesome, confusing, and poorly-designed old legacy date-time classes such as Calendar.
LocalDate
Collect the date portion. Catch any DateTimeParseException in case of bad input.
DateTimeFormatter f = DateTimeFormatter.ofPattern( "dd/MM/uuuu" ) ;
try {
LocalDate ld = LocalDate.parse( input , f ) ;
} catch ( DateTimeParseException e ) {
…
}
Seems like you are not explicitly tracking the time-of-day. So the LocalDate class suffices.
ISO 8601
When you serialize your data to a text file, you should be using standard ISO 8601 formats. These formats are designed to be practical & unambiguous, easy to parse by machine, easy to read by humans across cultures, and not assume proficiency in English.
The java.time classes use the standard formats by default when parsing and generating strings. Merely call toString to generate.
String output = ld.toString() ; // Ex: 2017-01-23
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
The ThreeTenABP project adapts ThreeTen-Backport (mentioned above) for Android specifically.
See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
Is there anyway to validate if a given date(yyyy-MM-dd) is a valid date?
It should handle leap year too. eg(2015-02-29) should be invalid.
I'm retrieving the date as a string and putting it into a joda DateTime object.
The previous responses should be fine, but given that the OP specifically asked for a Joda-Time version, this alternative will also work:
#Test
public void test() {
String testDateOk = "2015-02-25"; // Normal date, no leap year
String testDateOk2 = "2016-02-29"; // Edge-case for leap year
String testDateWrong = "2017-02-29"; // Wrong date in a non-leap year
String testDateInvalid = "2016-14-29"; // plain wrong date
assertTrue(isValidDate(testDateOk));
assertTrue(isValidDate(testDateOk2));
assertFalse(isValidDate(testDateWrong));
assertFalse(isValidDate(testDateInvalid));
}
boolean isValidDate(String dateToValidate){
String pattern = "yyyy-MM-dd";
try {
DateTimeFormatter fmt = DateTimeFormat.forPattern(pattern);
fmt.parseDateTime(dateToValidate);
} catch (Exception e) {
return false;
}
return true;
}
This should work for you, I think (if you want to keep it simple).
You have to do setLenient(false) on a SimpleDateFormat.
public static boolean validateDate(String dateString){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setLenient(false);
try {
sdf.parse(dateString);
return true;
} catch (ParseException ex) {
return false;
}
}
Use SimpleDateFormat
public boolean valiDate(String dateString){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setLenient(false);
try {
Date date = sdf.parse(dateString);
return true;
} catch (ParseException ex) {
return false;
}
}
tl;dr
try { … java.time.LocalDate.parse( input ) … }
catch ( java.time.format.DateTimeParseException e ) { … }
java.time
The Joda-Time project is now in maintenance mode, with the team advising migration to the java.time classes.
The LocalDate class represents a date-only value without time-of-day and without time zone.
LocalDate ld = LocalDate.parse( "2015-02-29" ) ;
To detect invalid inputs, trap for a DateTimeParseException.
String input = "2015-02-29";
try
{
LocalDate ld = LocalDate.parse( input );
System.out.println( "ld.toString(): " + ld ) ;
} catch ( DateTimeParseException e )
{
// … handle exception
System.out.println( e.getLocalizedMessage( ) );
}
Text '2015-02-29' could not be parsed: Invalid date 'February 29' as '2015' is not a leap year
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
The ThreeTenABP project adapts ThreeTen-Backport (mentioned above) for Android specifically.
See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
I am taking in two dates as command line arguments and want to check if the first one is after the second date. the format of the date it "dd/MM/yyy".
Example: java dateCheck 01/01/2014 15/03/2014
also i will need to check if a third date hardcoded into the program is before the second date.
try {
System.out.println("Enter first date : (dd/MM/yyyy)");
BufferedReader bufferRead = new BufferedReader(new InputStreamReader(System.in));
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
Date date1 = sdf.parse(bufferRead.readLine());
System.out.println("Enter second date : (dd/MM/yyyy)");
Date date2 = sdf.parse(bufferRead.readLine());
System.out.println(date1 + "\n" + date2);
if (date1.after(date2)) {
System.out.println("Date1 is after Date2");
} else {
System.out.println("Date2 is after Date1");
}
} catch (IOException e) {
e.printStackTrace();
}
To compare two dates :
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyy");
Date firstDate = sdf.parse("01/01/2014");
Date secondDate = sdf.parse("15/03/2014");
if(firstDate.before(secondDate)){
System.out.println("firstDate < secondDate");
}
else if(firstDate.after(secondDate)){
System.out.println("firstDate > secondDate");
}
else if(firstDate.equals(secondDate)){
System.out.println("firstDate = secondDate");
}
tl;dr
LocalDate ld1 = LocalDate.parse( "01/01/2014" , DateTimeFormatter.ofPattern( "dd/MM/uuuu" ) ) ;
LocalDate ld2 = LocalDate.parse( "15/03/2014" , DateTimeFormatter.ofPattern( "dd/MM/uuuu" ) ) ;
LocalDate ld3 = LocalDate.of( 2014 , Month.JULY , 1 ) ;
Boolean isFirstDateBeforeSecondDate = ld1.isBefore( ld2 ) ;
Boolean isThirdDateBeforeSecondDate = ld3.isBefore( ld2 ) ;
Boolean result = ( isFirstDateBeforeSecondDate && isThirdDateBeforeSecondDate ) ;
return result ;
Using java.time
The modern approach uses the java.time classes rather than the troublesome old legacy date-time classes (Date, Calendar, etc.).
The LocalDate class represents a date-only value without time-of-day and without time zone.
Define a formatting pattern to match your input strings using the DateTimeFormatter class.
String input = "15/03/2014" ;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "dd/MM/uuuu" );
LocalDate ld = LocalDate.parse( input , f );
ld.toString(): 2014-03-15
To specify a fixed date, pass year, month, and dayOfMonth. For the month, you may specify a number, sanely numbered 1-12 for January-December (unlike the crazy 0-11 in the legacy classes!). Or you may choose to use the Month enum objects.
LocalDate firstOf2014 = LocalDate.of( 2014 , Month.JANUARY , 1 );
Compare using isBefore, isEqual, or isAfter methods.
Boolean isInputDateBeforeFixedDate = ld.isBefore( firstOf2014 ) ;
isInputDateBeforeFixedDate.toString(): false
ISO 8601
If possible, replace your particular date string format with the standard ISO 8601 format. That standard defines many useful practical unambiguous string formats for date-time values.
The java.time classes use the standard formats by default when parsing/generating strings. You can see examples in the code above. For a date-only value, the standard format is YYYY-MM-DD.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
The ThreeTenABP project adapts ThreeTen-Backport (mentioned above) for Android specifically.
See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
Use SimpleDateFormat to convert a string to Date.
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
Date date1 = sdf.parse("01/01/2017");
Date has before and after methods and can be compared to each other as follows:
if(todayDate.after(historyDate) && todayDate.before(futureDate)) {
// In between
}
For an inclusive comparison:
if(!historyDate.after(todayDate) && !futureDate.before(todayDate)) {
/* historyDate <= todayDate <= futureDate */
}
To read a date and check before:
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyy");
try {
Date date1 = sdf.parse(string1);
Date date2 = sdf.parse(string2);
if(date1.before(date2)) {
// do something
}
} catch(ParseException e) {
// the format of the read dates is not the expected one
}