I use the following code to convert the timestamp of a GPS location to a human-readable form:
String.format("%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS", location.getTime())
As per the docs, GPS location timestamps should be in UTC. However, the string I get back is in local time (tested on two different devices).
I have tried using the other form of String.format(), which takes an additional Locale argument, and passing it a null locale (which, as per the docs, means "no localization") – still the same. (And the documentation of Locale doesn't mention time zones at all, thus I doubt locales are the issue here.)
My other suspicion was that the GPS stack might not behave as specified, supplying local time instead of UTC. I tested this with
String.format("%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS", (long) 0)
which returns
1970-01-01 01:00:00
which is the beginning of the epoch plus the timezone offset for CET (the time zone of the device). Thus the offset is clearly added by String.format().
Is String.format() supposed to do any time zone conversion? How can I influence this behavior, i.e. choose which time zone to convert to or suppress conversion altogether?
String.format represent a date/time in default timezone. To format in UTC, use a SimpleDateFormat instead, where you can explicitly set timezone which you want to use:
String formatInUtc(long millis) {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ROOT);
df.setTimeZone(TimeZone.getTimeZone("UTC"));
return df.format(new Date(millis));
}
E. g.:
System.out.println(formatInUtc(0L)); // 1970-01-01 00:00:00
By the way, locale and timezone are orthogonal things: locale determines aspects of textual representation (digits, signs, separators, language), while timezone determines how the clock is shifted from UTC.
String.format is not time zone aware. Use Joda time library. The datetime zone and datetimeformat classes in the library will let you format datetime that will be timezone aware. You will get a lot of examples online on how to do that, so I am not getting into details here. :)
Related
how to parse this date format in java?
2022-01-19T18:14:17+03:00
I tried SimpleDateFormat("yyyy-MM-dd HH:mm:ssz"), but it didn't work.
Use a ZonedDateTime.
ZonedDateTime t = ZonedDateTime.parse("2022-01-19T18:14:17+03:00");
This uses the default zone you are in (only the offset +03:00 is given. toString is the inverse operation.
The format shown is that of an OffsetDateTime, which needs some time zone, a (default) time zone ID, to have a full ZonedDateTime. Note that an OffsetDateTime is incomplete without the time zone. It migh be used to indicate a latitude on earth, but the local clocks might provide several differing times. Normally choose a ZonedDateTime.
The format of the input is the ISO standard with T for time.
This means you do not need a DateTimeFormatter.
If you need to use the obsolete old Date:
Date date = Date.fromInstant(t.toInstant());
Maybe it doesn't read 'T' "2022-01-19T18:14:17+03:00", or try to fit the data to Examples formats
I am aware that SimpleDateFormat.parse rely on the Calendar API which depends on local JVM timezone (computer's). Assume JVM timezone is IST.
SimpleDateFormat srcDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
srcDateFormat.setTimeZone(TimeZone.getTimeZone("EST"));
Date objDt = srcDateFormat.parse("2018-10-16 11:28:25"); //Time : 21:58:25
From the output it seems it converts from EST to IST(JVM local timezone).
SimpleDateFormat srcDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
srcDateFormat.setTimeZone(TimeZone.getTimeZone("IST"));
Date objDt = srcDateFormat.parse("2018-10-16 11:28:25"); //Time : 11:28:25
It keeps time unmodified in this case. In this case I set timezone same as JVM local timezone.
Please help me to understand the behavior of the parse method. Nevertheless, I am curious to know the reason behind such behavior.
I know that java.util.Date and java.text.SimpleDateFormat legacy classes are obsolete now.
References:
Why SimpleDateFormat.format() and SimpleDateFormat.parse() are giving different time though setting only one TimeZone?
How do I convert date/time from one timezone to another?
SimpleDateFormat parses a string to wrong time
https://www.codeproject.com/Tips/1190426/When-Parsing-Formatting-Dates-in-Java-Make-Sure-Yo
First, you are correct that Date and SimpleDateFormat are legacy classes and now obsolete. So I recommend you don’t use them and use java.time, the modern Java date and time API, instead. Among many advantages it is much more explicit about conversions between time zones, which I would expect to help you understand what the code does.
Second, you are doing a number of things incorrectly or at least inadequately:
Don’t store date-times as strings. Always store them as date-time objects in Java. When you do this, you will never need to convert a date-time string from one zone to another. Instant (a class from java.time) is a point in time and as far as I can see the one you should use here.
Don’t rely in three letter time zone abbreviations. Very many of them are ambiguous, including both of IST and EST, and the latter isn’t a true time zone, so what you get at this time of year (when America/New_York zone uses EDT rather than EST), I don’t know.
And repeating myself, use the modern classes, not the obsolete ones.
Out of curiosity what happened?
An old-fashioned Date represents a point in time independently of time zone (internally it stores its value as a count of milliseconds since the epoch, but this is an implementation detail that we need not know or concern ourselves with).
In your first example your string is parsed into a point in time that corresponds to 16:28:25 UTC, 21:58:25 in India, 12:28:25 in New York or 11:28:25 in Jamaica. I mention Jamaica because it’s one of the few places that happens to use Eastern Standard Time (EST) all year. Most of the locations that use EST only do so in winter, not at this time of year. When you look at the Date in your debugger, the debugger calls toString on the Date to get a string to show you. toString in turn uses the JVM’s time zone for generating the string. In your case it’s Asia/Kolkata, which is why you get 21:58:25.
In the second case the same string is parsed into a point in time that corresponds to 05:58:25 UTC, 11:28:25 in India, 01:58:25 in New York or 00:58:25 in Jamaica. Your debugger again calls toString, which again uses your JVM’s time zone and converts back into 11:28:25 IST. When you parse and print in the same time zone, you get the same time of day back.
Links
EST – Eastern Standard Time / Eastern Time (Standard Time)
Oracle tutorial: Date Time explaining how to use java.time.
Time Zone Converter – Time Difference Calculator, online, practical for converting between Asia/Kolkata, UTC, Kingston (Jamaica), New York and other time zones.
How should I parse this datetime value that is in the PDT timezone?
06/24/2017 07:00 AM (PDT)
I want to maintain the timezone so that I can then represent the time in other timezones depending on the website visitors preferences.
I tried using ZonedDateTime but I get a parse error:
java.time.ZonedDateTime.parse("06/24/2017 07:00 AM (PDT)")
The error is:
java.time.format.DateTimeParseException: Text '06/24/2017 07:00 AM (PDT)' could not be parsed at index 0
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
at java.time.ZonedDateTime.parse(ZonedDateTime.java:597)
at java.time.ZonedDateTime.parse(ZonedDateTime.java:582) ... 29 elided
Also, do you agree that I should be using a ZonedDateTime?
Since your format is non-standard, you need to specify it to the parser:
ZonedDateTime.parse(
"06/24/2017 07:00 AM (PDT)",
DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm a (zzz)")
);
The parse method expects a String in a specific format, like 2007-12-03T10:15:30+01:00[Europe/Paris]. As your input is in a different format, you need a DateTimeFormatter.
One detail to notice is that the API uses IANA timezones names (always in the format Continent/City, like America/Sao_Paulo or Europe/Berlin).
Avoid using the 3-letter abbreviations (like CST or PST) because they are ambiguous and not standard.
The API makes some exceptions with specific IDs and provides some defaults for them. For PDT, it defaults to America/Los_Angeles.
Another detail is that in the example below I used lowercase hh in the pattern: the format has AM/PM indication, so I think that hh is the correct pattern, as its value is from 1 to 12 (the common values when there's the AM/PM indicator).
If you use uppercase HH, it allows values from 0 to 23 (and it's not common to use this with AM/PM), and it will throw an exception if the input contains an hour like 07:00 PM.
So the code will be like:
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm a (zzz)");
ZonedDateTime z = ZonedDateTime.parse("06/24/2017 07:00 AM (PDT)", fmt);
System.out.println(z);
The output is:
2017-06-24T07:00-07:00[America/Los_Angeles]
But not all the 3-letter timezone names will be recognized by the API and will throw an exception.
Anyway, there are other timezones that also are in PDT (like America/Vancouver) - you can get a list of all by calling ZoneId.getAvailableZoneIds(). If you want to use a different timezone as the default, you can create a set of preferred zones and build a formatter with this set:
Set<ZoneId> preferredZones = new HashSet<>();
// set America/Vancouver as preferred zone
preferredZones.add(ZoneId.of("America/Vancouver"));
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
// pattern
.appendPattern("MM/dd/yyyy hh:mm a (")
// append timezone with set of prefered zones
.appendZoneText(TextStyle.SHORT, preferredZones)
// finish the pattern
.appendPattern(")")
// create formatter
.toFormatter();
System.out.println(ZonedDateTime.parse("06/24/2017 07:00 AM (PDT)", fmt));
The API will use the preferred zones set (in this case, America/Vancouver) instead of the default (America/Los_Angeles). The output will be:
2017-06-24T07:00-07:00[America/Vancouver]
It's not clear where the input String's come from. If you can't control their format, then you have no choice: they need to be parsed this way. Then you can convert it to another timezone using the withZoneSameInstant method:
// parse the input string
ZonedDateTime z = ZonedDateTime.parse("06/24/2017 07:00 AM (PDT)", fmt);
// convert to another timezone
ZonedDateTime other = z.withZoneSameInstant(ZoneId.of("America/Sao_Paulo")); // 2017-06-24T11:00-03:00[America/Sao_Paulo]
The value of other will be 2017-06-24T11:00-03:00[America/Sao_Paulo].
But if you can control the output, it's always better (IMO) to internally work with UTC (java.time.Instant), and convert to some timezone only when displaying to users:
// convert ZonedDateTime to instant
ZonedDateTime z = // parse input
// convert to UTC (Instant is always in UTC)
Instant instant = z.toInstant();
// internally work with instant (as it's always in UTC)
// convert instant to some timezone only when necessary (like displaying to users)
ZonedDateTime converted = instant.atZone(ZoneId.of("Europe/London"));
The error you get is well covered in the other answers already.
Also, do you agree that I should be using a ZonedDateTime?
Yes and no. Your string should definitely be parsed into a ZonedDateTime. I recommend you convert it to an Instant and store this. Then when you need to present it to a user according to his/her time zone preference, you may either convert the Instant to a ZonedDateTime again or just format it using a DateTimeFormatter with the desired default time zone.
Why do it this way? First, common practice is to store Instants. Some prefer to store just milliseconds since the epoch, I think this some (often misunderstood) performance measure. Certainly such milliseconds I quite unreadable while Instants can be deciphered on eye-sight, at least roughly. The only other alternative I respect is when you know for certain that your application will never need to be concerned with a time zone (does this ever happen?), then sometimes LocalDateTime is used for storage.
If I understand your situation correctly, you need to store the point in time for display into multiple time zones. You don’t need to store the time zone in which the time was originally entered (like PDT, except PDT is not really a full time zone). Instant is time zone neutral, which is one reason I prefer it over storing the time in some time zone, as ZonedDateTime would. Also an Instant is simpler conceptually, and my guess is that it is also simpler implementation-wise.
There are a couple of much better answers here: Best practices with saving datetime & timezone info in database when data is dependant on datetime.
I have date in String format I need to parse. The format is as following with timezone from all over the world :
String stringDate = "2016-04-29 12:16:49.222+04:30";
String pattern = "yyyy-MM-dd HH:mm:ss.SSSZ";
It seems that java.util.Date doesn't accept timezone with : separator. So I'm trying with Jodatime library :
DateTime formattedDate = DateTimeFormat.forPattern(pattern).parseDateTime(stringDate);
LocalDateTime formattedDate2 = DateTimeFormat.forPattern(pattern).parseLocalDateTime(stringDate);
MutableDateTime formattedDate3 = DateTimeFormat.forPattern(pattern).parseMutableDateTime(stringDate);
System.out.println(formattedDate);
System.out.println(formattedDate2);
System.out.println(formattedDate3);
These lines output :
2016-04-29T09:46:49.222+02:00
2016-04-29T12:16:49.222
2016-04-29T09:46:49.222+02:00
As far as I understand the formatter modify output timezone to comply on mine (I'm in Paris, UTC+2), but I want the output keep its original timezone. Is it possible to do it with Jodatime library? Or should I change for another?
Edit :
Actually I need to get a Date object on which the timezone offset would be 270 (the timezone offset of the stringDate : 4 hour and 30 minutes) in place of 120 (my local timezone offset):
System.out.println(formattedDate.toDate().getTimezoneOffset()); // I expect 270 but I get 120
What you missed is DateTimeFormatter#withOffsetParsed:
Returns a new formatter that will create a datetime with a time zone equal to that of the offset of the parsed string.
Otherwise the formatter will parse it into your local time zone (surprising, I know).
#Test
public void preserveTimeZone() {
String stringDate = "2016-04-29 12:16:49.222+04:30";
String pattern = "yyyy-MM-dd HH:mm:ss.SSSZ";
DateTime dt = DateTimeFormat.forPattern(pattern).withOffsetParsed().parseDateTime(stringDate);
System.out.println(dt); // prints "2016-04-29T12:16:49.222+04:30"
}
As for your edit - java.util.Date does not hold time zone information and the deprecated getTimezoneOffset() method only
Returns the offset, measured in minutes, for the local time zone relative to UTC that is appropriate for the time represented by this Date object.
So you'd better use Joda Time or java.time classes to handle time zones properly.
When I run the same code that you have posted, I end up with
2016-04-29T02:46:49.222-05:00
2016-04-29T12:16:49.222
2016-04-29T02:46:49.222-05:00
which if you will notice, has different hour values AND time-zone values. However, if you look at their millis:
System.out.println(formattedDate.getMillis());
System.out.println(formattedDate2.toDateTime().getMillis());
System.out.println(formattedDate3.getMillis());
you'll see the output
1461916009222
1461950209222
1461916009222
So they have the same epoch time, but are printed out differently. This is due to the mechanism of toString() on DateTime objects, and how they are to be interpreted.
DateTime and LocalDateTime(MutableDateTime is just a mutable version of DateTime) deal with the same epoch time in different ways. LocalDateTime will always assume that epoch time is UTC time(per the javadoc for LocalDateTime), while DateTime will assume that epoch is represented in the time zone of the Chronology which it holds(per the javadoc again). If the TimeZone is not specified at construction time, then the Chronology will assume that you want the timezone of your default Locale, which is set by the JVM. In your case, the default Locale is Paris France, while mine is St. Louis USA. Paris currently holds a +2:00 time zone offset, while St. Louis has -5:00, leading to the different time zone representations when we print it.
To get even more annoying, those offsets can change over time. If I come back in 6 months and try to answer this again, my values will show -6:00 (stupid Daylight savings time!)
The important thing to remember is that these two dates have the same epoch time: we are talking about the same instant in time, we are just representing that time differently when we print it out.
If you want to use a different time zone for representing the output of the parse result, then you can set the DateTimeZone during formatting using DateTimeFormat.withZone() or DateTimeFormat.withLocale:
DateTimeFormatter sdf = DateTimeFormat.forPattern(pattern).withZone(DateTimeZone.forOffsetHoursMinutes(4,30));
System.out.println(formattedDate.getMillis());
System.out.println(formattedDate2.toDateTime().getMillis());
System.out.println(formattedDate3.getMillis());
which will print
2016-04-29 12:16:49.222+0430
2016-04-29 12:16:49.222
2016-04-29 12:16:49.222+0430
notice that the LocalDateTime version still prints out without the TimeZone. That's kind of the feature of LocalDateTime: it is represented without having to deal with all this business.
So that is why your printing values look weird. To further your question about getting a java.util.Date object from the parsed DateTime object: toDate will give you a java.util.Date which represents the same epoch time. However, java.util.Date behaves similarly to DateTime, in that unless otherwise stated, it will use the TimeZone of the default Locale. If you know the Locale ahead of time, then you can use the toDate(Locale) method to ensure you use that Locale's TimeZone offset.
It gets a lot harder if you don't know the TimeZone ahead of time; in the past, I've had to hand-parse the TimeZone hour and minute offsets to determine the proper TimeZone to use. In this exact case that's not too difficult, since the last 6 characters are extremely well-formed and regular(unless, of course, they aren't :)).
Before Java-8 I got accustomed to always keep anything date/time related as milliseconds since Epoch and only ever deal with human readable dates/times on the way out, i.e. in a UI or a log file, or when parsing user generated input.
I think this is still safe with Java-8, and now I am looking for the most concise way to get a formatted date out of a milliseconds time stamp. I tried
df = Dateformatter.ofPattern("...pattern...");
df.format(Instant.ofEpochMilli(timestamp))
but it bombs out with Unsupported field: YearOfEra in Instant.getLong(...) which I half understand. Now what to use instead of Instant?
LocalDateTime.ofEpoch(Instant, ZoneId) seems wrong, since I don't care to have local time. I just want to see the local time zone when applying the formatter. Internally it should be just the Instant.
The same goes for ZonedDateTime.ofInstant(Instant, ZoneId), I thought to apply the ZoneId only when formatting. But I notice that the DateTimeFormatter does not itself deal anymore with time zones, it seems, so I reckon I need to use one of the above.
Which one is preferred and why? Or should I use yet another way to format an epoch-millis time stamp as a date/time with time zone?
An Instant does not contain any information about the time-zone, and unlike in other places, the default time-zone is not automatically used. As such, the formatter cannot figure out what the year is, hence the error message.
Thus, to format the instant, you must add the time-zone. This can be directly added to the formatter using withZone(ZoneId) - there is no need to manually convert to ZonedDateTime *:
ZoneId zone = ZoneId.systemDefault();
DateTimeFormatter df = DateTimeFormatter.ofPattern("...pattern...").withZone(zone);
df.format(Instant.ofEpochMilli(timestamp))
* regrettably, in early Java 8 versions, the DateTimeformatter.withZone(ZoneId) method did not work, however this has now been fixed, so if the code above doesn't work, upgrade to the latest Java 8 patch release.
Edit: Just to add that Instant is the right class to use when you want to store an instant in time without any other context.
The error you have when formatting an Instant using a formatter built with a year or other fields is expected; an Instant does not know which year or month or day it is, it only knows how much milliseconds have elapsed since the Epoch. For the same instant, it could be 2 different days on 2 different places of the Earth.
So you need to add a time zone information if you want to print the day. With an Instant, you can call atZone(zone) to combine it with a ZoneId in order to form a ZonedDateTime. This is very much like an instant, only that it has a time zone information. If you want to use the system time zone (the one of the running VM), you can get it with ZoneId.systemDefault().
To print it, you can use the two built-in formatter ISO_OFFSET_DATE_TIME or ISO_ZONED_DATE_TIME. The difference between the two is that the zoned date time formatter will add the zone id to the output.
Instant instant = Instant.now();
DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
System.out.println(formatter.format(instant.atZone(ZoneId.systemDefault())));
System.out.println(formatter.format(instant.atZone(ZoneId.of("America/Los_Angeles"))));
when run on my machine, which has a system time zone of "Europe/Paris", you'll get:
2016-07-31T18:58:54.108+02:00
2016-07-31T09:58:54.108-07:00
You can of course build your own formatter if those one do not suit you, using ofPattern or the builder DateTimeFormatterBuilder.
I agree that this is somewhat confusing, especially when compared with it's predecessor Joda DateTime.
The most confusing thing is that the documentation for LocalDateTime says that it is "A date-time without a time-zone", and yet LocalDateTime.ofInstant method takes both an instant and a timezone as parameters.
That said, I think that you can achieve what you want by using Instant and LocalDateTime.ofInstant by using the UTC timezone.
public LocalDateTime millisToDateTime(long millis) {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.of("Z");
}