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.
Related
I am rewriting piece of GO code to java and I have doubths about the following snippet.
Go code:
time.Parse("20060102", someString);
Is it analog of ?
ZonedDateTime zdt = ZonedDateTime.parse(credElements[0], DateTimeFormatter.ofPattern("yyyyMMdd")
A quick look at the Go documentation reveals that:
A Time represents an instant in time with nanosecond precision.
Which is similar to Java's Instant type.
Also, from the docs of Parse,
Elements omitted from the layout are assumed to be zero or, when zero is impossible, one, so parsing "3:04pm" returns the time corresponding to Jan 1, year 0, 15:04:00 UTC (note that because the year is 0, this time is before the zero Time).
[...]
In the absence of a time zone indicator, Parse returns a time in UTC.
Knowing this, we can first create a LocalDate from your string that does not contain any zone or time information, then "assume" (as the Go documentation calls it) that it is at the start of day, and at the UTC zone, in order to convert it to an Instant:
var date = LocalDate.parse(someString, DateTimeFormatter.BASIC_ISO_DATE);
var instant = date.atStartOfDay(ZoneOffset.UTC).toInstant();
Since the result of the line of Go you provided includes an offset, a zone and the time of day, you will have to explicitly attach those and use a specific formatter in Java:
public static void main(String[] args) {
// example input
String date = "20060102";
// parse the date first, using a built-in formatter
LocalDate localDate = LocalDate.parse(date, DateTimeFormatter.BASIC_ISO_DATE);
// then add the minimum time of day and the desired zone id
ZonedDateTime zdt = ZonedDateTime.of(localDate, LocalTime.MIN, ZoneId.of("UTC"));
// the formatter
DateTimeFormatter dtfOut = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss Z VV", Locale.ENGLISH);
// the result of the Go statement
String expected = "2006-01-02 00:00:00 +0000 UTC";
// print the result
System.out.println("Expected: " + expected);
System.out.println("Actual: " + zdt.format(dtfOut));
}
Output:
Expected: 2006-01-02 00:00:00 +0000 UTC
Actual: 2006-01-02 00:00:00 +0000 UTC
Posts about y and u (actually accepted answers to the questions)
uuuu versus yyyy in DateTimeFormatter formatting pattern codes in Java
What is the difference between year and year-of-era?
In my spring boot application I have to convert ISO 8601 datetime to localdatetime without using JODA. Currently what I am doing is
String receivedDateTime = "2019-11-13T00:11:08+05:00";
ZonedDateTime zonedDateTime = ZonedDateTime.parse(receivedDateTime);
DateFormat utcFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
utcFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
Date date = new Date();
try {
date = utcFormat.parse(zonedDateTime.toString());
} catch (ParseException e) {
e.printStackTrace();
}
When I am using receivedDateTime with +00:00 like "2019-11-13T00:11:08+00:00" then it does not give any parsing error but not converting either. When I use +01:00 at the end then it also gives the parsing error.
UPDATE: 1
As per #Deadpool answer, I am using it like
String receivedDateTime = "2019-11-13T00:11:08+05:00";
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
.optionalStart().appendOffset("+HH:MM", "+00:00").optionalEnd()
.optionalStart().appendOffset("+HHMM", "0000").optionalEnd()
.toFormatter();
OffsetDateTime dt = OffsetDateTime.parse(receivedDateTime, formatter);
LocalDateTime ldt = dt.toLocalDateTime();
System.out.println(ldt);
and the the value of ldt it print is 2019-11-13T00:11:08.
UPDATE 2:
I tried using C# the same example and it gives me this date time {2019-11-12 11:11:08 AM}, which looks correct as the input time GMT +5 Hours and local time is EST America. So, when it converted it then it went back to 12th of Nov. Here is the code
var timeString = "2019-11-13T00:11:08+05:00";
DateTime d2 = DateTime.Parse(timeString, null, System.Globalization.DateTimeStyles.RoundtripKind);
Console.WriteLine("Hello World!" + d2);
UPDATE 3: So it boils down to following solution input String "2019-11-13T06:01:41+00:00" and output is local date "2019-11-13T00:01:41" Where system defauld ZoneId is "America/Chicago" which is -06:00 GMT
private LocalDateTime convertUtcStringToLocalDateTime(String UtcDateTime) {
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
.optionalStart().appendOffset("+HH:MM", "+00:00").optionalEnd()
.optionalStart().appendOffset("+HHMM", "0000").optionalEnd()
.toFormatter();
OffsetDateTime dateTime = OffsetDateTime.parse(UtcDateTime, formatter);
return dateTime.atZoneSameInstant(ZoneId.of(ZoneId.systemDefault().getId())).toLocalDateTime();
}
Using java.time alone this is simpler than you seem to think:
String receivedDateTime = "2019-11-13T00:11:08+05:00";
OffsetDateTime parsedDateTime = OffsetDateTime.parse(receivedDateTime);
ZonedDateTime dateTimeInMyTimeZone
= parsedDateTime.atZoneSameInstant(ZoneId.systemDefault());
System.out.println(dateTimeInMyTimeZone);
When I ran this in America/Toronto time zone, the output was:
2019-11-12T14:11:08-05:00[America/Toronto]
Since your string contains an offset, +05:00, and no time zone, like Asia/Karachi, use an OffsetDateTime for parsing it. Then convert to your local time zone using the atZoneSameInstant method. Even though you asked for your local time, don’t be fooled into using LocalDateTime. That class represent a date and time without any time zone, which is not what you need (and seldom needed at all).
Fortunately it’s easy to avoid the old classes SimpleDateFormat, DateFormat, TimeZone and Date. They were always poorly designed, the first two in particular are notoriously troublesome. They are all long outdated now. Instead get all the functionality we dream of from java.time, the modern Java date and time API.
What happened in your code?
Don’t use 'Z' in a format pattern string (and I repeat, don’t use SimpleDateFormat).
No matter if you use ZonedDateTime or OffsetDateTime, when you use toString with offset zero (as parsed from +00:00), the offset is printed as Z, which matches the 'Z' in your format pattern string, so your second parsing works. Only parsing once, converting back to string and parsing again is needlessly complicated. Worse when the original offset was +01:00 or +05:00. These are rendered the same again from toString, so don’t match 'Z', which caused your ParseException. Never use 'Z' in a format pattern string. Z denotes an offset of zero and needs to be parsed as an offset for you to get the correct result.
By using DateTimeFormatter you can customize the date format with different offset format by making them optional
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
.optionalStart().appendOffset("+HH:MM", "+00:00").optionalEnd()
.optionalStart().appendOffset("+HHMM", "0000").optionalEnd()
.toFormatter();
And the use the OffsetDateTime to parse string representing with offset
A date-time with an offset from UTC/Greenwich in the ISO-8601 calendar system, such as 2007-12-03T10:15:30+01:00.
OffsetDateTime dateTime = OffsetDateTime.parse("2019-11-13T00:11:08+0000", formatter);
OffsetDateTime dateTime = OffsetDateTime.parse("2019-11-13T00:11:08+05:00", formatter);
If you want to convert it into local time zone time LocalDateTime then use atZoneWithSameInstant()
LocalDateTime local = dateTime.atZoneSameInstant(ZoneId.of("America/New_York")).toLocalDateTime()
Note : Don't use SimpleDateFormat and util.Date which are legacy old framework
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
In first step we generate date in timestamp format ;
And in second step (other feature of our application) we need to extract the date only; and it's important to keep the day before and not day after midnight.
Thanks for your support.
RL
In Java-8, there is partial support for 24:00-time (midnight at end of day) ONLY on parsing level, and then only for LocalTime.
Automatical transfer of 24:00 to next day:
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
TemporalAccessor parsed = dtf.parse("2016-11-14 24:00");
Period extraDays = parsed.query(DateTimeFormatter.parsedExcessDays());
LocalDateTime ldt = LocalDateTime.from(parsed);
System.out.println(ldt);
System.out.println(extraDays);
Output:
2016-11-15T00:00
P0D (24:00 has been lost!!!)
For LocalDateTime, there does not seem to exist any way to find out that the original parsed time was "24:00". However, if you work with the type LocalTime then you can query the original time this similar way:
DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("HH:mm");
TemporalAccessor parsed2 = dtf2.parse("24:00");
Period extraDays2 = parsed2.query(DateTimeFormatter.parsedExcessDays());
System.out.println(extraDays2); // P1D
LocalTime lt = LocalTime.from(parsed2);
System.out.println(lt); // 00:00
You could set up two formatters, one for the date part only and one for the time part as given in last example. However, what you can NEVER do is storing the time 24:00 as instance of LocalTime. Instead, you must work with the formatter method parsedExcessDays() So you find that the overall support is very limited. The best solution I found is this:
DateTimeFormatter dtf1 = DateTimeFormatter.ofPattern("yyyy-MM-dd ");
DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("HH:mm");
String input = "2016-11-14 24:00";
ParsePosition pp = new ParsePosition(0);
LocalDate ld = LocalDate.from(dtf1.parse(input, pp));
TemporalAccessor timePart = dtf2.parse(input, pp);
Period extraDays = timePart.query(DateTimeFormatter.parsedExcessDays());
LocalTime lt = LocalTime.from(timePart);
System.out.println(ld); // 2016-11-14
System.out.println(lt); // 00:00
System.out.println(extraDays); // P1D
Maybe you can consider my library Time4J as alternative format and parse engine. A major difference here is: The Time4J-type PlainTime can store the value "24:00".
But like Java-8, the type PlainTimestamp (as pendant to LocalDateTime) also does an automatic transfer of excess days to date part. Main reason is to avoid difficulties in sorting and implementation of the natural order. Consider for example the timestamps 2016-11-14 24:00 and 2016-11-15 00:00. Both values would be temporally equal (simultaneous) but are not equal (regarding the whole state), so the natural order would be inconsistent with equals(). Therefore a PlainTimestamp does not store 24:00 but automatically resolves it to next day.
But you can use this solution keeping date and time separately:
ChronoFormatter<PlainDate> dateF =
ChronoFormatter.ofDatePattern("yyyy-MM-dd ", PatternType.CLDR, Locale.ROOT).with(
Attributes.TRAILING_CHARACTERS,
true
);
ChronoFormatter<PlainTime> timeF =
ChronoFormatter.ofTimePattern("HH:mm", PatternType.CLDR_24, Locale.ROOT);
String input = "2016-11-14 24:00";
ParseLog plog = new ParseLog();
PlainDate date = dateF.parse(input, plog);
PlainTime time = timeF.parse(input, plog);
System.out.println(date); // 2016-11-14
System.out.println(time); // T24
Of course, the transformation to Java-8-types is in general possible but keep in mind that the conversion of PlainTime to LocalTime via the method toTemporalAccessor() will map the time value 24:00 to 00:00 (lossy conversion).
This is continuation to one of my previous question where I am not able to parse the date which is resolved now. In the below code, I have a date string and I know the time zone for the date string even though the string itself doesn't contain it. Then I need to convert the date into EST time zone.
String clientTimeZone = "CST6CDT";
String value = "Dec 29 2014 11:36PM";
value=StringUtils.replace(value, " ", " ");
DateTimeFormatter df = DateTimeFormat.forPattern("MMM dd yyyy hh:mma").withZone(DateTimeZone.forID(clientTimeZone));
DateTime temp = df.parseDateTime(value);
System.out.println(temp.getZone().getID());
Timestamp ts1 = new Timestamp(temp.getMillis());
DateTime date = temp.withZoneRetainFields(DateTimeZone.forID("EST"));//withZone(DateTimeZone.forID("EST"));
Timestamp ts = new Timestamp(date.getMillis());
System.out.println(ts1+"="+ts);
When I am running the code I am expecting ts1 to remain same and ts to be up by 1 hr. But iam getting below which I don't understand. I thought EST is one hour ahead of CST and so if it is 11 in CST, it should be 12 in EST. Also there seems to be offset by about eleven and half hours. Any clues on what I am missing.
2014-12-30 11:06:00.0=2014-12-30 10:06:00.0
I think the below code will help you.
String clientTimeZone = "CST6CDT";
String toStimeZone = "EST";
String value = "Dec 29 2014 11:36PM";
TimeZone fromTimeZone = TimeZone.getTimeZone(clientTimeZone);
TimeZone toTimeZone = TimeZone.getTimeZone(toStimeZone);
Calendar calendar = Calendar.getInstance();
calendar.setTimeZone(fromTimeZone);
SimpleDateFormat sf = new SimpleDateFormat("MMM dd yyyy KK:mma");
Date date = sf.parse(value);
calendar.setTime(date);
System.out.println(date);
calendar.add(Calendar.MILLISECOND, fromTimeZone.getRawOffset() * -1);
if (fromTimeZone.inDaylightTime(calendar.getTime())) {
calendar.add(Calendar.MILLISECOND, calendar.getTimeZone().getDSTSavings() * -1);
}
calendar.add(Calendar.MILLISECOND, toTimeZone.getRawOffset());
if (toTimeZone.inDaylightTime(calendar.getTime())) {
calendar.add(Calendar.MILLISECOND, toTimeZone.getDSTSavings());
}
System.out.println(calendar.getTime());
Copied from : http://singztechmusings.wordpress.com/2011/06/23/java-timezone-correctionconversion-with-daylight-savings-time-settings/
The method withZoneRetainFields() preserves the fields in the timezone CST (= UTC-06) hence your local timestamp (as LocalDateTime) but combines it with a different timezone (EST = UTC-05) which is one hour ahead in offset and result in a different instant. You should it interprete it this way: The same local time happens one hour earlier in New York compared to Chicago.
The rule is to subtract positive offsets and to add negative offsets in order to make timestamp representations of instants comparable (normalizing to UTC offset).
Alternatively: Maybe you don't want this but want to preserve the instant instead of the local fields. In this case you have to use the method withZone().
Side notice: Effectively, you compare the instants represented by the variables temp and date and finally use your default timezone to print these instants in the JDBC-escape-format (explanation - you implicitly use Timestamp.toString()). I would rather recommend to use a dedicated instant formatter for this purpose or simpler (to have the offsets in focus):
System.out.println(temp.toInstant() + " = " + date.toInstant());