SimpleDateFormat
and Calendar
uses the JVM default timezone (unless you set a different one on them), and the default timezone can be different in each device/machine/environment. Not only that, this default can be changed without notice, even at runtime, so it's better to always make it explicit which one you're using.
When you do things like:
Calendar c = Calendar.getInstance(Locale.getDefault());
dateFormatter.setTimeZone(c.getTimeZone());
monthFormat.setTimeZone(TimeZone.getTimeZone(TimeZone.getDefault().getDisplayName()));
The Calendar
is created with the default timezone, so dateFormatter
will also have the same zone. So does monthFormat
, and also the other formatters you created. The only formatter set to a different zone is the first one (which is set to UTC).
Also, the second formatter is redundant (it does the same thing that the first one is already doing: parsing the String
to a Date
), so you can remove it.
Assuming that your input is a String
with the value 2018-09-30T13:45:00Z
: the Z
in the end indicates that this date is in UTC. So you should parse it using a formatter set to UTC. So, instead of using c.getTimeZone()
and TimeZone.getDefault()
, you should use only TimeZone.getTimeZone("UTC")
.
For the output, you must set the formatters with the timezone you want to convert to. If the timezone is "EDT", set to it (but don't use exactly "EDT", see below). If you want to use the JVM default, use TimeZone.getDefault()
- just check this value before, to make sure the default is what you need.
Just keep in mind that short names like "EDT" and "EST" are not real timezones. Those abbreviations are ambiguous and not standard. Prefer to use IANA timezones names (always in the format Region/City
, like America/New_York
or Europe/Berlin
).
So, when you do TimeZone.getTimeZone("EDT")
, it usually returns "GMT" (because "EDT" is not recognized, and "GMT" is returned as default). That's because "EDT" is used by more than one timezone, so you must choose specifically which one you're using (I'm using America/New_York
in these examples).
Another detail is that in the first 2 formatters you use hh
, which means "hour of am/pm" (values from 1 to 12), but the input doesn't have AM/PM designators to properly resolve this. You need to change it to HH
("hour of day", with values from 0 to 23).
// input is in UTC
TimeZone inputZone = TimeZone.getTimeZone("UTC");
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
formatter.setTimeZone(inputZone);
Date localStartDate = formatter.parse(startTime);
Date localEndDate = formatter.parse(endTime);
...
// removed the second formatter (it was redundant)
// output is in EST (America/New_York)
// or use TimeZone.getDefault() to get JVM default timezone
TimeZone outputZone = TimeZone.getTimeZone("America/New_York");
SimpleDateFormat monthFormat = new SimpleDateFormat("MMM");
monthFormat.setTimeZone(outputZone);
...
SimpleDateFormat dateFormat = new SimpleDateFormat("dd");
dateFormat.setTimeZone(outputZone);
...
SimpleDateFormat dayNameFormat = new SimpleDateFormat("EEEE");
dayNameFormat.setTimeZone(outputZone);
...
SimpleDateFormat timeFormat = new SimpleDateFormat("hh:mm a");
timeFormat.setTimeZone(outputZone);
...
System.out.println("My Start date and end date==>>>" + startTimeName + " " + endTimeName);
With this, you're explicity using UTC for input and a specific timezone for output, instead of relying on the JVM default timezone (which can be different in each device and you can't control).
The output is:
My Start date and end date==>>>08:15 AM 09:45 AM
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).
First you can use a org.threeten.bp.Instant
to parse the input, because it's in UTC (designated by the Z
in the end). Then you use a org.threeten.bp.ZoneId
to convert it to a org.threeten.bp.ZonedDateTime
:
// output timezone
// or use ZoneId.systemDefault() to get JVM default timezone
ZoneId zone = ZoneId.of("America/New_York");
// parse the inputs
ZonedDateTime startDate = Instant.parse(startTime).atZone(zone);
ZonedDateTime endDate = Instant.parse(endTime).atZone(zone);
Then you can use these objects to get the other fields:
// get month name
System.out.println(startDate.getMonth().getDisplayName(TextStyle.SHORT, Locale.getDefault()));
This is equivalent to MMM
pattern, and it will print the month name in the default locale. If you want the month name in a specific language, just use another java.util.Locale
value (such as Locale.ENGLISH
or any other one as described in the javadoc).
The org.threeten.bp.format.TextStyle
defines if the month name will be narrow (usually just one letter), short (usually 2 or 3 letters) or full (the full name). The output varies according to the locale used.
I personally prefer to not use the default locale, because it can be changed without notice, even at runtime. It's always better to specify the locale you want.
To get the day of month, you can choose to get it as an int
or as a formatted String
(using a org.threeten.bp.format.DateTimeFormatter
):
// get day of month as int
int day = startDate.getDayOfMonth(); // 30
// get day of month as formatted string
String dayStr = DateTimeFormatter.ofPattern("dd").format(startDate); // 30
To get the day of week, it's similar to the code used to get the month:
// get day of week
System.out.println(startDate.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault()));
The same logic applies here: the TextStyle
defines how the name will be (in this case, FULL
is equivalen to EEEE
, and it prints the full name), and the locale defines the language used.
Finally, to get the corresponding time, you can use another DateTimeFormatter
:
// get time
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("hh:mm a");
System.out.println(fmt.format(startDate)); // 08:15 AM
System.out.println(fmt.format(endDate)); // 09:45 AM
This will the date/time in the timezone you chose for the output.
If you're going to use the JVM default (ZoneId.systemDefault()
), just check its value before to make sure it's the one you want (it might not be because this can be changed at runtime, so it's always better to specify one).