Generic date and time parsing in java 8 - java

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

Related

Date toString without timezone conversion for MessageFormat

I'm using ibm's MessageFormat library to localize an incoming date.
The task here is to run a few checks on the date before showing it to the end user. I get a ZonedDateTime object and I need to make sure that it doesn't fall in the weekend, which I do using the getDayOfWeek.
My problem happens when I try to convert my date to a string using MessageFormat. Since MessageFormat accepts only java.util.Date objects, I convert my ZonedDateTime -> Instant -> Date. Unfortunately, this method results in my "Monday" becoming a "Sunday," as shown below.
I noticed that this "loss" happens upon the Date conversion. This is because the Date.toString() object is being invoked by MessageFormat, and the former uses the JVM's default timezone (in my case, PST). As a result, my UTC gets implicitly converted to a PST and I lose a day.
Any ideas how to tackle this? Is there anything else that I can pass to MessageFormat? Is there a way to use Date but not get this undesired behavior? Is there another localization library I can use?
Internally, MessageFormat uses a DateFormat object but does not allow you to set its timezone. #Assylias linked a question where the answer tries to pull out the internal DateFormat, set its timezone, and then use the MessageFormat as usual, which resolves the issue.
However, I found that to be too wordy, particularly because you have to create a new MessageFormat everytime (as opposed to reusing the MessageFormat that you already set the timezone for).
What I opted for was to simply use SimpleDateFormat directly.
// I have a ZonedDateTime zonedDateTime that I want to print out.
final SimpleDateFormat dateFormat = new SimpleDateFormat("EEEE, MM dd", locale);
dateFormat.setTimeZone(TimeZone.getTimeZone(zonedDateTime.getZone()));
final String formattedDateString = dateFormat.format(Date.from(zonedDateTime.toInstant()));
I then use String.format to insert my formatted date into a larger string. Hope this helps.

Format a XMLGregorianCalendar with Italian date format(dd/mm/yyyy) with no time [duplicate]

This question already has answers here:
want current date and time in "dd/MM/yyyy HH:mm:ss.SS" format
(11 answers)
Closed 4 years ago.
I am trying to convert a java.util.Date to XMLGregorianCalendar in Italian format (dd/mm/yyyy) with no time. Whatever I try the output always prints yyyy-mm-dd.
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("Europe/Rome"));
XMLGregorianCalendar xmlDate = DatatypeFactory.newInstance().newXMLGregorianCalendarDate(cal.get(Calendar.YEAR),
cal.get(Calendar.MONTH)+1, cal.get(Calendar.DAY_OF_MONTH),
DatatypeConstants.FIELD_UNDEFINED);
System.out.println(xmlDate);
I am a consumer of SOAP web-service, and the date attribute is defined as XMLGregorianCalendar.
Please advise how can I change the code to get the output with format (dd/mm/yyyy).
You don’t need an XMLGregorianCalender. It will not, cannot give you what you ask for. Instead you need a LocalDate and a DateTimeFormatter:
DateTimeFormatter italianDateFormatter
= DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
.withLocale(Locale.ITALIAN);
LocalDate date = LocalDate.now(ZoneId.of("Europe/Rome"));
String formattedDate = date.format(italianDateFormatter);
System.out.println(formattedDate);
When I ran this code today, the output was:
21/01/19
The difference from what you ask for is the two digit year, 19. If instead of FormatStyle.SHORT we use FormatStyle.MEDIUM, we get four digit year, but the month as a three letter abbreviaiton:
21-gen-2019
The advantage is that the code lends itself very well to internationalization: you just need to change the locale to get proper code for some other language and country. If you do need 21/01/2019 (with four digit year), specify the format explicitly using a pattern:
DateTimeFormatter italianDateFormatter
= DateTimeFormatter.ofPattern("dd/MM/uuuu");
21/01/2019
What’s wrong with using XMLGregorianCalendar?
When you call System.out.println(xmlDate), you are implicitly calling toString on your XMLGregorianCalendar. When it hasn’t got time and zone offset, toString always generates yyyy-MM-dd format, there is no way you can change that. On the other hand you can have any format you like in a String. Next obstacle is, there is no formatter that can format an XMLGregorianCalendar directly. You would need to convert to a different type, like ZonedDateTime, for example, first. Since you only want the date, neither the time of day nor the time zone, it’s simpler and easier to start out from LocalDate from the start. Not least for those reading and maintaining your code after you.
Your question mentions java.util.Date and your code uses GregorianCalendar too. Both of those classes are poorly designed and long outdated, fully replaced by java.time, the modern Java date and time API. So I suggest you don’t use them.
Link
Oracle tutorial: Date Time explaining how to use java.time, the modern Java date and time API.

Time zone of a country, whether day light savings is in effect

Consider the below code:
if(strTimeZoneCd.equals("A"))
locTz = TimeZone.getTimeZone("AST");
else if(strTimeZoneCd.equals("M"))
locTz = TimeZone.getTimeZone("MST");
else if(strTimeZoneCd.equals("P"))
locTz = TimeZone.getTimeZone("PST");
else if(strTimeZoneCd.equals("H"))
locTz = TimeZone.getTimeZone("HST");
else if(strTimeZoneCd.equals("C"))
locTz = TimeZone.getTimeZone("CST");
else if(strTimeZoneCd.equals("E"))
locTz = TimeZone.getTimeZone("EST");
else if(strTimeZoneCd.equals("G"))
locTz = TimeZone.getTimeZone("GMT");
else if(strTimeZoneCd.equals("Y"))
locTz = TimeZone.getTimeZone("AKST");
Here if I am passing A, it will give me AST. But instead of that I need to determine if I should return AST or ADT.
I need to determine if AST is under daylight saving now. If it is under daylight saving, I can return ADT and if it is not I can return AST. But I am not getting how to determine whether that timezone is under daylight saving or not.
Can someone please help me?
First of all, avoid using the short abbreviations for timezones names (like CST, PST or CEST) because they are ambiguous and not standard.
CST, for example, is used by more than one timezone: it can be "Central Standard Time", "China Standard Time" or "Cuba Standard Time". And each one has different rules regarding Dayligh Saving Time, so using the abbreviation might not necessarily get the results you expect.
The TimeZone class assumes some defaults for those short names (all arbitrary choices, as any default is) and also has the bizarre behaviour of returning GMT when the name is unknown.
To avoid those ambiguities, it's better to use IANA timezones names (always in the format Region/City, like Asia/Kolkata or America/New_York).
You can get a list of available timezones (and choose the one that fits best your system) by calling TimeZone.getAvailableIDs().
Then you can use the inDaylightTime() method, as already explained in the other answers.
Another alternative is to use a formatter, because it checks automatically if it's in Daylight Saving Time and prints the zone short name. I also use a java.util.Locale to indicate that the names should be in English (I'm not sure if different languages affect the short zone names, it's a "just in case" approach):
// formatter with `z` (zone short name)
SimpleDateFormat sdf = new SimpleDateFormat("z", Locale.ENGLISH);
// set timezone in the formatter
sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
// prints the zone name for the current date
System.out.println(sdf.format(new Date()));
The code above prints EDT (because today, September 13th 2017, New York is in Daylight Saving Time and the abbreviation used is EDT).
In this case, you could create the formatter, and use your if logic to set the correct timezone in it. Then, the formatter takes care of checking if the date is in Daylight Saving Time, returning the correct abbreviation.
Java new Date/Time API
The old classes (Date, Calendar and SimpleDateFormat) have lots of problems and design issues, and they're being replaced by the new APIs.
If you're using Java 8, consider using the new java.time API. It's easier, less bugged and less error-prone than the old APIs.
If you're using Java <= 7, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, you'll also need the ThreeTenABP (more on how to use it here).
The code below works for both.
The only difference is the package names (in Java 8 is java.time and in ThreeTen Backport (or Android's ThreeTenABP) is org.threeten.bp), but the classes and methods names are the same.
First I create a DateTimeFormatter with the z pattern (that corresponds to zone short name) and English locale.
Then I use a ZonedDateTime, which represents a date and time in a specific timezone, and the now() method to get the current date/time. I also use ZoneId to especify the timezone I want:
// create formatter for short zone name and English locale
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("z", Locale.ENGLISH);
// format current date in New York timezone
System.out.println(fmt.format(ZonedDateTime.now(ZoneId.of("America/New_York"))));
This also prints EDT as well. You could apply the same if logic above to define which timezone will be used in ZoneId.of(). Just remind to not use the short names (CST, etc), because the new API is not so lenient as the old one.
TimeZone assumes lots of arbitrary defaults and returns "GMT" when the zone doesn't exist, but ZoneId will throw an exception if you try to get an invalid zone (some abbreviations should work for retro-compatibility reasons, but the defaults are arbitrary as well, and you should avoid them).
Custom map of zone names
You can optionally create a custom map of timezone names, so you don't need to make lots of if clauses to determine the corresponding zone. Something like this:
// create custom map of zone names
Map<String, String> customZones = new HashMap<>();
// map "E" to New York
customZones.put("E", "America/New_York");
// map "G" to GMT
customZones.put("G", "GMT");
...
// create timezone using the custom map ("E" will create "America/New_York" zone)
ZoneId zone = ZoneId.of("E", customZones);
// format current date in specified timezone
System.out.println(fmt.format(ZonedDateTime.now(zone)));
work around this:
For any particular TimeZone
TimeZone tz = TimeZone.getTimeZone("EST");
boolean inDs = tz.inDaylightTime(new Date());
You can use this program to determine whether that timezone is under day light saving or not.
Also see this link.
TimeZone.getTimeZone("CST") returns GMT
import java.util.Date;
import java.util.TimeZone;
public class test {
public static void main(String[] args) {
System.out.println(TimeZone.getTimeZone("EST").inDaylightTime( new Date() ));
System.out.println(TimeZone.getTimeZone( "GMT-9:00").inDaylightTime( new Date() ));
}
}

DateTime Manipulation in C# vs Java

I'm new to Java. I have a time I am getting from a web-page, this is in the "hh:mm" format (not 24 hour). This comes to me as a string. I then want to combine this string with todays date in order to make a Java Date I can use.
In C#:
string s = "5:45 PM";
DateTime d;
DateTime.TryParse(s, out d);
in Java I have attempted:
String s = "5:45 PM";
Date d = new Date(); // Which instantiates with the current date/time.
String[] arr = s.split(" ");
boolean isPm = arr[1].compareToIgnoreCase("PM") == 0;
arr = arr[0].split(":");
int hours = Integer.parseInt(arr[0]);
d.setHours(isPm ? hours + 12 : hours);
d.setMinutes(Integer.parseInt(arr[1]));
d.setSeconds(0);
Is there a better way to achieve what I want?
Is there a better way to achieve what I want?
Absolutely - in both .NET and in Java, in fact. In .NET I'd (in a biased way) recommend using Noda Time so you can represent just a time of day as a LocalTime, parsing precisely the pattern you expect.
In Java 8 you can do the same thing with java.time.LocalTime:
import java.time.*;
import java.time.format.*;
public class Test {
public static void main(String[] args) {
String text = "5:45 PM";
DateTimeFormatter format = DateTimeFormatter.ofPattern("h:mm a");
LocalTime time = LocalTime.parse(text, format);
System.out.println(time);
}
}
Once you've parsed the text you've got into an appropriate type, you can combine it with other types. For example, to get a ZonedDateTime in the system time zone, using today's date and the specified time of day, you might use:
ZonedDateTime zoned = ZonedDateTime.now().with(time);
That uses the system time zone and clock by default, making it hard to test - I'd recommend passing in a Clock for testability.
(The same sort of thing is available in Noda Time, but slightly differently. Let me know if you need details.)
I would strongly recommend against using java.util.Date, which just represents an instant in time and has an awful API.
The key points here are:
Parse the text with a well-specified format
Parse the text into a type that represents the information it conveys: a time of day
Combine that value with another value which should also be carefully specified (in terms of clock and time zone)
All of these will lead to clear, reliable, testable code. (And the existing .NET code doesn't meet any of those bullet points, IMO.)
To parse the time, you can do as explained in #Jon Skeet's answer:
String input = "5:45 PM";
DateTimeFormatter parser = DateTimeFormatter.ofPattern("h:mm a", Locale.ENGLISH);
LocalTime time = LocalTime.parse(input, parser);
Note that I also used a java.util.Locale because if you don't specify it, it'll use the system's default locale - and some locales can use different symbols for AM/PM field. Using an explicit locale avoids this corner-case (and the default locale can also be changed, even at runtime, so it's better to use an explicit one).
To combine with the today's date, you'll need a java.time.LocalDate (to get the date) and combine with the LocalTime, to get a LocalDateTime:
// combine with today's date
LocalDateTime combined = LocalDate.now().atTime(time);
Then you can format the LocalDateTime using another formatter:
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
System.out.println(combined.format(fmt));
The output is:
16/08/2017 17:45
If you want to convert the LocalDateTime to a java.util.Date, you must take care of some details.
A java.util.Date represents the number of milliseconds since 1970-01-01T00:00Z (aka Unix Epoch). It's an instant (a specific point in time). Check this article for more info.
So, the same Date object can represent different dates or times, depending on where you are: think that, right now, at this moment, everybody in the world are in the same instant (the same number of milliseconds since 1970-01-01T00:00Z), but the local date and time is different in each part of the world.
A LocalDateTime represents this concept of "local": it's a date (day, month and year) and a time (hour, minute, second and nanosecond), but without any relation to a specific timezone.
The same LocalDateTime object can represent different instants in time in different timezones. So, to convert it to a Date, you must define in what timezone you want it.
One option is to use the system's default timezone:
// convert to system's default timezone
ZonedDateTime atDefaultTimezone = combined.atZone(ZoneId.systemDefault());
// convert to java.util.Date
Date date = Date.from(atDefaultTimezone.toInstant());
But the default can vary from system/environment, and can also be changed, even at runtime. To not depend on that and have more control over it, you can use an explicit zone:
// convert to a specific timezone
ZonedDateTime zdt = combined.atZone(ZoneId.of("Europe/London"));
// convert to java.util.Date
Date date = Date.from(zdt.toInstant());
Note that I used Europe/London. The API uses IANA timezones names (always in the format Region/City, like America/Sao_Paulo or Europe/Berlin).
Avoid using the 3-letter abbreviations (like CST or PST) because they are ambiguous and not standard.
You can get a list of available timezones (and choose the one that fits best your system) by calling ZoneId.getAvailableZoneIds().
And there's also the corner cases of Daylight Saving Time (when a LocalDateTime can exist twice or can't exist due to overlaps and gaps). In this case, Jon's solution using ZonedDateTime avoids this problem).

Problems when moving from SimpleDateFormat to DateTimeFormatter

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

Categories