Hot questions for Using Joda-Time in datetime conversion

Top Java Programmings / Joda-Time / datetime conversion

Question:

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?


Answer:

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.


Java new Date/Time API

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

Question:

I have a stored UTC timestamp in the database. When I retrieve that UTC timestamp I cast it into a String. I want to take that UTC Timestamp String and convert it to the device's local time using Joda Time. Anyone who could possibly help out with this. It would be very appreciated! Here is what I am doing right now:

                String date = ""+ds.child("datecreated").getValue();

                DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");

                DateTime dt = formatter.parseDateTime(date);

                DateTime dt2 = new DateTime(dt).toDateTime(DateTimeZone.getDefault());

                String personalDate = dt2.toString();

                dateTV.setText(personalDate);

                System.out.println("THIS IS THE FIRST TIME: " + dt + "THIS IS THE SECOND TIME: " + dt2); 

The problem is is that it is giving me the exact same time when I convert it to my local time, which it shouldn't be doing since it is being stored in UTC and I am converting to Eastern Standard Time which is my phone's default.


Answer:

To show that Andreas in the comment has hit the nail right on: I ran the following snippet in America/Coral_Harbour time zone (since I didn’t know your exact time zone, Eastern Standard Time is used in several (though fewer after 8 March when Eastern Daylight Time began)).

    String date = "2020-03-12T01:23:45.678+0000";

    System.out.println("This is the string:      " + date); 

    DateTime dt = new DateTime(date);
    DateTime dt2 = new DateTime(dt).toDateTime(DateTimeZone.getDefault());

    System.out.println("This is the first time:  " + dt); 
    System.out.println("This is the second time: " + dt2); 

Output is:

This is the string:      2020-03-12T01:23:45.678+0000
This is the first time:  2020-03-11T20:23:45.678-05:00
This is the second time: 2020-03-11T20:23:45.678-05:00

Compare the first two lines and notice that the conversion from UTC to EST has already happened when parsing the string.

As an aside, since your string is in ISO 8601 format, you don’t need to specify any formatter for parsing it. The DateTime(Object) constructor accepts it. But the same conversion happened in your parsing.

What happened in your code?

Repeating the quote from Andreas’ comment:

If the withOffsetParsed() has been called, then the resulting DateTime will have a fixed offset based on the parsed time zone. Otherwise the resulting DateTime will have the zone of this formatter, but the parsed zone may have caused the time to be adjusted.

So your formatter has the default time zone of your device, and therefore also the DateTime object that you get from parsing.

So when creating dt2 you were converting from Eastern Standard Time to Eastern Standard Time and therefore got the same date-time again.

Link: Documentation of DateTimeFormatter.parseDateTime()

Question:

Is there any API available for datetime conversion between different timezones similar to google map's timezone api's.

Explaination for datetime conversion between different timezones -

Given the base timezone, datetime in base timezone and target timezone, an api which returns datetime in target timezone. (Which also considers concepts like dst as well)

Edit 1 -

Based On the negative reviews received on this question, had to give some more details for the need of this question and the efforts already wasted with different approaches.

Also with this question I am Expecting to know about some online available api, which could be hit all the time rather than depending on a library like ZonedDateTime provided with java8.

Some issues with ZonedDateTime of java 8-

See below scala code -

import java.time.{LocalDateTime, ZoneId, ZoneOffset, ZonedDateTime}
import java.time.format.DateTimeFormatter

val istanbul: ZoneId = ZoneId.of("Europe/Istanbul");
val str: String = "2017-03-29 17:00:00";
val str1: String = "2017-03-24 17:00:00";
val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
val localtDateAndTime: LocalDateTime = LocalDateTime.parse(str, formatter);
val localtDateAndTime1: LocalDateTime = LocalDateTime.parse(str1, formatter);
val dateAndTimeInIstanbul: ZonedDateTime = ZonedDateTime.of(localtDateAndTime, istanbul );
val dateAndTimeInIstanbul1: ZonedDateTime = ZonedDateTime.of(localtDateAndTime1, istanbul );


val utcDate: ZonedDateTime = dateAndTimeInIstanbul.withZoneSameInstant(ZoneOffset.UTC);
val utcDate1: ZonedDateTime = dateAndTimeInIstanbul1.withZoneSameInstant(ZoneOffset.UTC);

System.out.println("Original date and time in a particular timezone : " + dateAndTimeInIstanbul);
System.out.println("Converted date and time in UTC : " + utcDate);

System.out.println("Origianl date and time in a particular timezone : " + dateAndTimeInIstanbul1);
System.out.println("Converted date and time in UTC : " + utcDate1);

output generated by the above code is -

```

Original date and time in a particular timezone : 2017-03-29T17:00+03:00[Europe/Istanbul]
Converted date and time in UTC : 2017-03-29T14:00Z


Origianl date and time in a particular timezone : 2017-03-24T17:00+02:00[Europe/Istanbul]
Converted date and time in UTC : 2017-03-24T15:00Z

```

Now the problem is that from 2017 to 2020 there won't be any dst considered in Istanbul, which seems like is not considered in this ZonedDateTime library.

So would like to know about some other alternate. Preferably a web based API.


Answer:

Now the problem is that from 2017 to 2020 there won't be any dst considered in Istanbul, which seems like is not considered in this ZonedDateTime library.

That is not a problem with the library. If timezone information is incorrect (and I say "if"), then it is due to an issue with the timezone rules that your JVM is using.

Here is what the latest timezone rules from IANA say for for Istanbul:

# Zone  NAME            GMTOFF  RULES   FORMAT  [UNTIL]
Zone    Europe/Istanbul 1:55:52 -       LMT     1880
                        1:56:56 -       IMT     1910 Oct
                        2:00    Turkey  EE%sT   1978 Oct 15
                        3:00    Turkey  +03/+04 1985 Apr 20
                        2:00    Turkey  EE%sT   2007
                        2:00    EU      EE%sT   2011 Mar 27  1:00u
                        2:00    -       EET     2011 Mar 28  1:00u
                        2:00    EU      EE%sT   2014 Mar 30  1:00u
                        2:00    -       EET     2014 Mar 31  1:00u
                        2:00    EU      EE%sT   2015 Oct 25  1:00u
                        2:00    1:00    EEST    2015 Nov  8  1:00u
                        2:00    EU      EE%sT   2016 Sep  7
                        3:00    -       +03

The last line is saying that from 3am on 2016 Sep 7 onwards, the time offset is UTC + 3 hours. The source is the 2017a release of the IANA timezone database.

When I run zdump -V Europe/Istanbul on my Linux system, it agrees with this. (The timezone rule files are distributed via the package manager, assuming you keep your system patched.)

Now Java is a bit different. The Java libraries don't use the system timezone rules. Instead, they rely on a file derived from the IANA data (aka the Olson data) that is part of the Java installation. If you are running an old version of Java, you may have an old version of the timezone data. But there are two solutions to that:

  1. Update to the latest release of your version of Java. (This won't for end-of-lifed versions of Java; i.e. Java 7 and earlier ... as of March 2017.)
  2. Download the latest timezone database from IANA, and use the Oracle Timezone Updater Tool to update your JVM / JRE installation.