alibaba / Sentinel

A powerful flow control component enabling reliability, resilience and monitoring for microservices. (面向云原生微服务的高可用流控防护组件)
https://sentinelguard.io/
Apache License 2.0
22.45k stars 8.04k forks source link

Fixed time format assertion failing due to daylight saving time #3459

Open hermya opened 1 month ago

hermya commented 1 month ago

Describe what this PR does / why we need it

PR intends to fix the test EagleEyeCoreUtilsTest.testFormatTime.

Does this pull request fix one issue?

Fixes https://github.com/alibaba/Sentinel/issues/3458

Describe how you did it

Used TimeZone.getDefault().getDSTSavings()

Describe how to verify it

Reran the test-case, now passing successfully

Special notes for reviews

CLAassistant commented 1 month ago

CLA assistant check
All committers have signed the CLA.

robberphex commented 1 month ago

With DSTSavings in mind, I think there should be three test cases:

  1. In a time zone that does not observe the DST, the local standard time is returned
  2. If the current time is not in the DST time zone, the returned value must be the same as the local standard time
  3. If the current time is within the DST range, the local wall time, which is inconsistent with the local standard time, is returned
hermya commented 1 month ago

The latest commit contains TimeZone.getOffset() method. According to this, getOffset() should cater to DST, based on the zone and date.

I tried it with sample code for different zones and times as follows:

import java.sql.Date;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.stream.Collectors;

public class Main {

    public static DateTimeFormatter getFormatterByZone(String zoneId) {
        return DateTimeFormatter
            .ofPattern("yyyy-MM-dd HH:mm:ss.SSS")
            .withZone(ZoneId.of(zoneId));
    }

    public static void printZoneInformation(String zoneId) {
        var tz = TimeZone.getTimeZone(zoneId);
        System.out.println("Time zone: " + tz.getDisplayName() + ", RawOffset: " + tz.getRawOffset() + ", DST: " + tz.observesDaylightTime() + ", DSTSavings: " + tz.getDSTSavings());
    }

    public static long getOffsetForZone(String zoneId, long epoch) {
        return TimeZone.getTimeZone(zoneId).getOffset(epoch);
    }

    public static void main(String[] args) {
        // SAPST -> (UTC-05:00) Bogota, Lima, Quito No DST
        // IST -> (UTC+05:30) India Standard Time No DST
        // CDT -> (UTC-05:00) Central Daylight Time DST
        long epochNotInDST = 1729382400000l;
        long epochInDST = 1732060800000l;
        // 1729382400000l -> 10/20/2024 00:00:00.000 Date not in DST
        // 1732060800000l -> 11/20/2024 00:00:00.000 Date in DST

        System.out.println("\nDate in DST:    ");
        printZoneInformation("Etc/GMT+0");
        System.out.println(getFormatterByZone("Etc/GMT+0").format(Instant.ofEpochMilli(epochInDST - getOffsetForZone("Etc/GMT+0", epochInDST))));  
        printZoneInformation("America/Bogota");
        System.out.println(getFormatterByZone("America/Bogota").format(Instant.ofEpochMilli(epochInDST - getOffsetForZone("America/Bogota", epochInDST))));    
        printZoneInformation("Asia/Kolkata");
        System.out.println(getFormatterByZone("Asia/Kolkata").format(Instant.ofEpochMilli(epochInDST - getOffsetForZone("Asia/Kolkata", epochInDST))));    
        printZoneInformation("America/Chicago");
        System.out.println(getFormatterByZone("America/Chicago").format(Instant.ofEpochMilli(epochInDST - getOffsetForZone("America/Chicago", epochInDST))));    

        System.out.println("\nDate not in DST:    ");
        printZoneInformation("Etc/GMT+0");
        System.out.println(getFormatterByZone("Etc/GMT+0").format(Instant.ofEpochMilli(epochNotInDST - getOffsetForZone("Etc/GMT+0", epochNotInDST))));  
        printZoneInformation("America/Bogota");
        System.out.println(getFormatterByZone("America/Bogota").format(Instant.ofEpochMilli(epochNotInDST - getOffsetForZone("America/Bogota", epochNotInDST))));    
        printZoneInformation("Asia/Kolkata");
        System.out.println(getFormatterByZone("Asia/Kolkata").format(Instant.ofEpochMilli(epochNotInDST - getOffsetForZone("Asia/Kolkata", epochNotInDST))));    
        printZoneInformation("America/Chicago");
        System.out.println(getFormatterByZone("America/Chicago").format(Instant.ofEpochMilli(epochNotInDST - getOffsetForZone("America/Chicago", epochNotInDST)))); 
    }
}

And got the output:

Date in DST:
Time zone: Greenwich Mean Time, RawOffset: 0, DST: false, DSTSavings: 0
2024-11-20 00:00:00.000
Time zone: Colombia Standard Time, RawOffset: -18000000, DST: false, DSTSavings: 0
2024-11-20 00:00:00.000
Time zone: India Standard Time, RawOffset: 19800000, DST: false, DSTSavings: 0
2024-11-20 00:00:00.000
Time zone: Central Standard Time, RawOffset: -21600000, DST: true, DSTSavings: 3600000
2024-11-20 00:00:00.000

Date not in DST:
Time zone: Greenwich Mean Time, RawOffset: 0, DST: false, DSTSavings: 0
2024-10-20 00:00:00.000
Time zone: Colombia Standard Time, RawOffset: -18000000, DST: false, DSTSavings: 0
2024-10-20 00:00:00.000
Time zone: India Standard Time, RawOffset: 19800000, DST: false, DSTSavings: 0
2024-10-20 00:00:00.000
Time zone: Central Standard Time, RawOffset: -21600000, DST: true, DSTSavings: 3600000
2024-10-20 00:00:00.000