Problems when moving from SimpleDateFormat to DateTimeFormatter - java

I have been successfully using SimpleDateFormat for the last couple of years. I built a bunch of time utility classes using it.
As I ran into problems with SimpleDateFormat (SDF) not being thread safe, I spent the last couple of days refactoring these utility classes to internally use DateTimeFormatter (DTF) now. Since both classes' time patterns are almost identical, this transition seemed a good idea at the time.
I now have problems obtaining EpochMillis (milliseconds since 1970-01-01T00:00:00Z): While SDF would e.g. interpret 10:30 parsed using HH:mm as 1970-01-01T10:30:00Z, DTF does not do the same. DTF can use 10:30 to parse a LocalTime, but not a ZonedDateTime which is needed to obtain EpochMillis.
I understand that the objects of java.time follow a different philosophy; Date, Time, and Zoned objects are kept separately. However, in order for my utility class to interpret all strings as it did before, I need to be able to define the default parsing for all missing objects dynamically. I tried to use
DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
builder.parseDefaulting(ChronoField.YEAR, 1970);
builder.parseDefaulting(ChronoField.MONTH_OF_YEAR, 1);
builder.parseDefaulting(ChronoField.DAY_OF_MONTH, 1);
builder.parseDefaulting(ChronoField.HOUR_OF_DAY, 0);
builder.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0);
builder.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0);
builder.append(DateTimeFormatter.ofPattern(pattern));
but this does not work for all patterns. It seems to only allow defaults for parameters that are not defined in pattern. Is there a way to test which ChronoFields are defined in pattern to then selectively add defaults?
Alternatively, I tried
TemporalAccessor temporal = formatter.parseBest(time,
ZonedDateTime::from,
LocalDateTime::from,
LocalDate::from,
LocalTime::from,
YearMonth::from,
Year::from,
Month::from);
if ( temporal instanceof ZonedDateTime )
return (ZonedDateTime)temporal;
if ( temporal instanceof LocalDateTime )
return ((LocalDateTime)temporal).atZone(formatter.getZone());
if ( temporal instanceof LocalDate )
return ((LocalDate)temporal).atStartOfDay().atZone(formatter.getZone());
if ( temporal instanceof LocalTime )
return ((LocalTime)temporal).atDate(LocalDate.of(1970, 1, 1)).atZone(formatter.getZone());
if ( temporal instanceof YearMonth )
return ((YearMonth)temporal).atDay(1).atStartOfDay().atZone(formatter.getZone());
if ( temporal instanceof Year )
return ((Year)temporal).atMonth(1).atDay(1).atStartOfDay().atZone(formatter.getZone());
if ( temporal instanceof Month )
return Year.of(1970).atMonth((Month)temporal).atDay(1).atStartOfDay().atZone(formatter.getZone());
which does not cover all cases either.
What is the best strategy to enable dynamic date / time / date-time / zone-date-time parsing?

Java-8-solution:
Change the order of your parsing instructions inside the builder such that the defaulting instructions all happen AFTER the pattern instruction.
For example using this static code (well, your approach will use an instance-based combination of different patterns, not performant at all):
private static final DateTimeFormatter FLEXIBLE_FORMATTER;
static {
DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
builder.appendPattern("MM/dd");
builder.parseDefaulting(ChronoField.YEAR_OF_ERA, 1970);
builder.parseDefaulting(ChronoField.MONTH_OF_YEAR, 1);
builder.parseDefaulting(ChronoField.DAY_OF_MONTH, 1);
builder.parseDefaulting(ChronoField.HOUR_OF_DAY, 0);
builder.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0);
builder.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0);
FLEXIBLE_FORMATTER = builder.toFormatter();
}
Reason:
The method parseDefaulting(...) works in a funny way, namely like an embedded parser. That means, this method will inject a default value for defined field if that field has not been parsed yet. And the later pattern instruction tries to parse the same field (here: MONTH_OF_YEAR for pattern "MM/dd" and input "07/13") but with a possibly different value. If so then the composite parser will abort because it has found ambivalent values for same field and is unable to resolve the conflict (parsed value 7, but default value 1).
The official API contains following notice:
During parsing, the current state of the parse is inspected. If the
specified field has no associated value, because it has not been
parsed successfully at that point, then the specified value is
injected into the parse result. Injection is immediate, thus the
field-value pair will be visible to any subsequent elements in the
formatter. As such, this method is normally called at the end of the
builder.
We should read it as:
Dont't call parseDefaulting(...) before any parsing instruction for the same field.
Side note 1:
Your alternative approach based on parseBest(...) is even worse because
it does not cover all combinations with missing minute or only missing year (MonthDay?) etc. The default value solution is more flexible.
it is performancewise not worth to be discussed.
Side note 2:
I would rather have made the whole implementation order-insensitive because this detail is like a trap for many users. And it is possible to avoid this trap by choosing a map-based implementation for default values as done in my own time library Time4J where the order of default-value-instructions does not matter at all because injecting default values only happens after all fields have been parsed. Time4J also offers a dedicated answer to "What is the best strategy to enable dynamic date / time / date-time / zone-date-time parsing?" by offering a MultiFormatParser.
UPDATE:
In Java-8: Use ChronoField.YEAR_OF_ERA instead of ChronoField.YEAR because the pattern contains the letter "y" (=year-of-era, not the same as proleptic gregorian year). Otherwise the parse engine will inject the proleptic default year in addition to parsed year-of-era and will find a conflict. A real pitfall. Just yesterday I had fixed a similar pitfall in my time library for the month field which exists in two slightly different variations.

I have used new java.time package and it takes time getting used to it. But after a learning curve I have to say it is definitely very comprehensive and robust solution probably superseding Joda time library and other previous solutions. I wrote my own utilities for working with parsing Strings to Date. I wrote a summarizing article that explains how I implemented a feature that parsed String of unknown format to Date. It might be helpful. Here is the link to an article: Java 8 java.time package: parsing any string to date

Related

Generic date and time parsing in java 8

I was recently trying to make a generic date and time parsing method with the java 8 time API, mainly for interfacing with older code using Date.
I wanted to do something like that:
public static Date parse(String dateStr, String pattern) {
return Date.from(Instant.parse(dateStr, DateTimeFormatter.ofPattern(pattern)));
}
The problem is that with the time API, the class to use depends on the pattern DateTimeFormatter.parse will never fail but will return a TemporalAccessor which is horrible to work with and convert to a usable class.
And LocalDateTime.parse will fail if the pattern has no time information like "dd/MM/yyyy". Other classes like Instant, ZonedDateTime, etc. will all fail to parse if the pattern doesn't match the expected class.
Ideally, I'd like a way to parse leniently and return an Instant, with default values for missing fields, but I can't find a way to do that.
Any idea?
You can use DateTimeFormatterBuilder::parseDefaulting to set default values.
var now = ZonedDateTime.now();
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern(pattern)
.parseDefaulting(ChronoField.OFFSET_SECONDS, now.getOffset().getTotalSeconds())
.parseDefaulting(ChronoField.YEAR, now.getYear())
.parseDefaulting(ChronoField.MONTH_OF_YEAR, now.getMonthValue())
.parseDefaulting(ChronoField.DAY_OF_MONTH, now.getDayOfMonth())
.parseDefaulting(ChronoField.HOUR_OF_DAY, now.getHour())
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, now.getMinute())
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, now.getSecond())
.toFormatter(Locale.ROOT);
Instant dt = Instant.from(formatter.parse(str));
Note that it's important to first append the pattern using appendPattern, and then set all your defaults using parseDefaulting.
Also note that I used the current time stamp to fill the defaults. So, for example, if you left out the year, it takes the current year (2022 at the time of writing). Of course, the defaults depend on your exact use case.
Examples:
At the time of writing, it's 2022-06-09T17:18:36+02:00.
System.out.println(parse("9-6", "d-M"));
System.out.println(parse("2023", "uuuu"));
System.out.println(parse("10:13", "H:m"));
System.out.println(parse("25 Dec, 16:22", "d MMM, H:mm"));
resolves to
2022-06-09T15:18:36Z
2023-06-09T15:18:36Z
2022-06-09T08:13:36Z
2022-12-25T14:22:36Z

Best way to handle Java Calendar with working days?

I need to implement a labor calendar able to count working days and, of course, natural days. The calendar must be able to handle national holidays and these days must be submitted by the user.
So, if I need to calculate the difference between two days the counting must ignore Saturdays, Sundays, and Holidays.
The Java class Calendar, doesn't handle holidays or working days, so I need to make it by myself. I have think two possible ways:
First way:
I could implement a new Day class which would have a boolean isHoliday to check if that is a working day or not, then create a new class with all the methods I'd need to handle/count the days.
Pros:
Easy to handle
I can override/create methods like toString, toDate, etc...
Cons:
Heavy (Maybe?)
My doubt about this approach is how to store it. It'd mean to make 365 objects and store them in a Listor Linked List and that's a lot of data to handle.
Second way:
My second idea is to make it more simple. Create an array of Strings or Dates where I'd store the holidays.
Example new ArrayList<String> freeDays = ["01/01/2019", "05/01/2019", "06/01/2019"...] and with work with it using a new CalendarUtils class or something like that.
Pros:
More readable
Light
Cons:
Hard to work with
For me the first option looks better, however, I don't want to waste memory or use bad practices.
Which option looks better? Are there any third option?
Avoid legacy date-time classes
Never use Date or Calendar classes. Those terribly troublesome old classes are now legacy, supplanted by the java.time classes, specifically Instant and ZonedDateTime. You may find LocalDate helpful too.
Smart objects, not dumb strings
Never use strings to represent date-time within your Java code. Use objects, the java.time classes.
When exchanging date-time values as text, always use the standard ISO 8601 formats. The java.time classes use these formats by default when parsing/generating strings. For a date that would be YYYY-MM-DD such as 2018-01-23.
TemporalAdjuster interface
To skip weekends, use the TemporalAdjuster implementation found in the ThreeTen-Extra project.
nextWorkingDay
previousWorkingDay
Example:
LocalDate // Represent a date-only value, without a time-of-day and without a time zone.
.now( // Capture the current date.
ZoneId.of( "Africa/Tunis" ) // Time zone required. For any given moment the date varies around the globe by zone.
)
.with( // Invoke a `TemporalAdjuster` implementation.
org.threeten.extra.Temporals.nextWorkingDay()
) // Returns a `LocalDate`. Using immutable objects pattern, producing a fresh object based on the values of another while leaving the original unaltered.
To skip holidays, you must write your own code. No two people, companies, or countries share the same definition of holidays.
You’ll need to define your own list of holidays. I suggest writing that as an implementation of TemporalAdjuster for working neatly with the java.time classes. Perhaps nextBusinessDay and previousBusinessDay. That ThreeTen-Extra project mentioned above is open-source, so look to there for code to guide you. And I vaguely recall posting one or more implementations of TemporalAdjuster myself here on Stack Overflow.
You might store those holiday dates in a database for persistence. And represent them at runtime in chronological order as a List< LocalDate >, sorted with Collections.sort and searching with Collections.binarySearch. But beware of thread-safety. You’ll likely need to update that list during runtime. Writing while reading must be protected. Search for more info. And read the excellent book, Java Concurrency in Practice by Brian Goetz et al.
You can combine your holiday-skipping code with weekend-skipping code. Use a search engine to find my Answers on weekend-skipping using EnumSet and DayOfWeek enum. (The search feature built into Stack Overflow unfortunately skews towards Questions while ignoring Answers.)
Search Stack Overflow. All of this has been asked and answered before.

How can I pass an empty String value to a Date field in Java

I have a scenario in a current java program where if the user enters a certain date (01/01/1900) in an excel Date field I'm supposed to flag that and clear the value out in the database. In this case since the field is a Date type, I need to pass it to an xml as "" i.e and empty String since I can't just pass null. I'm getting an error because it is expecting a format of "yyyy-MM-dd" to pass to the xml and not "". Of course when setting the Date field to a certain value, I can't just pass an empty String in the setVesselDate() method because it will complain saying it must be in so so format. I have a method that validates the date (handleValidateDate) that returns the sql Date value.
Any recommendation to actually set the date field as "" would be helpful. Below is the code I have:
if (vesselAssignmentObj.getVesselDate().length() > 0)
{
String clearOutDateFlag = "1900-01-01";
String clearField = "";
//below returns a dateformat of yyyy-MM-dd at i
vesselDate = handleValidateDate(vesselAssignmentObj.getVesselDate(), VESSEL_ASSIGNMENTS_VESSEL, i);
if (vesselDate.toString().equals(clearOutDateFlag))
{
vesselAssignmentObj.setVesselDate(String.valueOf(vesselDate.toString().equals(clearField)));
}
else
{
vesselAssignmentObj.setVesselDate(String.valueOf(vesselDate).toString());
}
}
No easy answer.
… I'm supposed to flag that and clear the value out in the database…
Whoever instructed you should have defined what "clear the value out" means.
NULL
Usually in a database "clear the value out" might mean using a NULL value. But nulls bring a bag of hurt, raising ambiguity as to the meaning, affecting sorting and queries in various ways, and carrying ramifications in your app(s) and libraries.
While I generally avoid NULL like the plague (taking Dr. Chris Date’s advice), this may in fact be the best fit for your needs.
Arbitrary date as flag
You could choose an arbitrary date to use as flag that means 'empty' or 'not specified' or 'unknown'. But what date? One commenter suggested 0000-00-00 but this value may exceed the limits of some database’s date data-types. Furthermore, dates around year zero can be problematic because of how different calendaring systems handle the meaning of that period. Furthermore, any date before the adoption of the Gregorian calendar in the West is problematic as we lost some days in transitioning from Julian calendar to Gregorian.
1901-01-01
In a comment you said that a business rules requires dates to be over the year 1900. In that case I would choose 1901-01-01 as my flag date. The trick is making this clear to the entire team and to posterity. Be sure to use well-named constant in your programming, and document well in both your apps and in the database.
1970-01-01
Another common choice of arbitrary date flag is the first of 1970, 1970-01-01. That date is a commonly used epoch reference date used in Unix time and in java.time. This makes that particular date recognizable to many folks in the information trade. But you still need to document thoroughly, as this epoch date is far from the only one: At least a couple dozen other epoch dates have been in widespread usage.
Millenium
Yet another choice could be the date of the new millennium. Unfortunately, that phrase means different dates to different people, in year 2000 or 2001.
The Postgres database uses 2000-01-01 as its epoch. The Apple Cocoa framework, possibly the most widely distributed software in the world, uses 2001-01-01.
Use objects, not strings
You should not be passing strings to/from your database. Use objects instead. The job of your JDBC driver is to mediate between your Java data types and your database types.
Drivers compliant with JDBC 4.2 and later should be able to use java.time types via PreparedStatement::setObject and ResultSet::getObject. If not, fall back to using the java.sql types briefly and immediately convert to/from the java.time types. To convert, look to new methods added to the old classes.
All this JDBC date-time stuff has been covered many times already, so search Stack Overflow for more information. Search for class names Instant, ZonedDateTime, and ZoneId.

Dealing with Date Object and TimeZone in Java 7

I am developing a library to store complex data in an Object. One of the field in this object is a date. When I set the date using setter method, it is assumed that the date object is in GMT timezone. Internally, the Date is stored as a long with the number of milliseconds from epoch. In my get() method, I am doing the following :
return new Date(storedDateinMilliseconds);
The problem is that if anyone is calling toString() on the returned object, it uses the default timezone to return the date. Therefore, the returned date does not always match with the date provided in GMT. Is there a way to fix this? so that the user of this implementation will always get the GMT date when they invoke toString()?
I tried the following :
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
But this would modify the default timezone of the application using it.
As Sotirios Delimanolis’ comment said, you are giving a java.util.Date object to the calling programmer. What she does with it is up to her. And it is up to her to understand all the goofy problems that come with that object including its toString method applying the JVM’s current default time zone in generating a string representation of its date-time value.
If you want to return a date-time value with an assigned time zone, then return a different object.
Alternative Date-Time Objects
You have at least three alternatives to returning a java.util.Date object.
java.time
In Java 8 and later, the obvious choice is to use the new java.time framework (Tutorial). Give the calling programmer a ZonedDateTime object which is basically a Instant object plus a ZoneId object.
Tip: When specifying a time zone, use a proper time zone name. Never use the 3-4 letter codes like EST or IST.
Joda-Time
Joda-Time was the inspiration for java.time. This 3rd-party library is excellent, and well-worn from popular use. It supports multiple versions of Java and Android too.
The DateTime class is a moment on the timeline plus a time zone, similar to java.time’s ZonedDateTime.
ISO 8601
A third alternative is to give the calling programmer a string representation of a date-time value. The obvious choice of formats is to use those defined by the ISO 8601 standard. These formats are sensible, well thought-out, and unambiguous.
2015-09-16T18:06:14Z
…or…
2015-09-16T11:06:14-07:00
Both java.time and Joda-Time use these formats by default in parsing and generating strings. The formats are wisely extended by java.time to append the proper name of the time zone in square brackets.
2015-09-16T11:06:14-07:00[America/Los_Angeles]
Never Adjust Default Time Zone
You stated that setting the default time zone affected your entire app. Wrong. It affects all code of all apps in all threads running in that JVM. Worse, it does so immediately during runtime while that other code is running.
Set the default time zone only as a last resort when all other approaches to resolve a date-time problem have been exhausted. This is rare. The usual solution is to:
Use java.time or Joda-Time.
Always specify the desired/expected time zone rather than rely implicitly on the default.
Use UTC in most of your business logic, data storage, and data exchange.
Avoid whenever possible the mess that is java.util.Date/.Calendar.
Search StackOverflow
All of these topics have been discussed many times over on StackOverflow.com. Please search for more information and examples.
AFAIK, you have 2 options:
Option 1. This may sound like an overkill, but you could roll out your own Date object just for this complex class of yours, and overwrite the toString() method. Maybe something like
public class GMTDate extends java.util.Date {
#Override
public String toString() {
//return GMTDate
}
}
Option 2: Keep your date as java.util.Date, but don't expose a public getter for it. Expose instead a public getter that returns your date in GMT format, and maybe a public getter that returns your date as a long (with the number of milliseconds from epoch)
EDIT:
And a 3rd option: AspectJ. You can use aspect oriented programming to intercept calls to the toString() method and return a GMT string date
Relevant Stack Overflow Question: AspectJ: Intercept method execution/call and make it return

Using Joda Date & Time API to parse multiple formats

I'm parsing third party log files containing date/time using Joda. The date/time is in one of two different formats, depending on the age of the log files I'm parsing.
Currently I have code like this:
try {
return DateTimeFormat.forPattern("yyyy/MM/dd HH:mm:ss").parseDateTime(datePart);
} catch (IllegalArgumentException e) {
return DateTimeFormat.forPattern("E, MMM dd, yyyy HH:mm").parseDateTime(datePart);
}
This works but contravenes Joshua Bloch's advice from Effective Java 2nd Edition (Item 57: Use exceptions only for exceptional conditions). It also makes it hard to determine if an IllegalArgumentException occurs due to a screwed up date/time in a log file.
Can you suggest a nicer approach that doesn't misuse exceptions?
You can create multiple parsers and add them to the builder by using DateTimeFormatterBuilder.append method:
DateTimeParser[] parsers = {
DateTimeFormat.forPattern( "yyyy-MM-dd HH" ).getParser(),
DateTimeFormat.forPattern( "yyyy-MM-dd" ).getParser() };
DateTimeFormatter formatter = new DateTimeFormatterBuilder().append( null, parsers ).toFormatter();
DateTime date1 = formatter.parseDateTime( "2010-01-01" );
DateTime date2 = formatter.parseDateTime( "2010-01-01 01" );
Joda-Time supports this by allowing multiple parsers to be specified - DateTimeFormatterBuilder#append
Simply create your two formatters using a builder and call toParser() on each. Then use the builder to combine them using append.
Unfortunately I don't believe Joda Time has any such capabilities. It would be nice to have a "tryParseDateTime" method, but it doesn't exist.
I suggest you isolate this behaviour into your own class (one which takes a list of patterns, and will try each in turn) so that the ugliness is only in one place. If this is causing performance issues, you might want to try to use some heuristics to guess which format to try first. For example, in your case if the string starts with a digit then it's probably the first pattern.
Note that DateTimeFormatters in Joda Time are conventionally immutable - you shouldn't be creating a new one each time you want to parse a line. Create them once and reuse them.

Categories