2

EDIT: OK. I unfortunately have to admit my understanding of Java time was crucially flawed, making this question meaningless. I always thought that System.currentTimeMillis() returned the local time. Why? Because when you create a Date with it, it just contains that, and no reference to a timezone, but still prints out the local time when passed to System.out.println(). It never crossed my mind that it was toString() that converted the (UTC) time to then local time zone. :(

I want to create a client-server application in Java. Time plays an important role. So I decided to standardize on GMT/UTC for the time. I also know from experience that many user's PC have the totally wrong time and/or timezone. Finally, I would like to have more precise time then milliseconds.

So I created a class that connects to the Internet at start, and computes the offset to UTC, independently of what the local time and timezone is. In other words, I have a static Java method that returns the UTC time in nanoseconds, and works on any PC independently of the local time settings.

Now, my problem is that I wanted to use JSR 310 to manipulate the time (actually the Java 7 backport), and I fail to see how to define a clock which is not based on the local time.

It seems to me that the methods Clock.millis() and Clock.instant() have to use the local time for things to work correctly [EDIT: Because those methods blindly return System.currentTimeMillis(), independent of the set time zone attribute]. So I basically have to turn my UTC time to local time, to create an Instant, just so I can then create a UTC ZonedDateTime that will convert the local time back to UTC again. This is just insane. If I use the UTC time in Clock.millis() and Clock.instant(), and then create a UTC ZonedDateTime , the local time offset, which I don't want to have anything to do with, gets applied twice.

Converting from UTC to local and back again is expensive, and so I would really like to be able to use purely UTC. I'm working on a real-time game, rather then a web-app, where such small delays would be meaningless.

I could just return the UTC time, and "pretend" the timezone is "local" to get the correct numbers, but things would probably break at DST boundaries, and it would make converting the time to some other timezone much more complicated.

Here my Clock class

Sebastien Diot
  • 7,183
  • 6
  • 43
  • 85

2 Answers2

2

It seems to me that the methods Clock.millis() and Clock.instant() have to use the local time for things to work correctly.

Absolutely not. Instant isn't associated with a time zone, and millis is documented with:

This returns the millisecond-based instant, measured from 1970-01-01T00:00 UTC. This is equivalent to the definition of System.currentTimeMillis().

So again, it's not the local time. It uses the local system clock, but that's not the same as saying it's a local time in the normal sense.

It's very unclear what you're really trying to do, but I would at least try to do everything in terms of Instant, rather than ZonedDateTime. The point of Instant is that it's just a point on the time-line; it doesn't have a time zone or even a calendar system. So long as everything you use (across all systems) uses the same notional time line with the same epoch (e.g. if you use JSR-310 everywhere) then you should be fine. Instant uses the same epoch as Java - the start of 1970 in UTC.

Of course, you may still want to have your own source of time, but obviously you'll need to take into account network delays etc. Consider using standardized NTP or something similar, rather than trying to build your own system. You would probably then want to adapt it to implement a subclass of Clock yourself. If everything else just works in terms of Clock, your code will be nice and testable without involving your network service.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Hi. I just checked in the code on Github, and added a reference to in in the question. I *am* using NTP (if not behind firewall). And I do have my own Clock impl. But I can't change the user's clock, or timezone! So I have to make do with what Java offers. Instant isn't particularly useful; it lack things like getDayOfWeek() ... – Sebastien Diot Apr 14 '13 at 16:39
  • @Sebastien: We don't really know what you're trying to achieve, which makes it hard to help you. Usually when you're interested in very precise times, you don't care about human constructs such as days of the week. If you can tell us what you need to actually *do* with the values, we're more likely to be able to help. I suspect it'll just be a matter of converting the Instant into a ZonedDateTime in the UTC zone though, if that's what you're trying to represent. – Jon Skeet Apr 14 '13 at 17:00
  • OK. Seems I was totally lost. What I didn't know was that: System.currentTimeMillis() *always* returns the UTC time. That's all I needed to know. But if any third-party turns a date/time into a string, I probably still need to change the default time zone to get the correct string output. We'll see. Thanks for the help. – Sebastien Diot Apr 16 '13 at 17:47
0

The solution is: you can change the TimeZone:

TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

So to use UTC in the Clock, but still be able to display a time value in the local user timezone, you have to first get and save the default TimeZone, and then only replace it with UTC as above.

Then, the JVM will think that your local time zone is UTC, and so if the Clock actually returns UTC millis and instant, everything will work together. When you then want to display a time value, you get the original default time zone, and create another clock instance with that time zone, but the millis and instant method remain unchanged, such that they still return the UTC time.

The main benefit is that now no timezone conversion is needed anywhere, except when displaying to the user in the local time.

[EDIT On second thoughts, this has limited value, due to the fact that everything else will still use System.currentTimeMillis() (including indirectly, through java.util.Date and Calendar). So one probably cannot ever solve all the time-related problems like that.]

Sebastien Diot
  • 7,183
  • 6
  • 43
  • 85
  • That's changing the JVM time zone, but why would you bother doing that? Far better to specify UTC everywhere it's relevant, IMO. Unfortunately I can't give you any sample code as we still don't know what you're trying to achieve, but I'd urge *against* changing the JVM time zone arbitrarily. Just avoid calls which implicitly use the default time zone. – Jon Skeet Apr 15 '13 at 05:45
  • @Jon What I want to do is explained in the first three sentences of my question. Having users over the world, the only way to way to keep things clean is to 1) centralize on UTC, 2) never thrust the client clock. I can set my server timezone to GMT, but not the client's. Your "solution", to "specify UTC everywhere it's relevant", is, well, *everywhere*. And I'm simply not doing that, if there is any way around it. In fact, I've just decided to use bytecode-weaving to replace calls to System.currentTimeMillis() and new Date() in third-party libs, like log4j. :D – Sebastien Diot Apr 15 '13 at 11:48
  • No, it's really *not* explained. You haven't said what you want to do with the time at all. You've explained that time is relevant, but not in what sense. If you're determined to change the system time zone, then fine - but it's definitely not the approach I'd take. (If you structure your code appropriately, you really shouldn't need to change too many places to specify UTC explicitly. Basically if you're only interested in "the current time, in UTC" that sounds like a single method.) – Jon Skeet Apr 15 '13 at 11:52