Java API to get daylight saving boundaries for a year - java

I have a date range (start and end date) and require to know whether this falls within a Daylight Saving change over.
Is there any Java API available to check this or any Java code to achieve this?

Daylight Saving changes occur at different dates in each country/region, so the first thing to know is the name of the timezone you're checking.
I'm writing this answer using both Joda-Time and the new Java Date/Time API and both use the IANA's list of timezone names (in the format Continent/City). Both API's also avoid to use the 3-letter names because they are ambiguous and not standard.
For the code below I'm gonna use America/Sao_Paulo (the timezone where I live, which has DST changes every year), but you can replace it with the timezone you want.
The code below shows you how to check if a date is in DST and find the next date when a DST change will occur. So, if you have a start and end dates and want to know if both are in within a DST change, you can check if both are in DST or not and also find the next and previous DST changes (and check if the dates are between those changes - it's not clear to me how your check should be done).
Also be aware that Joda-Time is in maintainance mode and is being replaced by the new APIs, so I don't recommend start a new project with it. 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).".
Joda-Time
You can use the org.joda.time.DateTimeZone class. To know all the available timezones, call DateTimeZone.getAvailableIDs().
The code below checks if a date is in DST and also finds the next date when a DST change will occur:
// create timezone object
DateTimeZone zone = DateTimeZone.forID("America/Sao_Paulo");
// check if a date is in DST
DateTime inDst = new DateTime(2017, 1, 1, 10, 0, zone);
// isStandardOffset returns false (it's in DST)
System.out.println(zone.isStandardOffset(inDst.getMillis()));
// check when it'll be the next DST change
DateTime nextDstChange = new DateTime(zone.nextTransition(inDst.getMillis()), zone);
System.out.println(nextDstChange); // 2017-02-18T23:00:00.000-03:00
// check if a date is in DST
DateTime noDst = new DateTime(2017, 6, 18, 10, 0, zone);
// isStandardOffset returns true (it's not in DST)
System.out.println(zone.isStandardOffset(noDst.getMillis()));
// check when it'll be the next DST change
nextDstChange = new DateTime(zone.nextTransition(noDst.getMillis()), zone);
System.out.println(nextDstChange); // 2017-10-15T01:00:00.000-02:00
If you want to find the previous DST change (instead of the next), call previousTransition() instead of nextTransition().
Java new Date/Time API
If you're using Java 8, the new java.time API already comes natively.
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 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.
The code is very similar to Joda-Time's version. The main differences:
While Joda-Time has isStandardOffset() to check if the date is not in DST, the new API has isDaylightSavings() to check if the date is in DST.
Joda-Time provides the methods directly in the DateTimeZone class, but the new API has a dedicated class to its DST rules (java.time.zone.ZoneRules)
The methods for next and previous transitions return a java.time.zone.ZoneOffsetTransition instead of directly returning a date (this object provides more information about the DST change, as showed below).
Despite all those differences, the idea is very similar:
// create timezone object
ZoneId zone = ZoneId.of("America/Sao_Paulo");
// get the timezone's rules
ZoneRules rules = zone.getRules();
// check if a date is in DST
ZonedDateTime inDST = ZonedDateTime.of(2017, 1, 1, 10, 0, 0, 0, zone);
// isDaylightSavings returns true (it's in DST)
System.out.println(rules.isDaylightSavings(inDST.toInstant()));
// check when it'll be the next DST change
ZoneOffsetTransition nextTransition = rules.nextTransition(inDST.toInstant());
// getInstant() returns the UTC instant; atZone converts to the specified timezone
System.out.println(nextTransition.getInstant().atZone(zone)); // 2017-02-18T23:00-03:00[America/Sao_Paulo]
// you can also check the date/time and offset before and after the DST change
// in this case, at 19/02/2017, the clock is moved 1 hour back (from midnight to 11 PM)
ZonedDateTime beforeDST = ZonedDateTime.of(nextTransition.getDateTimeBefore(), nextTransition.getOffsetBefore());
System.out.println(beforeDST); // 2017-02-19T00:00-02:00
ZonedDateTime afterDST = ZonedDateTime.of(nextTransition.getDateTimeAfter(), nextTransition.getOffsetAfter());
System.out.println(afterDST); // 2017-02-18T23:00-03:00
// check if a date is in DST
ZonedDateTime noDST = ZonedDateTime.of(2017, 6, 1, 10, 0, 0, 0, zone);
// isDaylightSavings returns false (it's not in DST)
System.out.println(rules.isDaylightSavings(noDST.toInstant()));
// check when it'll be the next DST change
nextTransition = rules.nextTransition(noDST.toInstant());
// getInstant() returns the UTC instant; atZone converts to the specified timezone
System.out.println(nextTransition.getInstant().atZone(zone)); // 2017-10-15T01:00-02:00[America/Sao_Paulo]
// you can also check the date/time and offset before and after the DST change
// in this case, at 15/10/2017, the clock is moved 1 hour forward (from midnight to 1 AM)
beforeDST = ZonedDateTime.of(nextTransition.getDateTimeBefore(), nextTransition.getOffsetBefore());
System.out.println(beforeDST); // 2017-10-15T00:00-03:00
afterDST = ZonedDateTime.of(nextTransition.getDateTimeAfter(), nextTransition.getOffsetAfter());
System.out.println(afterDST); // 2017-10-15T01:00-02:00
If you want to find the previous DST change instead of the next, you can call rules.previousTransition() instead of rules.nextTransition().

Certainly there is. There are also more than one. The standard API to use is java.time.
Quite obviously you first need to decide the time zone you want this for.
You tagged your question gmt, and this is easy: GMT does not have daylight saving time (summer time) so there will never be a changeover in your range. If this is what you meant, you need to read no further.
Daylight saving changeover dates are not the same in North America and EU, and on the southern hemisphere they are yet completely different. Also many time zones do not apply DST at all. So get your intended time zone from ZoneId.of(), providing a string in the form continent/city, for example Europe/Stockholm. It accepts a number of cities, I think there’s at least one in every time zone and one in every country. Use ZoneId.getRules() to get a ZoneRules object. Please check the documentation for all the things you can do with this object. I think I would try nextTransistion() passing your start date. If I get a null back, there cannot be a changeover in the range (likely the zone does not apply DST). If I get a ZoneOffsetTransition back, use its getInstant() and check if the Instant lies before your end date.
java.time was described in JSR-310. It is built into Java 8 and later. If you are not yet using Java 8, use the ThreeTen Backport.
You tagged your question jodatime, and yes, Joda-Time should be an option too.
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).
Quoted from the Joda-Time homepage.

Related

How to change date and fast time

I need to change the time of a date to 23.59.
I can do it but the fast time remain the same so the date is actually not changed.
*start is my starting date exemple 6th december 2017 at 9.31am
Calendar calendar = Calendar.getInstance();
calendar.setTime(start);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE,0);
calendar.set(Calendar.SECOND,0);
start = calendar.getTime();
I have start date at 6th december 2017 at 23.59am but the fast time is still the same.
How can I do?
I recommend you stop using the Calendar class and use java.time, the modern Java date and time API for your task:
ZonedDateTime dateTime = ZonedDateTime.of(
2017, 12, 6, 9, 31, 0, 0, ZoneId.of("Canada/Newfoundland"));
// to change time to 0:00:00
dateTime = dateTime.truncatedTo(ChronoUnit.DAYS);
System.out.println("Changed to 0:00: " + dateTime);
// to change to end of day
dateTime = dateTime.with(LocalTime.MAX);
System.out.println("Changed to end of day: " + dateTime);
// if you need it without seconds
dateTime = dateTime.truncatedTo(ChronoUnit.MINUTES);
System.out.println("Changed to 23:59: " + dateTime);
This prints
Changed to 0:00: 2017-12-06T00:00-03:30[Canada/Newfoundland]
Changed to end of day: 2017-12-06T23:59:59.999999999-03:30[Canada/Newfoundland]
Changed to 23:59: 2017-12-06T23:59-03:30[Canada/Newfoundland]
The classes Date and Calendar are long outmoded. java.time is so much nicer to work with (it is also known as JSR-310).
I recommend you specify explicitly which time zone you want your time in. So fill in yours unless it happens to be Canada/Newfoundland. To use your JVM’s time zone setting use ZoneId.systemDefault(), but beware that this setting may be changed by other parts of your program or other programs running in the same JVM.
In case start is a Date you got from a legacy API that you do not want to change just now, start by converting it to java.time.Instant and do the remainder of your operations from there:
ZonedDateTime dateTime = start.toInstant().atZone(ZoneId.of("Canada/Newfoundland"));
The rest is the same as above. If the opposite was the case, you need to pass a Date to a legacy API at the end, the opposite conversion is:
start = Date.from(dateTime.toInstant());
What went wrong in your code?
There’s is nothing wrong with your code. At least on my computer it behaves as expected. I would be very surprised if it didn’t on yours. A bit of speculation: You may have noticed that the first and the last digits of the value of start.fastTime were the same, and this could have fooled you into thinking the value had not changed. If this was the problem, it may help you to add this line:
calendar.set(Calendar.MILLISECOND, 0);
This will make sure that fastTime ends in at least five zeroes, for example 1513206000000, and it will be more conspicuous that it has changed. Fortunately, the modern API offers the truncatedTo method, an easy way to obtain zeroes on the last places of your milliseconds value.
Links
Oracle tutorial trail: Date Time
Java Specification Request (JSR) 310

Loop a Task for one Year with GregorianCalendar

I have to write a program which shows a Timeplan about when to send emails.
The User is inputing a Start date and I have to show the timeplan for one year.
How do I loop the Task?
In this example the mails should be sent every 8 days.
if(recipient==0) {
System.out.println("send mail on this day:" +calendar.getTime());
calendar.add((GregorianCalendar.DAY_OF_YEAR),8);
return true;
}
I would like to loop the System.out.println and the calendar.add task until it is one year later.
edit: I have another case where it should send the emails every 16 days but when the day is a saturday or sunday it should send the mail on the following monday.
I did it like this but now I get more dates than I need.
if(empfaenger==1)
{
for (Date d=startDate; d.before(endDate); d.setTime(d.getTime() + (1000 * 60 * 60 * 24 * 8)))
{
if(calendar.get(calendar.DAY_OF_WEEK)==1)
{
calendar.add((GregorianCalendar.DAY_OF_YEAR),1);
System.out.println("mail will be sent on this day:"+calendar.getTime());
calendar.add((GregorianCalendar.DAY_OF_YEAR), 16);
}
else if(calendar.get(calendar.DAY_OF_WEEK)==7)
{
calendar.add((GregorianCalendar.DAY_OF_YEAR), 2);
System.out.println("mail will be sent on this day:"+calendar.getTime());
calendar.add((GregorianCalendar.DAY_OF_YEAR),16);
}
else
{
System.out.println("mail will be sent on this day:"+calendar.getTime());
calendar.add((GregorianCalendar.DAY_OF_YEAR),16);
}
//System.out.println(calendar.getTime;)
}
}
Here is a sample using java.time api from java 8 , it's much more easier to understand and use compered to calendar or date classes :
static void sendEveryEightDays(){
LocalDateTime timeToSendEmail= LocalDateTime.now();
LocalDateTime afterAYear = timeToSendEmail.plusYears(1);
while(timeToSendEmail.isBefore(afterAYear)){
System.out.println("SendTheEmail "+timeToSendEmail.toString());
timeToSendEmail=timeToSendEmail.plusDays(8);
}
}
if you want to take the user's time zone into consideration you can use ZonedDateTime instated off LocalDateTime :
static void sendEveryEightDays(ZoneId userTimeZone){
ZonedDateTime timeToSendEmail= ZonedDateTime.now(userTimeZone);
ZonedDateTime afterAYear = timeToSendEmail.plusYears(1);
while(timeToSendEmail.isBefore(afterAYear)){
System.out.println("SendTheEmail "+timeToSendEmail.toString());
timeToSendEmail=timeToSendEmail.plusDays(8);
}
}
I wonder why teachers are still teaching the old API (Date, Calendar and SimpleDateFormat), because they have lots of problems and design issues, and they're being replaced by the new APIs. (Java 8 was released in 2014, btw).
Anyway, if you have a GregorianCalendar, you can convert it to the new java.time classes and do the rest with them.
First, you can use the calendar to create an Instant:
Instant instant = Instant.ofEpochMilli(calendar.getTimeInMillis());
The only problem is that, if you create a Calendar and set the day, month and year, it will have the current time (hour/minute/seconds), so the Instant above will have the current time in UTC. If that's ok, you can convert this instant to your timezone:
ZoneId zone = ZoneId.of("America/Sao_Paulo");
ZonedDateTime start = instant.atZone(zone);
I used America/Sao_Paulo, but you can change to the timezone that makes sense to your system. The API uses IANA timezones names (always in the format Region/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.
You can get a list of available timezones (and choose the one that fits best your system) by calling ZoneId.getAvailableZoneIds(). You can also use the system's default if you want (ZoneId.systemDefault()), but note that this can be changed without notice, even at runtime, so it's always better to specify which timezone you're using. If you want to work with dates in UTC, you can use the built-in constant ZoneOffset.UTC.
The code above will create a ZonedDateTime with the calendar's date and time adjusted to the specified timezone. Just reminding that, if you do something like this:
Calendar calendar = new GregorianCalendar();
calendar.set(2017, 7, 12);
The date will be equivalent to August 12th 2017 (because months in the Calendar API start at zero, so month 7 is August), and the time will be the current time when the calendar is created.
If you want to specify the hour, you have some options to adjust it:
// change the hour/minute/second to 10:20:45
start = start.with(LocalTime.of(10, 20, 45));
// change just the hour to 10
start = start.withHour(10);
// set to start of the day
start = start.toLocalDate().atStartOfDay(zone);
With this, you can change the time (and also date) fields accordingly. Check the javadoc and Oracle's tutorial to see all the options available. The method atStartOfDay is better because it takes care of Daylight Saving Time changes (depending on DST shift, the day can start at 1AM instead of midnight, and this method takes care of all the details).
If you don't want to rely on Calendar, you can also create the date directly:
// creating August 12th 2017, at 10:00
start = ZonedDateTime.of(2017, 8, 12, 10, 0, 0, 0, zone);
Note that August is month 8 (one of the best and most obvious improvements from the old API).
Now that you have the starting date, you can loop through a whole year and check the dates according to your rules. I'm using the example of sending the email each 16 days and adjust to next monday if it's a weekend:
ZonedDateTime d = start;
// ends in 1 year - this method already takes care of leap years
ZonedDateTime end = start.plusYears(1);
while (end.isAfter(d)) {
d = d.plusDays(16);
if (d.getDayOfWeek() == DayOfWeek.SUNDAY || d.getDayOfWeek() == DayOfWeek.SATURDAY) {
// weekend, adjust to next monday
d = d.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
}
// send email
}
If you're using Java <= 7, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes.
The only difference from Java 8 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.
As #BasilBourque reminded me in the comments, you can also convert a GregorianCalendar to a ZonedDateTime using the toZonedDateTime() method (this will use the calendar's timezone - usually the system's default, if you don't set it). You can also convert it to an Instant using the toInstant() method. The only restriction is that those methods are only available in Java 8 (so, if you're using ThreeTen Backport, just use the way it's described above).

How do I add one day to a Calendar object and also account for daylight savings time?

I am trying to create a time limit for objects in a list. This could mean that the objects shelf life could be 23, 24 or 25 hours. Are there any Java libraries that could be useful? This is what I have so far.
My problem is that when I create a record at 9:30 am for example, it must be removed at 9:30 am on the following day. I get discrepancies when it is during the days which DST takes effect. The record is is either deleted at 8:30 or 10:30 depending if I spring forward or backward.
//baseValue = object that I want to check
Date dt = new Date();
Calendar c = Calendar.getInstance();
c.setTime(dt);
c.add(Calendar.DATE, -1);
if(baseValue.getTime() < c.getTime()){
array.remove(baseValue);
}
The old classes (Date, Calendar and SimpleDateFormat) have lots of problems and design issues, including difficulty to deal with DST changes, and they're being replaced by the new APIs.
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.
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 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 take care of DST changes, the ideal class is ZonedDateTime, which represents a date and time in a specific timezone. I also use the ZoneId class, which represents the timezone itself.
I'm using my timezone (America/Sao_Paulo), because here we have DST as well, but you can replace with yours (more on that below):
// create a date 1 day before DST change in Sao Paulo, at 9 AM
ZoneId zone = ZoneId.of("America/Sao_Paulo");
ZonedDateTime z = ZonedDateTime.of(2017, 10, 14, 9, 0, 0, 0, zone);
// get the next day, at 9 AM
ZonedDateTime nextDay = z.plusDays(1);
System.out.println(z);
System.out.println(nextDay);
The output is:
2017-10-14T09:00-03:00[America/Sao_Paulo]
2017-10-15T09:00-02:00[America/Sao_Paulo]
Note that the offset changed from -03:00 to -02:00 - it's due to DST starting in São Paulo timezone (clocks move forward 1 hour). But also note that the time (9 AM) was preserved correctly.
If we take the difference in hours, we can see that it's correct:
System.out.println(ChronoUnit.HOURS.between(z, nextDay));
The output is:
23
Which correctly means that 23 hours has passed between those 2 dates (because of clocks shifting 1 hour forward, so 1 hour is "lost").
In your case, you need to know if 1 day has already passed, so you just call:
long days = ChronoUnit.DAYS.between(z, nextDay);
In this case, days will be 1 (even if the difference in hours calculated above is 23, because the API is smart enough to consider DST effects).
So in your case, you just need to check if the difference in days is 1 (or greater than 1, I don't know) and do all that it needs to be done.
If you need to get the current date/time, you can call ZonedDateTime.now(zone).
To use your timezone instead of mine, first note 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.
You can get a list of timezones names with ZoneId.getAvailableZoneIds() - then choose the one that fits best to your case.
You can also use ZoneId.systemDefault() - it returns the system's default timezone. But this can be changed without notice - even at runtime - so it's recommended to use an explicit timezone.

Duration with daylight saving

I have an object Shift, with two fields: startDateTime and endDateTime as DateTime from Joda-Time.
And my shift includes Daylight Saving UK change. It starts on 25/03/2017 13:00 and ends on 26/03/2017 02:00 (basically should end on 26/03/2017 01:00, but this date does not exists, and endDate is shifted +1 hour). According to this site:
When local standard time was about to reach
Sunday, 26 March 2017, 01:00:00 clocks were turned forward 1 hour to
Sunday, 26 March 2017, 02:00:00 local daylight time instead.
Now if I want to track the number of hours worked by a employee
new Duration(startDateTime, endDateTime).toStandardHours().getHours() will give me 13 hours.
How can I detect if Daylight Saving starts or ends on my shift duration using Joda-Time?
You can use org.joda.time.DateTimeZone class. It has information about DST changes for all timezones in the world, so you don't need to detect the DST change: if you correctly inform which timezone you're using, the API does the job for you.
As you're working with UK timezone, you can use directly the Europe/London timezone - these names in the format Continent/City comes from the IANA database, and it's used by Joda-Time, Java and many other APIs. You can get a list of all timezones by calling DateTimeZone.getAvailableIDs().
When you use a DateTimeZone, it already contains all the DST shifts during history, so the API does all the math for you. You just need to create your DateTime instances at this timezone:
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Duration;
// London timezone - it contains all info about DST shifts
DateTimeZone london = DateTimeZone.forID("Europe/London");
// Start on 25/03/2017 13:00
DateTime start = new DateTime(2017, 3, 25, 13, 0, london);
// ends on 26/03/2017 02:00
DateTime end = new DateTime(2017, 3, 26, 2, 0, london);
// get the difference between start and end
Duration duration = new Duration(start, end);
System.out.println(duration.getStandardHours()); // 12
The output will be 12 (the API uses the information of the DateTime's timezones to calculate the difference, including DST shifts).
You can also use:
duration.toStandardHours().getHours()
Or the class org.joda.time.Hours:
Hours.hoursBetween(start, end).getHours()
All of them return 12, and they're all equivalent.
Java new Date/Time API
Joda-Time it's being discontinued and replaced by the new APIs, so if you're considering a migration, you can start 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.
The new API also uses IANA timezones names and contains the same historical information about the DST shifts (to get a list of all timezones: ZoneId.getAvailableZoneIds()). The code is very similar, and there's also more than one way to get the difference:
import java.time.Duration;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
// London timezone
ZoneId london = ZoneId.of("Europe/London");
// Start on 25/03/2017 13:00
ZonedDateTime start = ZonedDateTime.of(2017, 3, 25, 13, 0, 0, 0, london);
// ends on 26/03/2017 02:00
ZonedDateTime end = ZonedDateTime.of(2017, 3, 26, 2, 0, 0, 0, london);
// get the difference in hours
System.out.println(ChronoUnit.HOURS.between(start, end)); // 12
// get the duration in hours
Duration duration = Duration.between(start, end);
System.out.println(duration.toHours()); // 12
// using until() method
System.out.println(start.until(end, ChronoUnit.HOURS)); // 12
All the three methods above (ChronoUnit.HOURS.between(), duration.toHours() and start.until()) return 12. According to javadoc, between and until are equivalent, as the first just internally calls the second. Using a Duration is also equivalent, as it uses the nanoseconds between them and convert it to hours.
You should use TimeZone ID e.g., "Europe/London"
Convert start and end time into GMT
Now find the duration from GMT start and end times
Daylight is automatically handled when u use TimeZone IDs.

Handling date/time in java/android and daylight savings

I am making a diary application for Android and I want to allow the user to select the timezone they are in. Time has always been a area of confusion for me programatically.
I am going to create an enum for the available timezones.
I am going to save date/time entries to a sqlite in long UTC format, then handling offsets and DST programmatically in Java for display purposes.
I am actually aware of Java's limitations when it comes to date/time handling.
Calendar utc = Calendar.getInstance(TimeZone.getTimeZone("UTC")); //returns the current time in UTC format
Long utcLong = utc.getTimeInMillis(); //returns current utc time in long for database insertion
Question 1: How would I apply an offset to it and account for when to apply any additional DST offsets? Because not all timezones observe DST and DST comes into effect at different dates for different timezones.
Question 2: Java's TimeZone class has something like ~800 ids, it would be annoying to the user to have to scroll through ~800 options to find the one that applys to them. Is there a short list available? I'm thinking there are around ~50 useful timezones.
First of all, I recommend you to not use the Calendar class. It's outdated and has lots of bugs and design issues. This terrible API was replaced by much better ones:
for Java >= 8, use the new date-time API
for Java <= 7, use the ThreeTen Backport
for Android, you can also try ThreeTenABP
The code below works for all, the only difference is the package names (in Java 8 is java.time and in ThreeTen Backport is org.threeten.bp), but the classes and methods names are the same.
To get the UTC current date/time, the best choice is to use Instant class:
// current date/time in UTC - now() always returns the current instant in UTC
Instant instant = Instant.now();
System.out.println(instant); // 2017-06-03T18:03:55.976Z
// equivalent to calendar.getTimeInMillis(), it returns a long
System.out.println(instant.toEpochMilli()); // 1496513035976
To convert this instant to a timezone, you can use the ZoneId with a ZonedDateTime:
// ZoneId accepts the same IDs used by TimeZone
ZoneId zone = ZoneId.of("America/Sao_Paulo");
// convert instant to timezone
ZonedDateTime z = instant.atZone(zone);
System.out.println(z); // 2017-06-03T15:03:55.976-03:00[America/Sao_Paulo]
// converts back to UTC (returns an Instant)
System.out.println(z.toInstant()); // 2017-06-03T18:03:55.976Z
The code above already takes care of DST changes, so the conversion from and to UTC is straightforward.
Timezone list
You say that you have a list of ~50 "useful" timezones. I don't know what criteria you used to define that list, but what happens if an user is in a timezone that's not in the list?
There are some ideas of timezone-picking user interfaces in this link and here. You can choose one and adapt to your app.
I also suggest to not use (if possible) the 3-letter timezone abbreviations (like CST or PST) because they are ambiguous and not standard. It's better to use the full names (like America/Sao_Paulo or Europe/London) as they are the ones used by Java's APIs (you can get the full list with ZoneId.getAvailableZoneIds()) and they are configured with all DST changes for each zone.

Categories