Parse timestamp string in Java with variable number of nanoseconds - java

I have a CSV that contains timestamps in the following formats:
yyyy-MM-dd HH:mm:ssX
yyyy-MM-dd HH:mm:ss.SX
yyyy-MM-dd HH:mm:ss.SSX
yyyy-MM-dd HH:mm:ss.SSSX
yyyy-MM-dd HH:mm:ss.SSSSX
yyyy-MM-dd HH:mm:ss.SSSSSX
yyyy-MM-dd HH:mm:ss.SSSSSSX
How can I parse a string that could contain any one of the above formats?
The following code can parse the timestamp when 3-6 nanoseconds are present, but fails when the nano seconds aren't present or are less than 3:
String time = "2018-11-02 11:39:03.0438-04";
DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSSX");
Date date = sdf.parse(time);
System.out.println("Date and Time: " + date.getTime());
I currently have a method that iterates from 0-6 and generates a string with a number of "S" equal to the value of the iterated variable. The method attempts to parse the string within a try/catch until the string is successfully parsed. For example, the string 2018-11-02 11:39:03.0438-04 will attempt to be parsed five times before being successful.
The CSV is an export of a PostgreSQL table that has columns with type TIMESTAMP WITH TIME ZONE and appears to cut off trailing "0" nanosecond places.
I'm using Java 8 and am open to any external libraries (Joda?).

You'd better use Java Time API1, from the package java.time.
Date, SimpleDateFormatter and Calendar classes are flawed and obsolete.
The DateTimeFormatter class provides numerous options, so you can configure all you need. Note that by using the method appendFraction, the nanos are right-padded.
String[] dateStrs = {
"2018-11-02 11:39:03.4-04",
"2018-11-02 11:45:22.71-04",
"2018-11-03 14:59:17.503-04"
};
DateTimeFormatter f = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd HH:mm:ss.")
.appendFraction(ChronoField.NANO_OF_SECOND, 1, 9, false)
.appendPattern("X")
.toFormatter();
// Single item:
LocalDateTime date = LocalDateTime.parse("2018-11-02 11:39:03.7356562-04", f);
// Multiple items:
List<LocalDateTime> dates = Arrays.asList(dateStrs).stream()
.map(t -> LocalDateTime.parse(t, f))
.collect(Collectors.toList());
1 Java 8 new Date and Time API is heavily influenced by Joda Time. In fact the main author is Stephen Colebourne, the author of Joda Time.

The first 19 characters are identical.
Also, you have different lengths in the different cases. You can use a switch to test the length of the String and handle the separate cases for the different possible values.

I'm not sure but something like this seems to work for me:
String time = "2018-11-02 11:39:03.0438-04";
DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSSX");
Date date = sdf.parse(time);
System.out.println("Date and Time: " + date.getTime());
In general, you want to you the longest format possible, with 6x S in this case.

Related

Date and time to UTC given timezone

i have the following string: 2019120610000100 which corresponds to 2019/12/06 at 10:00 +1.
How can I convert this to utc time, in this case 2019/12/06 09:00?
This string could also have a +2, +3 ... -1, -2 ... timezone so I must be able to convert other strings too.
The + or - sign is given in another instance however, if it can be useful, it can be added to the time and date string.
(The string could become 201912061000 +0100)
Right now I'm converting it manually splitting the string but I'm trying to find a way to make this safe as it gets tricky with hours and minutes like 00 that have to change the day, possibly the month and year.
This is what I have made so far:
hour -= hourOffset;
if(hour<0){
hour += 24
}
minutes -= minutesOffset;
if(minutes<0){
minutes += 60
}
When dealing with dates and times, it is usually better to not do string operations but use one of the many classes that extend java.time.temporal.Temporal from the java.time package - introduced with Java 8.
In your case, you want to use an OffsetDateTime, as your string represents exactly that: A date-time with an offset. Note, that a ZonedDateTime is not really appropriate here, because the offset information (e.g. "+01:00") is not enough to represent a whole timezone. Look at this SO question for more information.
To get an OffsetDateTime from a string, you must simply parse it.
Let's do it.
Step 1: Adjust your string to contain the offset sign (plus or minus).
String offsetSign = "+";
String datetimeString = "2019120610000100";
datetimeString = new StringBuilder(datetimeString).insert(datetimeString.length() - 4, offsetSign).toString();
Step 2: Parse that string to an OffsetDateTime object.
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMddHHmmZ");
OffsetDateTime odt = OffsetDateTime.parse(datetimeString, dtf);
Step 3: Convert that OffsetDateTime to UTC.
OffsetDateTime odtUTC = odt.withOffsetSameInstant(ZoneOffset.UTC);
Printing out those variables
System.out.println(datetimeString);
System.out.println(odt);
System.out.println(odtUTC);
will get you the following output:
201912061000+0100
2019-12-06T10:00+01:00
2019-12-06T09:00Z
You can directly convert the time to UTC by the following code
String dateStr = "201912061000+0100";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmm");
final LocalDateTime parse = LocalDateTime.parse(dateStr.substring(0, dateStr.length()-5), formatter);
final ZoneId zone = ZoneId.of("GMT"+dateStr.substring(12,15)+":"+dateStr.substring(15));
final ZonedDateTime given = ZonedDateTime.of(parse, zone);
final String toUTC = given.withZoneSameInstant(ZoneId.of("UTC"))
.format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm"));
String dateStr = "2019120610000100";
DateTimeFormatter dtfInput = DateTimeFormatter.ofPattern("yyyyMMddHHmm Z");
DateTimeFormatter dtfOutput = DateTimeFormatter.ofPattern("yyyy/MM/dd hh:mm");
String adjustedDateStr = new StringBuilder(dateStr).insert(dateStr.length() - 4, " +").toString();
ZonedDateTime givenDate = ZonedDateTime.parse(adjustedDateStr, dtfInput);
ZonedDateTime timezoneAdjustedDate = ZonedDateTime.ofInstant(givenDate.toInstant(), ZoneId.of("UTC"));
System.out.println(dtfOutput.format(timezoneAdjustedDate));
Since you handle the plus or minus for the timezone offset externally, you can just insert it into the exsample above instead of the plus if need be.

LocalTime parsing with AM/PM

I have a time string coming from another source in format "hh:mma", for example, 10:00a or 07:30p. I need to create an instance of LocalTime from that string. I've tried to parse it by calling the method:
LocalTime.parse("10:00p", DateTimeFormatter.ofPattern("hh:mma")), but it throws an DateTimeParseException. In accordance with DateTimeFormatter API, part of the time should be in uppercase and consist of 2 letters (PM instead of p). But is there any method to parse time without changing the sourse line?
Replace p with PM, a with AM, then parse it with pattern hh:mm a.
String time = "10:00p";
time = time.replace("p", "PM").replace("a", "AM"); // 10:00PM
LocalTime localTime = LocalTime.parse(time, DateTimeFormatter.ofPattern("hh:mm a", Locale.US));
System.out.println(localTime); // 22:00
But is there any method to parse time without changing the sourse
line?
Yes, this formatter can do that for you:
Map<Long, String> ampmStrings = Map.of(0L, "a", 1L, "p");
DateTimeFormatter timeFormatter = new DateTimeFormatterBuilder()
.appendPattern("hh:mm")
.appendText(ChronoField.AMPM_OF_DAY, ampmStrings)
.toFormatter();
With DateTimeFormatterBuilder.appendText we can define our own texts for both formatting and parsing. I used the Java 9+ Map.of to initialize a map of two key-value pairs. If you are using Java 6, 7 or 8, I trust you to initialize the map differently. The rest should still work.
Let’s try it out:
String sourceLine = "10:00a";
LocalTime time = LocalTime.parse(sourceLine, timeFormatter);
System.out.println("Parsed time: " + time);
Output is:
Parsed time: 10:00
A yet better option would be if you could persuade your source to provide strings in ISO 8601 format (like 10:00 and 19:30).

Time parsing issue on Android

I am getting a parse exception when trying to parse the time string 02:22 p.m..
I have the following conversion function:
public static long convertdatetotimestamp(String datestring, String newdateformat, String olddateformat){
SimpleDateFormat originalFormat = new SimpleDateFormat(olddateformat,Locale.ROOT);
SimpleDateFormat targetFormat = new SimpleDateFormat(newdateformat,Locale.ROOT);
Date date = null;
try {
date = originalFormat.parse(datestring);
String formattedDate = targetFormat.format(date);
Date parsedDate = targetFormat.parse(formattedDate);
long nowMilliseconds = parsedDate.getTime();
return nowMilliseconds;
} catch (ParseException e) {
e.printStackTrace();
return 0;
}
}
The method is called in another activity with a time format "02:22 p.m.". olddateformat and newdateformat are the same: hh:mm a.
It causes following error in log:
java.text.ParseException: Unparseable date: "02:22 p.m." (at offset 6)
How to resolve this issue? Time is in exactly above mentioned format.
It so happens that a.m. and p.m. are called just this in Gaelic locale. At least on my Java 8. I am far from sure that it will be the case on (all) Android phones, but you may do some experiments with it.
String datestring = "02:22 p.m.";
Locale parseLocale = Locale.forLanguageTag("ga");
DateTimeFormatter originalFormat = DateTimeFormatter.ofPattern("hh:mm a", parseLocale);
System.out.println(LocalTime.parse(datestring, originalFormat));
This prints
14:22
As Hugo so warmly and rightly recommends is his answer, I am using the modern Java date and time API, so you will need ThreeTenABP for the above code. See How to use ThreeTenABP in Android Project. Alternatively you may want to try the same locale with your otherwise outdated SimpleDateFormat.
US Spanish locale shows the same behaviour on my Java 8, so you may try that too: Locale.forLanguageTag("es-US").
I believe that SimpleDateFormat can't be customized to parse the p.m. part (it only recognizes AM or PM).
So one alternative is to remove the dots:
String time = "02:22 p.m.";
SimpleDateFormat format = new SimpleDateFormat("hh:mm a", Locale.ROOT);
date = format.parse(time.replaceAll("\\.", ""));
One detail: to get the nowMilliseconds value, you need all the date fields (day/month/year) and a timezone. As those fields are not in the input String, SimpleDateFormat sets them to January 1st of 1970 (and also set the seconds and milliseconds to zero), and use the system's default timezone.
I'm not sure if this behaviour of getting January 1970 is consistent among all Java versions, which is another problem because you can get different values depending on the environment/device the code is running. Actually, you might have a different result anyway because it uses the system's default timezone and this can vary among different environments.
If I run this code in my machine, it uses my system's default timezone (America/Sao_Paulo), and the result is 62520000. But if I change the timezone to another (let's say, Asia/Kolkata), the result is 31920000. You must be aware of this variation and check if that's what you really need.
Another detail is that, if olddateformat and newdateformat are the same, there's no need to create 2 different formatters.
Java's 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.
In Android you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. You'll also need the ThreeTenABP (more on how to use it here).
All the relevant classes are in the org.threeten.bp package.
With this new API, you can customize the text that corresponds to AM/PM using a org.threeten.bp.format.DateTimeFormatterBuilder (so no need to remove the dots manually). And there are specific classes to each case - in this case, the input has only the time fields (hour and minutes), so I'm going to use the org.threeten.bp.LocalTime class (which represents only a time - hour/minute/second/nanosecond - without a date):
String time = "02:22 p.m.";
// map AM and PM values to strings "a.m." and "p.m."
Map<Long, String> map = new HashMap<Long, String>();
map.put(0L, "a.m.");
map.put(1L, "p.m.");
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// hour and minute
.appendPattern("hh:mm ")
// use custom values for AM/PM
.appendText(ChronoField.AMPM_OF_DAY, map)
// create formatter
.toFormatter(Locale.ROOT);
// parse the time
LocalTime parsedTime = LocalTime.parse(time, fmt);
The parsedTime variable will contain the values corresponding to 02:22 PM (and only this value, it has no date fields (day/month/year) nor a timezone).
To get the milliseconds value (number of milliseconds since 1970-01-01T00:00Z), you also need a date (day/month/year) and a timezone. As I said previously, those fields can affect the final value.
In the old API, SimpleDateFormat tries to be "smart" and sets default values for those fields (January 1st of 1970 in the system's default timezone), but the new API is more strict about that and you must tell explicity what date and timezone you want.
In this example, I'm using the Asia/Kolkata timezone but you can change it according to your needs (more on that below):
import org.threeten.bp.LocalDate;
import org.threeten.bp.ZoneId;
import org.threeten.bp.ZonedDateTime;
// timezone for Asia/Kolkata
ZoneId zone = ZoneId.of("Asia/Kolkata");
// current date in Kolkata timezone
LocalDate now = LocalDate.now(zone);
// get the parsed time at the specified date, at the specified zone
ZonedDateTime zdt = parsedTime.atDate(now).atZone(zone);
// get the millis value
long millis = zdt.toInstant().toEpochMilli();
If you want a specific date instead of the current date, you can use LocalDate.of(2017, 5, 20) - this will get May 20th, 2017, for example. With this, you can set the code above to the date and timezone you need.
Note that the API uses IANA timezones names (always in the format Region/City, like America/Sao_Paulo or Asia/Kolkata).
Avoid using the 3-letter abbreviations (like IST 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().
If you want to emulate exactly what SimpleDateFormat does, you can use LocalDate.of(1970, 1, 1) and use the default timezone with ZoneId.systemDefault() - but this is not recommended, because the system's default can be changed without notice, even at runtime. It's better to explicit what timezone you're using.
Or you can create a formatter that always sets default values for the date (using the org.threeten.bp.temporal.ChronoField class) and always uses the same timezone. So you can parse it directly to a org.threeten.bp.Instant and get the millis value:
String time = "02:22 p.m.";
ZoneId zone = ZoneId.of("Asia/Kolkata");
DateTimeFormatter fmt2 = new DateTimeFormatterBuilder()
// hour and minute
.appendPattern("hh:mm ")
// use custom values for AM/PM (use the same map from previous example)
.appendText(ChronoField.AMPM_OF_DAY, map)
// default value for day: 1
.parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
// default value for month: January
.parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
// default value for year: 1970
.parseDefaulting(ChronoField.YEAR, 1970)
// create formatter at my specific timezone
.toFormatter(Locale.ROOT).withZone(zone);
// get the millis value
long millis = Instant.from(fmt2.parse(time)).toEpochMilli();
Following changes that i've made works fine for me.
public static long convertdatetotimestamp(String datestring, String newdateformat, String olddateformat){
DateFormat originalFormat = new SimpleDateFormat(olddateformat,Locale.ENGLISH);
DateFormat targetFormat = new
SimpleDateFormat(newdateformat,Locale.ENGLISH);
Date date = null;
try {
date = originalFormat.parse(datestring.replaceAll("\\.", ""));
String formattedDate = targetFormat.format(date);
Date parsedDate = targetFormat.parse(formattedDate);
long nowMilliseconds = parsedDate.getTime();
return nowMilliseconds;
} catch (ParseException e) {
e.printStackTrace();
return 0;
}
}
Locale.ENGLISH you can use your locale, english solved my issue. Reference.
Thanks for responses and references.

How to convert Timestamp into Date format in java

I have a time stamp like this(form a json response) :
"/Date(1479974400000-0800)/"
I'm trying this function to convert time stamp into date:
public String getDate() {
Calendar cal = Calendar.getInstance(Locale.ENGLISH);
cal.setTimeInMillis(time);
String date = DateFormat.format("dd-MM-yyyy", cal).toString();
return date;
}
How to convert this Timestamp into Date format?
Parse directly into an OffsetDateTime
Java can directly parse your string into an OffsetDateTime. Use this formatter:
private static final DateTimeFormatter JSON_TIMESTAMP_FORMATTER
= new DateTimeFormatterBuilder()
.appendLiteral("/Date(")
.appendValue(ChronoField.INSTANT_SECONDS, 1, 19, SignStyle.NEVER)
.appendValue(ChronoField.MILLI_OF_SECOND, 3)
.appendOffset("+HHMM", "Z")
.appendLiteral(")/")
.toFormatter();
Then just do:
String time = "/Date(1479974400000-0800)/";
OffsetDateTime odt = OffsetDateTime.parse(time, JSON_TIMESTAMP_FORMATTER);
System.out.println(odt);
Output is:
2016-11-24T00:00-08:00
In your string 1479974400000 is a count of milliseconds since the epoch of Jan 1, 1970 at 00:00 UTC, and -0800 is an offset of -8 hours 0 minutes from UTC (corresponding for example to Pacific Standard Time). To parse the milliseconds we need to parse the seconds since the epoch (all digits except the last three) and then the millisecond of second (the last three digits). By specifying the width of the milliseconds field as 3 Java does this. For it to work it requires that the number is at least 4 digits and not negative, that is not within the first 999 milliseconds after the epoch or earlier. This is also why I specify in the formatter that the seconds must not be signed.
I specified Z for offset zero, I don’t know if you may ever receive this. An offset of +0000 for zero can still be parsed too.
Original answer: parse the milliseconds and the offset separately and combine
First I want to make sure the timestamp I have really lives up to the format I expect. I want to make sure if one day it doesn’t, I don’t just pretend and the user will get incorrect results without knowing they are incorrect. So for parsing the timestamp string, since I didn’t find a date-time format that would accept milliseconds since the epoch, I used a regular expression:
String time = "/Date(1479974400000-0800)/";
Pattern pat = Pattern.compile("/Date\\((\\d+)([+-]\\d{4})\\)/");
Matcher m = pat.matcher(time);
if (m.matches()) {
Instant i = Instant.ofEpochMilli(Long.parseLong(m.group(1)));
System.out.println(i);
}
This prints:
2016-11-24T08:00:00Z
If you want an old-fashioned java.util.Date:
System.out.println(Date.from(i));
On my computer it prints
Thu Nov 24 09:00:00 CET 2016
This will depend on your time zone.
It is not clear to me whether you need to use the zone offset and for what purpose. You may retrieve it from the matcher like this:
ZoneOffset zo = ZoneOffset.of(m.group(2));
System.out.println(zo);
This prints:
-08:00
The zone offset can be used with other time classes, like for instance OffsetDateTime. For example:
OffsetDateTime odt = OffsetDateTime.ofInstant(i, zo);
System.out.println(odt);
I hesitate to mention this, though, because I cannot know whether it is what you need. In any case, it prints:
2016-11-24T00:00-08:00
If by date you mean Date instance, then you can do this:
new Date(Long.parseLong("\/Date(1479974400000-0800)\/".substring(7, 20)));
I assume this info in holding the String representing an Epoch and a TimeZone
"/Date(1479974400000-0800)/"
you need to get rid off the all the not necessary parts and keeping only the
1479974400000-0800
then the epoch is 1479974400000 and I guess the Timezone is 0800
then do:
String[] allTimeInfo = "1310928623-0800".split("-");
DateFormat timeZoneFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
timeZoneFormat.setTimeZone(TimeZone.getTimeZone("Etc/GMT-8"));
Date time = new java.util.Date(Long.parseLong(allTimeInfo[0]));
System.out.println(time);
System.out.println(timeZoneFormat.format(time));
The solution works
for me is like this:
String str = obj.getString("eventdate").replaceAll("\\D+", "");
String upToNCharacters = str.substring(0, Math.min(str.length(), 13));
DateFormat timeZoneFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
timeZoneFormat.setTimeZone(TimeZone.getTimeZone("GMT-8"));
Date time = new java.util.Date(Long.parseLong(upToNCharacters));
// System.out.println(time);
model.setDate(String.valueOf(timeZoneFormat.format(time)));
Use time variable where you want

java.text.ParseException: Unparseable date when trying to convert "2013-07-01T04:37:14.771468Z" into Local time

I need to convert UTC time string I get into local time using following method,
String dateCreate = "2013-07-01T04:37:14.771468Z"
DateFormat dfParse = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss'Z'");
dfParse.setTimeZone(TimeZone.getTimeZone("UTC"));
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
df.setTimeZone(TimeZone.getTimeZone("Asia/Colombo"));
java.util.Date dateTime;
dateTime = dfParse.parse(dateCreate);
String dteCreate = df.format(dateTime);
Can someone plese give me a solution for this.? :)
EDIT: Now that I've checked it supports this easily, I'd strongly recommend that you use Joda Time. Its ISO-8601 parser works fine:
String dateCreate = "2013-07-01T04:37:14.771468Z";
DateTimeFormatter formatter = ISODateTimeFormat.dateTime();
DateTime parsed = formatter.parseDateTime(dateCreate);
By default that will convert to the system default time zone, but you can change that behaviour with calls on DateTimeFormatter.
Joda Time is also a much cleaner API than the built-in one - you'll find any date/time code is easier to write and easier to read.
Look at your input data and your pattern:
String dateCreate = "2013-07-01T04:37:14.771468Z";
DateFormat dfParse = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss'Z'");
They don't match at all. You need something like:
// Don't use this directly!
DateFormat dfParse = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'");
dfParse.setTimeZone(TimeZone.getTimeZone("UTC"));
Or:
// Don't use this directly!
DateFormat dfParse = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSX");
The latter will cope with any ISO-8601 time zone; the former restricts to UTC.
Unfortunately, the above will end up with the wrong number of milliseconds as it will take all the microseconds to be milliseconds. I don't know of a way of avoiding this in Java... you may need to trim the string first. For example:
// Remove the sub-millisecond part, assuming it's three digits:
int firstPartLength = "yyyy-MM-ddTHH:mm:ss.SSS".length();
String noMicros = dateCreate.substring(0, firstPartLength) +
dateCreate.substring(firstPartLength + 3);
// Now we've got text without micros, so create an appropriate pattern
DateFormat dfParse = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
Date date = dfParse.parse(noMicros);
Alternatively, if you know it's always going to end with "Z":
int firstPartLength = "yyyy-MM-ddTHH:mm:ss.SSS".length();
String noMicros = dateCreate.substring(0, firstPartLength);
DateFormat dfParse = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
dfParse.setTimeZone(TimeZone.getTimeZone("UTC"));
Date date = dfParse.parse(noMicros);
This is irritating, and it would be nice to be able to tell Java to treat any digits after the dot as "fractions of a second" but I don't know of any way of doing that using SimpleDateFormat. Note that you wouldn't be able to represent the sub-millisecond value using just Date anyway.
This is xsd dateTime format. You should use javax.xml.bind.DatatypeConverter for that
Calendar c = DatatypeConverter.parseDateTime(lexicalXSDDateTime);
Note that for SmipleDateFormat S means number of milliseconds so it will parse 771468 as 771468 ms not 0.771468 sec which adds extra 771 secs to the result date
Formatting part is OK

Categories

Resources