I am attempting to show transactions over a certain time period using Jodatime.
Our server requires a start date and end date to be in UTC (which is probably obvious). Therefore any business logic around these is using DateTime object with the timezone set to DateTimeZone.UTC
, e.g.
mStartDate = DateTime.now(UTC).withTimeAtStartOfDay();
That works well except when it comes to display the time I don't know how to augment it for the local (system default) timezone. Ideally I would like to use the DateUtils
formatDateRange function passing in two local timestamps. But the getMillis()
function doesn't seem to account for local offsets:
I have also tried this:
mTimePeriodTitle.setText(DateUtils.formatDateRange(mContext, f, mStartDate.getMillis(),
mEndDate.getMillis(), DateUtils.FORMAT_SHOW_TIME,
TimeZone.getDefault().getID()).toString());
But it hasn't made any difference. So my question is how can I get a nicely formatted local date range with 2 UTC timestamps?
If your DateTime
is in UTC and you want to convert it to another timezone, you can use the withZone
method to do the conversion.
For the examples below, my default timezone is America/Sao_Paulo
(you can check yours using DateTimeZone.getDefault()
):
// create today's date in UTC
DateTime mStartDate = DateTime.now(DateTimeZone.UTC).withTimeAtStartOfDay();
// date/time in UTC
System.out.println(mStartDate); // 2017-06-13T00:00:00.000Z
// date/time in my default timezone (America/Sao_Paulo)
System.out.println(mStartDate.withZone(DateTimeZone.getDefault())); // 2017-06-12T21:00:00.000-03:00
The output is:
2017-06-13T00:00:00.000Z
2017-06-12T21:00:00.000-03:00
Note that the withZone
method correctly converts the date and time to my timezone (in America/Sao_Paulo
the current offset is UTC-03:00
), so it was adjusted accordingly.
If you want to get just the time (hour/minute/second), you can use toLocalTime()
method:
System.out.println(mStartDate.withZone(DateTimeZone.getDefault()).toLocalTime()); // 21:00:00.000
The output is:
21:00:00.000
If you want another format (for example, don't print the 3 digits of fraction-of-second), you can use a DateTimeFormatter
. The good thing is that you can set a timezone in the formatter, so you don't need to convert the DateTime
:
// create formatter for hour/minute/second, set it with my default timezone
DateTimeFormatter fmt = DateTimeFormat.forPattern("HH:mm:ss").withZone(DateTimeZone.getDefault());
System.out.println(fmt.print(mStartDate)); // 21:00:00
The output is:
21:00:00
To get your range, you can use one of the methods above with your DateTime
's (mStartDate
and mEndDate
), and use the DateTimeFormatter
to change to whatever format you need.
PS: what I think you're missing when using getMillis()
is that both datetimes (in UTC and in default timezone) represents the same instant. You are just converting this instant to a local time, but the millis is the same (think that, right now, at this moment, everybody in the world are in the same instant (the same millis), but their local times might be different depending on where they are). So, when converting a UTC DateTime
to another timezone, we're just finding what is the local time in that zone, that corresponds to the same millis.
You can check this using the getMillis()
method on both objects:
System.out.println(mStartDate.getMillis()); // 1497312000000
System.out.println(mStartDate.withZone(DateTimeZone.getDefault()).getMillis()); // 1497312000000
Note that, even if I convert the object to another timezone, the millis remains the same (1497312000000
). That's because both represent the same instant, I'm just moving them to another timezone where the respective local time is different.
Joda-Time it's being discontinued and replaced by the new APIs, so I don't recommend start a new project with it. If that's your case, you can consider using the new Date/Time API, but if you have a big codebase using Joda or don't want to migrate it now, you can desconsider the rest of the answer.
Anyway, even in joda's website it says: "Note that Joda-Time is considered to be a largely “finished” project. No major enhancements are planned. If using Java SE 8, please migrate to java.time (JSR-310).".*
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. I'm not sure if it's already available to all Android versions (but see the alternative below).
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 a way to use it, with 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 get the current date at start of the day in UTC, you can do:
// UTC's today at start of the day
ZonedDateTime utc = LocalDate.now(ZoneOffset.UTC).atStartOfDay(ZoneOffset.UTC);
System.out.println(utc); // 2017-06-13T00:00Z
First I did LocalDate.now(ZoneOffset.UTC)
to find the current local date in UTC. If I use just LocalDate.now()
, it'll get the current date in my default timezone, which is not what we want (it might be different from UTC, depending on where - and when - you are and what the default timezone is).
Then I used atStartOfDay(ZoneOffset.UTC)
to get the start of the day at UTC. I know it sounds redundant to use UTC twice, but the API allows us to use any timezone in this method, and IMO it makes explicit what timezone we want (if the date is in a timezone with Daylight Saving changes, the start of day might not be midnight - the timezone parameter is to guarantee that the correct value is set).
The output is:
2017-06-13T00:00Z
To convert to my default timezone, I can use ZoneId.systemDefault()
, which in my case returns America/Sao_Paulo
. To convert it and get only the local time part, just do:
System.out.println(utc.withZoneSameInstant(ZoneId.systemDefault()).toLocalTime()); // 21:00
The output is:
21:00
If you want to change it, you can also use a formatter:
// formatter for localtime (hour/minute/second)
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("HH:mm:ss");
System.out.println(fmt.format(utc.withZoneSameInstant(ZoneId.systemDefault()))); // 21:00:00
The output is:
21:00:00