Android Studio 4.0, Java 6.
My GMT is GMT + 03:00
defaultConfig {
minSdkVersion 17
targetSdkVersion 28
const val LONDON_TIME_ZONE_ID = "Europe/London"
const val DEFAULT_DATE_JSON_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.000'Z'"
fun fromDateToLondonDate(localDate : Date) : Date? {
val dateLondonAsString = fromDate2LondonDateAsString(localDate)
val timeZoneLondon : TimeZone = TimeZone.getTimeZone(LONDON_TIME_ZONE_ID)
val dateLondon = getDateFromString(dateLondonAsString, timeZoneLondon)
if (BuildConfig.DEBUG)
Log.d("", "fromDateToLondonDate:" +
"\nlocalDate = $localDate"+
"\ndateLondonAsString = $dateLondonAsString"+
"\ndateLondon = $dateLondon")
return dateLondon
}
fun fromDate2LondonDateAsString(date : Date) : String? {
val timeZoneLondon : TimeZone = TimeZone.getTimeZone(LONDON_TIME_ZONE_ID)
val formatter: DateFormat = SimpleDateFormat(DEFAULT_DATE_JSON_FORMAT)
formatter.setTimeZone(timeZoneLondon)
val dateAsString : String = formatter.format(date)
return dateAsString
}
fun getDateFromString(str: String?, tz: TimeZone?): Date? {
return try {
val sdf = SimpleDateFormat(DEFAULT_DATE_JSON_FORMAT);
sdf.setTimeZone(tz)
val date = sdf.parse(str)
return date
} catch (e: ParseException) {
//e.printStackTrace();
null
}
}
and here result:
fromDateToLondonDate:
localDate = Tue Jun 16 13:14:59 GMT+03:00 2020
dateLondonAsString = 2020-06-16T11:14:59.000Z
dateLondon = Tue Jun 16 13:14:59 GMT+03:00 2020
as you can see the local date is Tue Jun 16 13:14:59 GMT+03:00 2020
and success convert to London date as String -> dateLondonAsString = 2020-06-16T11:14:59.000Z
Nice.
But I need to convert String to London Date. And I use method getDateFromString and result is not correct:
dateLondon = Tue Jun 16 13:14:59 GMT+03:00 2020
The correct London Date must be : Tue Jun 16 11:14:59 GMT+03:00 2020
Why getDateFromString not correct convert from string to date?
java.time and ThreeTenABP
To get a date-time object with a specific time zone turn to java.time, the modern Java date and time API. I am writing Java code, it’s what I can, and I trust you to translate:
ZoneId london = ZoneId.of("Europe/London");
Date date = getOldfashionedDateFromSomewhere();
System.out.println("Original Date: " + date);
ZonedDateTime dateTimeLondon = date.toInstant().atZone(london);
System.out.println("Date-time in London: " + dateTimeLondon);
Example output:
Original Date: Tue Jun 16 13:14:59 GMT+03:00 2020
Date-time in London: 2020-06-16T11:14:59+01:00[Europe/London]
A Date hasn’t got, as in cannot have a time zone. Yes, I know, when you print it, thereby implicitly calling its toString method, it prints a time zone. What happens is that it grabs the default time zone of your JVM and uses it for rendering the string. So as long as your JVM’s time zone setting is GMT+03:00, all of your Date objects will always print this time zone.
So if (in some another project) I need date with time zone
the better way is use java.time.ZonedDateTime (java 8) ?
It's at least the way I would recommend. As I said, using Date is not a way. Some ways are:
The poor and old-fashioned way: using GregorianCalendar.
The better way: using DateTime from Joda-Time. If accepting an external dependency, I'd prefer ThreeTenABP, though.
The good way: ZonedDateTime from java.time.
The advanced way: using Time4J. I haven't got experience, so I'd rather not recommend for or against. If your project has requirements that go beyond what java.time offers, I'd certainly research this option.
What went wrong in your code
First, you should probably not set your time zone to GMT+03:00. While your time zone uses this GMT offset now, that doesn’t mean that it always has nor that it always will. For correct results for historic and future dates use a real time zone ID such as Africa/Nairobi or Europe/Moscow, that is, in the region/city format. Just as you used Europe/London for British time (fortunately not GMT+00:00).
I have already mentioned the second point: You cannot use a Date for a date and time with a time zone because a Date hasn’t got a time zone. A Date is a point in time, no more, no less.
Next, 2020-06-16T11:14:59.000Z is wrong for the time in London. 11:14:59 is correct for the time of day. Z means UTC or offset 0 from UTC and is wrong since Great Britain uses summer time (DST) and hence is at offset +01:00 in June (as the output from my code above also says). The time at offset Z would have been 10:14:59. In other words, your time is 1 hour off. This definition of Z is part of the ISO 8601 standard. Your JSON format is ISO 8601 format. I include a link at the bottom. Since Z is an offset, you should always format and parse it as such and never hardcode it as a literal in your format pattern string.
Your conversion back from string to Date exhibits the same error, so it balances out with the error in converting to a String, and you succeed in getting an equivalent Date object back.
It's old Android project. And I can't use java.time. I can use only java.util.Date
You can certainly use java.time in old Android projects for old Android versions too.
In Java 8 and later and on newer Android devices (from API level 26) the modern API comes built-in.
In non-Android Java 6 and 7 get the ThreeTen Backport, the backport of the modern classes (ThreeTen for JSR 310; see the links at the bottom).
On (older) Android use the Android edition of ThreeTen Backport. It’s called ThreeTenABP. And make sure you import the date and time classes from org.threeten.bp with subpackages.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Java Specification Request (JSR) 310, where java.time was first described.
ThreeTen Backport project, the backport of java.time to Java 6 and 7 (ThreeTen for JSR-310).
ThreeTenABP, Android edition of ThreeTen Backport
Question: How to use ThreeTenABP in Android Project, with a very thorough explanation.
Wikipedia article: ISO 8601
All about java.util.Date on Jon Skeet’s coding blog
A related question about getting a Date with a time zone: TimeZone problem in Java
Joda-Time
Time4J - Advanced Date, Time, Zone and Interval Library for Java
Related
Having input date string could be possible as
"2020-01-25T21:59:27Z"
or
"Sat Jan 25 20:06:07 +0000 2020"
or a Long
and expect one to display is like
Jan 25, 2020
how to get the desired formatted date string?
update:
#Ole V.V provided a very good suggestion, it's just cannt apply it with android lib case.
but I guess there is no single format for all these three cases, so have to try out one by one. such as for the ISO8601 one to do something like:
return try {
val dateStr = "Sat Jan 25 20:06:07 +0000 2020". //ISO8601
val format = SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.getDefault())
val dsipFormat = SimpleDateFormat("MMM dd yyyy", Locale.getDefault()) // for display result
val date = format.parse(dateStr) // parse it to date
dsipFormat.format(date) // returning the display result
} catch (e: Exception) {
Log.e("+++", "+++ error: $e")
""
}
If there is better approach?
java.time and ThreeTenABP
My solution is to build three formatters for the three possible input formats and then for each input try the formatters in turn. For a simple demomstration of the idea:
DateTimeFormatter[] inputFormatters = {
DateTimeFormatter.ISO_INSTANT,
DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss xx yyyy", Locale.ROOT),
new DateTimeFormatterBuilder()
.appendValue(ChronoField.INSTANT_SECONDS)
.appendValue(ChronoField.MILLI_OF_SECOND, 3)
.toFormatter()
};
DateTimeFormatter displayFormatter
= DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.US);
for (String inputString : new String[] {
"2020-01-25T21:59:27Z",
"Sat Jan 25 20:06:07 +0000 2020",
"1566777888999"
}) {
// try the formatters in turn and see which one works
for (DateTimeFormatter formatter : inputFormatters) {
try {
ZonedDateTime dateTime = formatter.parse(inputString, Instant.FROM)
.atZone(ZoneId.systemDefault());
System.out.format("%-30s was parsed to %s%n",
inputString, dateTime.format(displayFormatter));
break;
} catch (DateTimeParseException ignore) {
// Ignore, try next format
}
}
}
In my time zone (Europe/Copenhagen) output from this snippet is:
2020-01-25T21:59:27Z was parsed to Jan 25, 2020
Sat Jan 25 20:06:07 +0000 2020 was parsed to Jan 25, 2020
1566777888999 was parsed to Aug 26, 2019
Since it is never the same date in all time zones, output will vary with time zone.
I am recommending java.time, the modern Java date and time API. I saw that you tagged the question simpledateformat, but the SimpleDateFormat class is notoriously troublesome and long outdated, so I recommend against using it. And I am exploiting the fact that your first format is standard ISO 8601 and that java.time has a built-in formatter for it, DateTimeFormatter.ISO_INSTANT.
My third input formatter, the one for the long value, regards the last three characters as milliseconds of the second and everything before it as seconds since the epoch. The net result is that it parses milliseconds since the epoch. A DateTimeFormatterBuilder was required to build this formatter.
A no-lib solution
I admit that I hate to write this. I would really have hoped that you could avoid the notoriously troublesome SimpleDateFormat class and its long outdated cronies like Date. Since I understand that yours is a no-lib app, both of Joda-Time and ThreeTenABP seem out of the question. Sorry. In this case since there is no way that SimpleDateFormat can parse a long, my approach is to take a taste of the string to determine the format and choose my way of parsing based on that.
DateFormat inputIso = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
// This format resembles the output from Date.toString
DateFormat inputDatelike
= new SimpleDateFormat("EEE MMM dd HH:mm:ss ZZZ yyyy", Locale.ROOT);
DateFormat displayFormat
= DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.US);
displayFormat.setTimeZone(TimeZone.getDefault());
for (String inputString : new String[] {
"2020-01-25T21:59:27Z",
"Sat Jan 25 20:06:07 +0000 2020",
"1566777888999"
}) {
Date parsedDate;
if (Character.isDigit(inputString.charAt(0))) {
if (inputString.contains("-")) {
parsedDate = inputIso.parse(inputString);
} else {
// long number of millis
parsedDate = new Date(Long.parseLong(inputString));
}
} else {
parsedDate = inputDatelike.parse(inputString);
}
System.out.format("%-30s was parsed to %s%n",
inputString, displayFormat.format(parsedDate));
}
Output is exactly the same as before:
2020-01-25T21:59:27Z was parsed to Jan 25, 2020
Sat Jan 25 20:06:07 +0000 2020 was parsed to Jan 25, 2020
1566777888999 was parsed to Aug 26, 2019
Please be aware that here invalid input may cause either a NumberFormatException or a ParseException, so catch both. And only resort to this solution if there is no way that you can avoid it.
The line displayFormat.setTimeZone(TimeZone.getDefault()); is technically superfluous, but it makes explicit that the output depends on the time zone, and maybe more importantly, it tells you where you need to modify the code if you want output in a different time zone.
Question: Doesn’t java.time require Android API level 26?
java.time works nicely on both older and newer Android devices. It just requires at least Java 6.
In Java 8 and later and on newer Android devices (from API level 26) the modern API comes built-in. Only in this case use the method reference Instant::from instead of the constant Instant.FROM.
In non-Android Java 6 and 7 get the ThreeTen Backport, the backport of the modern classes (ThreeTen for JSR 310; see the links at the bottom).
On (older) Android use the Android edition of ThreeTen Backport. It’s called ThreeTenABP. And make sure you import the date and time classes from org.threeten.bp with subpackages.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Java Specification Request (JSR) 310, where java.time was first described.
ThreeTen Backport project, the backport of java.time to Java 6 and 7 (ThreeTen for JSR-310).
ThreeTenABP, Android edition of ThreeTen Backport
Question: How to use ThreeTenABP in Android Project, with a very thorough explanation.
val startHour = SimpleDateFormat("HH:mm").format(pickup.begin())
The pickup.begin value is "Wed Apr 10 10:00:00 GMT+03:00 2019", so I need the start hour to be 10:00 +3h -> 13:00, but I get startHour value of 10:00.
I don't know how to add the GMT value to hour.
No, you’ve misunderstood. Wed Apr 10 10:00:00 GMT+03:00 2019 is (the textual representation of) a java.util.Date the value of which equals 2019-04-10T07:00 UTC. It seems your default time zone is GMT+03:00, and Date is trying to be friendly to you and print the time in this time zone, which is why it prints 10:00:00. 13:00 would certainly be incorrect no matter if you wanted the time in UTC or in your own default time zone.
The Date class returned from pickup.begin() is poorly designed and long outdated, so you may want to consider if a type from java.time, the modern Java date and time API, could be returned instead. It may also make the matter clearer.
Alternatively, convert that java.util.Date object to its modern counterpart, a java.time.Instant. Look for new conversion methods added to the old classes.
Instant instant = pickup.begin().toInstant() ; // Converting legacy `Date` object to modern `Instant` object.
Search Stack Overflow and read the Oracle Tutorial to learn more about Instant, OffsetDateTime, and ZonedDateTime classes.
You can use java.time on older Android versions if you add ThreeTenABP to your Android project. It’s the Android adaptation of the backport of java.time.
Links
ThreeTenABP, Android edition of ThreeTen Backport
Question: How to use ThreeTenABP in Android Project, with a very thorough explanation.
Try adding a Locale to your simpledateformat, matching the +3 timezone.
val startHour = SimpleDateFormat("HH:mm", Locale.Germany).format(pickup.begin())
You can try something like this
I take 'date' as string here
String date="something"
val sdf = SimpleDateFormat("HH:mm")
sdf.timeZone = TimeZone.getTimeZone("UTC")
val gmt = sdf.parse(date)
If this is your case
Date date="somevalue"
then
val gmt = sdf.format(date)
gmt will return you your local time
I want to test a method in a Date utility class that gets a Date from a String. The String that is passed is 1980-03-26T00:00:00.000+0200 and I want to compare the resulting date with assertEquals. The test failed with this output:
org.junit.ComparisonFailure:
Expected :Wed Mar 26 00:00:00 PST 1980
Actual :Wed Mar 26 00:00:00 SGT 1980
Here's my test:
INITIAL_DATA_DATE_FROM_STRING = "1980-03-26T00:00:00.000+0200";
EXPECTED_DATA_DATE_FROM_STRING = "Wed Mar 26 00:00:00 PST 1980";
// inside the method ...
date = DateUtils.getDateFromString(INITIAL_DATA_DATE_FROM_STRING);
assertEquals(EXPECTED_DATA_DATE_FROM_STRING, String.valueOf(date));
Here's the method that I am testing:
public static Date getDateFromString(String dateAsString) {
return getDateFromString(dateAsString, "dd/MM/yyyy");
}
public static Date getDateFromString(String dateAsString, String dateFormat) {
Date formattedDate = null;
if (StringUtils.notNull(dateAsString)) {
DateFormat format = new SimpleDateFormat(dateFormat);
try {
formattedDate = parseString(dateAsString, format);
} catch (ParseException e) {
try {
formattedDate = parseString(dateAsString, new SimpleDateFormat("yyyy-MM-dd"));
} catch (ParseException e1) {
// handle exception code
}
}
}
return formattedDate;
}
So the unit test is not timezone independent. Is there anyway to set the default timezone just for unit testing?
java.time or Joda-Time
The Date and SimpleDateFormat classes have design problems, so consider not using them. The first version of your question was tagged jodatime, and if you are using Joda-Time in your project, that’s already a sizable improvement. Joda-Time gives you all the functionality you need, so your method should probably return an approrpiate type from Joda-Time rather than an old-fashioned Date.
Joda-Time is also on its way to retirement, though, and its successor is java.time, the modern Java date and time API. So the latter is an option you may consider no matter if you were already using Joda-Time or not. Since your string contains a UTC offset, one option is to return an OffsetDateTime. To test a method that does this:
assertEquals(OffsetDateTime.of(1980, 3, 26, 0, 0, 0, 0, ZoneOffset.ofHours(2)),
DateUtils.getOffsetDateTimeFromString("1980-03-26T00:00:00.000+0200"));
Your example and your code may give the impression that you are only after the date from the string and don’t care about the time of day nor the offset. If this is correct, your method may return a LocalDate and be tested in this way:
assertEquals(LocalDate.of(1980, Month.MARCH, 26),
DateUtils.getLocalDateFromString("1980-03-26T00:00:00.000+0200"));
The latter will also free you from all time zone considerations. In both cases please note that I am passing date-time objects to assertEquals, not strings.
Your unit test is correct: your method is returning the wrong time
The failure you reported in your question was that while the expected Date from your method was Wed Mar 26 00:00:00 PST 1980, the actual value was Wed Mar 26 00:00:00 SGT 1980. It is correct that these differ. Midnight in Baja California, Yukon, Washington, Nevada, California and other places observing Pacific Time is not the same point in time as midnight in China.
Can I use java.time on Android?
Yes, java.time works nicely on older and newer Android devices. It just requires at least Java 6.
In Java 8 and later and on newer Android devices (from API level 26) the modern API comes built-in.
In Java 6 and 7 get the ThreeTen Backport, the backport of the new classes (ThreeTen for JSR 310; see the links at the bottom).
On (older) Android use the Android edition of ThreeTen Backport. It’s called ThreeTenABP. And make sure you import the date and time classes from org.threeten.bp with subpackages.
In case you don’t want your Android code to depend on a third-party library, you can still use java.time in your unit test. To test a method that returns an old-fashioned Date:
Instant expectedInstant = LocalDate.of(1980, Month.MARCH, 26)
.atStartOfDay(ZoneId.systemDefault())
.toInstant();
Date expectedDate = Date.from(expectedInstant);
Date actualDate = DateUtils.getDateFromString("1980-03-26T00:00:00.000+0200");
assertEquals(expectedDate, actualDate);
If using the backport (ThreeTen Backport and/or ThreeTenABP), the conversion from Instantot Date happens a little differently:
Date expectedDate = DateTimeUtil.toDate(expectedInstant);
Again note that I am comparing Date objects, not strings.
How to set the timezone for a SimpleDateFormat?
To answer the question in your title: use the setTimeZone method:
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXX");
df.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles")); // Pacific Time
System.out.println(df.parseObject("1980-03-26T00:00:00.000+0200"));
In this particular case it won’t make any difference, though, because the offset in the string is parsed and takes precedence over the time zone of the formatter.
Is there any way to set the default timezone just for unit testing?
There are a couple of options.
TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
Putting this into the beforeClass or before method of your unit test will set the time zone of your JVM to Pacific Time. It’s not bullet-proof since other code may set it to something else before the test finishes, but you may be able to control that that doesn’t happen. Normally I would discourage the use of the outdated TimeZone class. It too has design problems, but is the natural outdated choice if the methods you are testing are using the outdated SimpleDateFormat. One of the problems is it doesn’t report if the string passed is invalid. To obtain proper validation just go through the modern class:
TimeZone.setDefault(TimeZone.getTimeZone(ZoneId.of("America/Los_Angeles")));
Or using the backport:
TimeZone.setDefault(DateTimeUtils.toTimeZone(ZoneId.of("America/Los_Angeles")));
Links
Oracle tutorial: Date Time explaining how to use java.time.
Java Specification Request (JSR) 310, where java.time was first described.
ThreeTen Backport project, the backport of java.time to Java 6 and 7 (ThreeTen for JSR-310).
ThreeTenABP, Android edition of ThreeTen Backport
Question: How to use ThreeTenABP in Android Project, with a very thorough explanation.
I found that this gives a wrong date. but how i can not solve it. please someone help me.
I am new in android Development.
Thanks in advance;
String timestamp = "1538970640";
SimpleDateFormat formatter = new SimpleDateFormat("dd MMM 'at' hh:mm a z" );
String dateString = formatter.format(new Date(Long.parseLong(timestamp)));
This returns:
19 Jan at 01:29 AM GMT+06:oo
But it should be:
8 Oct at 9:50 AM GMT+06:00
The java.util.Date constructor accepts milliseconds since the Epoch, not seconds:
Allocates a Date object and initializes it to represent the specified number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT.
The following code which uses ms is working:
String timestamp = "1538970640000"; // use ms NOT s
SimpleDateFormat formatter = new SimpleDateFormat("dd MMM 'at' hh:mm a z" );
String dateString = formatter.format(new Date(Long.parseLong(timestamp)));
08 Oct at 05:50 AM CEST
Demo
Part of the problem you were facing is that your date format omitted the year component, which was actually coming up as 1970.
java.time and ThreeTenABP
I recommend you use java.time, the modern Java date and time API, for your date and time work.
DateTimeFormatter timestampFormatter = new DateTimeFormatterBuilder()
.appendValue(ChronoField.INSTANT_SECONDS)
.toFormatter();
DateTimeFormatter targetFormatter
= DateTimeFormatter.ofPattern("d MMM 'at' h:mm a z", Locale.ENGLISH);
String timestamp = "1538970640";
ZonedDateTime dateTime = timestampFormatter.parse(timestamp, Instant.FROM)
.atZone(ZoneId.systemDefault());
String dateString = dateTime.format(targetFormatter);
System.out.println(dateString);
Output is (when time zone is set to GMT+06:00, which by the way is not a true time zone):
8 Oct at 9:50 AM GMT+06:00
I am not very happy about converting date and time from one string format to another, though. In your app you should not handle date and time as strings but as proper date and time objects, for example Instant or ZonedDateTime. When you get a string from somewhere (a server?), parse it into a date-time object first thing. Only when you need to give string output, for example to the user, format your date and time into a string in the user’s time zone.
That said, java.time performs your conversion with just two formatters. No need to parse into a low-level long first.
Two more points:
Give your output formatter a locale to control the language used. Since AM and PM are hardly used in other languages than English, I figured that Locale.ENGLISH might be appropriate. You decide.
Since you want 8 Oct at 9:50 AM GMT+06:00, use just one d for day of month and one h for clock hour. Two digits will still be printed if the numbers go over 9, for example 10 Oct at 11:50 AM GMT+06:00.
What went wrong in your code?
Your number, 1538970640 (10 digits), denotes seconds since the epoch. This is the classical definition of a Unix timestamp. The Date constructor that you used expects milliseconds since the epoch. This is typical for the outdated Java date and time classes and methods. These years milliseconds since the epoch are typically 13 digits. As you can see, the modern Java date and time classes have better support for seconds here.
Question: Doesn’t java.time require Android API level 26?
java.time works nicely on both older and newer Android devices. It just requires at least Java 6.
In Java 8 and later and on newer Android devices (from API level 26) the modern API comes built-in. In this case, instead of the constant Instant.FROM use the method references Instant::from.
In non-Android Java 6 and 7 get the ThreeTen Backport, the backport of the modern classes (ThreeTen for JSR 310; see the links at the bottom).
On (older) Android use the Android edition of ThreeTen Backport. It’s called ThreeTenABP. And make sure you import the date and time classes from org.threeten.bp with subpackages.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Java Specification Request (JSR) 310, where java.time was first described.
ThreeTen Backport project, the backport of java.time to Java 6 and 7 (ThreeTen for JSR-310).
ThreeTenABP, Android edition of ThreeTen Backport
Question: How to use ThreeTenABP in Android Project, with a very thorough explanation.
When the string "2017-04-21T17:46:00Z" is passed into the first method the resulting formatted date string is "06:46 21 Apr 2017". Why is the hour moving by eleven hours? The input strings are being provided by an HTTP server application in JSON. I thought the Z suffix referred to Zulu, ie GMT.
private static final String DATE_TIME_FORMAT = "hh:mm dd MMM yyyy";
public static String formatTimestamp(String dateTimestamp) {
DateTime dateTime = getDateTimeFromTimestamp(dateTimestamp);
DateTimeFormatter fmt = DateTimeFormat.forPattern(DATE_TIME_FORMAT);
return fmt.print(dateTime);
}
private static DateTime getDateTimeFromTimestamp(String dateTimestamp) {
return new DateTime(dateTimestamp);
}
I suspect it relates to timezones but it's not clear how or where. The code is running on an Android device in the UK, in the GMT timezone.
I've made a test with java 7 and joda-time 2.7 (but not the Android's version)
That's how I could reproduce the problem:
// changing my default timezone (because I'm not in UK)
DateTimeZone.setDefault(DateTimeZone.forID("Europe/London"));
// calling your method
System.out.println(formatTimestamp("2017-04-21T17:46:00Z"));
The output is
06:46 21 Abr 2017
To check what's wrong, I've changed the date format to:
DATE_TIME_FORMAT2 = "hh:mm a dd MMM yyyy Z z zzzz";
Where a means "AM or PM", Z is the timezone offset/id, z is the timezone "short" name and zzzz is the timezone "long" name. Using this format, the output is:
06:46 PM 21 Abr 2017 +0100 BST British Summer Time
So the datetime created is 6PM, just one hour ahead of input, not eleven hours as you thought (actually if you change the format to HH instead of hh, the hours will be 18 instead of 06).
Also note the timezone fields: +0100 BST British Summer Time. The first part (+0100) means that this DateTime is one hour ahead of GMT, and BST British Summer Time means it's in British's Daylight Saving Time.
So, to have your output equals to your input, you have 2 alternatives:
1. Change your default timezone to UTC:
DateTimeZone.setDefault(DateTimeZone.UTC);
System.out.println(formatTimestamp("2017-04-21T17:46:00Z"));
The output will be:
05:46 21 Apr 2017
If you want to change the hours to be 17:46, change your date format, replacing hh by HH
2. Use the DateTime constructor that receives a DateTimeZone:
private static DateTime getDateTimeFromTimestamp(String dateTimestamp) {
// creates a DateTime in UTC
return new DateTime(dateTimestamp, DateTimeZone.UTC);
}
The output will be the same as alternative 1, but in this case you don't need to change the default timezone.
For me, alternative 2 makes more sense, because:
you don't need to change the default timezone (which can cause some mess in other parts of the application)
you already know that all dates handled by this code are in UTC time (because of the "Z" in the end)
Using java.time
The Answer by Hugo seems to be correct and informative. But FYI, the Joda-Time project is now in maintenance mode, with the team advising migration to the java.time classes. For Android, see the last bullet at bottom below.
Your input string is in standard ISO 8601 format. The java.time classes use standard formats when parsing & generating strings. So no need to specify a formatting pattern.
The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).
String input = "2017-04-21T17:46:00Z" ;
Instant instant = Instant.parse( input ) ;
instant.toString(): 2017-04-21T17:46:00Z
For more flexible formatting such as you desire, convert to an OffsetDateTime object were you can specify any offset-from-UTC in hours and minutes. We want UTC itself (an offset of zero) so we can use the constant ZoneOffset.UTC.
OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ;
odt.toString(): 2017-04-21T17:46Z
Define a formatting pattern to match your desired format. Note that you must specify a Locale to determine (a) the human language for translation of name of day, name of month, and such, and (b) the cultural norms deciding issues of abbreviation, capitalization, punctuation, separators, and such.
DateTimeFormatter f = DateTimeFormatter.ofPattern( "hh:mm dd MMM yyyy" , Locale.US ) ;
String output = odt.format( f ) ;
output: 05:46 21 Apr 2017
If you want to see this same moment through the lens of a region’s wall-clock time such as Europe/London or Pacific/Auckland, apply a time zone to get a ZonedDateTime.
Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as EST or IST or BST as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "Europe/London" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;
Note the time-of-day is an hour off because of Daylight Saving Time (DST).
zdt.toString(): 2017-04-21T18:46+01:00[Europe/London]
See this code run live at IdeOne.com.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
The ThreeTenABP project adapts ThreeTen-Backport (mentioned above) for Android specifically.
See How to use ThreeTenABP….