I have the following:
val startWithTZ = new DateTime("2016-10-01T00:00:00", DateTimeZone.UTC)
val endWithTZ = new DateTime("2016-10-01T11:00:00", DateTimeZone.UTC)
val intervalWithTZ = new Interval(startWithTZ, endWithTZ)
val startWithoutTZ = new DateTime("2016-10-01T00:00:00")
val endWithoutTZ = new DateTime("2016-10-01T11:00:00")
val intervalWithoutTZ = new Interval(startWithoutTZ, endWithoutTZ)
With this, I now do the following:
val utcInterval = new Interval(intervalWithoutTZ.getStart.toDateTime(DateTimeZone.UTC),
intervalWithoutTZ.getEnd.toDateTime(DateTimeZone.UTC))
What I see with the utcInterval is not what I was expecting:
2016-09-30T22:00:00.000Z/2016-10-01T09:00:00.000Z
Why is this? I was expecting the utcInterval to be:
2016-10-01T00:00:00.000Z/2016-10-01T11:00:00.000Z
Here is what I get when I use the Scala REPL:
scala> import org.joda.time._
import org.joda.time._
scala> val startWithoutTZ = new DateTime("2016-10-01T00:00:00")
startWithoutTZ: org.joda.time.DateTime = 2016-10-01T00:00:00.000+02:00
scala> val endWithoutTZ = new DateTime("2016-10-01T11:00:00")
endWithoutTZ: org.joda.time.DateTime = 2016-10-01T11:00:00.000+02:00
scala> val intervalWithoutTZ = new Interval(startWithoutTZ, endWithoutTZ)
intervalWithoutTZ: org.joda.time.Interval = 2016-10-01T00:00:00.000+02:00/2016-10-01T11:00:00.000+02:00
scala> val utcInterval = new Interval(intervalWithoutTZ.getStart.toDateTime(DateTimeZone.UTC),
| intervalWithoutTZ.getEnd.toDateTime(DateTimeZone.UTC))
utcInterval: org.joda.time.Interval = 2016-09-30T22:00:00.000Z/2016-10-01T09:00:00.000Z
scala>
Why is it not?
Logically, I've always felt that an Interval
should be between two Instant
values rather than two DateTime
values. However, the Joda Time API is what it is.
When you create an Interval
from two DateTime
values, the end time is converted into the time zone of the start time. In your case, this doesn't actually need to do anything because for intervalWithTZ
they're both in UTC, and for intervalWithoutTZ
they're both in your system default time zone. Your naming is inaccurate here, because all the values are in a time zone, even though you didn't specify one. A DateTime
always has a time zone; for a value without a time zone you want LocalDateTime
instead.
So, we can get rid of your intervalWithoutTZ
and simply write your code as:
DateTime startInDefaultZone = new DateTime("2016-10-01T00:00:00")
DateTime endInDefaultZone = new DateTime("2016-10-01T11:00:00")
val utcInterval = new Interval(startInDefaultZone.toDateTime(DateTimeZone.UTC),
endInDefaultZone.toDateTime(DateTimeZone.UTC));
That won't change the result at all... but the basic point is that you're converting a DateTime
of "2016-10-01T00:00:00 in your system default time zone" into UTC... and presumably your default time zone is 2 hours behind UTC, so 11am in UTC is 9am in your local time, etc.
In short, we can actually get rid of intervals entirely here - what you're seeing can be shown more simply as:
DateTime startInDefaultZone = new DateTime("2016-10-01T00:00:00")
DateTime startInUtc = startInDefaultZone.toDateTime(DateTimeZone.UTC);
You appear to expect that to be 2016-10-01T00:00:00Z, but it's actually "the instant in time represented by startInDefaultZone
, but expressed in UTC" which is 2016-10-01T02:00:00Z on your machine.
If you really want to "change time zone without changing local values" you could use withZoneRetainFields
, e.g.
DateTime startInDefaultZone = new DateTime("2016-10-01T00:00:00")
DateTime startInUtc = startInDefaultZone.withZoneRetainFields(DateTimeZone.UTC);
However, that usually means you've used the wrong type to start with, and should have been using LocalDateTime
. It's rarely a useful operation in a well-designed system.
See more on this question at Stackoverflow