Why do time slots with the same offset from UTC show different times?

I ran into this question today. I have set the clock to UTC-6.00 (Central America). I am converting the date "06/01/2015 :: 12: 00: 00 AM" (format "MM / dd / yyyy :: hh: mm: ss a") to a Java Date object. And then I rebuild the date object to String. There is a bit of a twist in the way I do it, though. I am listing the following conversion steps below -

  • Calculate UTC offset from the current time zone. (-21600000)
  • Get all available timezone IDs for this offset. (All have the same offset)
  • Select the first zone ID. (Will have the same offset)
  • Set this as your time zone.
  • Convert date to string format using Java Simple Date Format.

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

My questions:

  • Since the timezone offset is the same during creation and during transformation, I expect the time to be shown. But what I see is different. Why is this so?

  • Imagine that the re-transformation has to happen on the server and the creation that will happen in the client. I need to give the same date and time to a client. How to do it?

Please, help! Any help is greatly appreciated.

EDIT: Below is the code. Note that I have set the current timezone for 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;
}
}

      

+3


source to share


3 answers


  • Why different times:

The difference seems to be in the daylight savings time handling. Playing around with setting my machine to different time zones and printing 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 saving time and the other does not. The offset is all your code looking for a suitable TimeZone, but the date formatting also uses daylight saving time offset.



  1. How should I do it:

The way every project I used at this time was to have all internal time representation in UTC (or a similar concept). I would like your client to change the time to UTC on input (before sending it to the server), all server storage uses UTC, and then when the time is returned to the client, the client has a default TimeZone format for output to the user only.

This way, all your internal times are consistent and all the times you display are localized to a single client instance, so a user in America / Tegucigalpa might get the time at 12:00, but a user in America / Bahia _Banderas will see 1:00. Both are correct for the users to be displayed those times.

+4


source


The answer to this question is 1337joe . I'll add a few thoughts.

There is a lot of confusion on this issue.

Time Zone = Offset + Rules / Anomalies / Adjustments

First, the time zone is greater than the offset from UTC. A time zone is an offset plus a set of past, present, and future rules regarding daylight saving time and other anomalies and adjustments.

Therefore, whenever possible, use the timezone name instead of a simple offset. And, of course, don't confuse using offset with only time zones and expect reasonable results. This seems to be the main problem in this question.

So, dig deeper to discover the original intent of the programmers who designed your existing stored data. I suspect that they did have a specific time zone and not just an offset.

Use correct time zone names

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

As 1337Joe notes, offsets and time zones vary across Central America. For example, America/Managua

six hours behind UTC, but America/Panama

five.

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

.

Specify expected / desired time zone

When [a] you know that your input represents a specific time zone or offset, albeit implicitly, and [b] you want a specific time zone to be applied, do not call the default time zone. It takes trouble. The default time zone may vary depending on the host OS installation on machine by machine. And both host OS settings can be changed at any time by the administrator. Third, the current time zone of the JVMs can be changed at any time during execution by calling TimeZone.setDefault()

any code in any thread to any application in the same JVM.

So, instead of relying on the default timezone, specify the timezone you want.

Using UTC for logic and storage

As 1337joe said, your business logic, data storage, data exchange and database should be in UTC (almost always). Only use local time zone adjustments if expected by the user / consumer.



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

java.util.Date toString

The method toString

in java.util.Date will automatically apply the current JVMs timezone by default. This makes it difficult to work with time zone settings. One of the many reasons to avoid using the java.util.Date/.Calendar and java.text.SimpleDateFormat classes.

Use the best time library

Use 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 sample code in Joda-Time .

According to the authors' comments, the input string implicitly represents a date value for a specific known time zone. This timezone is not specified, so I arbitrarily use the Panama timezone. In this first part, we parse the string by specifying the timezone 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 tune to UTC. Here it is for demonstration. In real code, you usually do 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 timezone and in the same format as our input.

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

      

At startup.

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

      

+3


source


For Question # 1: The timezone offset can be the same for different timezones, but DST can be used or not, and this leads to a difference.

For question number 2:

Going forward, you can only be safe while using UTC. (you can work if your time data is "latest" - see below)

In the past, you cannot reliably extract the correct time.

General advice for conversions:

I was working on a project related to time zones and DST in a JDBC driver. There were problems with storing time values ​​and correcting them correctly. I was working / really hard / trying to get the right to convert, so we could save the larger work of switching to UTC. There is no correct conversion without UTC. (/ real hard /: Think of Pulp Fiction, where Jules says, "I try really hard to be a shepherd. :-))

Question # 2 / Future:

If your client is unable to send UTC time (possibly because it is a third party system):

When your server receives time data (not UTC) from a client that you know is current for a few minutes (maybe a little longer), you can try using UTC time and match that to the client. Imagine that your client sends "2015-06-01 15:45" and you know it is "2015-06-01 18:51 UTC", then you can interpret the client's time as "2015-06-01 18: 45 UNIVERSAL GLOBAL TIME ". If the time data sent by the customer may be older than an hour, in some cases this will fail.

Or in other words: Tell your customer to record temperature values. If the data sent by the client is no older than a few minutes, you can map that to UTC time. If your client records the temperature on the same day and sends it to you at the end of the day, you can't match the time correctly.

Why can't you convert completely (!)?

Suppose the night that daylight saving time changes is changing so that the hours change from 03:00 to 02:00. You have 02:30 before the switch and another 02:30 after the switch. The first 02:30 has a different UTC time than the second 02:30. So you're fine with UTC. But only with "client local" 02:30 you will never be sure.

Back to client data age: If your client sends data no older than a few minutes at 02:30 and then a second at the second 02:30, you can highlight this on the server. If at 04:00 you receive two entries in 02:30, you will no longer be able to recover UTC.

Question # 2 / Past:

Can you add a flag to the database so that new times that were passed as UTC are marked "reliable" and the old values ​​are not?


Output and source:

Result of working with a modified source on my system, which has a TZ "Europe / Berlin". Note that this uses DST, but the first TZ received ("Algeria") does not use DST.

formatter TZ is sun.util.calendar.ZoneInfo[id="Europe/Berlin",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=143,lastRule=java.util.SimpleTimeZone[id=Europe/Berlin,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]]
internal date value = 1433109600000 as UTC = 31/05/2015::10:00:00 PM
Before conversion --> 01/06/2015::12:00:00 AM
Conversion: offset != 0, using TZ sun.util.calendar.ZoneInfo[id="Africa/Algiers",offset=3600000,dstSavings=0,useDaylight=false,transitions=35,lastRule=null]
After conversion --> 31/05/2015::11:00:00 PM

Setting UTC...

formatter TZ is sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]
internal date value = 1433116800000 as UTC = 01/06/2015::12:00:00 AM
Before conversion --> 01/06/2015::12:00:00 AM
Conversion: offset != 0, using TZ sun.util.calendar.ZoneInfo[id="Africa/Algiers",offset=3600000,dstSavings=0,useDaylight=false,transitions=35,lastRule=null]
After conversion --> 01/06/2015::01:00:00 AM

      

Source:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

public class TimeTest {
    static TimeZone utc = TimeZone.getTimeZone("UTC");

    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";
        SimpleDateFormat utcformatter = new SimpleDateFormat("dd/MM/yyyy::hh:mm:ss a");
        utcformatter.setTimeZone(utc);

        try {    
            Date date = formatter.parse(dateInString);
            System.out.println("formatter TZ is " + formatter.getTimeZone());
            System.out.println("internal date value = " +  date.getTime() + " as UTC = " + utcformatter.format(date));
            System.out.println("Before conversion --> " + formatter.format(date));
            System.out.println("After conversion --> " + convertDateValueIntoString(date));

            System.out.println("\nSetting UTC...\n");
            formatter.setTimeZone(utc);

            date = formatter.parse(dateInString);
            System.out.println("formatter TZ is " + formatter.getTimeZone());
            System.out.println("internal date value = " +  date.getTime() + " as UTC = " + utcformatter.format(date));
            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) {
            System.out.println("Conversion: offset == 0 -- setting UTC");
            dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
            date = dateFormat.format(dateValue);
        } else {        
            String TZ[] = TimeZone.getAvailableIDs(offset);
            String timeZone = TZ[0];
            if (timeZone == null) {
                System.out.println("Conversion: offset != 0, did not find TZ, tz of dateFormat is " + dateFormat.getTimeZone());
                date = dateFormat.format(dateValue);
            } else {
                TimeZone tz = TimeZone.getTimeZone(timeZone);
                System.out.println("Conversion: offset != 0, using TZ " + tz);
                dateFormat.setTimeZone(tz);
                date = dateFormat.format(dateValue);
            }           
        }

        return date;
    }
}

      

+2


source







All Articles