I'm trying to convert a date in RFC 1123 format to number of milliseconds from epoch in Java.
My usecase is that I've uploaded a file to my pCloud storage (directly from my browser) and then from java, request the REST API to retrieve the last modified datetime of this file.
The string I've received is "Fri, 08 Apr 2022 15:57:48 +0000".
But from my computer, the file last modification is at 17:57:48.
But I'm in Europe/Paris, so that I'm at timezone offset +2.
I tried to do:
String modified = "Fri, 08 Apr 2022 15:57:48 +0000";
DateTimeFormatter formatter = DateTimeFormatter.RFC_1123_DATE_TIME;
LocalDateTime localDateTime = LocalDateTime.parse(modified, formatter);
ZonedDateTime zdt = ZonedDateTime.of(localDateTime, ZoneId.systemDefault());
At this point, if I output variables:
System.out.println(localDateTime);
System.out.println(zdt);
It displayed:
2022-04-08T15:57:48
2022-04-08T15:57:48+02:00[Europe/Paris]
Now to convert to milliseconds from epoch time, I've tried:
localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
zdt.toInstant().toEpochMilli();
localDateTime.toInstant(OffsetDateTime.now().getOffset()).toEpochMilli();
And all these give
1649426268000
And if I use the following formatter :
new SimpleDateFormat("YYYY/MM/dd HH:mm:ss").format(long milliseconds).
It display:
2022/04/08 15:57:48
So it missing my timezone offset?! (the "+02:00[Europe/Paris]").
I found this solution:
TimeZone tz = TimeZone.getTimeZone(ZoneId.systemDefault());
long milli = zdt.toInstant().toEpochMilli() + tz.getOffset(new Date().getTime())
And formmating milli as just before with the same SimpleDateFormat, I have:
2022/04/08 17:57:48
which is the correct value.
Is there a cleaner way to have the correct long millisecond to epoch from my string date in RFC 1123 format?
Especially I think in my solution I have to do something like
tz.getOffset(extact date from "modified" string)
because the offset is not the same according to DST (summer or winter), and I hope this use case must be natively managed with all the Class of Java ?
You are using a LocalDateTime, which stores only date and time components. Then you proceed to apply the local time zone. Your local time zone is not relevant to this task, so should not be used.
Instead, since your input contains a date, time, and offset, you should parse to an OffsetDateTime.
String modified = "Fri, 08 Apr 2022 15:57:48 +0000";
DateTimeFormatter formatter = DateTimeFormatter.RFC_1123_DATE_TIME;
OffsetDateTime odt = OffsetDateTime.parse(modified, formatter);
Then you can use either of the following to get milliseconds since the epoch:
odt.toInstant().toEpochMilli()
or
odt.toEpochSecond() * 1000
Either will give you the correct value of 1649433468000. See similar code run live at IdeOne.com.
(Note, there is no toEpochMilli method directly on an OffsetDateTime, so either go through an Instant first, or get seconds and multiply by 1000.)
In Java 8 I need a way to get the local datetime (GMT+1) from a GMT datetime in ISO 8601 format.
A simple example:
Client sends me (Server) this datetime "2020-01-11T23:00:00.000Z"
Client sends me this when the user choose the 12 Jan 2020 from the datepicker. Is the 12 Jan for GMT+1 but the day before for GMT.
For the reason above then I know that for me this datetime is not the 11 Jan 2020 but 12 Jan 2020 in GMT+1.
So I need this value "2020-01-12T00:00:00.000"
To be precise I don't need to print this with simpleDateFormat but just covert "2020-01-11T23:00:00.000Z" to "2020-01-12T00:00:00.000" in a java.util.Date class field
Thanks.
The problem is that the source system took the pure date value, but added time at midnight, then converted that to UTC, but you want the pure date value in a java.util.Date, which by default prints in your local time zone, i.e. the JVM's default time zone.
So, you have to parse the string, revert the value back to the time zone of the source system, the treat that local time as a time in your own JVM's default time zone.
You can do that like this, showing all the intermediate types:
String sourceStr = "2020-01-11T23:00:00.000Z";
ZoneId sourceTimeZone = ZoneOffset.ofHours(1); // Use real zone of source, e.g. ZoneId.of("Europe/Paris");
// Parse Zulu date string as zoned date/time in source time zone
Instant sourceInstant = Instant.parse(sourceStr);
ZonedDateTime sourceZoned = sourceInstant.atZone(sourceTimeZone);
// Convert to util.Date in local time zone
ZonedDateTime localZoned = sourceZoned.withZoneSameLocal(ZoneId.systemDefault());
Instant localInstant = localZoned.toInstant();
Date localDate = Date.from(localInstant); // <== This is your desired result
// Print value in ISO 8601 format
String localStr = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS").format(localDate);
System.out.println(localStr);
Output
2020-01-12T00:00:00.000
The code can of course be merged together:
String input = "2020-01-11T23:00:00.000Z";
Date date = Date.from(Instant.parse(input).atZone(ZoneOffset.ofHours(1))
.withZoneSameLocal(ZoneId.systemDefault()).toInstant());
System.out.println(date);
Output
Sun Jan 12 00:00:00 EST 2020
As you can see, the date value is correct, even though I'm in the US Eastern time zone.
I have a field that is defined as TIMESTAMP WITH TIME ZONE.
The value to be saved starts off as: "09-23-2019 10:03:11 pm" in the zone of US/Hawaii.
This is what I am trying to save to the DB (all of the date information plus the Zone)
The database stores time information in UTC format.
As of now, the date is being stored in the DB so that it looks like this:
DAYS
---------------------------------------------------------------------------
23-SEP-19 10.03.11.000000 PM -05:00
23-SEP-19 10.03.11.000000 PM -05:00
During the processing, it runs through this code:
dateStr: the date (as seen above)
ZoneLoc: 'US/Hawaii'
public Calendar convDateStrWithZoneTOCalendar(String dateStr,
String ZoneLoc) throws Exception {
// convert the string sent in from user (which uses AM/PM) to one that uses military time (24HR)
// it
String formattedDate = null;
DateFormat readFormat = new SimpleDateFormat(this.getPattern());
DateFormat writeFormat = new SimpleDateFormat("MM-dd-yyyy'T'HH:mm:ss'Z'");
writeFormat.setTimeZone(TimeZone.getTimeZone(ZoneLoc));
Date date = null;
date = readFormat.parse(dateStr);
formattedDate = writeFormat.format(date);
// see if you can parse the date needed WITH the TimeZone
Date d;
SimpleDateFormat sdf = new SimpleDateFormat("MM-dd-yyyy'T'HH:mm:ss'Z'");
sdf.setTimeZone(TimeZone.getTimeZone(ZoneLoc));
d = sdf.parse(formattedDate);
Calendar cal = Calendar.getInstance();
cal.setTime(d);
system.out.println(" ZONELOC VALUE " + ZoneLoc);
system.out.println(" RETURNED VALUE " + cal );
return cal;
}
The calendar info that is returned is:
ZONELOC VALUE IS US/Hawaii
RETURNED VALUE IS
java.util.GregorianCalendar[time=1577678591000,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Chicago",offset=-21600000,dstSavings=3600000,useDaylight=true,transitions=235,lastRule=java.util.SimpleTimeZone[id=America/Chicago,offset=-21600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2019,MONTH=11,WEEK_OF_YEAR=1,WEEK_OF_MONTH=5,DAY_OF_MONTH=29,DAY_OF_YEAR=363,DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=5,AM_PM=1,HOUR=10,HOUR_OF_DAY=22,MINUTE=3,SECOND=11,MILLISECOND=0,ZONE_OFFSET=-21600000,DST_OFFSET=0]
It looks as though US/Hawaii is not being set in the RETURNED VALUE.
What can I do to be sure that this gets set?
After that, I can place it in the DB and see if the setting will "stick" and not revert back to America/Chicago
Update
#Patrick H - thanks for the input. I made the change with the pattern you specified and was able to save the data. It now looks like this:
2017-08-02 13:38:49 TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [26] as [TIMESTAMP] - [java.util.GregorianCalendar[time=1569294191000,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Chicago",offset=-21600000,dstSavings=3600000,useDaylight=true,transitions=235,lastRule=java.util.SimpleTimeZone[id=America/Chicago,offset=-21600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2019,MONTH=8,WEEK_OF_YEAR=39,WEEK_OF_MONTH=4,DAY_OF_MONTH=23,DAY_OF_YEAR=266,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=4,AM_PM=1,HOUR=10,HOUR_OF_DAY=22,MINUTE=3,SECOND=11,MILLISECOND=0,ZONE_OFFSET=-21600000,DST_OFFSET=3600000]]
The data in the DB looks like this:
23-SEP-19 10.03.11.000000 PM -05:00
The Zone is still America/Chicago even through US/Hawaii was specified. How can one get US/Hawaii to stick and not revert back to America/Chicago?
According to this output:
java.util.GregorianCalendar[time=1569294191000,...
The time value above (which means 1569294191000 milliseconds since unix epoch (1970-01-01T00:00Z)) is equivalent to 09-23-2019 10:03 PM in Chicago. That's because readFormat is using the system's default timezone (which is probably America/Chicago, just check the value of TimeZone.getDefault()).
To parse the input 09-23-2019 10:03:11 pm and consider it as the local time in Hawaii, you just need to set the corresponding timezone to the SimpleDateFormat instance (in this case, to readFormat, as it needs to know in what timezone the input date is - as you didn't set any, it uses the system's default). You also don't need the other formatters (writeFormat and sdf), only one formatter can be used to get the corresponding date:
SimpleDateFormat parser = new SimpleDateFormat("MM-dd-yyyy hh:mm:ss a");
// the input is in Hawaii timezone
parser.setTimeZone(TimeZone.getTimeZone("US/Hawaii"));
Date date = parser.parse("09-23-2019 10:03:11 pm");
The date above will be equivalent to 10:03 PM in Hawaii. Actually, the date itself contains just the milliseconds from the unix epoch (date.getTime() returns 1569312191000) and has no format nor any timezone information.
You can then set it to a Calendar instance (don't forget to set the calendar's timezone):
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("US/Hawaii"));
cal.setTime(date);
It's been some time since I used oracle's timestamp with timezone type, but I think that'll be enough to save the correct values. The value of calendar is:
java.util.GregorianCalendar[time=1569312191000,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="US/Hawaii",offset=-36000000,dstSavings=0,useDaylight=false,transitions=7,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2019,MONTH=8,WEEK_OF_YEAR=39,WEEK_OF_MONTH=4,DAY_OF_MONTH=23,DAY_OF_YEAR=266,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=4,AM_PM=1,HOUR=10,HOUR_OF_DAY=22,MINUTE=3,SECOND=11,MILLISECOND=0,ZONE_OFFSET=-36000000,DST_OFFSET=0]
Java 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.
One of the main problems is how hard and confusing it is to work with different timezones.
If you're using Java 8, consider using the new java.time API. It's easier, less bugged and less error-prone than the old APIs.
If you're using Java <= 7, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, there's the ThreeTenABP (more on how to use it here).
The code below works for both.
The only difference is the package names (in Java 8 is java.time and in ThreeTen Backport (or Android's ThreeTenABP) is org.threeten.bp), but the classes and methods names are the same.
To parse the input 09-23-2019 10:03:11 pm you can use a DateTimeFormatter and parse it to a LocalDateTime - the input has no timezone information, so we consider only the date and time, and then we can convert it to a timezone.
// parse the input
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// parse AM/PM and am/pm
.parseCaseInsensitive()
// input pattern
.appendPattern("MM-dd-yyyy hh:mm:ss a")
// use English locale for am/pm symbols
.toFormatter(Locale.ENGLISH);
LocalDateTime dt = LocalDateTime.parse("09-23-2019 10:03:11 pm", fmt);
// convert to Hawaii timezone
ZonedDateTime hawaiiDate = dt.atZone(ZoneId.of("US/Hawaii"));
The most recent JDBC drivers have support to the new API (but only for Java 8, I guess), but if you still need to work with Calendar, you can easily convert a ZonedDateTime to it:
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("US/Hawaii"));
calendar.setTimeInMillis(hawaiiDate.toInstant().toEpochMilli());
In Java 8, you can also do:
Calendar calendar = GregorianCalendar.from(hawaiiDate);
If you need interoperability with the old Calendar and Date API's, you can use the new API internally to do the calculations and convert from/to the API's when needed.
According to SimpleDateFormat, I think your formatting string is wrong. You can also see in the returned value that the month, and day are wrong. MONTH=11,DAY_OF_MONTH=29
This is what you currently have:
23-SEP-19 10.03.11.000000 PM -05:00
I think the formatting string should be: 'dd-MMM-yy hh.mm.ss.SSSSSS a Z'
It also looks like the timezone issue could be because there is a colon inside it. The documentation for SimpleDateFormat indicates it needs to be in this format instead for a RFC 822 time zone: -0500 You may find it easier to use the General time zone component instead.
I have problems with formatting server time. Server is on central time I guess. I need to format following string 2016-08-22T10:29:22 in default zone (Central European Summer Time = GMT+2). I tried with Joda-Time library, I need to get 12:29:22, but I only managed to get same date with +02:00 the end, with code like this:
DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss");
DateTime dateTime = formatter.withZone(DateTimeZone.getDefault()).parseDateTime(time);
Output of this code is: 2016-08-22T10:29:22.000+02:00, when I try to dateTime.getHourOfDay(); - I getting 10 again.
Where am I going wrong?
2016-08-22T10:29:22 in default zone (Central European Summer Time = GMT+2).
10:29 in GMT+2 is 10:29 in GMT+2 and 8:29 in GMT, and the 2016-08-22T10:29:22 simply lacks information of timezone. So either make server return timezone or manually add it (i.e. append Z) prior converting.
I ran into a strange issue. Here is a snippet of code that describes it:
DateTimeZone dtz = DateTimeZone.forOffsetHours(0);
DateTime dt = new DateTime(dtz);
System.out.println(dt);
System.out.println(dt.toDate());
the output is:
2012-02-29T17:24:39.055Z
Wed Feb 29 19:24:39 EET 2012
I'm located UTC+2, but this action is supposed to create a java.util.Date object which is initialized for UTC time. What am I missing?
Date doesn't know about a time zone at all - it only represents an instant in time (like Joda Time's Instant type). It's just a number of milliseconds since the Unix epoch. When you call Date.toString(), it always uses the system local time zone for converting that into a readable text form.
So there's nothing wrong here - just an expectations failure over either the meaning of java.util.Date or its toString() behaviour, or both.
(As an aside, prefer DateTimeZone.UTC over creating your own.)
To get a JDK Date that matches Joda's DateTimeconvert to LocalDateTimefirst.
As explained in the other answers, the time in milliseconds does not change depending on the timezone:
DateTime local = DateTime.now()
Date localJDK = local.toDate()
assert localJDK.getTime() == local.toInstant().getMillis()
DateTime differentTimeZone = DateTime.now(DateTimeZone.forID('America/Chicago'))
Date localJDK2 = differentTimeZone.toDate()
assert differentTimeZone.toInstant().getMillis() == localJDK2.getTime()
assert localJDK.getTime() == localJDK2.getTime()
Converting a LocalDateTime to Date will change that:
Date differentTimeZoneJDK = differentTimeZone.toLocalDateTime().toDate()
assert localJDK.getTime() != differentTimeZoneJDK.getTime()
The behaviour you want is this:
Date jdkDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dt.toString("yyyy-MM-dd HH:mm:ss"));
Like Jon noted, JDK date is time zone agnostic. Hope this helps someone.