How to convert timestamp from GMT to Unix epoch - java

I'm parsing a timestamp which is "2022-01-12T17:17:34.512492+0000", this format is "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'ZZZZ" (ISO8601).
I want to convert it in epoch unix time, I'm using java.text.SimpleDateFormat.
I tried two methods but both don't work:
1- First Method
val parsed = "2022-01-12T17:17:34.512492+0000"
val df: SimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'ZZZZ'")
val date = df.parse(parsed.toString)
val epoch = date.getTime
Error showed:
java.text.ParseException: Unparseable date: "2022-01-12T17:17:34.512492+0000"
2- This second Method shows an output but is incorrect
val parsed = "2022-01-12T17:17:34.512492+0000"
val df: SimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'+0000'")
val date = df.parse(parsed.toString)
val epoch = date.getTime
println(epoch)
Output:
1642004766492
If you convert this epoch to HUMAN DATE: "Wednesday, 12 January 2022 16:26:06.492"
The hours,minutes and seconds are wrong.

SimpleDateFormat is outdated, as Gael pointed out.
The Time API now supports up to nanoseconds, so microseconds are not an issue here. You should use DateTimeFormatter with ZonedDateTime. Your pattern is slightly wrong. Checking the docs for Offset Z:
Offset Z: This formats the offset based on the number of pattern
letters. One, two or three letters outputs the hour and minute,
without a colon, such as '+0130'. The output will be '+0000' when the
offset is zero. Four letters outputs the full form of localized
offset, equivalent to four letters of Offset-O. The output will be the
corresponding localized offset text if the offset is zero. Five
letters outputs the hour, minute, with optional second if non-zero,
with colon. It outputs 'Z' if the offset is zero. Six or more letters
throws IllegalArgumentException.
You can also print the time using toInstant to make sure it was parsed correctly:
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
val parsed = "2022-01-12T17:17:34.512492+0000"
val p = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSZZZ"
val dtf = DateTimeFormatter.ofPattern(p)
val zdt = ZonedDateTime.parse(parsed, dtf)
println(zdt.toInstant) // 2022-01-12T17:17:34.512492Z
println(zdt.toInstant.toEpochMilli) // 1642004254512
Here is a nice article that explains in detail converting an ISO 8601 in Java. The comments at the end are particularly useful, as it shows the difference between the different patterns used.

looks like epoch has data in internal datetime format.
and you should convert it to string format
like this in java
public static String dateToString(Date d, String string_format) {
String result = "";
if(d != null) {
result = new SimpleDateFormat(string_format).format(d);
}
return result;
}

The timestamp you have has microseconds precision. Such precision is not supported by SimpleDateFormat. Also, Unix epoch time is usually up to milliseconds precision.
Possible solution here is to explicitly round the microseconds to milliseconds in the string before parsing, then use the yyyy-MM-dd'T'HH:mm:ss.SSSZ format.
String parsed = "2022-01-12T17:17:34.512492+0000";
String upToSeconds = parsed.substring(0, "yyyy-MM-ddTHH:mm:ss".length());
String microseconds = parsed.substring("yyyy-MM-ddTHH:mm:ss.".length(), "yyyy-MM-ddTHH:mm:ss.".length() + "SSSSSS".length());
String timezone = parsed.substring("yyyy-MM-ddTHH:mm:ss.SSSSSS".length());
String roundedMilliseconds = new BigDecimal(microseconds).divide(new BigDecimal("1000"), 0, RoundingMode.HALF_UP).toString();
String reformatted = upToSeconds + "." + roundedMilliseconds + timezone;
System.out.println(reformatted); // 2022-01-12T17:17:34.512+0000
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
long epoch = sdf.parse(reformatted).getTime();
System.out.println(epoch); // 1642007854512
System.out.println(Instant.ofEpochMilli(epoch)); // 2022-01-12T17:17:34.512Z

Related

Calculate Number of Days between Given time in ISO format and Current time

I have to find out number of days between a given Time and current time. Given time is in ISO format and one example is "2021-01-14 16:23:46.217-06:00".
I have tried it using "java.text.SimpleDateFormat" but it's not giving me accurate results.
In Below Given date, for today's time I am getting output as "633" Days which isn't correct. somehow after parsing it is taking date as "21 december 2020" which isn't correct
String TIMESTAMP_FORMAT = "YYYY-MM-DD hh:mm:ss.s-hh:mm" ;
int noOfDays = Utility.getTimeDifferenceInDays("2021-01-14 16:23:46.217-06:00", TIMESTAMP_FORMAT);
public static int getTimeDifferenceInDays(String timestamp, String TIMESTAMP_FORMAT) {
DateFormat df = new SimpleDateFormat(TIMESTAMP_FORMAT);
try {
Date date = df.parse(timestamp);
long timeDifference = (System.currentTimeMillis() - date.getTime());
return (int) (timeDifference / (1000*60*60*24));
} catch (ParseException e) {
e.printStackTrace();
}
return 0;
}
Looking for a better solution which gives me correct number of days. Thanks
Use java.time API
Classes Date and SimpleDateFormat are legacy.
Since Java 8 (which was released 10 years ago) we have a new Time API, represented by classes from the java.time package.
To parse and format the data, you can use DateTimeFormatter. An instance of DateTimeFormatter can be obtained via static method ofPattern(), or using DateTimeFormatterBuilder.
ofPattern():
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSXXX");
DateTimeFormatterBuilder:
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd HH:mm:ss.") // main date-time part
.appendValue(ChronoField.MILLI_OF_SECOND, 3) // fraction part of second
.appendOffset("+HH:MM", "+00:00") // can be substituted with appendPattern("zzz") or appendPattern("XXX")
.toFormatter();
The string "2021-01-14 16:23:46.217-06:00", which you've provided as an example, contains date-time information and UTC offset. Such data can be represented by OffsetDateTime.
To get the number of days between two temporal objects, you can use ChronoUnit.between() as #MC Emperor has mentioned in the comments.
That's how the whole code might look like:
String toParse = "2021-01-14 16:23:46.217-06:00";
OffsetDateTime dateTime = OffsetDateTime.parse(toParse, formatter);
System.out.println("parsed date-time: " + dateTime);
Instant now = Instant.now();
long days = ChronoUnit.DAYS.between(dateTime.toInstant(), now);
System.out.println("days: " + days);
Output:
parsed date-time: 2021-01-14T16:23:46.217-06:00
days: 615
Note that since in this case you need only difference in days between the current date instead of OffsetDateTime you can use LocalDateTime, UTC offset would be ignored while parsing a string. If you decide to do so, then the second argument passed to ChronoUnit.between() should be also of type LocalDateTime.

java.time.format.DateTimeParseException: Text '103545' could not be parsed at index 2

I'm trying to parse two different dates and calculate the difference between them, but the next error appears:
java.time.format.DateTimeParseException: Text '103545' could not be parsed at index 2
Here's the code:
String thisDate= mySession.getVariableField(myVariable).toString().trim();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("ddMMyyyy");
LocalDate theDate= LocalDate.parse(thisDate, formatter);
That’s as expected (approximately).
Your format pattern string, ddMMyyyy specifies two digits day of month, two digits month and (at least) four digits year for a total of (at least) eight (8) digits. So when you give it a string consisting of only 6 digits, parsing will necessarily fail.
If your user or another system is required to give you a date in ddMMyyyy format and they give you 103545, they are making an error. Your validation caught the error, which is a good thing. You will probably want to give them a chance to try again and give you a string like for example 10112021 (for 10 November 2021).
In case (just guessing) 103545 was meant to denote a time of day, 10:35:45 then you need to use the LocalTime class for it, and you also need to change the format pattern string to specify hours, minutes and seconds instead of year, month and date.
String thisDate = "103545";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HHmmss");
LocalTime theTime = LocalTime.parse(thisDate, formatter);
System.out.println(theTime);
Output from this snippet is:
10:35:45
The problem here is the date parser has to recieve a date in the format specified (in this case "ddMMyyyy")
For example, this is what you would need to input for the parser to return a valid date:
String thisDate = '25Sep2000';
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("ddMMyyyy");
LocalDate theDate = LocalDate.parse(thisDate, formatter);
I think what you want is to convert a date in milliseconds to a date with a specific format. This is what you can do:
//Has to be made long because has to fit higher numbers
long thisDate = 103545; //Has to be a valid date in milliseconds
DateFormat formatter = new SimpleDateFormat("ddMMyyyy"); //You can find more formatting documentation online
Date theDate = new Date(thisDate);
String finalDate = formatter.format(theDate);

Time offset between two dates

I have two timestamps and a localized date that I use to find the timezone offset and add it to these dates. How can I calculate the time offset between dates simpler? My method doesn't work with negative values (if (tsOffset.toSecondOfDay() > 0 always true).
fun parseDateTime(startTs: Long, endTs: Long, localizedDateTime: String): Pair<String, String> {
val dateUtcStart = LocalDateTime.ofInstant(Instant.ofEpochMilli(startTs), ZoneOffset.UTC)
val dateUtcEnd = LocalDateTime.ofInstant(Instant.ofEpochMilli(endTs), ZoneOffset.UTC)
val formatter = DateTimeFormatterBuilder()
.parseCaseInsensitive()
.parseDefaulting(ChronoField.YEAR_OF_ERA, dateUtcStart.year.toLong())
.append(DateTimeFormatter.ofPattern("E dd MMM hh:mm a"))
.toFormatter(Locale.ENGLISH)
val localDateTime = LocalDateTime.parse(localizedDateTime, formatter)
val localTs = Timestamp.valueOf(localDateTime).time
val tsOffset = LocalTime.ofInstant(Instant.ofEpochMilli(localTs - startTs), ZoneOffset.systemDefault())
val tzString = if (tsOffset.toSecondOfDay() > 0) "+$tsOffset" else tsOffset.toString()
val startDate = dateUtcStart.toString() + tzString
val endDate = dateUtcEnd.toString() + tzString
return Pair(startDate, endDate)
}
#Test
fun parseDateTime() {
val pair1 = parseDateTime(1626998400000, 1627005600000, "Fri 23 Jul 10:30 am")
val pair2 = parseDateTime(1626998400000, 1627005600000, "Thu 22 Jul 11:30 pm")
// pass
assertEquals("2021-07-23T00:00+10:30", pair1.first)
assertEquals("2021-07-23T02:00+10:30", pair1.second)
// fails
assertEquals("2021-07-23T00:00-00:30", pair2.first)
assertEquals("2021-07-23T02:00-00:30", pair2.second)
}
Also I tried
val dur = Duration.between(dateUtcStart, localDateTime)
But don't sure how to convert it in the string or add to the dates properly.
Here startTs - start of an event timestamp. endTs - end of this event timestamp. localizedDateTime is used to show the start time in the actual time zone (real city) while timestamps show time in UTC. I need to extract this timezone from localizedDateTime and add it to start and end string dateTimes (start = "2021-07-23T00:00+10:30", end = "2021-07-23T02:00+10:30" for startTs = 1626998400000 and endTs = 1627005600000 accordingly).
It’s no simpler than your code, on the contrary, but it fixes a couple of issues that you had.
Disclaimer: I haven’t got your Pair class and I cannot write nor run Kotlin. So I am printing the resulting strings from withint the method, which you will have to change for your purpose. And you will have to hand translate my Java.
private static void parseDateTime(long startTs, long endTs, String localizedDateTime) {
// startTs and localizedDateTime are both representations of the event start time.
// Use this information to obtain the UTC offset of the local time.
Instant startInstant = Instant.ofEpochMilli(startTs);
OffsetDateTime startUtc = startInstant.atOffset(ZoneOffset.UTC);
// Corner case: the local year may be different from the UTC year if the event is close to New Year.
// Check whether this is the case. First get the UTC month and the local month.
Month utcMonth = startUtc.getMonth();
DateTimeFormatter baseSyntaxFormatter = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ofPattern("E dd MMM hh:mm a"))
.toFormatter(Locale.ENGLISH);
Month localMonth = baseSyntaxFormatter.parse(localizedDateTime, Month::from);
int utcYear = startUtc.getYear();
int localYear;
if (utcMonth.equals(Month.DECEMBER) && localMonth.equals(Month.JANUARY)) {
// Local date is in the following year
localYear = utcYear + 1;
} else if (utcMonth.equals(Month.JANUARY) && localMonth.equals(Month.DECEMBER)) {
localYear = utcYear - 1;
} else {
localYear = utcYear;
}
DateTimeFormatter finalFormatter = new DateTimeFormatterBuilder()
.append(baseSyntaxFormatter)
.parseDefaulting(ChronoField.YEAR_OF_ERA, localYear)
.toFormatter(Locale.ENGLISH);
LocalDateTime startLocal = LocalDateTime.parse(localizedDateTime, finalFormatter);
// Now calculate offset
Duration durationOfOffset = Duration.between(startUtc.toLocalDateTime(), startLocal);
ZoneOffset offset = ZoneOffset.ofTotalSeconds(Math.toIntExact(durationOfOffset.getSeconds()));
String startString = startLocal.atOffset(offset).toString();
// Calculate end date and time
String endString = Instant.ofEpochMilli(endTs)
.atOffset(offset)
.toString();
System.out.format("%s - %s%n", startString, endString);
}
Let’s try it with your example data:
parseDateTime(1_626_998_400_000L, 1_627_005_600_000L, "Fri 23 Jul 10:30 am");
parseDateTime(1_626_998_400_000L, 1_627_005_600_000L, "Thu 22 Jul 11:30 pm");
Output:
2021-07-23T10:30+10:30 - 2021-07-23T12:30+10:30
2021-07-22T23:30-00:30 - 2021-07-23T01:30-00:30
You notice that the local start times are now 10:30 and 23:30 as in your input strings, which I believe corrects an error that you had.
Let’s also try an example that bridges New Year:
Instant start = Instant.parse("2021-01-01T00:00:00Z");
parseDateTime(start.toEpochMilli(),
start.plus(2, ChronoUnit.HOURS).toEpochMilli(),
"Thu 31 Dec 10:30 pm");
2020-12-31T22:30-01:30 - 2021-01-01T00:30-01:30
I have used basically the same way of calculating the offset that you present in your own answer.
Issues with your code
First, as I said, the times that your unit test asserts do not agree with the times in your input. For the required output you have taken the UTC time of day and combined with the local offset, which gives a different point in time. A UTC offset is always used with a time of day at that offset (for UTC time of day one would use either offset Z or +00:00).
Consider having your method return a Pair<OffsetDateTime, OffsetDateTime>, not a Pair<String, String>. Strings are for presentation to the user and sometimes for data exchange. Inside your program you should use proper date-time objects, not strings.
As I said in the comments, New Year doesn’t happen at the same time in all time zones. So the year that you get from dateUtcStart.year.toLong() needs not be the year that was assumed in the string from a different time zone. My code takes this into account.
Don’t involve the Timestamp class. It’s poorly designed and long outdated. All it gives you is an extra conversion, hence extra complication.
Your basic problem was using a LocalTime for a duration that might be positive or negative. A LocalTime is for a time of day, not for an amount of time. Your own solution already got rid of this problem.
This is wrong:
val startDate = dateUtcStart.toString() + tzString
First, you should not want to use string manipulation for date and time math. The classes from java.time that you are using produce the string that you need more easily and with less risk of errors. Second, you have indeed got an error here: you are appending the calculated offset to a string that is in UTC, that is, assumes offset 0 (or Z). This was why you were in fact able to produce the incorrect results that your unit test expected.
I got an answer on the Kotlin forum:
val duration = Duration.between(dateUtcStart, localDateTime)
val offset = ZoneOffset.ofTotalSeconds(duration.seconds.toInt())
val startDate = dateUtcStart.atOffset(offset).toString()
val endDate = dateUtcEnd.atOffset(offset).toString()

How to get local time from a specific string adding hh:mm

I have this date in this format:
String date = "2018-12-08T07:50:00+01:00";
And I'd like to get the local time in this format (adding the hours over GMT) but I'm not able to do it
date = "2018-12-08 08:50:00";
Other example:
String date = "2018-12-08T07:50:00+04:00";
Result:
date = "2018-12-08 11:50:00";
Any help?
As Sun already said in another answer, you misunderstood: 2018-12-08T07:50:00+01:00 is the same point in time as 2018-12-08 06:50:00 in UTC (roughly the same as GMT), not 08:50. +01:00 means that the time comes from a place where the clocks are 1 hour ahead of UTC, so to get the UTC time 1 hour should be subtracted, not added.
DateTimeFormatter desiredFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss");
String date = "2018-12-08T07:50:00+01:00";
OffsetDateTime dateTime = OffsetDateTime.parse(date);
OffsetDateTime dateTimeInUtc = dateTime.withOffsetSameInstant(ZoneOffset.UTC);
date = dateTimeInUtc.format(desiredFormatter);
System.out.println(date);
Output from this snippet is:
2018-12-08 06:50:00
Using your other example, 2018-12-08T07:50:00+04:00, the output is
2018-12-08 03:50:00
I am taking advantage of the fact that your string is in ISO 8601 format. OffsetDateTime parses this format as its default, that is, we don’t need to specify the format in a DateTimeFormatter (as we do for your desired result format).
Link: Wikipedia article: ISO 8601
2018-12-08T07:50:00+01:00, the +01:00 in the end does not mean adding hh:mm, it means the datetime is already local date time, in GMT it is 2018-12-08T06:50:00.
You can use string.replaceAll to remove T and +01:00:
String input = "2018-12-08T07:50:00+01:00";
input = input.replaceAll("T", " ");
input = input.replaceAll("\\+.*", "");
System.out.println(input); // 2018-12-08 07:50:00
or parse and re-format it:
OffsetDateTime offsetDateTime = OffsetDateTime.parse("2018-12-08T07:50:00+01:00");
String time = offsetDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME).replace("T", " ");
System.out.println(time); // 2018-12-08 07:50:00

Milliseconds automatically added to java.util.Date / Joda DateTime

I'm trying to create a java.util.Date object without the milliseconds part. eg: 2018-03-19T15:04:23+00:00. This is the code I have:
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
Date d = new Date();
String strFromD = sf.format(d);
Date dFromStr = sf.parse(strFromD);
When I debug this and inspect the variables, I see this:
The String which I get by formatting the date does not have any milliseconds. However, when I create a date back from the String, it has the milliseconds part.
I tried the same using Joda DateTime as well:
DateTimeFormatter dateFormatter = ISODateTimeFormat.dateTimeNoMillis();
DateTime dt = new DateTime();
String strFromDt = dateFormatter.print(dt);
DateTime dtFromStr = dateFormatter.parseDateTime(strFromDt);
System.out.println("DT : "+dt);
System.out.println("String from DT : "+strFromDt);
System.out.println("DT from String : "+dtFromStr);
And this is the output:
DT : 2018-03-22T09:30:22.996-07:00
String from DT : 2018-03-22T09:30:22-07:00
DT from String : 2018-03-22T09:30:22.000-07:00
Again, when I try to get the DateTime from the String, it adds the milliseconds back.
Am I missing something here? Do I need to use 2 different formatters or something?
If your SDK expects a java.util.Date, there's no point talking about a format, because dates don't have a format.
The Date class represents one numerical value: the number of milliseconds since Unix Epoch (Jan 1st 1970, at midnight, in UTC). To make a date without the milliseconds, you could truncate this milliseconds value:
Date d = new Date();
// truncate the number of milliseconds since epoch (eliminate milliseconds precision)
long secs = d.getTime() / 1000;
// create new Date with truncated value
d = new Date(secs * 1000);
In Joda-Time, it's a little bit simpler:
// set milliseconds to zero
DateTime dt = new DateTime().withMillisOfSecond(0);
// convert to java.util.Date
Date date = dt.toDate();
If your SDK expects a String, though, then it makes sense talking about formats. A date can be represented (aka "transformed in text") in many different ways:
2018-03-22T09:30:22-07:00
March 22nd 2018, 9:30:22 AM
22/03/2018 09:30:22.000
and so on...
Objects like java.util.Date and Joda's DateTime don't have a format. They just hold values (usually, numerical values), so if your SDK expects one of those objects, just pass them and don't worry about it.
If the SDK expects a String in a specific format (a text representing a date), then you should transform your date objects to that format.
And if this format doesn't allow milliseconds, so be it:
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
String dateFormattedAsString = sdf.format(d);
With Joda-Time:
DateTime dt = new DateTime();
DateTimeFormatter fmt = ISODateTimeFormat.dateTimeNoMillis();
String dateFormattedAsString = fmt.print(dt);
Those will not change the date's values, but the strings won't have the milliseconds printed.

Categories