Asked  5 Months ago    Answers:  5   Viewed   271 times

I am trying to write a DateTimeFormatter that will allow me to take in multiple different String formats, and then convert the String formats to a specific type. Due to the scope of the project and the code that already exists, I cannot use a different type of formatter.

E.g., I want to accept MM/dd/yyyy as well as yyyy-MM-dd'T'HH:mm:ss but then when I print I only want to print to MM/dd/yyyy format and have it in the format when I call LocalDate.format(formatter);

Could someone suggest ideas on how to do this with the java.time.format.*;

Here is how I could do it in org.joda:

// MM/dd/yyyy format
DateTimeFormatter monthDayYear = DateTimeFormat.forPattern("MM/dd/yyyy");
// array of parsers, with all possible input patterns
DateTimeParser[] parsers = {
        // parser for MM/dd/yyyy format
        monthDayYear.getParser(),
        // parser for yyyy-MM-dd'T'HH:mm:ss format
        DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss").getParser()
};
DateTimeFormatter parser = new DateTimeFormatterBuilder()
    // use the monthDayYear formatter for output (monthDayYear.getPrinter())
    // and parsers array for input (parsers)
    .append(monthDayYear.getPrinter(), parsers)
    // create formatter (using UTC to avoid DST problems)
    .toFormatter()
    .withZone(DateTimeZone.UTC);

I have not found a good/working example of this online.

 Answers

46

I've tested this with JDK 1.8.0_131 for Mac OS X and JDK 1.8.0111 for Windows (both worked).

I've created a DateTimeFormatter with optional sections (delimited by []), to parse both cases (MM/dd/yyyy and yyyy-MM-dd'T'HH:mm:ss).

The same formatter worked for your case (LocalDate), but there are some considerations below.

// parse both formats (use optional section, delimited by [])
DateTimeFormatter parser = DateTimeFormatter.ofPattern("[MM/dd/yyyy][yyyy-MM-dd'T'HH:mm:ss]");

// parse MM/dd/yyyy
LocalDate d1 = LocalDate.parse("10/16/2016", parser);
// parse yyyy-MM-dd'T'HH:mm:ss
LocalDate d2 = LocalDate.parse("2016-10-16T10:20:30", parser);

// parser.format(d1) is the same as d1.format(parser)
System.out.println(parser.format(d1));
System.out.println(parser.format(d2));

The output is:

10/16/2016
10/16/2016


PS: this works only with LocalDate. If I try to format an object with time fields (like LocalDateTime), both formats are used:

System.out.println(parser.format(LocalDateTime.now()));

This prints:

06/18/20172017-06-18T07:40:55

Note that it formatted with both patterns. My guess is that the formatter checks if the object has the fields in each optional section. As the LocalDate has no time fields (hour/minute/second), the second pattern fails and it prints only the first one (MM/dd/yyyy). But the LocalDateTime has all the time fields, and both patterns are valid, so both are used to format.

My conclusion is: this isn't a general solution (like the Joda-Time's version), it's more like a "lucky" case where the patterns involved created the desired situation. But I wouldn't rely on that for all cases.

Anyway, if you are only using LocalDate, you can try to use this code. But if you're working with another types, then you'll probably have to use another formatter for the output, like this:

// parser/formatter for month/day/year
DateTimeFormatter mdy = DateTimeFormatter.ofPattern("MM/dd/yyyy");
// parser for both patterns
DateTimeFormatter parser = new DateTimeFormatterBuilder()
    // optional MM/dd/yyyy
    .appendOptional(mdy)
    // optional yyyy-MM-dd'T'HH:mm:ss (use built-in formatter)
    .appendOptional(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
    // create formatter
    .toFormatter();

// parse MM/dd/yyyy
LocalDate d1 = LocalDate.parse("10/16/2016", parser);
// parse yyyy-MM-dd'T'HH:mm:ss
LocalDate d2 = LocalDate.parse("2016-10-16T10:20:30", parser);

// use mdy to format
System.out.println(mdy.format(d1));
System.out.println(mdy.format(d2));

// format object with time fields: using mdy formatter to avoid multiple pattern problem
System.out.println(mdy.format(LocalDateTime.now()));

The output is:

10/16/2016
10/16/2016
06/18/2017

Friday, July 2, 2021
 
Anax
answered 5 Months ago
89

I will skip the obvious answer "List is not thread safe" - this you already know.

List items are kept in an internal array. There are at least two stages (from logical point of view) when adding an item to a List. First, List gets an index indicating where to put new item. It puts new item into array using this index. Then it increments the index and this is stage two. If second (or third, forth, ...) thread happens to add new item at the same time it may be that there will be two (3, 4, ...) new items put into the same array location before the index is incremented by the first thread. Items are overwritten and lost.

The internal operations of adding new item and incrementing index must be always done in one go for the list to be thread safe. That's what is called critical section. This can be achieved by locks.

Hope this explains a bit.

Thursday, July 29, 2021
 
JackTheKnife
answered 4 Months ago
97

Use HH for hour of day instead of hh

The problem that you are asking about is that you are using lowercase hh in your format pattern string (both times). You need uppercase HH for hour day from 00 through 23. hh is for hour within AM or PM from 01 through 12. So what went wrong was that java.time didn’t know whether 12 in your string referred to 12 AM or 12 PM and refused to make a guess for you.

If you read the exception message closely you will also notice that it says that HourOfAmPm=0 was parsed. It doesn’t say HourOfDay.

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    String timestampString = LocalDateTime.now().format(formatter);
    System.out.println("timestampString: " + timestampString);

    Instant instant = LocalDateTime.parse(timestampString, formatter)
            .toInstant(ZoneOffset.UTC);

    System.out.println("instant: " + instant);

When I ran this snippet just now, I got this output:

timestampString: 2019-06-08 19:22:51
instant: 2019-06-08T19:22:51Z

This is wrong! I ran the snippet around 17:22 UTC, not 19:22. Since Denmark is still using summer time (damn), the local time here was 19:22, which was used for the result and translated into the same wall clock time in UTC, not the same instant. You should always pass your desired time zone to the now method to avoid such bugs. Since you wanted UTC:

    String timestampString = LocalDateTime.now(ZoneOffset.UTC).format(formatter);
timestampString: 2019-06-08 17:27:57
instant: 2019-06-08T17:27:57Z

Still better, don’t use LocalDateTime for holding something that you want to use as a moment in time. Use Instant, OffsetDateTime or ZonedDateTime instead.

There is more information on using hh, HH or kk for formatting and parsing hour values in this question and its answers: Difference between java HH:mm and hh:mm on SimpleDateFormat. The question is asking about the notoriously troublesome SimpleDateFormat, but the answers are valid for DateTimeFormatter too.

Saturday, July 31, 2021
 
kwichz
answered 4 Months ago
40

The week-based-year field, according to javadoc, depends on 2 things: what's the first day of the week, and the minimum number of days in the first week.

The ISO standard defines Monday as the first day of the week, and a minimum of 4 days in the first week:

System.out.println(WeekFields.ISO.getFirstDayOfWeek()); // Monday
System.out.println(WeekFields.ISO.getMinimalDaysInFirstWeek()); // 4

(WeekFields.ISO.weekBasedYear() is equivalent to IsoFields.WEEK_BASED_YEAR, with minor differences regarding another calendar systems)

Considering, for example, January 2nd 2009, which was a Friday. Checking the javadoc for the week-based-year field:

Week one(1) is the week starting on the getFirstDayOfWeek() where there are at least getMinimalDaysInFirstWeek() days in the year. Thus, week one may start before the start of the year.

Considering ISO definition (week starts on Monday, and minimum days in first week is 4), week 1 starts on December 29th 2008 and ends in January 4th 2009 (that's the first week that starts on a Monday and it has at least 4 days in 2009), so January 2nd 2009 has a week-based-year equals to 2009 (with ISO definition):

// January 2st 2009
LocalDate dt = LocalDate.of(2009, 1, 2);
System.out.println(dt.get(WeekFields.ISO.weekBasedYear())); // 2009
System.out.println(dt.get(WeekFields.ISO.weekOfWeekBasedYear())); // 1
// WeekFields.ISO and IsoFields are equivalent
System.out.println(dt.get(IsoFields.WEEK_BASED_YEAR)); // 2009
System.out.println(dt.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR)); // 1

But if I consider a WeekFields instance for en_MT locale (English (Malta)), the first day of week is Sunday, and the minimum number of days in first week is 4:

WeekFields wf = WeekFields.of(new Locale("en", "MT"));
System.out.println(wf.getFirstDayOfWeek()); // Sunday
System.out.println(wf.getMinimalDaysInFirstWeek()); // 4
System.out.println(dt.get(wf.weekBasedYear())); // 2008
System.out.println(dt.get(wf.weekOfWeekBasedYear())); // 53

The first week that starts on a Sunday and has at least 4 days in 2009 is the week from January 4th to 10th. So, according to the week definition for en_MT locale, January 2nd 2009 belongs to the 53th week of the week-based-year 2008.

Now, if I take the ar_SA locale (Arabic (Saudi-Arabia)), the week starts on Saturday, and the minimum number of days in first week is 1:

WeekFields wf = WeekFields.of(new Locale("ar", "SA"));
System.out.println(wf.getFirstDayOfWeek()); // Saturday
System.out.println(wf.getMinimalDaysInFirstWeek()); // 1
System.out.println(dt.get(wf.weekBasedYear())); // 2009
System.out.println(dt.get(wf.weekOfWeekBasedYear())); // 1

For this locale, week 1 starts in December 27th 2008 and ends in January 2nd 2009 (it's the first week that starts on a Saturday and has at least 1 day in 2009). So, the week-based-year for January 2nd 2009 in ar_SA locale is also 2009 (the same value I've got using IsoFields, even though the week definition is completely different from ISO).


While IsoFields.WEEK_BASED_YEAR uses ISO's definition, the pattern YYYY will use the WeekFields instance corresponding to the locale set in the formatter (or the JVM default locale, if none is set).

Depending on the definition of each locale (first day of week and minimum number of days in first week), the week-based-year from the localized pattern (YYYY) might have the same value (or not) of the ISO field.

Although it sounds strange that a week can start or end in another year, the javadoc says that it's perfectly valid:

The first and last weeks of a year may contain days from the previous calendar year or next calendar year respectively.


The pattern letters of java.time were based on CLDR (The Unicode Common Locale Data Repository). This link about Week based patterns says:

The year indicated by Y typically begins on the locale’s first day of the week and ends on the last day of the week

Anyway, CLDR is all about localization, so Y is localized as well - as stated by Stephen Colebourne's comment below:

The whole purpose of CLDR is localization, so yes, the "Y" pattern letter is localized. While I understand the desire for a pattern letter that always operates using the ISO rules, it doesn't exist and getting CLDR to add it would be hard to impossible. (Java is following CLDR closely)


My conclusion is, if you want ISO week fields, don't use the localized patterns. Or, as a - not ideal, quite ugly - workaround, use a locale that matches ISO's week definition (in my JVM, Locale.FRENCH does the trick, as WeekFields.ISO.equals(WeekFields.of(Locale.FRENCH)) returns true). The only problem is that the locale will also affect other fields (in case you have month or day of week names, such as MMM or EEE, and any other locale sensitive data).

Friday, August 20, 2021
 
Vinícius Gobbo A. de Oliveira
answered 4 Months ago
75

I do not believe <uses-library> is relevant here.

Your options are:

  1. Implement your "library" as a JAR, to be included in other projects at compile time. So long as your library is not attempting to define resources, you are in fine shape. See the CWAC projects out on my github page for samples of how to set this up.

  2. Implement your "library" as a separate APK containing a remote service, defined using AIDL. Do not attempt to blend their build paths as you are presently doing in Eclipse, but rather follow the AIDL rules and have each project use a common AIDL definition. You will also need to arrange for your users to install both APKs.

  3. Implement your "library" as a separate APK containing a ContentProvider. Do not attempt to blend their build paths as you are presently doing in Eclipse, but rather follow the ContentProvider rules and have the client access the provider via a ContentResolver and a defined Uri. You will also need to arrange for your users to install both APKs.
Thursday, October 14, 2021
 
jwaliszko
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share