Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are timezones with same offset from UTC are showing different times?

I bumped into this issue today. I have set my clock to UTC-6.00 (Central America) time zone. I am converting the Date "06/01/2015::12:00:00 AM" ("MM/dd/yyyy::hh:mm:ss a" format) to a java Date object. And then I am reconverting the date object to String. There is a slight twist in how I am doing this though. I am listing the re conversion steps below -

  1. Calculate UTC offset from current time zone. (-21600000)
  2. Get all available timezone ids for this offset. (All have same offset)
  3. Select the first time zone id. (Will have same offset)
  4. Set this as the timezone.
  5. Convert the date to string format using Java's Simple Date Format.

I see that the time now rendered is "06/01/2015::01:00:00 AM"

My questions :

  1. Since the timezone offset is same during the creation and during conversion I expect the same time to be shown. But what I see is different. Why is it so?

  2. Imagine the re conversion to be happening in the server and the creation to be happening in the client. I need to render back the same date and time to the client. How do I do this?

Please help! Any help is much appreciated.

EDIT : Following is the code. Note that I have set my current timezone to Central America.

public class TimeTest {

public static void main (String args[]) {

    SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy::hh:mm:ss a");
    String dateInString = "01/06/2015::12:00:00 AM";

    try {    
        Date date = formatter.parse(dateInString);
        System.out.println("Before conversion --> " + formatter.format(date));
        System.out.println("After conversion --> " + convertDateValueIntoString(date));


    } catch (ParseException e) {
        e.printStackTrace();
    }       
}

private static String convertDateValueIntoString(Date dateValue){
    SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy::hh:mm:ss a");       
    String date;
    int offset = TimeZone.getDefault().getRawOffset();
    if (offset == 0) {
        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
        date = dateFormat.format(dateValue);
    } else {        
        String TZ[] = TimeZone.getAvailableIDs(offset);
        String timeZone = TZ[0];
        if (timeZone == null) {
            date = dateFormat.format(dateValue);
        } else {
            TimeZone tz = TimeZone.getTimeZone(timeZone);
            dateFormat.setTimeZone(tz);
            date = dateFormat.format(dateValue);
        }           
    }

    return date;
}
}
like image 697
coder Avatar asked Dec 19 '22 03:12

coder


2 Answers

  1. Why are the times different:

The difference appears to be in the handling of daylight savings time. Playing around with setting my machine to different time zones and printing the TimeZone toString() I ended up with:

Initial: sun.util.calendar.ZoneInfo[id="America/Tegucigalpa",offset=-21600000,dstSavings=0,useDaylight=false,transitions=9,lastRule=null]
Result: sun.util.calendar.ZoneInfo[id="America/Bahia_Banderas",offset=-21600000,dstSavings=3600000,useDaylight=true,...

Note that these two TimeZones have the same offset, but one uses daylight savings time and the other does not. The offset is all your code is looking at to find an appropriate TimeZone but the date formatting also uses the daylight savings offset.

  1. How do I handle this:

The way every project I've been on that used times did it was to have all internal representation of time be in UTC (or a similar concept). I would have your client convert the time to UTC on input (before sending it to the server), have all server storage use UTC, then when times go back to the client have the client format to the default TimeZone only for output to the user.

That way all your internal times are consistent and all your displayed times are localized for the individual instance of the client, so a user in America/Tegucigalpa may get the time as 12:00 but the user in America/Bahia_Banderas would see 1:00. Both are correct for the users those times would be displayed to.

like image 71
1337joe Avatar answered Dec 22 '22 01:12

1337joe


The Answer by 1337joe is correct. I'll add a few thoughts.

This Question has much confusion floating around.

Time Zone = Offset + Rules/Anomalies/Adjustments

First, a time zone is more than an offset from UTC. A time zone is an offset plus a set of past, present, and future rules about Daylight Saving Time and other anomalies & adjustments.

So whenever possible, use a named time zone rather than a mere offset. And certainly do not mix usage of offset-only with usage of time zones and expect sensible results. That seems to be the core problem in this Question.

So, dig deeper to discover the original intent of the programmers who devised your existing stored data. I suspect they did indeed have a particular time zone in mind rather than a mere offset.

Use Proper Time Zone Names

There is no such time zone as "Central America".

As 1337Joe points out, offsets and time zones vary around Central America. For example, America/Managua is six hours behind UTC while America/Panama is five.

By the way, avoid the 3-4 letter codes for time zones such as "EST" as they are neither standardized nor unique. The one exception is UTC of course.

Specify Your Expected/Desired Time Zone

When [a] you know your incoming data represents a particular time zone or offset, albeit implicitly, and [b] you desire a certain time zone to be applied, do not call on the default time zone. That is asking for trouble. The default time zone can vary by host OS setting on machine by machine. And both the host OS settings can be changed at any time by an admin person. Thirdly, the JVM’s current default time zone can be changed at any moment during runtime by a call to TimeZone.setDefault() by any code in any thread in any app in that same JVM.

So, instead of relying on the default time zone, specify the desired time zone.

Use UTC For Logic & Storage

As 1337joe said, your business logic, data storage, data communication, and database should all be in UTC (almost always). Only apply adjustments to local time zones when expected by the user/consumer.

In comments, the author said their project is already saddled with existing stored data implicitly representing a certain time zone or offset.

java.util.Date toString

The toString method on java.util.Date automatically applies the JVM’s current default time zone. This makes working with time zone adjustments tricky. One of many reasons to avoid using the java.util.Date/.Calendar & java.text.SimpleDateFormat classes.

Use Better Date-Time Library

Use either the new java.time package in Java 8 and later (Tutorial), or the Joda-Time library (which inspired java.time).

Joda-Time

Here is some example code in Joda-Time.

According to the author’s comments, the incoming string implicitly represents a date-time value for a certain known time zone. That time zone is not stated, so I'll arbitrarily use Panama time zone. In this first part, we parse a string while specifying the time zone to be used during parsing and assigned to the resulting object.

DateTimeZone zonePanama = DateTimeZone.forID( "America/Panama" );
DateTimeFormatter formatter = DateTimeFormat.forPattern( "dd/MM/yyyy::hh:mm:ss a" );
String input = "06/01/2015::12:00:00 AM";
DateTime dateTimePanama = formatter.withZone( zonePanama ).parseDateTime( input );
System.out.println( "Input as string: " + input + " becomes object: " + dateTimePanama + " with time zone: " + dateTimePanama.getZone() );

Now let's adjust to UTC. Here this is for demonstration. In real code you would generally do any further work using this UTC value.

DateTime dateTimeUtc = dateTimePanama.withZone( DateTimeZone.UTC );
System.out.println( "dateTimeUtc: " + dateTimeUtc );

For output, our user/consumer expects a String representation in the same Panama time zone and in the same format as our input.

String output = formatter.print( dateTimeUtc.withZone( zonePanama ) );
System.out.println( "Output in special format: " + output );

When run.

Input as string: 06/01/2015::12:00:00 AM becomes object: 2015-01-06T00:00:00.000-05:00 with time zone: America/Panama
dateTimeUtc: 2015-01-06T05:00:00.000Z
Output in special format: 06/01/2015::12:00:00 AM
like image 28
Basil Bourque Avatar answered Dec 21 '22 23:12

Basil Bourque