0
votes

I'm sending date from the Angular app as String to server and converting to java Date object to store in the database.

Also sending timeZoneOffset from UI to use the client's time zone while converting. (After googling I found this approach to get the proper result based on the user location)

Written the following code to convert:

public static void main(String args[]) throws ParseException {
    String inputDate = "04/05/2018"; // This date coming from UI
    int timeZoneOffset = -330; // This offset coming from UI 
                               // (new Date().getTimeZoneOffset())
    getDate(inputDate, timeZoneOffset);
}

public static Date getDate(String inputDate, int timeZoneOffset)
        throws ParseException {
    SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");
    ZoneOffset zoneOffset = ZoneOffset.ofTotalSeconds(-timeZoneOffset * 60);
    System.out.println("Default time zone: " + TimeZone.getDefault().getID());
    TimeZone timeZone = TimeZone.getTimeZone(zoneOffset);
    System.out.println("Time zone from offset: " + timeZone.getID());
    dateFormat.setTimeZone(timeZone);
    Date date = dateFormat.parse(inputDate);
    System.out.println("Converted date: " + date);
    return date;
}

Expected output:

Default time zone: America/New_York
Time zone from offset: GMT+05:30
Converted date: Thu April 5 00:00:00 IST 2018

Actual result in server:

Default time zone: America/New_York
Time zone from offset: GMT+05:30
Converted date: Wed April 4 14:30:00 EDT 2018

Why is the date decreasing to one day even I set the users time zone? I'm new to Date and Time related concepts and I googled a couple of times didn't find answer, could someone please help on this.

Thanks in advance

2

2 Answers

1
votes

The Answer by Godfrey is correct.

tl;dr

LocalDate.parse( 
    "04/05/2018" ,
    DateTimeFormatter.ofPattern( "MM/dd/uuuu" ) 
)
.atStartOfDay(
    ZoneId.of( "Asia/Kolkata" ) 
)
.toString()

2018-04-05T00:00+05:30[Asia/Kolkata]

For storage in your database, use UTC.

When a new day starts in India, the date at UTC is still “yesterday”, so April 4th rather than April 5th. Same moment, same point on the timeline, different wall-clock time.

LocalDate.parse( 
    "04/05/2018" ,
    DateTimeFormatter.ofPattern( "MM/dd/uuuu" ) 
)
.atStartOfDay(
    ZoneId.of( "Asia/Kolkata" ) 
)
.toInstant()

2018-04-04T18:30:00Z

java.time

You are using terrible old date-time classes that have proven to be poorly designed, confusing, and troublesome. They are now supplanted by the java.time classes.

Avoid legacy date-time classes entirely

ZoneOffset zoneOffset = ZoneOffset.ofTotalSeconds(-timeZoneOffset * 60);
…
TimeZone timeZone = TimeZone.getTimeZone(zoneOffset);

You are mixing the modern classes (ZoneOffset) with the troublesome legacy classes (TimeZone). Do not mix the modern classes with the legacy classes. Forget all about the old classes including Date, Calendar, and SimpleDateFormat. The java.time classes are designed to entirely supplant the legacy classes.

Instead of TimeZone, use ZoneId (and ZoneOffset).

LocalDate

Parse your input string as a LocalDate. The LocalDate class represents a date-only value without time-of-day and without time zone.

String input = "04/05/2018" ;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "MM/dd/uuuu" ) ;
LocalDate ld = LocalDate.parse( input , f ) ;

Offset versus Time Zone

int timeZoneOffset = -330;

An offset-from-UTC is not a time zone. An offset is simply a number of hours, minutes, and seconds of displacement from UTC. Your choice of variable name indicates possible confusion on this point.

ZoneOffset offset = ZoneOffset.of( -3 , 30 ) ;  

A time zone is a history of past, present, and future changes in offset used by the people of a particular region. So a time zone is always preferable to an offset.

Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).

ZoneId z = ZoneId.of( "Asia/Kolkata" ) ;  // India time zone. Currently uses offset of +05:30 (five and a half hours ahead of UTC). 

First moment of the day

You seem to be aiming for the first moment of that date in that zone. Let java.time determine that first-moment-of-the-day. Do not assume that time is 00:00:00. In some zones on some dates, the day may start at another time such as 01:00:00.

ZonedDateTime zdt = ld.atStartOfDay( z ) ;  // Determine the first moment of the day on this date in this zone. Not always 00:00:00.

As an example of why you should be using time zones rather than mere offset-from-UTC, look at your example data of -330 which I might easily misinterpret to be three and a half hours behind UTC. This offset is currently only used in the zone America/St_Johns, and only used there for part of the year. So if you applied an offset of -03:30 to a date in the wrong part of the year, your results would be invalid yet go undetected.

Using offset (not recommended)

But your example lacks time zone, so let’s go with offset-from-UTC rather than zone.

Your use of an int integer number to represent an offset-from-UTC is a poor choice of types. First of all, it is ambiguous. That -330 might be interpreted to be a clumsy attempt at -03:30 offset of three and a half hours behind schedule. Secondly, it makes parsing trickier than need be. Thirdly, as a number of minutes, it ignores the possibility of an offset with seconds. Fourthly, you use a negative number for an offset ahead of UTC (apparently) despite common usage and standard usage being the opposite. Lastly, it ignores the clear standard set by ISO 8601 for representing offsets as text: ±HH:MM:SS (and variations). By the way, the padding zero is optional in the standard, but I recommend always including because various libraries and protocols expect it.

Your intent appears to be a number of minutes intended by the integer number.

long seconds =( TimeUnit.MINUTES.toSeconds( - 330 ) * -1 );  // Multiply by negative one to flip the sign to standard ISO 8601 usage, where `+` means “ahead* of UTC and `-` means *behind* UTC.

seconds: 19800

ZoneOffset offset = ZoneOffset.ofTotalSeconds( ( int ) seconds );

offset.toString(): +05:30

Last step: get the first moment of the day in this offset. Caveat: We do not know for certain if this offset is valid on this date, as we lack a time zone.

Convert from the returned ZonedDateTime to an OffsetDateTime. As discussed above, determining first moment of day should always be done with a time zone, and thereby get a ZonedDateTime. We are violating that sensible practice to use an offset, but using the returned ZonedDateTime object would be misleading as ours would lack a true time zone, and have only a mere offset. So the OffsetDateTime class makes our intentions clear and our code more self-documenting.

    OffsetDateTime odt = ld.atStartOfDay( offset ).toOffsetDateTime();

Again, this approach using offset is not recommending, as you should be instead gathering a time zone name from the user as input rather than an offset.

UTC

Generally best to store moments in UTC.

Extract a Instant from your OffsetDateTime or ZonedDateTime to get the same moment as UTC.

Instant instant = zdt.toInstant() ;

2018-04-04T18:30:00Z


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

1
votes

It's not decreasing by one day, it is decreasing by 11.5 hours. That happens to be the time difference between GMT+05:30 and "America/New_York", which is GMT-04:30 or GMT-05:30 (depending on time of year).

GMT+05:30 is somewhere in India, I think, since that is about the only place to use a 30 minute offset rather than a whole hour. When it is April 5th in India, it is still April 4th in New York.

The problem may be you aren't getting a time from the client, so it will assume midnight. If you are doing time zone conversion, it is best to include the actual time.