DateTimeFormatter weekday seems off by one - java-8

I'm porting an existing application from Joda-Time to Java 8 java.time.
I ran into a problem where parsing a date/time string that contains a 'day of week' value triggered an exception in my unit tests.
When parsing:
2016-12-21 20:50:25 Wednesday December +0000 3
using format:
yyyy'-'MM'-'dd' 'HH':'mm':'ss' 'EEEE' 'MMMM' 'ZZ' 'e
I get:
java.time.format.DateTimeParseException:
Text '2016-12-21 20:50:25 Wednesday December +0000 3'
could not be parsed: Conflict found:
Field DayOfWeek 3 differs from DayOfWeek 2 derived from 2016-12-21
When letting the DateTimeFormatter indicate what it expects:
String logline = "2016-12-21 20:50:25 Wednesday December +0000";
String format = "yyyy'-'MM'-'dd' 'HH':'mm':'ss' 'EEEE' 'MMMM' 'ZZ";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format).withLocale(Locale.ENGLISH);;
ZonedDateTime dateTime = formatter.parse(logline, ZonedDateTime::from);
format = "yyyy'-'MM'-'dd' 'HH':'mm':'ss' 'EEEE' 'MMMM' 'ZZ' 'e";
formatter = DateTimeFormatter.ofPattern(format).withLocale(Locale.ENGLISH);
System.out.println(formatter.format(dateTime));
I now get this output:
2016-12-21 20:50:25 Wednesday December +0000 4
So effectively the root cause of the problem is that the e flag in Joda-Time considers Monday to be 1 yet the Java 8 java.time considers Monday to be 0.
Now for the patterns that java.time.DateTimeFormatter supports I find in both the Oracle documentation and in JSR-310 this:
e/c localized day-of-week number/text 2; 02; Tue; Tuesday; T
This explicit example of 2 and 'Tuesday' leads me to believe that Wednesday should also in java.time be 3 instead of 4.
What is wrong here?
Do I misunderstand?
Is this a bug in Java 8?

There's a difference on how Joda-Time and java.time interprets the pattern e.
In Joda-Time, the e pattern designates the numeric value of day-of-week:
Symbol Meaning Presentation Examples
------ ----------- ------------ -------
e day of week number 2
So, using e is equivalent to getting the day of the week from a date object:
// using org.joda.time.DateTime and org.joda.time.format.DateTimeFormat
DateTime d = new DateTime(2016, 12, 21, 20, 50, 25, 0, DateTimeZone.UTC);
DateTimeFormatter fmt = DateTimeFormat.forPattern("e").withLocale(Locale.ENGLISH);
System.out.println(d.toString(fmt)); // 3
System.out.println(d.getDayOfWeek()); // 3
System.out.println(d.dayOfWeek().getAsText(Locale.ENGLISH)); // Wednesday
Note that both the formatter and getDayOfWeek() return 3. The getDayOfWeek() method returns a value defined in DateTimeConstants class, and Wednesday's value is 3 (the third day of the week according to ISO's definition).
In java.time API, the pattern e has a different meaning:
Pattern Count Equivalent builder methods
------- ----- --------------------------
e 1 append special localized WeekFields element for numeric day-of-week
It uses the localized WeekFields element, and this can vary according to the locale. The behaviour might be different when compared to the getDayOfWeek() method:
ZonedDateTime z = ZonedDateTime.of(2016, 12, 21, 20, 50, 25, 0, ZoneOffset.UTC);
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("e", Locale.ENGLISH);
System.out.println(z.format(fmt)); // 4
System.out.println(z.getDayOfWeek()); // WEDNESDAY
System.out.println(z.getDayOfWeek().getValue()); // 3
Note that the formatter uses the localized day of week for English locale, and the value is 4, while calling getDayOfWeek().getValue() returns 3.
That's because e with English locale is equivalent to using a java.time.temporal.WeekFields:
// using localized fields
WeekFields wf = WeekFields.of(Locale.ENGLISH);
System.out.println(z.get(wf.dayOfWeek())); // 4
While getDayOfWeek() is equivalent to using ISO's definition:
// same as getDayOfWeek()
System.out.println(z.get(WeekFields.ISO.dayOfWeek())); // 3
That's because ISO's definition uses Monday as the first day of the week, while WeekFields with English locale uses Sunday:
// comparing the first day of week
System.out.println(WeekFields.ISO.getFirstDayOfWeek()); // MONDAY
System.out.println(wf.getFirstDayOfWeek()); // SUNDAY
So the e pattern might behave differently or not to getDayOfWeek(), according to the locale set in the formatter (or the JVM default locale, if none is set). In French locale, for example, it behaves just like ISO, while in some arabic locales, the first day of the week is Saturday:
WeekFields.of(Locale.FRENCH).getFirstDayOfWeek(); // MONDAY
WeekFields.of(new Locale("ar", "AE")).getFirstDayOfWeek(); // SATURDAY
According to javadoc, the only patterns that return a numeric value for the day of week seem to be the localized ones. So, to parse the input 2016-12-21 20:50:25 Wednesday December +0000 3, you can use a java.time.format.DateTimeFormatterBuilder and join the date/time pattern with a java.time.temporal.ChronoField to indicate the numeric value of the day of week (the ISO non-locale sensitive field):
String input = "2016-12-21 20:50:25 Wednesday December +0000 3";
DateTimeFormatter parser = new DateTimeFormatterBuilder()
// date/time pattern
.appendPattern("yyyy-MM-dd HH:mm:ss EEEE MMMM ZZ ")
// numeric day of week
.appendValue(ChronoField.DAY_OF_WEEK)
// create formatter with English locale
.toFormatter(Locale.ENGLISH);
ZonedDateTime date = ZonedDateTime.parse(input, parser);
Also note that you don't need to quote the -, : and space characters, so the pattern becomes more clear and readable (IMO).
I also set the English locale, because if you don't set, it'll use the JVM default locale, and it's not guaranteed to always be English. And it can also be changed without notice, even at runtime, so it's better to specify one, specially if you already know in what language the input is.
Update: probably the ccccc pattern should work, as it's equivalent to appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW_STANDALONE) and in my tests (JDK 1.8.0_144) it returns (and also parses) 3:
DateTimeFormatter parser = DateTimeFormatter
.ofPattern("yyyy-MM-dd HH:mm:ss EEEE MMMM ZZ ccccc", Locale.ENGLISH);
ZonedDateTime date = ZonedDateTime.parse(input, parser);

In Locale.ENGLISH Wednesday is the 4th day of week, as week starts on Sunday.
You can check first day of week with
WeekFields.of(Locale.ENGLISH).getFirstDayOfWeek(); //it's SUNDAY

Related

Odd Behavior from Date.strptime()

I'm doing a date conversion using strptime and it is normalizing my data to always have 2020 as the year and I'm not sure why. Specifically:
value = "2015/03/21"
value2 = Date.strptime(value, '%m/%d/%y')
produces a result where value2 = "2020-03-21" How do I get Date.strptime() to appropriately reflect the year?
The second argument of strptime is the input format in which the date is provided not the expected output format. Therefore change it to:
value = "2015/03/21"
value2 = Date.strptime(value, '%Y/%m/%d')
#=> #<Date: 2015-03-21 ((2457103j,0s,0n),+0s,2299161j)>
In value year is first, month is second. You want to create date object from string, so to correctly parse you should use correct order:
value = "2015/03/21"
value2 = Date.strptime(value, '%Y/%m/%d')
=> Sat, 21 Mar 2015

How to get the quantity today reminders using AppleScript?

Could not find a solution on the Internet! These code not work!
tell application "Reminders"
get count (reminders whose due date is (current date))
end tell
Thanks!
The due date of a reminder will probably never match as the due date is expressed as e.g. date "Wednesday, December 4, 2019 at 5:00:00 PM" and (current date) will be expressed as e.g. date "Wednesday, December 4, 2019 at 11:03:13 AM", so unless this is run precisely at e.g. date "Wednesday, December 4, 2019 at 5:00:00 PM", your get count ... as formed will not match.
I'd target the date string of (current date), e.g. "Wednesday, December 4, 2019", however Reminders doesn't understand date string of due date even though due date is expressed as a date object.
So, one way to get a count is to make a list of the due date of every reminder and then evaluate the date string of each against date string of (current date), using the following example AppleScript code:
tell application "Reminders"
set ddList to the due date of every reminder
end tell
set ds to the date string of (current date)
set c to 0
repeat with d in ddList
if d does not contain missing value then
if date string of d contains ds then
set c to c + 1
end if
end if
end repeat
return c
Note: The example AppleScript code is just that and does not contain any error handling as may be appropriate. The onus is upon the user to add any error handling as may be appropriate, needed or wanted. Have a look at the try statement and error statement in the AppleScript Language Guide. See also, Working with Errors.

Java 8 DateTimeFormatter two digit year 18 parsed to 0018 instead of 2018?

With Java 8, the code below parses "18" into year "0018" instead of "2018".
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M/d/y");
return LocalDate.parse(date, formatter);
input date is "01/05/18".
1) why the result is "0018"? Does DateTimeFormatter not follow the 80-20 rule?
2) How to control SimpleDateFormat parse to 19xx or 20xx? talked about SimpleDateFormat.set2DigitYearStart(Date) can be used to fix the year. Is there something similar to that for DateTimeFormatter?
I was hoping "M/d/y" will parse both 2 and 4 digit years.
"M/d/yy" throws Exception for 4 digit years and parses "01/05/97" to "2097-01-05". Ideally this should be parsed to "1997-01-05".
"M/d/yyyy" throws Exception for 2 digit years.
There is not a single string of y or u that will allow you to parse both two and four digit years. However, you may use optional parts in the format pattern string to specify that a two or four digit year may be present:
public static LocalDate parseDateString(CharSequence date) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M/d/[uuuu][uu]");
return LocalDate.parse(date, formatter);
}
Try it:
System.out.println(parseDateString("01/05/18"));
System.out.println(parseDateString("01/06/2018"));
This printed:
2018-01-05
2018-01-06
In the format pattern string you need to put the four digit year first. With the opposite order, when trying to parse a four digit year, the formatter will parse two digits, decide it was successful this far, and then complain about unparsed text after the two digits.
If you want more precise control over how two digit years are interpreted:
DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendPattern("M/d/")
.optionalStart()
.appendPattern("uuuu")
.optionalEnd()
.optionalStart()
.appendValueReduced(ChronoField.YEAR, 2, 2, 1920)
.optionalEnd()
.toFormatter();
Using this formatter in the above method let’s try:
System.out.println(parseDateString("01/05/22"));
This prints:
1922-01-05
Giving 1920 as base (as in my example code) will cause two digit years to end up in the interval from 1920 through 2019. Adjust the value to your requirements.
Change your Formatter string to
"M/d/yy"

How to handle all Zone Offset in one DateTimeFormater Java 8

I need to create a DateTimeFormatter for the following valid dates.
String date1 = "2017-06-20T17:25:28";
String date2 = "2017-06-20T17:25:28.477777";
String date3 = "2017-06-20T17:25:28.477777Z";
String date4 = "2017-06-20T17:25:28.477777UTC";
String date5 = "2017-06-20T17:25:28.477777-05";
String date6 = "2017-06-20T17:25:28.477777+05";
String date7 = "2017-06-20T17:25:28.477777+05:30";
String date8 = "2017-06-20T17:25:28.477777-05:30";
String date9 = "2017-06-20T17:25:28.477777+0530";
String date10 = "2017-06-20T17:25:28.477777-0530";
I have tried the following date time formatter, but this fails for last two dates (date9, date10).
private static final DateTimeFormatter DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd'T'HH:mm:ss")
.appendFraction(ChronoField.MICRO_OF_SECOND, 0, 6, true)
.optionalStart().appendZoneId().optionalEnd()
.optionalStart().appendOffset("+HH", "+00").optionalEnd()
.optionalStart().appendOffset("+HH:mm", "+00:00").optionalEnd()
.optionalStart().appendOffset("+HHmm", "+0000").optionalEnd().toFormatter();
All dates from date1 to date8 work fine but I get a DateTimeParseException when trying to parse last two dates:
Exception in thread "main" java.time.format.DateTimeParseException: Text '2017-06-20T17:25:28.477777+0530' could not be parsed, unparsed text found at index 29
For parsing the date I am using following.
LocalDateTime.parse(date1, DATE_TIME_FORMATTER);
Valid Pattern for Offset From OffsetIdPrinterParser:
static final class OffsetIdPrinterParser implements DateTimePrinterParser {
static final String[] PATTERNS = new String[] {
"+HH", "+HHmm", "+HH:mm", "+HHMM", "+HH:MM", "+HHMMss", "+HH:MM:ss", "+HHMMSS", "+HH:MM:SS",
}; // order used in pattern builder
I am not able to understand while I am using valid ZoneOffset patterns, why my last two dates fail.
Simply reverse the order of your optional sections:
private static final DateTimeFormatter DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd'T'HH:mm:ss")
.appendFraction(ChronoField.MICRO_OF_SECOND, 0, 6, true)
.optionalStart().appendZoneId().optionalEnd()
.optionalStart().appendOffset("+HHmm", "+0000").optionalEnd()
.optionalStart().appendOffset("+HH:mm", "+00:00").optionalEnd()
.optionalStart().appendOffset("+HH", "+00").optionalEnd()
.toFormatter();
This parses all your 10 sample date-time strings.
I am not quite sure why it works. I suppose that it is now trying +HHmm before +HH, which makes sure it gets alle four digits when there are four, instead of leaving the last two unparsed.
Another alternative is to use optional sections, delimited by [], and the respective offset patterns (VV and x):
DATE_TIME_FORMATTER = DateTimeFormatter
// pattern with optional sections: fraction of seconds and offsets
.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSSSSS][VV][x][xx][xxx]");
Each pair of [] is equivalent to one optionalStart and optionalEnd section. Note that I also had to include the uppercase S (fraction of second) as optional, to parse the case where this field is not present.
The other patterns (VV and x) correspond to the various offsets you need. From the javadoc:
Pattern Count Equivalent builder methods
------- ----- --------------------------
VV 2 appendZoneId()
x 1 appendOffset("+HHmm","+00")
xx 2 appendOffset("+HHMM","+0000")
xxx 3 appendOffset("+HH:MM","+00:00")
This works for all your input dates.
The only difference is that [.SSSSSS] accepts exactly 6 digits in the fraction-of-seconds field (or zero digits, as it's an optional section), while appendFraction accepts any quantity from 0 to 6 digits. To get exactly this same behaviour, you must use the DateTimeFormatterBuilder:
DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()
// date and time
.appendPattern("yyyy-MM-dd'T'HH:mm:ss")
// fraction of seconds, from 0 to 6 digits
.appendFraction(ChronoField.MICRO_OF_SECOND, 0, 6, true)
// optional offset patterns
.appendPattern("[VV][x][xx][xxx]")
.toFormatter();

What kind of date format is this and how do I transform it?

Making a GET request to a private (no public documentation) API returns data in JSON format.
The value for date looks as follows:
AanmeldDatum: "/Date(1262300400000+0100)/"
There's another variable called AangebodenSindsTekst which means OfferedSinceText and it's value is "8 augustus 2014". So the unknown Date format should get parsed into that specific value.
I'm wondering what kind of date format it is and how can I transform this to something like this 2014-08-08 with Ruby?
I've tried this:
require 'time'
t = '1262300400000+0100'
t2 = Time.parse(t)
# => ArgumentError: no time information in "1262300400000+0100"
Ruby's Time class is your friend, especially the strptime method:
require 'time'
foo = Time.strptime('1262300400000+0100', '%N') # => 2014-08-08 16:57:25 -0700
foo = Time.strptime('1262300400000+0100', '%N%z') # => 2014-08-08 08:57:25 -0700
%N tells Ruby to use nanoseconds. It's throwing away the precision after the 9th digit which is OK if you don't need the rest of the value. Nanosecond accuracy is good enough for most of us.
%z tells Ruby to find the timezone offset, which it then applies to the returned value.
While parse can often figure out how to tear apart an incoming string, it's not bullet-proof, nor is it all-knowing. For speed, I'd recommend learning and relying on strptime if your strings are consistent.
As the Tin Man pointed out in this answer, use the following instead:
Time.strptime('1262300400000+0100', '%Q%z')
it could be milliseconds since epoc, take off the last 3 zeros and plug it into a unix time stamp converter, comes out as Dec 31st 2009
TIME STAMP: 1262300400
DATE (M/D/Y # h:m:s): 12 / 31 / 09 # 11:00:00pm UTC

Resources