I encounter strange behaviour with Jodatime and Android. I want to parse string:
2014-05-19T18:13:00.000+02:00
to DateTime, and get year, month, hours to int. I started with some test on IntelliJ Studio, and I done something like that:
String date = "2014-05-19T18:13:00.000+02:00";
DateTime dateTime = new DateTime(date);
System.out.println(dateTime.toString());
System.out.println(dateTime.getYear());
System.out.println(dateTime.getMonthOfYear());
System.out.println(dateTime.getDayOfMonth());
System.out.println(dateTime.getHourOfDay());
System.out.println(dateTime.getMinuteOfHour());
System.out.println(dateTime.getMillis());
Which gave me correct answers:
2014-05-19T18:13:00.000+02:00
2014
5
19
18
13
1400515980000
Now, when I changed IDE to Android Studio, and do the same:
String dateT = "2014-05-19T18:13:00.000+02:00";
DateTime dateTime = new DateTime(dateT);
Lo.g(dateTime.getHourOfDay() + "");
Lo.g(dateTime.toString());
my results are:
16
2014-05-19T16:13:00.000Z
For some reason DateTime on Android Studio / Android not take into account the timezone which is +2:00.
I can not find solution for this. Also there is no simple method "addTimeZone" in Joda.
How to display correct time with DataTime? I tried LocalDateTime, construct DateTime with DateTimeZone.getDefault() (which gaves me UTF...)
Since you said that you use the same Joda-Time version on both platforms and regarding the fact that Joda-Time has its own timezone repository independent from system timezone data, there is probably only one explanation left why you observe different behaviour: Different input either explicit or implicit. Let's go into details:
Well, you say, obviously there is the same input given the same input string:
"2014-05-19T18:13:00.000+02:00"
So we have the same (explicit) input. But wait, there is another thing: implicit default settings which can also be considered as kind of input in an abstract way. You use the constructor DateTime(Object). This constructor first delegates to super constructor of class BaseDateTime as you can see in the source code.
public DateTime(Object instant) {
super(instant, (Chronology) null);
}
The javadoc of this super-constructor says:
"Constructs an instance from an Object that represents a datetime,
using the specified chronology. If the chronology is null, ISO in the
default time zone is used.
The recognised object types are defined in ConverterManager and
include ReadableInstant, String, Calendar and Date."
So finally we see that Joda-Time uses the default timezone. This is really the only possibility for different behaviour I can see by studying the source code and the documentation. All other things are equal: Same library version and same explicit string input and same test scenario.
Conclusion: You have different default timezones on your platforms. Note that you get the same instant on both platforms however, just represented with different local timestamps and offsets due to different internal timezone/offset setting inside the DateTime-object.
Update: I have tested the zone overriding behaviour with this code:
String date = "2014-05-19T19:13:00.000+03:00"; // same instant as yours (just with offset +03)
DateTime dateTime = new DateTime(date);
System.out.println(dateTime.toString());
// output: 2014-05-19T18:13:00.000+02:00 (in my default timezone Europe/Berlin)
So the default timezone has precedence over any offset in string input. If you instead want to use the parsed offset then look at using the DateTimeFormatter and its method withOffsetParsed().
Related
I recently moved to Java 8 to, hopefully, deal with local and zoned times more easily.
However, I'm facing an, in my opinion, simple problem when parsing a simple date.
public static ZonedDateTime convertirAFecha(String fecha) throws Exception {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(
ConstantesFechas.FORMATO_DIA).withZone(
obtenerZonaHorariaServidor());
ZonedDateTime resultado = ZonedDateTime.parse(fecha, formatter);
return resultado;
}
In my case:
fecha is '15/06/2014'
ConstantesFechas.FORMATO_DIA is 'dd/MM/yyyy'
obtenerZonaHorariaServidor returns ZoneId.systemDefault()
So, this is a simple example. However, the parse throws this exception:
java.time.format.DateTimeParseException: Text '15/06/2014' could not
be parsed: Unable to obtain ZonedDateTime from TemporalAccessor:
{},ISO resolved to 2014-06-15 of type java.time.format.Parsed
Any tips? I've been trying different combinations of parsing and using TemporalAccesor, but without any luck so far.
This does not work because your input (and your Formatter) do not have time zone information. A simple way is to parse your date as a LocalDate first (without time or time zone information) then create a ZonedDateTime:
public static ZonedDateTime convertirAFecha(String fecha) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate date = LocalDate.parse(fecha, formatter);
ZonedDateTime resultado = date.atStartOfDay(ZoneId.systemDefault());
return resultado;
}
This is a bug, see JDK-bug-log. According to that information the problem was solved for Java 9 and Java 8u20. Try to download the latest Java 8 - version. Today on 2014-05-12: There is an early access release 8u20 available.
UPDATE:
Personally I think, since you only have and expect "dd/MM/yyyy" as pattern you should use LocalDate as your primary type as #assylias has already proposed. Regarding your context, it is almost sure a design failure to use ZonedDateTime. What do you want to do with objects of this type? I can only think of specialized timezone calculations as use-case. And you cannot even directly store these ZonedDateTime-objects in a database, so this type is far less useful than many people believe.
What I described as your use-case problem is indeed a new aspect introduced with Java-8 compared with the old GregorianCalendar-class (which is an all-in-one-type). Users have to start thinking about choosing the proper temporal type for their problems and use-cases.
In simple words, the line
ZonedDateTime.parse('2014-04-23', DateTimeFormatter.ISO_OFFSET_DATE_TIME)
throws an exception:
Text '2014-04-23' could not be parsed at index 10
java.time.format.DateTimeParseException: Text '2014-04-23' could not be parsed at index 10
It looks like a bug for me.
I used this workaround:
String dateAsStr = '2014-04-23';
if (dateAsStr.length() == 10) {
dateAsStr += 'T00:00:00';
}
ZonedDateTime.parse(dateAsStr, DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneId.systemDefault()));
If coming from Google:
Instead of doing:
ZonedDateTime.from(new Date().toInstant());
Try this:
ZonedDateTime.ofInstant(new Date(), ZoneId.of("UTC"));
Just an example conversions, I believe some folks will get the exception below
(java.time.DateTimeException: Unable to obtain LocalDateTime from TemporalAccessor: 2014-10-24T18:22:09.800Z of type java.time.Instant)
if they try
LocalDateTime localDateTime = LocalDateTime.from(new Date().toInstant());
to resolve the issue, please pass in Zone -
LocalDateTime localDateTime = LocalDateTime.from(new Date()
.toInstant().atZone(ZoneId.of("UTC")));
Background
I'm using threetenbp backport for Android (here), to handle various time related data operations.
One of them is to convert a time to a different timezone (current to UTC and back).
I know this is possible if you use something like that:
LocalDateTime now = LocalDateTime.now();
LocalDateTime nowInUtc = now.atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneId.of("UTC")).toLocalDateTime();
This works just fine, and it's also quite easy to do the opposite.
The problem
I'm trying to avoid initialization of the library, which loads quite a large file of zones into it. I've already figured out how to handle various date/time related operations without this, except this case of converting to UTC and back.
What I got has an error of a whole 1 hour off from the correct conversion.
What I've tried
This is what I've found and tried:
// getting the current time, using current time zone:
Calendar cal = Calendar.getInstance();
LocalDateTime now = LocalDateTime.of(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY),
cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND), cal.get(Calendar.MILLISECOND) * 1000000);
//the conversion itself, which is wrong by 1 hour in my tests:
LocalDateTime alternativeNowInUtc = now.atZone(ZoneOffset.ofTotalSeconds(TimeZone.getDefault().getRawOffset() / 1000)).withZoneSameInstant(ZoneId.ofOffset("UTC", ZoneOffset.ofHours(0))).toLocalDateTime();
The question
What's wrong exactly with what I wrote? How can I get an alternative code for converting the time without initialization of the library?
Given an instance of LocalDateTime as input, how can I convert it from current timezone to UTC, and from UTC to current timezone ?
This is probably happening because your JVM's default timezone is in Daylight Saving Time (DST).
To get the correct offset, you should check if the timezone is in DST and add this to the offset:
Calendar cal = Calendar.getInstance();
TimeZone zone = TimeZone.getDefault();
// if in DST, add the offset, otherwise add zero
int dst = zone.inDaylightTime(cal.getTime()) ? zone.getDSTSavings() : 0;
int offset = (zone.getRawOffset() + dst) / 1000;
LocalDateTime alternativeNowInUtc = now.atZone(ZoneOffset.ofTotalSeconds(offset))
.withZoneSameInstant(ZoneId.ofOffset("UTC", ZoneOffset.ofHours(0)))
.toLocalDateTime();
Another way to create the nowInUtc as a LocalDateTime is to create an Instant from the Calendar:
LocalDateTime nowInUtc = Instant.ofEpochMilli(cal.getTimeInMillis())
.atOffset(ZoneOffset.ofHours(0)).toLocalDateTime();
Actually, you don't need the Calendar at all, just use Instant.now() to get the current instant:
LocalDateTime nowInUtc = Instant.now().atOffset(ZoneOffset.ofHours(0)).toLocalDateTime();
Or, even shorter, use an OffsetDateTime directly:
LocalDateTime nowInUtc = OffsetDateTime.now(ZoneOffset.ofHours(0)).toLocalDateTime();
Not sure if any of those loads timezone data, it's up to you to test.
And I think that the constant ZoneOffset.UTC can be used instead of ZoneOffset.ofHours(0), because it won't load tz data as well (but I haven't tested it).
Final solution
Assuming the default timezone is in Israel (TimeZone.getDefault() is Asia/Jerusalem):
// April 11th 2018, 3 PM (current date/time in Israel)
LocalDateTime now = LocalDateTime.of(2018, 4, 11, 15, 0, 0);
TimeZone zone = TimeZone.getDefault();
// translate DayOfWeek values to Calendar's
int dayOfWeek;
switch (now.getDayOfWeek().getValue()) {
case 7:
dayOfWeek = 1;
break;
default:
dayOfWeek = now.getDayOfWeek().getValue() + 1;
}
// get the offset used in the timezone, at the specified date
int offset = zone.getOffset(1, now.getYear(), now.getMonthValue() - 1,
now.getDayOfMonth(), dayOfWeek, now.getNano() / 1000000);
ZoneOffset tzOffset = ZoneOffset.ofTotalSeconds(offset / 1000);
// convert to UTC
LocalDateTime nowInUtc = now
// conver to timezone's offset
.atOffset(tzOffset)
// convert to UTC
.withOffsetSameInstant(ZoneOffset.UTC)
// get LocalDateTime
.toLocalDateTime();
// convert back to timezone
LocalDateTime localTime = nowInUtc
// first convert to UTC
.atOffset(ZoneOffset.UTC)
// then convert to your timezone's offset
.withOffsetSameInstant(tzOffset)
// then convert to LocalDateTime
.toLocalDateTime();
The answer of carlBjqsd is okay, just awkward and should maybe a little bit clearer.
Why one hour difference
See the final solution of #carlBjqsd: It uses the expression
int offset = zone.getOffset(1, now.getYear(), now.getMonthValue() - 1, now.getDayOfMonth(), dayOfWeek, now.getNano() / 1000000);
instead of
getRawOffset().
That has caused the difference of one hour you observed. Applications have normally no need only to calculate with the raw offset which leaves out the dst-offset for some periods of the year. It is only the total offset which matters in any conversion from local timestamp to UTC and back. The main purpose of the fine-granular differentiation of partial offsets like raw offsets or dst offsets is just proper naming of the zone (shall we call it standard time or not?).
Misleading title of question: "without loading zones"
No, you can never avoid loading zones if you want to convert between local timestamps and UTC using zones. Your real question is rather: How to avoid loading the zones of ThreetenABP and to use/load the zones of the Android platform instead. And your motivation seems to be:
I'm trying to avoid initialization of the library, which loads quite a
large file of zones into it
Well, I have not measured which zone data have more impact on performance. I can only say based on my studies and knowledge of the source code of involved libraries that java.time and ThreetenBP load the whole file TZDB.dat into a binary array cache in memory (as first step) and then pick out the relevant part for a single zone (i.e. interprete a part of the binary data array via deserialization into a set of zone rules and finally a single ZoneId). Old Java platforms instead work with a set of different zi-files (one for each zone), and I suspect that Android zones behave in a similar way (but please correct me if you know that detail better).
If only ONE zone shall be used at all then the traditional approach of using separate zone files might be better but once you want to iterate over all available zones then it is better to have only one zone file at all.
Personally, I think that the performance aspect is neglectable. If you use the Android zones you will also have some loading times, inevitably. In case you really want to speed up the initialization time of ThreetenABP, you should consider to load it in a background thread.
Are Android zones and ThreetenABP zones equivalent?
Generally not. Both timezone repositories might give the same offset for a concrete zone. And often they do so but sometimes there will be differences which are not under your control. Although both timezone repositories use the data of iana.org/tz in final consequence, differences are mainly caused by possible different versions of tzdb-data. And you cannot control which version of zone data exists on the Android platform because this is up to the user of mobile phone how often he/she updates the Android OS. And this is also true for the data of ThreetenABP. You can offer the latest version of your app including the latest version of ThreetenABP but you cannot control if the mobile device user really updates the app.
Other reasons why to care about choosing the proper tz repository?
Beyond performance and initialization times, there is indeed one special scenario which might be interesting for the choice. If the Android OS is somehow old and uses an outdated version of zone rules then some mobile phone users do not update their operating system but manipulate the device clock in order to compensate the wrong timezone data. This way, they still get the correct local times on the mobile phone (in ALL apps).
In this scenario, ThreetenABP does not offer a good solution because combining their correct zone data with wrong device clock will result in wrong local timestamps (annoying the user). This has been a problem for example in Turkey which changed the dst-rules not a long time ago.
Using just the old calendar and timezone API of Android (in the package java.util) can take into account the problem so correct local timestamps are created. However, if an app communicates UTC-times (for example as count of millisecs since 1970-01-01T00Z) to other hosts (for example servers) then the wrong device clock is still a problem.
We could say why bother because the user has done "nonsense" with the device configuration but we also live in real world and should think about how to make even such users happy. So when thinking about a solution I had introduced at least in my calendar library Time4A methods like SystemClock.inPlatformView() which uses the (probably) most actual zone data and obtains the correct UTC clock based on the assumption that the user will at least observe correct local device time (whatever he/she had done to achieve this goal, either by updating the OS or by clock/zone configuration). I am quite happy with avoiding the old calendar and zone API altogether this way. My API even allows to simultaneously use both zone repositories:
Timezone.of("java.util.TimeZone~Asia/Jerusalem") // uses Android data
Timezone.of("Asia/Jerusalem") // uses Time4A data
Maybe you can profit from these ideas when to find/develop suitable helper classes for your usage of ThreetenABP. Time4A is open source.
Jargon:
CET : Central European Time.
Daylight saving time : UTC+1 in winter, UTC+2 in summer.
In the CET region, Android and iOS libraries behave as if there never was a daylight saving time before 1996 while .Net behaves as it has always existed.
To illustrate this behaviour, here's some code written in .NET/Java executed on CET machines.
In .Net :
static void PrintDate(String input)
{
String format = "yyyy-MM-ddTHH:mm:ss.fffffffzzz";
var date = DateTime.ParseExact(input, format, CultureInfo.InvariantCulture);
var output = date.ToString(format, CultureInfo.InvariantCulture);
System.Diagnostics.Debug.WriteLine(input + " => " + output);
}
In Android and iOS (just the java example but both behave in the same manner).
static void printDate(String input)
{
String format = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSZZZZZ";
Date date = new SimpleDateFormat(format).parse(input);
String output = new SimpleDateFormat(format).format(date);
Log.i("MyTag", input + " => " + output);
}
A simple call to the method :
public static void Main()
{
PrintDate("1993-10-06T00:00:00.0000000+02:00");
PrintDate("1993-12-06T00:00:00.0000000+02:00");
PrintDate("1996-10-06T00:00:00.0000000+02:00");
PrintDate("1996-12-06T00:00:00.0000000+02:00");
}
Here's the output in .Net :
1993-10-06T00:00:00.0000000+02:00 => 1993-10-06T00:00:00.0000000+02:00
1993-12-06T00:00:00.0000000+02:00 => 1993-12-05T23:00:00.0000000+01:00
1996-10-06T00:00:00.0000000+02:00 => 1996-10-06T00:00:00.0000000+02:00
1996-12-06T00:00:00.0000000+02:00 => 1996-12-05T23:00:00.0000000+01:00
And here's the output in Android/iOs
1993-10-06T00:00:00.0000000+02:00 => 1993-10-05T23:00:00.0000000+01:00
1993-12-06T00:00:00.0000000+02:00 => 1993-12-05T23:00:00.0000000+01:00
1996-10-06T00:00:00.0000000+02:00 => 1996-10-06T00:00:00.0000000+02:00
1996-12-06T00:00:00.0000000+02:00 => 1996-12-05T23:00:00.0000000+01:00
How could I homogenize the behaviour between these three polatforms?
In Java, SimpleDateFormat uses the JVM default timezone if you don't set one in it. (check what's yours with TimeZone.getDefault()).
So 1993-10-06T00:00+02:00 is converted to 1993-10-05T23:00+01:00 probably because the default timezone is one with +01:00 offset in October 1993, while in October 1996 it was in DST (+02:00). My guess is Europe/Paris, but it can be other, as lots of timezones uses CET as a short name.
Anyway, just check the history of DST in Paris and note that in October 1993 the offset was +01:00 while in October 6th 1996 it was +02:00. So it's a good guess, but any timezone with the same rules will have the same behaviour.
Also, +02:00 is an offset, not a timezone. Just being +02:00 doesn't necessarily mean that it's CET during DST, because there's more than one timezone that uses this offset. And short names like CET are ambiguous and not standard, so you should consider using IANA timezones names (always in the format Region/City, like America/Sao_Paulo or Europe/Paris).
Anyway, if you don't want to have variable offsets, you shouldn't rely on the JVM default timezone, because it can have DST effects and the offset will vary according to the date (and the default timezone can be changed without notice, even at runtime). One way to avoid it, is to set a fixed offset in the formatter:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSZZZZZ");
// set the offset +02:00, so all dates will be formatted using this
// (instead of the current offset for the JVM default timezone)
sdf.setTimeZone(TimeZone.getTimeZone("GMT+02:00"));
Date date = sdf.parse("1993-10-06T00:00:00.0000000+02:00");
System.out.println(sdf.format(date));
Just some quick notes:
SimpleDateFormat doesn't work well with more than 3 digits after the decimal point. In the case above, it works fine because it's all zeroes, but if you have any value different from zero and more than 3 digits, you can have strange, wrong, unexpected results. In this case, you should remove the extra digits, because this class simply can't handle more than 3 (and it also doesn't work well for formatting).
I'm testing with JDK 7, so the pattern ZZZZZ doesn't work for parsing. Instead, I've used yyyy-MM-dd'T'HH:mm:ss.SSSSSSSXXX, which parses the inputs above, and formats the date to 1993-10-06T00:00:00.0000000+02:00 (but note that the X is not available in JDK 6)
If you want the output in another offset, just change it accordingly in the getTimeZone method. If you want UTC, use getTimeZone("UTC")
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.
In Android, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. To make it work, you'll also need the ThreeTenABP (more on how to use it here).
One improvement in this new API is the support to nanoseconds (up to 9 digits after the decimal point), so it can handle your inputs without the problems of SimpleDateFormat.
This new API also has lots of new date/time types suited for different situations. In this case, you have a date and time in a specific offset, and wish to maintain it. So, the best class is a org.threeten.bp.OffsetDateTime:
OffsetDateTime odt = OffsetDateTime.parse("1993-10-06T00:00:00.0000000+02:00");
System.out.println(odt.toString()); // 1993-10-06T00:00+02:00
Note that the toString() method omits the seconds and nanoseconds if they are zero. If you want the output exactly like the input (with 7 digits after the decimal point), just create a org.threeten.bp.format.DateTimeFormatter:
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSXXX");
System.out.println(fmt.format(odt)); // 1993-10-06T00:00:00.0000000+02:00
To change this to another offset (or to UTC), use a org.threeten.bp.ZoneOffset:
// convert to UTC
odt = odt.withOffsetSameInstant(ZoneOffset.UTC);
System.out.println(fmt.format(odt)); // 1993-10-05T22:00:00.0000000Z
// convert to another offset (+01:00)
odt = odt.withOffsetSameInstant(ZoneOffset.ofHours(1));
System.out.println(fmt.format(odt)); // 1993-10-05T23:00:00.0000000+01:00
I am trying to parse date string with timezone using this code for tests:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmZZZZZ", Locale.US);
Calendar calendar = Calendar.getInstance();
calendar.setTime(sdf.parse("2017-07-26T06:00-06:00"));
int offset = calendar.getTimeZone().getRawOffset();
I am trying to change timezone from -06 to +09, but offset always contains 10800000.
How to parse date with timezone correctly (I need time and timezone both)?
Note: -06:00 is an offset, not a timezone - those 2 concepts are related, but they are different things (more on that below).
The problem with SimpleDateFormat and Calendar is that they use the system's default timezone, so even though you parse a date with a different offset (like -06:00), the resulting Calendar will have the default timezone (you can check what zone is by calling TimeZone.getDefault()).
That's just one of the many problems and design issues of this old API.
Fortunately, there's a better alternative, if you don't mind adding a dependency to your project (in this case, I think it's totally worth it). In Android you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, you'll also need the ThreeTenABP to make it work (more on how to use it here).
To work with offsets, you can use the org.threeten.bp.OffsetDateTime class:
// parse the String
OffsetDateTime odt = OffsetDateTime.parse("2017-07-26T06:00-06:00");
This will parse all the fields correctly (date/time and offset). To get the offset value, similar to calendar.getTimeZone().getRawOffset(), you can do:
// get offset in milliseconds
int totalSeconds = odt.getOffset().getTotalSeconds() * 1000;
I had to multiply by 1000 because calendar returns the value in milliseconds, but ZoneOffset returns in seconds.
To convert this to another offset (+09:00), it's straightforward:
// convert to +09:00 offset
OffsetDateTime other = odt.withOffsetSameInstant(ZoneOffset.ofHours(9));
As I said, timezone and offset are different things:
offset is the difference from UTC: -06:00 means "6 hours behind UTC" and +09:00 means "9 hours ahead UTC"
timezone is a set of all the different offsets that a region had, has and will have during its history (and also when those changes occur). The most common cases are Daylight Saving Time shifts, when clocks change 1 hour back or forward in a certain region. All these rules about when to change (and what's the offset before and after the change) are encapsulated by the timezone concept.
So, the code above works fine if you're working with offsets and wants to convert to a different one. But if you want to work with a timezone, you must convert the OffsetDateTime to a ZonedDateTime:
// convert to a timezone
ZonedDateTime zdt = odt.atZoneSameInstant(ZoneId.of("Asia/Tokyo"));
// get the offset
totalSeconds = zdt.getOffset().getTotalSeconds() * 1000;
The getOffset() method above will check the history of the specified timezone and get the offset that was active in that corresponding instant (so, if you take a date during DST, for example, the offset (and also date and time) will be adjusted accordingly).
The API uses IANA timezones names (always in the format Region/City, like America/Sao_Paulo or Europe/Berlin).
Avoid using the 3-letter abbreviations (like CST 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().
You can also use the system's default timezone with ZoneId.systemDefault(), but this can be changed without notice, even at runtime, so it's better to explicity use a specific one.
So time formatting and adjusting has always been my biggest nemesis in programing and I'm having some issues in Android/Java that I can't figure out. I get a timestamp from a server that is formatted in UTC (here's an example 2016-06-17T18:30:00-07:00. Now this time needs to get formatted to the users local time (so for a user in PST it should show as 11:30AM) but so far whatever I try I either get 1AM or 6:30PM (so I know I'm doing something wrong I just don't know what). Here's what I've been trying to do
public static DateTime convertISOStringToDate(String inputString) {
//setup the ISO Date Formatter with GMT/UTC format
DateTimeFormatter formatter = ISODateTimeFormat.dateTimeParser()
.withLocale(Locale.US)
.withZone(DateTimeZone.forOffsetHours(0));
DateTime dateTime = formatter.parseDateTime(inputString);
//now convert the datetime object to a local date time object
DateTimeFormatter localFormatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss")
.withZone(DateTimeZone.getDefault());
String localString = localFormatter.print(dateTime);
DateTime localDateTime = localFormatter.parseDateTime(localString);
return localDateTime;
So at this point I'm getting 1:30AM, so I know I'm messing it up somewhere in the conversion process but I can't figure it out. I've been trying to google around but so far haven't found much that use the ISODateTimeFormat parser so they don't work either when I try them.
You seem to have a basic mis-understanding of how dates are represented.
A date (in almost every known programming language / library) is represented internally as an offset from a specific 'origin time', known as the 'Epoch'.
In java.util.Date as well as joda dates, the internal representation is the number of milliseconds since midnight, Jan 1, 1980, UTC.
As such, a date does not have a timezone. You only introduce a timezone when you format a date (turn it into a String representation of the date).
You have made the common mistake of parsing a String into a date object, serializing (printing) it back out with a different timezone than the the original string indicated, and then parsing back into a date again, expecting something to have changed. If you do that correctly, you will get back exactly the same date that you started with.
In your case, the "localString" that you get shows the correct time in the local timezone. I'm in EDT, which is UTC-4:00, and I correctly get 2016-06-17 21:30:00 as the result.
As I said, parsing that back into a DateTime, and then looking at it is useless, because:
You'll get the same DateTime back that you started with
Your IDE (or whatever you're using to inspect the DateTime) probably isn't showing what you expect.
You should re-evaluate what you're doing here, and whether you really need to "convert" the DateTime, or to just parse it, and really understand how date formatting works with respect to timezones.