Java8 LocalDateTime and NANO_OF_SECOND strange formatting - java

I try this code:
import java.time.*;
...
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(
"dd-MMM-yyyy HH:mm:ss.n");
System.out.format("Now = %s %n", now.format(formatter));
in order to get an output with subsecond information
Now = 12-Apr-2018 14:47:38.039578300
Unfortunately, in the first 100 ms of every second, the leading zero of the subsecond information is omitted and I get a very misleading output Now = 12-Apr-2018 14:47:38.39578300 , which can be easily misinterpreted as about 38.4 sec, or 396 ms after the full second, instead of the real 38.04 sec.
The only workarond I found, is a format of ss.nnnnnnnnn with exactly 9 n, to get my desired output.
Edit:
There is something nicer, which I missed in this area when posting this question.
I'm not really interested in Nanoseconds, but a fractional part of seconds (about ms resolution), is what I'm really looking for.
Then, this one is much more suitable
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
The capital S indicates the number of subsecond digits, including leading zeros of course.

To get better control of fractions you can use the builder, not just pattern letters. Specifically appendFraction.
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("dd-MMM-yyyy HH:mm:ss")
.appendFraction(ChronoField.NANO_OF_SECOND, 1, 9, true)
.toFormatter();
Pattern letter "n" is rarely what you want.

If you want just ms resolution, you can use S instead of n:
DateTimeFormatter formatter = DateTimeFormatter
.ofPattern("dd-MMM-yyyy HH:mm:ss.SSS", Locale.US);
This will print just the first 3 fractional digits (which is ms resolution):
12-Apr-2018 14:47:38.039
Note that I used a java.util.Locale to define the language to be used for the month name. That's becase the JVM might not always be set to English, and the results can't be what you expect. Ex: my JVM is set to Portuguese and the month name is "abr". Setting a specific locale eliminates this problem.
To print all the 9 digits, using either nnnnnnnnn or SSSSSSSSS will work.
We can see why it behaves like this when we check the javadoc. S and n have different presentations:
Symbol Meaning Presentation Examples
------ ------- ------------ -------
S fraction-of-second fraction 978
n nano-of-second number 987654321
S is a fraction, while n is a number. The docs tell you the difference:
Number: If the count of letters is one, then the value is output using the minimum number of digits and without padding.
Fraction: Outputs the nano-of-second field as a fraction-of-second. The nano-of-second value has nine digits, thus the count of pattern letters is from 1 to 9. If it is less than 9, then the nano-of-second value is truncated, with only the most significant digits being output.
So, just 1 n will print the value without padding (without the 0 in the beginning), leading to the wrong output you've got, while SSS will give you the correct output.

I don't believe there is anything better than nnnnnnnnn. As per DateTimeFormatter docs for n pattern the leading zeros will be truncated if less than 9 pattern letters are used:
Fraction: Outputs the nano-of-second field as a fraction-of-second.
The nano-of-second value has nine digits, thus the count of pattern
letters is from 1 to 9. If it is less than 9, then the nano-of-second
value is truncated, with only the most significant digits being
output.
n and N are the only nano fields supported by DateTimeFormatter.

Related

Round instant with more than 6 digits (nanoseconds) after comma

I have postgresql with TIMESTAMP field, which have Instant with date and time. For example: 2021-10-13T15:04:24.944921Z.
As you can see, there are 6 digits after comma - 944921. But what if I have more digits, for example: 2021-10-13T07:14:47.791616921Z. How can I correctly round such Instant to 2021-10-13T07:14:47.791617Z?
The code is short but requires a bit of explanation. You are right, the best we have is the truncatedTo method, and it always rounds down. Instead we want the half-up rounding that we learned in school: If the 7th decimal is 4 or less, round down. If it is 5 or higher, round up.
To obtain this I first add 500 nanoseconds. Then truncate. If the 7th decimal was 4 or less, it is still 9 or less, so the digits before it have not been changed, and truncation will do the rounding-down that we want. If the 7th decimal was 5 or more, adding 500 nanoseconds will overflow it, the 6th decimal will be incremented by 1, and the 7th decimal will end up in the 0 through 4 range. Then truncation will effectively do the rounding up of the original value that we wanted.
Instant sourceValue = Instant.parse("2021-10-13T07:14:47.791616921Z");
Instant rounded = sourceValue.plusNanos(500).truncatedTo(ChronoUnit.MICROS);
System.out.println(rounded);
Output is the desired:
2021-10-13T07:14:47.791617Z
Let’s try the edge cases too.
Instant sourceValue = Instant.parse("2021-10-13T07:14:00.000000499Z");
2021-10-13T07:14:00Z
Instant sourceValue = Instant.parse("2021-10-13T07:14:59.999999500Z");
2021-10-13T07:15:00Z

DateTimeFormatter single digit month

I'm trying to parse a date where the month goes from 1 to 12 (And not from 01 to 12).
I'm trying this:
System.out.println(DateTimeFormatter.ofPattern("[[MM][M]yyyy").parse("112019"));
System.out.println(DateTimeFormatter.ofPattern("[[MM][M]yyyy").parse("82019"));
The first line works, the second fails.
Even Myyyy fails to parse the second line. I have not been able to find any pattern able to parse 82019 :(
[M][MM] as well as [MM][M] fail. Reading the documentation it says:
M/L month-of-year number/text 7; 07; Jul; July; J
So M is supposed to parse both 8 and 11.
Anyone has been able to get something working?
This works:
System.out.println(DateTimeFormatter.ofPattern("M-yyyy").parse("8-2019"));
But the data I got don't have any separation between the month and the year.
Perhaps DateTimeFormatterBuilder is what you are looking for:
String s = "112019";
System.out.println(new DateTimeFormatterBuilder()
.appendPattern("M")
.appendValue(ChronoField.YEAR, 4)
.toFormatter()
.parse(s)
);
DateTimeFormatter yearMonthFormatter = new DateTimeFormatterBuilder()
.appendValue(ChronoField.MONTH_OF_YEAR, 1, 2, SignStyle.NEVER)
.appendValue(ChronoField.YEAR, 4)
.toFormatter();
System.out.println(YearMonth.parse("112019", yearMonthFormatter));
System.out.println(YearMonth.parse("82019", yearMonthFormatter));
Output from this snippet is:
2019-11
2019-08
Unintuitively (but very practical in many other situations) a single pattern letter for a numerical field does not mean 1 digit, but as many digits as it takes. So M is not what we need here. What we do need instead is a DateTimeFormatterBuilder and its appendValue method, which comes in a number of overloaded versions. What we use for parsing fields that haven’t got any delimiter between them is known as adjacent value parsing. java.time can do this when all fields except the first have fixed widths, which you fulfil nicely.
I prefer to parse into a YearMonth, it’s a perfect match for the information in your strings, but that’s not the point here.
Link: Documentation of DateTimeFormatterBuilder.appendValue(TemporalField, int) explaining adjacent value parsing.
If all of your dates are given in the format you provided, consider isolating the values like so:
String s = "112019";
int yearIndex = s.length() - 4;
String pretty = s.substring(0, yearIndex) + " " + s.substring(yearIndex);
System.out.println(DateTimeFormatter.ofPattern("[M] yyyy").parse(pretty));

Java String to date conversion error for format yyyyMMddhhmmss

I am getting error only for the specific string field ,I have given my code below
public static void main(String[] args) throws ParseException {
String sDate1="201710164425";
Date date1=new SimpleDateFormat("yyyyMMddhhmmss").parse(sDate1);
System.out.println(sDate1+"\t"+date1);
}
If i change String to addding 0 before 4 sDate1="2017101604425"; it works fine, I get Mon Oct 16 04:42:05 IST 2017, but my input comes in this way sDate1="201710164425";
Help me to have a better solution for this ,Thanks in advance
yyyyMMddhhmmss <- format
201710164425 <- data
so for 4th hour you need to do 04 (hh) instead of 4. Same for minutes.
What you want is to have an "hour value" without leading zero, I also see that minutes and/or seconds should be the same, so you could use a time format like Hms.
H : Hour in day (0-23) or (k : Hour in day (1-24) )
m : Minute in hour
s : Second in minute
Compared to HHmmss, having a format like Hms would only take the least amount of digit for each field (that more complicated but it's the idea).
Problem
That could be miss interpreted. Let say I want to send you a time like 01:21:05, it will look like 1215 to match the pattern you need.
new SimpleDateFormat("Hms")
1215 -> 01:02:15
See the problem ? 01:21:05 will be come 01:02:15.
I always suggest to use leading zero (two digit) for every field to prevent any mistake.
Solution
Use leading zero
Use two digit for each of your field to prevent any problem
new SimpleDateFormat("HHmmss")
012105 -> 01:21:05
Add some separators
You can define a pattern to use separator between each field:
new SimpleDateFormat("H:m:s")
1:21:5 -> 01:21:05
You can achieve what you want using another pattern in SimpleDateFormat:
yyyyMMddKmmssa
Where
K - Hour in am/pm (0-11)
a - Am/pm marker
Notice that you need the last 'a'. If you want just a single 4 to indicate both 04:00 and 16:00, you need something to differentiate between them.

java.time ISO date format with fixed millis digits (in Java 8 and later)

By default, the toString method of Instant uses the DateTimeFormatter.ISO_INSTANT formatter. That formatter won’t print the digits for fraction-of-second if they happen to be 0.
java-time examples:
2015-10-08T17:13:07.589Z
2015-10-08T17:13:07Z
Joda-Time examples (and what I'd expect from java.time):
2015-10-08T17:13:07.589Z
2015-10-08T17:13:07.000Z
This is really frustrating to parse in some systems. Elasticsearch was the first problem I encountered, there's no pre-defined format that supports optional millis, but I can probably work around that with a custom format. The default just seems wrong.
It appears that you can’t really build your own format string for Instants anyway. Is the only option implementing my own java.time.format.DateTimeFormatterBuilder.InstantPrinterParser?
Just create a DateTimeFormatter that keeps three fractional digits.
DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendInstant(3).toFormatter();
Then use it. For example:
System.out.println(formatter.format(Instant.now()));
System.out.println(formatter.format(Instant.now().truncatedTo(ChronoUnit.SECONDS)));
…prints (at the time I run it):
2015-10-08T21:26:16.571Z
2015-10-08T21:26:16.000Z
Excerpt of the class doc:
… The fractionalDigits parameter allows the output of the fractional second to be controlled. Specifying zero will cause no fractional digits to be output. From 1 to 9 will output an increasing number of digits, using zero right-padding if necessary. The special value -1 is used to output as many digits as necessary to avoid any trailing zeroes. …

how can I get the date from String using regex in java?

here's my code:
String t1="postby <span title=\"2011-4-5 17:22\">yesterday 17:22</span>";
String t2="postby 2010-11-12 10:02";
I want get 2011-4-5 17:22 , 2010-11-12 10:02 from t1 or t2,using one regex expression
(input t1 or t2,output the date)
how to do? (please give to me some example code,thanks)
\d{4}-\d{1,2}-\d{1,2} \d{2}:\d{2}
A few notes:
you will have to escape the slashes in a string: String pattern = "\\d{4}-\\d{1,2}....."
\d means "digit" (0-9)
{x} means "x times"
{x,y} means "at least x, but not more than y times"
Reference: java.util.regex.Pattern
How many false matches will you allow? Bozho already suggested the pattern
\d{4}-\d{1,2}-\d{1,2} \d{2}:\d{2}
But that matches the following questionable cases: 0000-1-1 00:00 (there is no year zero), 2011-0-1 00:00 (there is no month zero), 2011-13-1 00:00 (there is no month 13), 2011-1-32 00:00 (there is no month-day 32) 2011-12-31 24:00 (there is at most one leap second) and 2011-12-31 23:61 (there is at most one leap seond).
You are wanting to parse date-times that are almost, but not quite, in ISO-8601 format. If you can, please use that international standard format.
In one of my programs (a shell script using grep), I've used the following regular expression:
^20[0-9][0-9]-[01][0-9]-[0-3][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]UTC$
I had an extra T and UTC to deal with, was interested only in dates in this century, and parsed with seconds precision. I see I was not so restrictive on hour and minute values, probably because traditional C/C++ conversions can handle them.
I guess you therefore could use something like the following:
\d{4}-[01]\d-[0-3]\d [0-2]\d:[0-6]\d

Categories