Home > Back-end >  How to map offset integer to IANA timezone
How to map offset integer to IANA timezone

Time:07-16

Using a system that stores timezone information for historical object as follows:

o1:    dt= 2022-07-15 13:32:00  offset=5   dst=false    
o2:    dt= 2022-01-15 13:32:00  offset=5   dst=true     
o3:    dt= 2022-07-15 13:32:00  offset=7   dst=false    

Life would be much easier if it stored IANA timezones like "America/New York" but this is what I have to deal with. My end goal is to compare these historical dates to the current time and get a time difference between them.

Creating current time is easy. I can get it in UTC

   ZonedDateTime.now(ZoneId.of("UTC"))
   2022-07-15T15:33:04.177Z[UTC]

Or my local timezone

ZonedDateTime.now()
2022-07-15T11:33:28.648-04:00[America/New_York]

The question is, how can I read the table data into a ZonedDateTime for an apples to apples comparison. For example some magical method like

ZonedDateTime.fromOffset(5).fromDst(false).asTz('UTC')

CodePudding user response:

You can use ZoneOffset with the specified offset, the DST true/false doesn't seem relevant here. Assuming the timestamps are shown are local (and not UTC), you can do something like:

public static void main(String[] args) {
    record TimestampAndOffset(String timestamp, int offsetHours) { }
    
    var timeData = List.of(
            new TimestampAndOffset("2022-07-15T13:32:00", 5),
            new TimestampAndOffset("2022-01-15T13:32:00", 5),
            new TimestampAndOffset("2022-07-15T13:32:00", 7));

    timeData.forEach(to -> {
        LocalDateTime localTimestamp = LocalDateTime.parse(to.timestamp());
        var zoneOffset = ZoneOffset.ofHours(to.offsetHours());
        var zonedValue = ZonedDateTime.of(localTimestamp, zoneOffset);
        System.out.println(zonedValue);
        System.out.println(zonedValue.withZoneSameInstant(ZoneOffset.UTC));
    });
}

To simplify parsing for this example, I use ISO-8601 format with T as the separator.

Output:

2022-07-15T13:32 05:00
2022-07-15T08:32Z
2022-01-15T13:32 05:00
2022-01-15T08:32Z
2022-07-15T13:32 07:00
2022-07-15T06:32Z

Important caveat as pointed out by user16320675 in the comments, double check the meaning of your offsets, as based on the original version of your question, your data might be using an inverse offset (e.g. 5 instead of -5), in which case you need to use ZoneOffset.ofHours(-to.offsetHours()) instead.

CodePudding user response:

@Mark Rotteveel answered this question. I had to adapt his answer a bit to fit my exact needs and the constraint of being on Java 8 so am posting my exact solution in case useful.

LocalDateTime localDt = LocalDateTime.parse("2022-07-15T13:32:00");
//IF DST MAKE THIS 4
ZoneOffset offset = ZoneOffset.ofHours(-5);
ZonedDateTime zonedDt = ZonedDateTime.of(localDt, offset);
ZonedDateTime zonedDtUtc = zonedDt.withZoneSameInstant(ZoneOffset.UTC);
log.info("Local: {}, Zoned:{},  Zoned UTC: {}", localDt, zonedDt, zonedDtUtc);

Which outputs

Local: 2022-07-15T13:32, Zoned:2022-07-15T13:32-05:00,  Zoned UTC: 2022-07-15T18:32Z

CodePudding user response:

OffsetDateTime

The other two Answers by Mark Rotteveel and by Adam Hughes are both basically correct. But they both use ZonedDateTime rather than the more appropriate class, OffsetDateTime. And they do not address the specific parsing needs asked in the Question. And apparently the input data is already in objects, so I will represent that as records.

public record History( String dt , int offset , boolean dst ) { }

Populate some data.

List < History > historyList =
        List.of(
                new History( "2022-07-15 13:32:00" , 5 , false ) ,
                new History( "2022-01-15 13:32:00" , 5 , true ) ,
                new History( "2022-07-15 13:32:00" , 7 , false )
        );

We need to produce a java.time.OffsetDateTime object for each of those inputs. We can add a method to our record to perform that work.

To parse the given inputs, we can either define a DateTimeFormatter with a formatting pattern to match, or we can do string manipulation to bring the inputs into compliance with the ISO 8601 standard format used by default in OffsetDateTime.parse. I choose the latter. We simply need to replace the SPACE in the middle with a T.

public record History( String dt , int offset , boolean dst )
{
    OffsetDateTime toOffsetDateTime ( )
    {
        LocalDateTime ldt = LocalDateTime.parse( this.dt.replace( " " , "T" ) );
        ZoneOffset o = ZoneOffset.ofHours( this.offset );
        OffsetDateTime odt = ldt.atOffset( o );
        return odt;
    }
}

Capture the current moment.

Instant now = Instant.now() ;

Your goal is to determine elapsed time. Use Duration for that. We can add another method to History record for that purpose.

package work.basil.example.history;

import java.time.*;

public record History( String dt , int offset , boolean dst )
{
    OffsetDateTime toOffsetDateTime ( )
    {
        LocalDateTime ldt = LocalDateTime.parse( this.dt.replace( " " , "T" ) );
        ZoneOffset o = ZoneOffset.ofHours( this.offset );
        OffsetDateTime odt = ldt.atOffset( o );
        return odt;
    }

    Duration elapsed ( final Instant instant )
    {
        Duration d = Duration.between( this.toOffsetDateTime().toInstant() , instant );
        return d;
    }
}

Loop the inputs to report their elapsed time.

Instant now = Instant.now();
for ( History history : historyList )
{
    String message = history.elapsed( now ).toString()   " from "   history.toString()   " to "   now.toString();
    System.out.println( message );
}

When run.

PT16H27M40.119075S from History[dt=2022-07-15 13:32:00, offset=5, dst=false] to 2022-07-16T00:59:40.119075Z
PT4360H27M40.119075S from History[dt=2022-01-15 13:32:00, offset=5, dst=true] to 2022-07-16T00:59:40.119075Z
PT18H27M40.119075S from History[dt=2022-07-15 13:32:00, offset=7, dst=false] to 2022-07-16T00:59:40.119075Z
  • Related