My goal is to parse a date string with the format yyyy-MM-dd hh:mm:ss.SSS for a given timezone, using only Java 6 without any external libraries. My first approach was to configure a SimpleDateFormatter with a timezone. However, I'm not able to understand the results. Have a look at the following code:
List<String> zones = Arrays.asList("UTC", "CET", "Europe/London", "Europe/Berlin");
for(String zone: zones) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");
df.setTimeZone(TimeZone.getTimeZone(zone));
Date result = df.parse("1970-01-01 00:00:00.000");
System.out.println(zone + ": " + result.getTime());
}
The output is as follows:
UTC: 0
CET: -3600000
Europe/London: -3600000
Europe/Berlin: -3600000
The first two results are as expected. In UTC, the unix epoch starts at exactly zero milliseconds, and there is a one hour difference to CET.
What I don't understand are the values for London and Berlin. Since the milliseconds for London equal the milliseconds for CET instead of UTC, I first assumed that daylight savings time is taken into account (since it's currently summer on the northern hemisphere and summertime in London is UTC+1, which equals CET). However, why is the value for Berlin the same? Shouldn't it be UTC+2 (or CET+1 if you want)?
My questions:
What's the explanation for the results? Is DST really taken into account?
Is there a better way for doing the conversion in Java 6?
The results are correct, because that's the offsets used by London and Berlin in 1970 (both used +01:00).
It's not related to Daylight Saving Time, because nowadays it happens during March and October in these countries. Actually one could argue that London was in a "long" DST period, from 1968 to 1971, because the offset was +01:00 during this whole period.
It's just the nature of timezones: they're defined by governments and laws, and the respective politicians can change the offsets used by their countries for whatever reasons they have.
In the same links above, you can see that Berlin adopted DST only in 1980, and London changed the offset to zero (GMT) in 1971.
PS: as discussed in the comments, short names like CET are ambiguous and not standard (for lots of them, there's more than one timezone that uses the same abbreviation). Always prefer to use IANA timezones names (always in the format Region/City, like America/Sao_Paulo or Europe/Berlin).
Some names (such as CET) are recognized and set to arbitrary defaults - mainly due to retro-compatibility (or bad design) issues - but it's usually done in unexpected ways. Avoid such short names if you can: names like Europe/Berlin are much more clear and have no ambiguities.
Related
I'm trying to retrieve the Unix timestamp for midnight of the current day. Timezone not relevant.
I'm looking for something like: 1625716800
Every tutorial I've found is for retrieving formatted strings, such as: "Thu Jul 08 2021 04:00:00"
It needs to be midnight. Not just the current seconds or milliseconds.
Any advice is appreciated.
Many thanks.
I'm trying to retrieve the Unix timestamp for midnight of the current day. Timezone not relevant.
This is impossible. The concept of 'a day' does not exist in unix timestamp space. This space just ticks away 1 unit every millisecond that passes, since some universally defined epoch (in unix space, that epoch occurred at that instant in time when someone in greenwich, UK, would tell you that right this very moment it is jan 1st, 1970, midnight). There's no such thing as days in this system, no such thing as dates. Just 'millis since the epoch', and that's all you get.
If you want concepts like 'day', 'month', or 'hour', you simply can't do that in this space; these are human concepts and you can't know what the right answer is unless you involve a political unit which decides how to translate such an epoch to an actual 'it is this date in this month in this year, this hour, this minute, etc'. Political units have timezones, daylight savings times, and more - and they all effect when 'midnight' might be.
When it is midnight in Europe/Amsterdam, it's not midnight in Asia/Singapore. Hopefully this helps you understand that the concept 'at midnight' doesn't make sense unless you add to this some notion of 'where'. Could be 'whereever the system thinks it is', (e.g. platform default timezone), could be at the essentially fictional UTC timezone, could be at some specific timezone, could be at a user-specified timezone. But it's gotta be in some timezone.
Furthermore, 'current day' is ambiguous. That, too, just isn't a thing unless timezones are involved. Current day where? Amsterdam? Singapore? Los Angeles? As I said, millis-since-epoch has no idea what 'day' means. It just knows when the epoch was and what a millisecond is, and it knows nothing more.
In fact, 'midnight' doesn't work at all. It may not exist. Most places will advance the clock by an hour, if they do so at all, at 2 AM, but not all zones do. Thus, midnight may simply be a time that never was. One moment in time it is 23:59:59 on March 22nd. The next moment in time, it is 01:00:00 on March 23rd. Presumably what you actually mean is 'start of day', as in, the very first instant in time that can be called 'the date is now X' in the decided time zone. Which usually is 00:00:00 but there are extremely exotic cases where it's not. The problem is, 'extremely exotic' is not equal to 'never happens'.
Let's assume the zone relevant for 'current day' and 'at the start of this day' are all from the same zone, then what you're looking for:
ZoneId zone = ....;
ZonedDateTime zdt = ZonedDateTime.now(zone);
zdt = zdt.toLocalDate().atStartOfDay(zone);
long v = zdt.toInstant().toEpochMilli();
Where zone can be many things. For example, ZoneOffset.UTC, or ZoneId.systemDefault(), or ZoneId.of("Europe/Amsterdam") for example.
Let's give it a whirl with ZoneOffset.UTC:
ZoneId zone = ZoneOffset.UTC;
ZonedDateTime zdt = ZonedDateTime.now(zone);
zdt = zdt.toLocalDate().atStartOfDay(zone);
long v = zdt.toInstant().toEpochMilli();
System.out.println(v);
See this code run live at IdeOne.com:
1625702400000
import java.util.Calendar;
class test{
public static void main(String[] args) {
Calendar cal = Calendar.getInstance();
cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DATE), 0, 0, 0);
System.out.println(cal.getTimeInMillis());
}
}
Is it possible to find the list of time zone ID's for a given time zone abbreviation? For example, for the abbreviation IST, the time zone ID's are Asia/Jerusalem, Asia/Kolkata and Europe/Dublin.
Interesting question. Since the abbreviations aren’t standardized, there cannot be an authoritative answer nor a bulletproof way to get such an answer. Off the top of my head I thought of two approaches:
Get them from your JVM.
Find them on the net.
Getting the zones from your JVM:
String givenAbbr = "IST";
LocalDateTime summerSouthernHemisphere = LocalDate.of(2018, Month.JANUARY, 31).atStartOfDay();
LocalDateTime summerNorthernHemisphere = LocalDate.of(2018, Month.JULY, 31).atStartOfDay();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("z");
Set<ZoneId> zones = new HashSet<>();
for (String id : ZoneId.getAvailableZoneIds()) {
ZoneId zone = ZoneId.of(id);
String abbr = summerSouthernHemisphere.atZone(zone).format(dtf);
if (abbr.equals(givenAbbr)) {
zones.add(zone);
}
abbr = summerNorthernHemisphere.atZone(zone).format(dtf);
if (abbr.equals(givenAbbr)) {
zones.add(zone);
}
}
System.out.println(zones);
This prints:
[Asia/Calcutta, Eire, Europe/Dublin, Asia/Jerusalem, Asia/Tel_Aviv, Israel, Asia/Kolkata, Asia/Colombo]
Some of these are just names for the same time zone, though. For example Eire has the same rules as Europe/Dublin. So a further filtering could be made if desired. You may use oneZoneId.getRules().equals(anotherZoneId.getRules()) to determine if two ZoneId objects have the same zone rules.
For abbreviation CST the list is even longer and has more synonyms:
[PRC, America/Matamoros, Asia/Taipei, America/Regina, America/El_Salvador,
America/North_Dakota/New_Salem, Asia/Harbin, America/Costa_Rica,
America/North_Dakota/Center, America/Guatemala, America/Winnipeg,
Asia/Chongqing, America/Rankin_Inlet, America/Indiana/Knox,
America/Belize, SystemV/CST6CDT, Mexico/General,
America/North_Dakota/Beulah, CST6CDT, America/Swift_Current,
America/Knox_IN, Asia/Chungking, Asia/Macao, Asia/Shanghai,
America/Indiana/Tell_City, America/Menominee, America/Bahia_Banderas,
America/Managua, Canada/East-Saskatchewan, Asia/Macau, America/Havana,
America/Resolute, US/Central, US/Indiana-Starke, Cuba, America/Monterrey,
America/Chicago, America/Merida, America/Mexico_City, Canada/Central,
America/Tegucigalpa, America/Rainy_River, Canada/Saskatchewan, SystemV/CST6]
One limitation of my approach is that some time zones are known by more than one name and therefore more than one three or four letter abbreviation. My code above catches only one of these.
Another limitation is that picking two dates like I do will never give you all possibilites in the past and the future, and may even miss some where I just don’t hit the right date. I have tried to pick one date where it is winter on the northern hemisphere and summer on the southern, and one where it is the other way around. This will cover most cases for the present, but you never know if there is a time zone or three where the transition don’t follow summer and winter as we know it. If you want better coverage, there are a couple of excellent suggestions in Hugo’s answer.
Get them from the Internet
The other answer is, of course, the one that your search has no doubt already brought up: such lists are public on the Internet. For example Wikipedia’s List of time zone abbreviations and Time Zone Abbreviations – Worldwide List on timeanddate.com. As expected, the two lists mentioned do not agree. For example, the latter knows two interpretations of ADT, the former only one. The latter list gives many synonym abbreviations and thereby illustrates my point above that each zone can have more than one abbreviation.
#Ole V.V.'s answer already gives great and detailed information, like the fact that the 3-letter abbreviations (like IST or PST) are ambiguous and not standard, and you should prefer the long IANA timezones names (always in the format Continent/City, like Asia/Kolkata or Europe/Berlin).
But there's one tricky detail in that answer: it's taking January and July of 2018 as the base dates for winter and summer (so the abbreviation can be checked against standard and Daylight Saving periods). But it's not guaranteed that it'll take both winter and summer time for all cases, because timezones rules can change - just because a timezone has DST today, it doesn't mean it'll have it forever (the opposite is also true).
So, instead of picking some date/time and hope that all timezones have a DST change between them, the best approach is to get all the changes from the ZoneRules object - it contains all the transition dates (the moment when the offset changes for that timezone - due to DST start/end or because some government decided that their country will now be in another timezone).
It also covers the case where a timezone used an abbreviation in the past, but then changed to another, as I'm checking through all the timezone's changes history.
The code is very similar. The only difference is that, instead of using a fixed date (Jan/Jul 2018), I'm looking at all the transitions of the zone (and if a timezone has no transitions - which means it never had DST or any other changes - I get the current date). I also created a Set of String (as you want just the names, but you could store the ZoneId objects as well):
String ist = "IST";
Set<String> zones = new HashSet<>();
// formatter to get the abbreviation
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("z");
for (String id : ZoneId.getAvailableZoneIds()) {
ZoneId zone = ZoneId.of(id);
ZoneRules rules = zone.getRules();
List<ZoneOffsetTransition> transitions = rules.getTransitions();
if (transitions.isEmpty()) {
// no transitions found, just pick any date
String abbrev = fmt.format(ZonedDateTime.now(zone));
if (ist.equals(abbrev)) {
zones.add(id);
}
} else {
for (ZoneOffsetTransition transition : transitions) {
// get the instant that the transition occurred and convert to this zone
String abbrev = fmt.format(transition.getInstant().atZone(zone));
if (ist.equals(abbrev)) {
zones.add(id);
}
}
}
}
System.out.println(zones);
The output, in this case, will be the same:
[Asia/Calcutta, Eire, Europe/Dublin, Asia/Jerusalem, Asia/Tel_Aviv, Israel, Asia/Kolkata, Asia/Colombo]
Although this code looks more redundant (as it traverses through all the dates when a DST change occurred), it's more guaranteed to get all cases. If you look for a timezone that had DST in the past but it won't have in 2018 (or in any other arbitrary date you get), using this arbitrary date won't work. Only by checking all transitions you can be sure that all cases were covered.
One example: if instead of IST, I'd like to check the abbreviation AEDT (Australian Eastern Daylight Time).
Using #Ole V.V.'s code, I'll get:
[Australia/Sydney, Australia/Melbourne, Australia/Hobart, Australia/Victoria, Australia/ACT, Australia/Canberra, Australia/NSW, Australia/Tasmania, Australia/Currie]
Using my code, I'll get:
[Australia/Sydney, Australia/Brisbane, Australia/Melbourne, Australia/Queensland, Australia/Hobart, Australia/Victoria, Australia/ACT, Australia/Canberra, Australia/NSW, Australia/Tasmania, Australia/Currie, Australia/Lindeman]
Note the differences. One example is Australia/Brisbane, which had DST until the 90's, but now it doesn't (so it won't have it in 2018 as well). So, if you try to get AEDT (summer time) in 2018, this timezone won't be picked by #Ole V.V.'s code, because it won't have DST in 2018.
But I'm checking all the changes it had during history, no matter when it happened. This guarantees that I'm covering all cases.
PS: if you want to get the abbreviations that were valid in a specific date, then you can use #Ole V.V.'s code (just change the dates accordingly).
Another way (not easier) is to download the IANA Time Zone Database file and follow this tutorial to understand how to read the files (not trivial, IMO). Take, for example, the Dublin's entry:
# Zone NAME GMTOFF RULES FORMAT [UNTIL]
Zone Europe/Dublin -0:25:00 - LMT 1880 Aug 2
-0:25:21 - DMT 1916 May 21 2:00 # Dublin MT
-0:25:21 1:00 IST 1916 Oct 1 2:00s
0:00 GB-Eire %s 1921 Dec 6 # independence
0:00 GB-Eire GMT/IST 1940 Feb 25 2:00
0:00 1:00 IST 1946 Oct 6 2:00
... etc
You can see that IST is used for Europe/Dublin. Well, this is not the most straightforward way, but every time IANA updates its database, it takes some time for changes to be included in the JDK (although you can update just the timezone data if you want).
So, if you want the most up-to-date information, you can regularly check for updates in IANA's website.
Refer to Java 8 docs for ZoneDateTime api on oracle docs.
Link of a sample maven project on github implementing this method.
Implementation wise you can use the code below,
ZoneId losAngeles = ZoneId.of("America/Los_Angeles");
ZoneId berlin = ZoneId.of("Europe/Berlin");
// 2014-02-20 12:00
LocalDateTime dateTime = LocalDateTime.of(2014, 02, 20, 12, 0);
// 2014-02-20 12:00, Europe/Berlin (+01:00)
ZonedDateTime berlinDateTime = ZonedDateTime.of(dateTime, berlin);
// 2014-02-20 03:00, America/Los_Angeles (-08:00)
ZonedDateTime losAngelesDateTime = berlinDateTime.withZoneSameInstant(losAngeles);
int offsetInSeconds = losAngelesDateTime.getOffset().getTotalSeconds(); // -28800
// a collection of all available zones
Set<String> allZoneIds = ZoneId.getAvailableZoneIds();
i'm building an android application which have a chat.
in this chat i each message to have its time sent signature.
my question is as follow:
lets say that the time in my country is X. my friend is abroad and his time is X minus 7 hours.
i'm sending him a message at 16:00 local time.
i want to avoid the situation that he will get at 09:00 a message which it signature will be 16:00 (which is a time in future if you're looking in the eyes of that friend in his country).
is there a way that in my phone the message will be written as 16:00 and in his phone it will be written as 09:00 ? i there a way to convert a time to a local time ?
System.currentTimeMillis() does give you the number of milliseconds since January 1, 1970 00:00:00 UTC. Date object does not save your local timezone.
You can use DateFormats to convert Dates to Strings in any timezone:
DateFormat df = DateFormat.getTimeInstance();
df.setTimeZone(TimeZone.getTimeZone("gmt"));
String gmtTime = df.format(new Date());
linked response
You should keep all time communications using UTC time. Then localize it for display based on the devices current timezone setting.
Use a long to save your time information as milliseconds since "epoch" (which is January 1, 1970, 00:00:00 GMT). It can be retreived with the Date.getTime() method and new Date objects are easily created using the Date(long millis) constructor. The Date objects are then displayed using the local timezone settings on each device.
EDIT:
Epoch is a defined point in time which is expressed differently in different time zones: 1970-01-01 00:00:00 GMT but
1969-12-31 19:00:00 EST. The timestamp is just the number of milliseconds elapsed since that time. So, for example the timestamp 1341169200 corresponds to 2012-07-01 19:00:00 GMT and 2012-07-01 14:00:00 EST.
You will need to save the time zone which your message will be saved in, and transfer it (or send the unix epoch time) and then on the other side make sure you read it in with the Locale time (using the Android documentation for things like http://developer.android.com/reference/java/util/Calendar.html can help).
Take a look at the answer over here:
https://stackoverflow.com/a/6094475/346232
You need to change the time to UTC and then convert on the device to the timezone.
Avoid java.util.Date/.Calendar
The java.util.Date/.Calendar classes bundled with Java (and Android) are notoriously troublesome, flawed in both design and implementation.
Joda-Time
The Joda-Time library is the way to go. This library inspired the java.time package now built into Java 8 (not available on Android).
UTC
As other answers suggested, the best practice (generally) is to keep your business logic and data storage/communication in UTC time zone (which some think of as no time zone or an "anti" time zone). Adjust to a specific time zone only when expected by the user or data-consumer.
Time Zone
The DateTime class in Joda-Time represents a date-time value along with an assigned time zone.
Note that it is best to specify a time zone in all your operations. Otherwise you will be implicitly relying on the JVM’s current default time zone. This is risky because that zone can change – even at runtime at any moment by any code in any thread of any app running within your app’s JVM. And use proper time zone names, never the 3-4 letter codes.
Example Code
Example code in Joda-Time 2.7.
DateTime sent = DateTime.now( DateTimeZone.getDefault() ) ;
DateTime sentUtc = nowMine.withZone( DateTimeZone.UTC ) ; // Generally, use this for your work, including communicating to other threads and apps and such.
When ready to display to the other user, adjust to the expected time zone.
DateTimeZone zone = DateTimeZone.forID( "America/Montreal" ) ; // Or DateTimeZone.getDefault() if you want to rely on their JVM’s current default. To be absolutely sure of expected time zone, you really must ask the user.
DateTime sentMontréal = sentUtc.withZone( zone );
To generate a textual representation of those date-time objects, search the many Questions and Answers on StackOverflow.com on that subject. Search for terms like "joda" and "DateTimeFormatter" and "DateTimeFormat".
I want to convert Strings like "20000603163334 GST" or "20000603163334 -0300" to UTC time. The problem is that time zones in my strings can be 'general time zones', I mean they can be strings as CET, GST etc. etc. And I don't know how to convert these ones.
Because of these string time zones I can not use Joda Time's DateTimeFormat.forPattern("yyyyMMddhhmmss z").withZone(DateTimeZone.UTC);, because according to the documentation: "Time zone names ('z') cannot be parsed".
So, one question I have is if you know a method to go around this limitation in Joda Time? I would prefer to use Joda Time, if possible, instead of the standard Java API.
Another in which I thought I can solve this problem with time zone's names is to use the Java's SimpleDateFormat.
So I make something like:
SimpleDateFormat f = new SimpleDateFormat("yyyyMMddhhmmss z");
//f.setTimeZone(TimeZone.getTimeZone("UTC"));
f.setCalendar(new GregorianCalendar(TimeZone.getTimeZone("UTC")));
Date time = f.parse("20000603163334 GST");
The SimpleDateFormat parses the String (I don't care here about the problem that there are multiple time zones with the same name - what this class parses it's good for me).
The problem is that I don't know how to convert it from here to UTC. How can I do this?
The fact that I set the f's time zone to UTC (in both the two ways from above) doesn't help. I hope someone can help me fix this, I read a lot of questions and answers on this theme here, on stackoverflow, but I haven't found a solution yet.
I found two solutions to your problem. The first was to set the default time zone to UTC:
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
I'm not sure what other side effect this might have.
The second solution I found was to use a different SimpleDateFormat for output.
SimpleDateFormat f = new SimpleDateFormat("yyyyMMddhhmmss z");
f.setTimeZone(TimeZone.getTimeZone("UTC"));
Date time = f.parse("20000603163334 GST");
System.out.println(time);
System.out.println("(yyyyMMddhhmmss z): " + f.format(time));
SimpleDateFormat utc = new SimpleDateFormat("yyyyMMddhhmmss z");
utc.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println("(yyyyMMddhhmmss z): " + utc.format(time));
Using two SimpleDateFormat objects allowed the output to be put in UTC Time. Here is the output from running this code:
Sat Jun 03 08:33:34 EDT 2000
(yyyyMMddhhmmss z): 20000603043334 GST
(yyyyMMddhhmmss z): 20000603123334 UTC
Here may be the reason why Joda does not support 3 letter zone ids. This is from the TimeZone ( http://download.oracle.com/javase/6/docs/api/java/util/TimeZone.html ) JavaDoc. As far as Joda goes, I didn't see a workaround, but I'm not very familiar with that library.
Three-letter time zone IDs For
compatibility with JDK 1.1.x, some
other three-letter time zone IDs (such
as "PST", "CTT", "AST") are also
supported. However, their use is
deprecated because the same
abbreviation is often used for
multiple time zones (for example,
"CST" could be U.S. "Central Standard
Time" and "China Standard Time"), and
the Java platform can then only
recognize one of them.
I am using simple date format to allow users to specify which time zone they are sending data in:
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,z");
This works fine:
e.g.
df.parse("2009-05-16 11:07:41,GMT");
However, if someone is always sending time in London time (i.e. taking into account daylight savings), what would be the approriate time zone String to add?
e.g. this doesnt work:
df.parse("2009-05-16 11:07:41,Western European Time");
System.out.println(date);
Sat May 16 12:07:41 BST 2009
I want to match the time to british time taking into daylight savings.
Thanks.
In daylight saving time, it's BST. In the rest of the year it's GMT.
I suggest that you use the generic name (for the whole year), which is Europe/London. You can use something like this:
String userInput = "2009-05-16 11:07:41,Europe/London";
String[] tokens = userInput.split(",");
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
df.setTimeZone(TimeZone.getTimeZone(tokens[1]));
System.out.println(df.parse(tokens[0]));
The output in this case is:
Sat May 16 11:07:41 GMT+01:00 2009
So what exactly is your question - what ID for the TimeZone you should use? The following will print a list of all the available time zone identifiers:
for (String id : TimeZone.getAvailableIDs()) {
System.out.println(id);
}
I don't think it's possible to do exactly what you want to do using SimpleDateFormat on its own. TimeZone.parse(String) only accepts time zones, not time zone IDs, for example:
Time Zone ID Time Zone (Winter) Time Zone (Summer)
-------------------------------------------------------------
Europe/London GMT BST
If parse(...) accepted Europe/London there would be one hour in spring that would not be a valid Europe/London time and one hour in autumn that would map to two UTC times.
I think that the best you can do is follow Bruno Rothgiesser's suggestion, however you could accept the time zone ID as a separate user input, or do an additional string processing step to separate the time zone id from the user input string, and use it to work out whether the user probably means GMT or BST. The user's Locale might be a better way of working out what he/she means - although there are some assumptions involved in that idea.
The "what the user probably means" algorithm has to deal with two special cases - you can use TimeZone.inDaylightTime(Date) with userTime +/- 1 hour to work out if you might have one of these.