• 时区


    0 原则

    0.1 前端(浏览器时间)-json序列化-jdbcurl(服务器内存,db时区相对于服务器及jvm时区)-db(db时间)

    0.2 时间戳在地球的每一个角落都是相同的,但是在相同的时间点会有不同的表达方式,所以有了另外一个时间概念,叫时区。

    结论:

    时间戳代表绝对时间

    mysql db datetime存日期时间字符串,timestamp存时间戳(绝对时间)

    db建库前应核准时区,通过select now和show variables like '%time_zone%'

    jdbc 日期时间字符串参与传输,时区及时间戳不参与传输,故应用层应在内存做好server-db的时区转换字符串

    jdbc开启url时区,insert prepared statement与query getObject会自动转换时区;insert 字符串不会

    建库后,应随即进行server-db时区 查询、插入更新核准

    Date与Timestamp差不多,都包含了时区信息与时间戳;序列化框架不会改时间戳,但是会根据序列化框架的默认时区转显示日期时间

    国际化应用应建立 浏览器-服务器-db时区链,前者通过request response 增加浏览器时区参数,反射调整Date类型;后者通过内存手动或jdbc调整时区

    sequel或sql developer不会像jdbc那样帮忙转显示日期时间,显示的日期时间就是db timezone的;证据:1)该客户端连接db时无需db时区,仅有操作系统时区它也无从下手(当然不排除它自己去获取服务器时区,但可能性不大);2)修改本地时区,sql developer上的显示时间不会变,说明即使它自己去拿了服务器时区,也没有将服务器时区与本地时区转换调整显示时间,sql dev就是按服务器时区显示时间

    经oracle实践成功

    1 国际化最佳实践:

    1.1 db最好存储时间戳,时区无关;mysql的Datetime类型就是纯日期型,没有时区信息,你db哪个时区,它都显示一样的日期时间,一旦db时区被修改,绝对时间点信息则丢失,3.4证明

    1.2 db时区最好取0时区,select now()核准当前session时区,show variables like '%time_zone%'核准全局时区

    1.3 若像本文手动处理服务器Date-db的转换,应jdbc url不另外设置时区;

    spring jdbctemplate/mybatis

    jdbc 所有query

    jdbc prepared statement insert

    选择url处理,url的时区==db时区,服务器有自己的时区信息,但需要额外知道db是什么时区,将服务器时间与url中的db时区做转换

    比如,服务器+8北京,db 0 UTC,插入更新时Date-8;查询时Date+8

    jdbc string only insert 应用层 插入更新与查询分别自行反向处理

    *url中的时区信息仅对mybatis 等框架以及jdbc prepared statement插入更新有用,jdbc字符串插入更新操作本身不处理这个参数

    *mysql jdbc driver 无论是字符串、还是setTimestamp,最终转换为时区无关的日期时间字符串(1)(2),时间戳不参与通信,一律使用date字符串tcp通信;而且这个字符串,db服务器接收到后默认为db时区的日期时间(3)

    插入时,driver接收到参数,先用服务器时区与db时区比划转换date,然后传输date字符串,db接收到date,用db时区转为时间戳入库相应的timestamp类型字段,date类型字段则直接入库

    查询时,db对于date类型字段直接返回,对于timestamp类型字段,通过db时区搞成date字符串传输,jvm拿到date,再用本地时区与db时区比划转换

    【这是未证明的重要假定】:初步依据有:

    1)从insert日期字符串可见一斑,时区及时间戳在sql中没有;而且很可能mysql jdbc文本协议,通过抓包未发现除sql文本外其它明显的涉及时区的传输https://www.oschina.net/question/1175066_235198?sort=time mysql 协议抓包

    2)prepeared statement insert也是业务层做时区转换,传输日期时间字符串

    3)如果传输 服务器时区日期时间字符串,db没有办法锁定绝对时间即时间戳;

    如果传输 UTC 0 时区字符串,那么jcbc url中就不需要指明db时区了,何必多此一举

    因此,传输的是db时区的日期时间字符串,db再用自己的时区搞成绝对时间

    4)7.1.2,7.2.1,黄色背景蓝色字,这是本文最重要的结论

    具体流程,见附录图

    1.4 应用层依据db时区(z),做服务器时间(x)核准【时区核准方法论】

    1.5 序列化配合浏览器做个性化json序列化,假设request里包含了一个时区,那么server接收到request,反序列化后,扫描request所有Date类型参数,调整时区;response反射扫描所有Date类型,以这个时区参数序列化

    *序列化springboot默认有时候用UTC,比如7.1.3

    1.6 前端依据服务器时间(x),做浏览器端时间(y)核准

    1.7【时区核准方法论】:

    1.7.1 插入,服务器时间 new Date.getTime 与 db的timestamp (select UNIX_TIMESTAMP(date_field) from my_orm)两个时间戳和谐

    1.7.2 查询,db的timestimp(select UNIX_TIMESTAMP(date_field) from my_orm)与内存中查出来的Date.getTime时间戳和谐

    2 实践准备

    2.1 show variables like "%time_zone%";

    system_time_zone UTC
    time_zone UTC

    docker 的mysql镜像

    2.2 select now();

    2020-04-14 08:16:03

    实际北京时间下午16点16分

    3 预先数据

    3.1

    显示 2019-08-08 21:33:44 2019-08-08 21:33:44
    时间戳 1565300024 1565300024
    实际北京时间 2019-08-09 05:33:44 2019-08-09 05:33:44

    *select unix_timestamp(f_date), unix_timestamp(f_timestamp) from my_timezone

    因此,sequel pro显示的是db所在UTC时区时间,不是北京时间,sequel显示的时间你要自己转换,它不会像jdbc那样帮你转换,不要相信自己的眼睛

    3.2 修改时区

    > set global time_zone = '+8:00'; ##修改mysql全局时区为北京时间,即我们所在的东8区

    > set time_zone = '+8:00'; ##修改当前会话时区
    > flush privileges; #立即生效
     

    3.3 修改后显示时间为

    3.3.1

    显示 2019-08-08 21:33:44 2019-08-09 05:33:44
    时间戳 1565271224 1565300024
    实际北京时间 2019-08-08 21:33:44 2019-08-09 05:33:44

    3.3.2 select now()

    2020-04-14 16:29:56

    3.4 可以看到

    datetime类型,显示没变,时间戳变了

    timestamp类型,显示变了,时间戳没变

    证明datetime存储可想为一个字符串,该字符串的显示不会随db时区变化而变化,但它表达的时间点会随db时区变化而变化

    而timestimp即是一个时区无关的long,它的显示会随着db时区变化而变化,但它表达的时间点不会

    4 jdbc 查询核准

    set global time_zone = 'UTC';
    set time_zone = 'UTC';

    private static final String URL_NO_TIMEZONE="jdbc:mysql://127.0.0.1:53306/mytest?useUnicode=true&characterEncoding=utf-8&useSSL=false";
    private static final String URL="jdbc:mysql://127.0.0.1:53306/mytest?useTimezone=true&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false";

    4.1 内存date转换后时间戳

    {f_date=2019-08-08 21:33:44.0, f_timestamp=2019-08-08 21:33:44.0, id=1}  未转换,jdbc直接出来Timestamp类型,根据debug,显示+0800时区(可以想象,jdbc用一个Timestamp对象接收,初始化Timestamp对象时继承了jvm的时区+0800,再把db的日期时间字符串塞入)
    transferred timestamp : 转换后datetime字段时间戳:1565271224000
    transferred timestamp : 转换后timestamp字段时间戳:1565271224000
    {f_date=2019-08-09 05:33:44.0, f_timestamp=2019-08-09 05:33:44.0, id=1} jdbc url转换,同样Timestamp类型,根据debug,显示+0800时区
    transferred timestamp : 转换后datetime字段时间戳:1565300024000
    transferred timestamp : 转换后timestamp字段时间戳:1565300024000

    设只有字符串参与传输

      对于datetime,直接传输

      对于timestamp,db根据时区搞成日期时间字符串后回传

    前者将sequel显示的UTC时间传过来了,这个数据还要本地jvm时区(上海)化,21点的北京时间与db UTC 21点差异

    后者帮忙转成北京时间了

    4.2

    select unix_timestamp(f_date), unix_timestamp(f_timestamp) from my_timezone

    1565300024 1565300024

    4.3 根据1.7.2,

    后者与db一致,前者错误

    5 insert 字符串日期时间

    String tt = "2020-04-14 14:47:19";
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                Date date = sdf.parse(tt);
                System.out.println(date);
                System.out.println(date.getTime());
                PreparedStatement pstmt = conn.prepareStatement("INSERT INTO `my_timezone` (`f_date`, `f_timestamp`)
    " +
                        "VALUES
    " +
                        "	('" + sdf.format(date) + "', '"+ sdf.format(date) +"')");
                pstmt.executeUpdate();
    

     注意,jdbc url带时区信息

    useTimezone=true&serverTimezone=UTC&

    我们本意插入服务器所在时区 4.14 下午2:47分的时间数据

    设只有字符串参与传输

      对于datetime,直接入库

      对于timestamp,db根据时区搞成日期时间字符串后入库

    5.1 

    origin jvm date : Tue Apr 14 14:47:19 CST 2020 Date类型,根据debug,显示+0800时区
    origin timestamp : 1586846839000

     

    5.2 db

    10 2020-04-14 14:47:19 2020-04-14 14:47:19当我从sequel看到这个值,意味着它表示UTC时间 4.14 下午2:47分,而不是服务器所在时区的4.14 下午2:47分

    select unix_timestamp(f_date), unix_timestamp(f_timestamp) from my_timezone

    1586875639 1586875639 ,北京时间 2020-04-14 22:47:19

    5.3 根据1.7.1,该方法不可直接用,除非db与server时区相同

    6 prepared statement 插入

                PreparedStatement pstmt = conn.prepareStatement("INSERT INTO `my_timezone` (`f_date`, `f_timestamp`)
    " +
                        "VALUES
    " +
                        "	(?, ?)");
                String tt = "2020-04-14 14:47:19";
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                Date date = sdf.parse(tt);
                System.out.println(date);
                System.out.println(date.getTime());
                pstmt.setTimestamp(1, new Timestamp(date.getTime()));
                pstmt.setTimestamp(2, new Timestamp(date.getTime()));
                pstmt.executeUpdate();
    

    6.1

    origin jvm date : Tue Apr 14 14:47:19 CST 2020 Date类型,根据debug,显示+0800时区
    origin timestamp : 1586846839000

    6.2 db

    13 2020-04-14 06:47:19 2020-04-14 06:47:19 UTC6点,北京14点,ok

    select unix_timestamp(f_date), unix_timestamp(f_timestamp) from my_timezone

    1586846839 1586846839 

    6.3 根据1.7.1,该方法可用

    7 myrom

    7.1 查询

    7.1.1 db:

    1 2019-08-08 21:33:44 2019-08-08 21:33:44

    select unix_timestamp(f_date), unix_timestamp(f_timestamp) from my_timezone

    1565300024 1565300024

    7.1.2 内存:

    origin db date : 2019-08-08 21:33:44.0 未转换,jdbc直接出来Timestamp类型,根据debug,显示+0800时区,这一步看出已经有问题了,库里是UTC的21:33,出来变成+8时区的21:33,说明库里db的时区UTC没有参与db-》server的传输
    2020-04-14 22:31:10.347 [https-jsse-nio-8080-exec-8] INFO com.example.demo.testcase.timezone.TimezoneManager - server zone offset : 28800000 +8
    2020-04-14 22:31:10.358 [https-jsse-nio-8080-exec-8] INFO com.example.demo.testcase.timezone.TimezoneManager - db zone offset : 0
    2020-04-14 22:31:10.359 [https-jsse-nio-8080-exec-8] INFO com.example.demo.testcase.timezone.TimezoneManager - timezone transfer value : 8
    transferred timestamp : 1565300024000
    class java.util.Date:class java.sql.Timestamp
    origin db date : 2019-08-08 21:33:44.0 未转换,jdbc直接出来Timestamp类型,根据debug,显示+0800时区,这一步看出已经有问题了,库里是UTC的21:33,出来变成+8时区的21:33
    transferred timestamp : 1565300024000
    查询 : Fri Aug 09 05:33:44 CST 2019 转换后,Date类型,根据debug,显示+0800时区
    查询 : Fri Aug 09 05:33:44 CST 2019 转换后,Date类型,根据debug,显示+0800时区
    查询 : 1565300024000
    查询 : 1565300024000

    7.1.3,默认springboot使用UTC时区序列化到客户端

    {"id":1,"f_date":"2019-08-08T21:33:44.000+0000","f_timestamp":"2019-08-08T21:33:44.000+0000"}

     从内存的+8时区5:33,不转换(因为序列化传输本身包含了时区信息,不同于jdbc通信,没有时区信息,见【这是未证明的重要假定】),只是改变显示,到浏览器成UTC的21:33 (-1天),与3.1呼应

    7.2 插入

    7.2.1 内存:

    插入 : Tue Apr 14 14:47:19 CST 2020 Date类型,根据debug,显示+0800时区
    插入 : 1586846839000
    origin timestamp : 1586846839000
    transferred db date : Tue Apr 14 06:47:19 CST 2020 表示+8的6:47,可以看到,与7.2.2相比(UTC的6:47),server jvm的时区CST不参与server-》db的传输
    origin timestamp : 1586846839000
    transferred db date : Tue Apr 14 06:47:19 CST 2020

    7.2.2 db:

    18 2020-04-14 06:47:19 2020-04-14 06:47:19 变成了UTC的6:47

    1586846839 1586846839

    8 oracle实践

    8.1 背景

    select dbtimezone from dual;

    DBTIMEZONE:-04:00

    select sessiontimezone from dual;

    SESSIONTIMEZONE:Asia/Shanghai

    8.2 方式:

    8.2.1 jvm内存-db核准,根据1.7.1 、1.7.2

    oracle没有mysql unix_timestamp的函数,无法让我从原位获取原始long型时间戳

    8.2.2

    老插新查 ok

    新插老查 ok

    9 2020.5.12 补充

    #url设置为UTC(db)会导致查询时orm TimezoneController返回错误,因为经过2次转换,相当于+16时区;insert由于是手动转换字符串不受影响
    #url应设置为+8,与服务器一致,免去jdbc自行根据url的自动时区处理
    #jdbc.sybase.url=jdbc:mysql://127.0.0.1:53306/mytest?useUnicode=true&characterEncoding=utf-8&useSSL=false&useTimezone=true&serverTimezone=UTC
    jdbc.sybase.url=jdbc:mysql://127.0.0.1:53306/mytest?useUnicode=true&characterEncoding=utf-8&useSSL=false&useTimezone=true&serverTimezone=Asia/Shanghai
    #jdbc.sybase.url=jdbc:mysql://127.0.0.1:53306/mytest?useUnicode=true&characterEncoding=utf-8&useSSL=false

    10 2020.5.28 出现问题

    引出:

    北京时间2.05pm在服务器(-5)新后台插入,刷新后显示2.05pm,但本地(-8)新后台显示3.05pm

    发现插入的new Date,入db后为3.05am(-4时区),理论上北京的2.05pm在库(-4)中应为2.05am,此处为3.05am,所以入库的时间错了,判断insert时时区转换没做好

    排查:

    server zone offset : -18000000  -5
    db zone offset : -4
    timezone transfer value : -1

    可以看到程序按服务器为-5时区来处理,然而,new Date显示为:

    origin timestamp : 1590645901950  2020/5/28 14:5:1(北京)
    transferred db date : Thu May 28 03:05:01 EDT 2020  可以看到jvm以EDT(美国东部夏令时-4)在处理Date类型

    linux:date显示与北京12小时时差

    锁定:

    种种迹象表明-new Date给我-4的时间,然而,TimeZone.getDefault给了我-5,尼玛

    北京3:54 pm再次补全日志操作确认一次:

            Date date = new Date();
            int hourMod = date.getTimezoneOffset();
            log.info("origin new Date zone {}", hourMod);
    

    插入前后

    //                logger.info("origin timestamp : {} {}", date, date.getTime());
                    date = TimezoneManager.timezoneJvmToDbWhenInsertAndUpdate(date);
    //                logger.info("transferred db date : {} {}", date, date.getTime());
    

      

    origin new Date zone 240

    server zone offset : -18000000
    db zone offset : -4
    timezone transfer value : -1

     尼玛,new Date的时区和TimeZone.getDault的时区不一样

    2020-05-28 03:54:08.707 [ GUI-Thread-11, TaskID:257, Start Time:05-28 03:54:08 ] - [ INFO ] [ : -1 ] - origin timestamp : Thu May 28 03:54:08 EDT 2020 1590652448500  与北京相差12小时,服务器确实以-4在处理Date
    2020-05-28 03:54:08.708 [ GUI-Thread-11, TaskID:257, Start Time:05-28 03:54:08 ] - [ INFO ] [ : -1 ] - transferred db date : Thu May 28 04:54:08 EDT 2020 1590656048500

    修复:

        static {
            TimeZone timeZoneCurrent = TimeZone.getDefault();
            int offset = timeZoneCurrent.getRawOffset();
            log.info("server zone offset : {}", offset);
            log.info("db zone offset : {}", db);
            int hour = offset/1000/60/60;
            {
                Date date = new Date();
                int hourMod = -date.getTimezoneOffset() / 60;
                log.info("origin new Date zone {}", hourMod);
                if(hourMod != hour) {
                    log.info("[warn] - hourMod not eq hour");
                    hour = hourMod;
                }
            }
            timezonePlus = hour - db;
            log.info("timezone transfer value : {}", timezonePlus);
        }
    

      

    北京时间4.23pm操作,服务器(-4)新后台显示4.23pm插入——本地(+8)新后台check刚才那条时间为4.23pm——老后台check 4.23pm,db(-4)显示4.23am,done

    2020-05-28 04:16:31.492 [ GUI-Thread-6, TaskID:95, Start Time:05-28 04:16:31 ] - [ INFO ] [: -2 ] - server zone offset : -18000000
    2020-05-28 04:16:31.497 [ GUI-Thread-6, TaskID:95, Start Time:05-28 04:16:31 ] - [ INFO ] [: -2 ] - db zone offset : -4
    2020-05-28 04:16:31.497 [ GUI-Thread-6, TaskID:95, Start Time:05-28 04:16:31 ] - [ INFO ] [: -2 ] - origin new Date zone -4
    2020-05-28 04:16:31.497 [ GUI-Thread-6, TaskID:95, Start Time:05-28 04:16:31 ] - [ INFO ] [: -2 ] - [warn] - hourMod not eq hour
    2020-05-28 04:16:31.498 [ GUI-Thread-6, TaskID:95, Start Time:05-28 04:16:31 ] - [ INFO ] [: -2 ] - timezone transfer value : 0

    https://blog.csdn.net/kongxx/article/details/38356607 说说Java中的TimeZone夏令时问题

    11 突然反应过来:目前的双数据源代码不支持各自时区,时区校正作为全局变量存在,若要达到各自时区校正,要与各数据源的ormsession绑定

    12 双数据源时区

    mysql 5.7  UTC  1 2019-08-08 21:33:44 2019-08-08 21:33:44  1565300024 1565300024

    mysql   8   -4  1 2019-08-08 17:33:44 2019-08-08 17:33:44  1565300024 1565300024

    *mysql的jdbc:useTimezone=true&serverTimezone=Asia/Shanghai

    12.1 查询

    mysql 5.7  {"id":1,"f_date":"2019-08-08T21:33:44.000+0000","f_timestamp":"2019-08-08T21:33:44.000+0000"}

    查询 : 1565300024000
    查询 : 1565300024000

    mysql 8.    {"id":1,"f_date":"2019-08-08T21:33:44.000+0000","f_timestamp":"2019-08-08T21:33:44.000+0000"}  

    查询 : 1565300024000
    查询 : 1565300024000

    12.2 插入          2020-04-14 14:47:19 +8         1586846839000

    mysql 5.7  UTC  5 2020-04-14 06:47:19 2020-04-14 06:47:19  1586846839 1586846839

    mysql 8     -4    6 2020-04-14 02:47:19 2020-04-14 02:47:19  1586846839 1586846839

    13 附录图

    package com.example.demo.testcase.timezone;
    
    import com.example.demo.testcase.ScefLogbackFactory;
    
    import java.util.Calendar;
    import java.util.Date;
    import java.util.TimeZone;
    
    /**
     * https://www.cnblogs.com/silyvin/p/12696872.html
     * Created by joyce on 2020/4/14.
     */
    public class TimezoneManager {
    
        private static final ScefLogbackFactory.ScefLogger log = ScefLogbackFactory.getLogger(TimezoneManager.class);
    
        private static int timezonePlus;
        private static int db = 0;
    
        static {
            TimeZone timeZoneCurrent = TimeZone.getDefault();
            int offset = timeZoneCurrent.getRawOffset();
            log.info("server zone offset : {}", offset);
            log.info("db zone offset : {}", db);
            int hour = offset/1000/60/60;
            timezonePlus = hour - db;
            log.info("timezone transfer value : {}", timezonePlus);
        }
    
        public static int getTimezonePlus() {
            return timezonePlus;
        }
    
        private static Date dealTimeZone(Date date, int offset) {
            Calendar cal = Calendar.getInstance();
            cal.setTime(date);
            cal.add(Calendar.HOUR, offset);
            return cal.getTime();
        }
    
        public static Date timezoneJvmToDbWhenInsertAndUpdate(Date date) {
            return dealTimeZone(date, -TimezoneManager.getTimezonePlus());
        }
    
        public static Date timezoneDbToJvmWhenQuery(Date date) {
            return dealTimeZone(date, TimezoneManager.getTimezonePlus());
        }
    }
    

     query: 会将java.sql.Timestamp 转为java.util.Date

                        // https://www.cnblogs.com/silyvin/p/12696872.html
                        if(val instanceof java.util.Date) {
                            Date date = (Date)val;
                            System.out.println("origin db date : " + date);
                            date = TimezoneManager.timezoneDbToJvmWhenQuery(date);
                            System.out.println("transferred timestamp : " + date.getTime());
                            val = date;
                        }
    
                        field.set(domain, val);
                    }
    
                    listDomain.add(domain);
                } catch (Exception e) {
                    throw new DBException(e);
                }
            }
    
            return listDomain;
    

    insert:

                // https://www.cnblogs.com/silyvin/p/12696872.html
                if(o instanceof java.util.Date) {
                    Date date = (Date)o;
                    System.out.println("origin timestamp : " + date.getTime());
                    date = TimezoneManager.timezoneJvmToDbWhenInsertAndUpdate(date);
                    System.out.println("transferred db date : " + date);
                    domain.setFieldValue(date);
                }
    
                String domainFieldName = domain.getFieldName();
                domain.setFieldName(domainFieldName.replaceAll("[A-Z]", "_$0").toLowerCase());
    
                if(dealWithJoinFieldUpdate(domain, field, o))
                    list.add(domain);
            }
    
            ormUnit.setListDomain(list);
            return ormUnit;
        }
    

     14

    2020.7.29补充

    oracle mysql 说明  
    timestamp datetime 只有时间字符串,无论哪个时区,返回的就是字符串  
    timestamp local timezone timestamp

    时间字符串+oracle时区;

    绝对时间戳(但jdbc协议只传输时间字符串,所以这个时间戳按timezone折合,客户端依靠url timezone再次折合为客户端本地时间)

     
    timestamp timezone   时间字符串+oracle客户端session时区  

    2020.9.18补充

    https://m.newsmth.net/article/Database/61083 这篇文章经典,证实了这种说法

    Oracle 9i 开始向timestamp引进time zone概念。
    10g提供了
    
    timestamp
    timestamp with timezone
    timestamp with local timezone
    几种数据类型。
    
    这里比较一下这些数据类型的差异。
    
    一个统一的时间应由 时刻+时区来指定,
    比如2005-4-6 14:00:00.000并不能说清楚到底这是
    日本的下午2点还是中国的下午两点。
    
    2005-4-6 14:00:00.000 +8:00 才是北京时间
    2005-4-6 14:00:00.000 +9:00 则是东京时间
    
    假设有一个online meeting system
    DB服务器在英国       dbtimezone       (+0:00) [select dbtimezone from dual;]
    一个客户端c-cn在中国 session timezone (+8:00) [select sessiontimezone from
    dual;]
    一个客户端c-jp在日本 session timezone (+9:00) [select sessiontimezone from
    dual;]
    管理客户端c-en在英国 session timezone (+0:00) [select sessiontimezone from
    dual;]
    
    ===================================================
    Timestamp 不能包含任何时区信息
    ===================================================
    DB有TABLE定义如下:
    create table meeting_table( id number(10) primary key, ctime timestamp );
    
    中国用户插入一个会议,早上8点开会
    insert into meeting_table values (1, '05-06-29 8:00:00,000');
    commit;
    
    日本用户也插入一个会议,早上8点开会
    insert into meeting_table values (2, '05-06-29 8:00:00,000');
    commit;
    
    英国的管理员查询一下这张表,发现两个会议是同时的,
            ID CTIME
    ---------- ------------------------------
             1 05-06-29 08:00:00,000000
             2 05-06-29 08:00:00,000000
    
    而实际上应该日本的会议比中国的早一个小时。
    英国的管理员如果想参加2号会议的话,他到底该几点去呢?
    八点?0点?前一天的晚上11点?
    数据库完全不能给他一个明确的答复。
    
    c-cn, c-jp来查询也都得到相同的模糊结果:
            ID CTIME
    ---------- ------------------------------
             1 05-06-29 08:00:00,000000
             2 05-06-29 08:00:00,000000
    
    ===================================================
    Timestamp with time zone 显式包含时区信息
    ===================================================
    DB有TABLE定义如下:
    create table meeting_table2( id number(10) primary key, ctime timestamp with
    time zone);
    
    中国、日本用户同样插入上例中的两个会议
    英国的管理员查询表时,返回的结果就清晰多了:
    
    select * from meeting_table2;
    
            ID CTIME
    ---------- ----------------------------------------
             1 05-06-29 08:00:00,000000 +08:00
             2 05-06-29 08:00:00,000000 +09:00
    
    他可以知道 meeting 2是在东九区的早上八点开始的,去参加的话,
    前一天晚上11:00他就要接进web meeting了。
    
    c-cn, c-jp来查询也都与c-en结果相同
    结果都包含时区信息,所以是精确的,不可能被混淆。
    
    
    ===================================================
    Timestamp with local time zone 隐式式包含时区信息
    ===================================================
    
    用Timestamp with local time zone插入或显示的时间信息会根据
    客户session里面时区的不同自动转换:
    
    * 插入时,从客户端的时区 转到 数据库时区
    * 显示时,从数据库时区   转到 客户端的时区
    
    DB有TABLE定义如下:
    create table meeting_table3( id number(10) primary key, ctime timestamp with
    local time zone);
    
    c-cn, c-jp同样插入上例中的两个会议
    
    c-en 的查询结果:
            ID CTIME
    ---------- ----------------------------------------
             1 05-06-29 00:00:00,000000
             2 05-06-28 23:00:00,000000
    
    c-cn 的查询结果:
            ID CTIME
    ---------- ----------------------------------------
             1 05-06-29 08:00:00,000000
             2 05-06-29 07:00:00,000000
    
    c-jp 的查询结果:
            ID CTIME
    ---------- ----------------------------------------
             1 05-06-29 09:00:00,000000
             2 05-06-29 08:00:00,000000
    
    这样连换算都不需要了,每个客户查出来的时间
    直接就是客户端所在的区域的当地时间。
    
    不过需要注意的是:timestamp with local time zone
    应用在c-s结构中没有问题,但在三层结构中,由于app server
    是db server的客户端,所以转换发生在 app server - db server
    之间,而非 db - browser之间。所以应用timestamp with local time zone
    可能引发潜在的问题。
    
    
    ===================================================
    常用命令:
    ===================================================
    更改 session time zone:
    alter session set time_zone='+9:00';
    
    取得 server 当地时间:
    select systimestamp from dual;
    
    取得以客户session时区表示的 server 时间
    select current_timestamp from dual;
    
    数据库的时区是创建数据库时设置的,
    用 alter database set time_zone='0:00'
    可以更改。但是如果数据库已经有 timestamp with local time zone
    类型的数据时,不能更改数据库时区。
    

      

    15

    2020.8.8 补充

    mysql的timezone表示,我mysql建立连接时将以什么时区率先解析一次时间戳,你客户端再解析一次

    16

    2020.8.17补充

    第10点中的问题:

    尼玛,new Date的时区和TimeZone.getDault的时区不一样

    原来是有夏令时到冬令时的区别

    1)oracle

    在整个我们ormsession开发过程中,2020.4.13开始涉及到时区,db在纽约,oracle实践时,

    select dbtimezone from dual;

    DBTIMEZONE:-04:00

    显示在-4区,而纽约在西五区,这里面就是oracle启动了夏令时

    如果在冬天执行这条语句,显示的很有可能是-5

    2)linux

    到第10点出现的时刻,2020.5.28,也就是首次部署到服务器(也在纽约),出现Timezone.getDault.getRawOffset与new Date所展现的所在时区不一致,这是linux夏令时的表现

    https://zhuanlan.zhihu.com/p/98424435

    TimeZone itemTimeZone = TimeZone.getTimeZone(时区名);
    itemTimeZone.getOffset(long data);//显示当前时区和0时区的偏移量,和令时制相关
    itemTimeZone.getRawOffset();//显示当前时区和0时区的偏移量,和令时制无关
    

    另一个linux处理夏令时的证据为,linux date显示时间换算后,时区是在-4区,与北京相差12小时 

    3)纽约

    西五区

    当地时间 2020年03月08日,02:00:00 时钟向前调整 1 小时 变为 2020年03月08日,03:00:00,开始夏令时,此时为-4,与北京差12

    纽约在当地时间 2020年11月01日,02:00:00 时钟向后调整 1 小时 变为 2020年11月01日,01:00:00,结束夏令时,此时为-5,与北京差13

    4)项目

    项目oracle使用timestamp,近乎相当于字符串

    原项目不对时区做任何处理,依靠服务器

    new Date-> date string ->jdbc ->oracle

    那么在本项目中,我们在sqldeveloper等工具看到的日期时间(字符串)不同季节代表了不同时区的时间

    2020.3.7 2:00:00,代表-5区的2点

    2020.4.28 2:00:00,代表-4区的2点

    5)解决方案

    此前无论是服务器还是数据库,时区都是写死的(包括mybatis timezonehandlermybatis orm解决方案),服务器也仅是加载时自动按new Date计算,但一旦到冬天,需要重新启动jvm

    第4点的项目背景决定了,要运行期动态判断夏令时还是冬令时

    开1个后台线程,每天2:01执行,判断是否已经到更改令时的日期了,如果是,将内存中已计算的服务器时区和写死在代码中的db时区一起更改

    6)如果不改——纽约

    我们处理时区,是处理时区的相对值,只要能保证db和服务两边相对时区差一致即可:

    比如

    夏天服务器-4,db-4

    冬天服务器-5,db-5

    冬天服务器还以-4,db也还以-4,并不产生差别

    再比如

    冬天,原来为我们修正时区的new Date时区显示为-5,db还用写死的-4,那就产生1小时的差距,这就要求运行期修改写死的db时区-4更改为-5

    所以,timezone继续使用Timezone.getDefault.getRawOffset,让其返回纽约-5,然后oracle db的时区也在代码中定为-5,则可,这样对于夏令时,两边仍取-5,但相对差不变

    7)如果不改——北京

    第6)点所述,要求服务器和db同时开启夏令时或冬令时,冬天和夏天的相对时区差不变

    而北京没有夏令时,北京时间14:00pm在夏天和冬天对纽约有不同的时间概念,入库应不同(除非就约定,库里的时间就是-5或-4区的,相应的,服务器做额外的令时转换再给用户)

    夏天服务器+8,db-4

    冬天服务器+8,db-5

    不像服务器和db都在纽约,或服务器在其他与纽约有同样令时规则的地区,并开启linux令时功能,夏天和冬天的14:00pm入库都是14:00

    夏天服务器洛杉矶 -7,db-4

    冬天服务器洛杉矶-8,db-5

    都相差3小时

    8)本质

    本来,服务器所在时区非实时动态,db时区静态

    现在,服务器所在时区要实时动态,db时区实时动态

    纽约的时区会自己变,而北京时区固定

    17

    2020.9.18 补充

    要解决16的问题,需要开1个后台线程,每天2:01执行,判断是否已经到更改令时的日期了,如果是,将内存中已计算的服务器时区和写死在代码中的db时区一起更改                 实时更新服务器和db的时区(潜在夏令时)

    1) jvm当前时区

    相对好处理,Date,包含了daylight

    需要确认jvm不重启情况下,跨冬夏时前后jvm自动处理daylight

    2) db时区

    已经知道db 纽约(-5),需要如果从任何地区服务器(假定服务器不在纽约)得知某Date时间戳对应于纽约的daylight(是否冬令时+0还是夏令时+1)

    public static void main(String [] f) throws Exception {
    
            TimeZone thisTimeZone = TimeZone.getDefault();
            TimeZone newYorkTimeZone = TimeZone.getTimeZone("America/New_York");
    
            System.out.println(thisTimeZone);
            System.out.println(newYorkTimeZone);
    
            String [] ddsBJ = {
                    "2020-03-08 14:59:59",
                    "2020-03-08 15:00:01",
                    "2020-11-01 13:59:59",
                    "2020-11-01 14:00:01"
            };
    
            // 系统切换到美东时区
            String [] ddsEST = {
                    "2020-03-08 01:59:59",
                    "2020-03-08 02:00:01",  // 实际不存在这个美东时间
                    "2020-10-31 12:59:59",
                    "2020-11-01 01:00:01"   // 代表2个时间,回拨前后都有1:00:01
            };
            String strDateFormat = "yyyy-MM-dd HH:mm:ss";
            SimpleDateFormat sdf = new SimpleDateFormat(strDateFormat);
    
            for(String dd : ddsBJ) {
                //for(String dd : ddsBJ) {
                Date date = sdf.parse(dd);
                System.out.println(date);
                Date convert = convertTimezone(date, thisTimeZone, newYorkTimeZone);
                System.out.println(convert);
    
                boolean isSummer = isSummer(date, newYorkTimeZone);
                System.out.println(isSummer);
            }
        }
    
        /**
         * http://www.timeofdate.com/city/United%20States/New%20York%20City/timezone/change
         * https://www.cnblogs.com/timfruit/p/11788366.html
         * @param sourceDate
         * @param sourceTimezone
         * @param targetTimezone
         * @return
         */
        public static Date convertTimezone(Date sourceDate, TimeZone sourceTimezone, TimeZone targetTimezone){
    
            Calendar calendar = Calendar.getInstance();
            long sourceTime = sourceDate.getTime();
            calendar.setTimeInMillis(sourceTime);
            System.out.println(sourceTime);
    
            calendar.setTimeZone(sourceTimezone);
    
            int sourceZoneOffset = calendar.get(Calendar.ZONE_OFFSET);
            int sourceDaylightOffset = calendar.get(Calendar.DST_OFFSET);
    
            calendar.setTimeZone(targetTimezone);
    
            int targetZoneOffset = calendar.get(Calendar.ZONE_OFFSET);
            int targetDaylightOffset = calendar.get(Calendar.DST_OFFSET);
    
            long targetTime = sourceTime + (targetZoneOffset + targetDaylightOffset) - (sourceZoneOffset + sourceDaylightOffset);
    
            return new Date(targetTime);
        }
    
        public static boolean isSummer(Date sourceDate, TimeZone targetTimezone){
    
            Calendar calendar = Calendar.getInstance();
            long sourceTime = sourceDate.getTime();
            calendar.setTimeInMillis(sourceTime);
    
    //        calendar.setTimeZone(sourceTimezone);
    
    //        int sourceZoneOffset = calendar.get(Calendar.ZONE_OFFSET);
    //        int sourceDaylightOffset = calendar.get(Calendar.DST_OFFSET);
    
            calendar.setTimeZone(targetTimezone);
    
            int targetZoneOffset = calendar.get(Calendar.ZONE_OFFSET);
            int targetDaylightOffset = calendar.get(Calendar.DST_OFFSET);
    
            return targetDaylightOffset != 0;
        }
    

      

    String [] ddsBJ = {
    "2020-03-08 14:59:59",
    "2020-03-08 15:00:01",
    "2020-11-01 13:59:59",
    "2020-11-01 14:00:01"
    };

    // 系统切换到美东时区
    String [] ddsEST = {
    "2020-03-08 01:59:59",
    "2020-03-08 02:00:01", // 实际不存在这个美东时间
    "2020-11-01 00:59:59",
    "2020-11-01 01:00:01" // 代表2个时间,回拨前后都有1:00:01
    };

    输出

    sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=29,lastRule=null]
    sun.util.calendar.ZoneInfo[id="America/New_York",offset=-18000000,dstSavings=3600000,useDaylight=true,transitions=235,lastRule=java.util.SimpleTimeZone[id=America/New_York,offset=-18000000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]]
    Sun Mar 08 14:59:59 CST 2020
    1583650799000
    Sun Mar 08 01:59:59 CST 2020
    false
    Sun Mar 08 15:00:01 CST 2020
    1583650801000
    Sun Mar 08 03:00:01 CST 2020
    true
    Sun Nov 01 13:59:59 CST 2020
    1604210399000
    Sun Nov 01 01:59:59 CST 2020
    true
    Sun Nov 01 14:00:01 CST 2020
    1604210401000
    Sun Nov 01 01:00:01 CST 2020
    false


    sun.util.calendar.ZoneInfo[id="America/New_York",offset=-18000000,dstSavings=3600000,useDaylight=true,transitions=235,lastRule=java.util.SimpleTimeZone[id=America/New_York,offset=-18000000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]]
    sun.util.calendar.ZoneInfo[id="America/New_York",offset=-18000000,dstSavings=3600000,useDaylight=true,transitions=235,lastRule=java.util.SimpleTimeZone[id=America/New_York,offset=-18000000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]]
    Sun Mar 08 01:59:59 EST 2020
    1583650799000
    Sun Mar 08 01:59:59 EST 2020
    false
    Sun Mar 08 03:00:01 EDT 2020
    1583650801000
    Sun Mar 08 03:00:01 EDT 2020
    true
    Sat Oct 31 12:59:59 EDT 2020
    1604206799000    比北京的2020-11-01 13:59:59少3600s,一个小时
    Sat Oct 31 12:59:59 EDT 2020
    true
    Sun Nov 01 01:00:01 EST 2020
    1604210401000
    Sun Nov 01 01:00:01 EST 2020
    false  优先以冬令时处理

    3)对原系统的验证
    北京时间
    2020-11-01 13:50
    2020-11-01 14:10-2020-11-01 13:10
    在原系统插入2条,看时间,第2条会不会在第1条前order

    纽约linux系统查看是否改变时区

    纽约db oracle查看是否改变时区

    4)此前使用服务器当前时间判断时区差,这只能解决服务器-》db的问题,通过获取服务器某Date对应于db 纽约的夏/冬

    但不能解决db-》服务器的查询,比如db有数据:

    服务器-在北京
    对于sqldeveloper中28-FEB-18 02.05.59.285000000 AM (逻辑上代表,因为oracle timestamp无时区信息,该字符串体现了原项目-部署在纽约的服务器时间意志,即自己调整夏/冬的linux当前时间

    代表纽约冬令时上午2点——对应北京时间当天下午3点

    然而我们的查询检测时,28-Feb-2018, 02:05 PM CST,检测当前纽约为夏天,与北京12小时时差,以此转换,出错,【第一个错误】原因为错误的使用了当前时间戳判定纽约所处daylight,对于db中既有数据则出错

    老后台db-服务器无任何时区处理,不错,服务器到前端处理成了03:05 pm

    部署在纽约的新后台,服务器与db由于都在纽约,无时差,处理为了03:05pm,说明纽约的后台-北京的前端处理正确

    5)为了解决db-》server的查询,使用两阶段转换

    按标准时转换

    查看该时间在纽约是否是夏天

    会有边界点

    abstract public class TimezoneTypeHandler extends BaseTypeHandler<Date> {
        protected int dbZone;
    
        public TimezoneTypeHandler(int dbZone) {
            this.dbZone = dbZone;
        }
    
        abstract int dayLight(Date sourceDateJvm);
    
        @Override
        public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date dateJvm, JdbcType jdbcType) throws SQLException {
            if(dateJvm != null) {
                Date dateDb = TimezoneManager.timezoneJvmToDbWhenInsertAndUpdate(dateJvm, this.dbZone + dayLight(dateJvm));
                preparedStatement.setTimestamp(i, new java.sql.Timestamp(dateDb.getTime()));
            }
        }
    
    。。。
    
        private Date getJvmDateByDbDate(java.sql.Timestamp dateDb) {
            if(dateDb == null) return null;
            Date firstTurn = TimezoneManager.timezoneDbToJvmWhenQuery(new Date(dateDb.getTime()), this.dbZone);
            return TimezoneManager.timezoneDbToJvmWhenQuery(new Date(dateDb.getTime()), this.dbZone + dayLight(firstTurn));
        }
    
        /**
         * whether the jvm datetime in targetTimezone is in summer according to the timestamp
         * @param targetTimezone
         * @return
         */
        protected boolean isSummer(Date sourceDate, TimeZone targetTimezone){
    
            Calendar calendar = Calendar.getInstance();
            long sourceTime = sourceDate.getTime();
            calendar.setTimeInMillis(sourceTime);
    
    //        calendar.setTimeZone(sourceTimezone);
    
    //        int sourceZoneOffset = calendar.get(Calendar.ZONE_OFFSET);
    //        int sourceDaylightOffset = calendar.get(Calendar.DST_OFFSET);
    
            calendar.setTimeZone(targetTimezone);
    
            int targetZoneOffset = calendar.get(Calendar.ZONE_OFFSET);
            int targetDaylightOffset = calendar.get(Calendar.DST_OFFSET);
    
            return targetDaylightOffset != 0;
        }
    }
    

      

    public class TimezoneNewYorkTypeHandler extends NullableTimezoneTypeHandler {
    
        private static final int DB_ZONE_NEW_YORK = -5;
        private static final TimeZone newYorkTimeZone = TimeZone.getTimeZone("America/New_York");
    
        public TimezoneNewYorkTypeHandler() {
            super(DB_ZONE_NEW_YORK);
        }
    
        @Override
        int dayLight(Date sourceDateJvm) {
            return isSummer(sourceDateJvm, newYorkTimeZone) ? 1 : 0;
        }
    }
    

      

    2020-03-08 01:59:59 按标准13转 2020-03-08 14:59:59,isSummer false,按13第二次转 2020-03-08 14:59:59
    2020-03-08 02:00:01 不应出现 2020-03-08 15:00:01,isSummer true, 按12第二次转 2020-03-08 14:00:01,实际相当于纽约2020-03-08 01:00:01,一个冬令时时间被用当前夏令时错误的处理了
    2020-03-08 03:00:01 按标准13转 2020-03-08 16:00:01,isSummer true, 按12第二次转 2020-03-08 15:00:01

    2020-11-01 00:59:59 按标准13转 2020-11-01 13:59:59,isSummer true, 按12第二次转 2020-11-01 12:59:59
    夏令的01:00:00-01:59:59,无法表示
    2020-11-01 01:00:01 按标准13转 2020-11-01 14:00:01,isSummer false,按13第二次转 2020-11-01 14:00:01

     (2020.10.19)

    6)对于server本身又自带夏令时冬令时(手动将server由北京调整为纽约)的情况,由于代码出错,5)的结果出现问题(2020.10.19)

    此前代码:

    public class TimezoneManager {
    
        private static int getJVMTimezone() {
            TimeZone timeZoneCurrent = TimeZone.getDefault();
            int offset = timeZoneCurrent.getRawOffset();
            int hour = offset / 1000 / 60 / 60;
    
            Date date = new Date();
            int hourMod = -date.getTimezoneOffset() / 60;
            if (hourMod != hour) {
                hour = hourMod;
            }
    
            return hour;
        }
    
        private static Date dealTimeZone(Date date, int offset) {
            Calendar cal = Calendar.getInstance();
            cal.setTime(date);
            cal.add(Calendar.HOUR, offset);
            return cal.getTime();
        }
    
        public static Date timezoneJvmToDbWhenInsertAndUpdate(Date date, int dbZone) {
            int offset = dbZone - getJVMTimezone();
            return dealTimeZone(date, offset);
        }
    
        public static Date timezoneDbToJvmWhenQuery(Date date, int dbZone) {
            int offset = dbZone - getJVMTimezone();
            return dealTimeZone(date, -offset);
        }
    

      可以看到getJvmTimezone由于是纽约夏,返回-4,db由于走了输入db时间,该时间是冬令时,算出来是-5,导致一个小时误差

    而插入comments没问题,是因为comments测试时使用当前时间new Date,server北京当前+8,db对应该new Date夏令时-4;server纽约当前夏令时-4,db对应该new Date夏令时-4

    【第二个错误】错误的原因为,取服务器时区所有操作均使用new Date当前时间,而没有使用db实际查出来的字段时间,而又因为北京本身没有daylight隐藏了问题;当时怎么没有在纽约的dev试一下?或者只是简单看了最近夏季的数据

    如果server纽约情况插入comments使用一个冬令时时间也会出问题。插入24-JAN-20 06.00.04.625000000,server -4,db -5,进库则出现1小时误差,逻辑上代表的时间就错了;当插入当前的10.20,server -4,db -4,进库无问题

     例子:服务器调整为纽约,db时间24-JAN-20 06.00.04.625000000 AM,代表纽约冬令时,服务器当前为纽约夏令时,导致我们程序显示24-Jan-2020, 07:00 AM EST

     之所以之前没暴露出来,是因为服务器使用了北京时区,服务器始终在+8,把db对应于db某个冬令时时间的时区算准后,就可以了,而服务器如果本身自带夏令时冬令时则不行

    代码调整为:

    public class TimezoneManager {
    【重点】由于冬令时夏令时,jvm时区与db时区与具体日期挂钩,增加一个输入日期字段,时间戳使代码可读性更强 private static int getJVMTimezone(long timestamp) { int hour = getJVMTimezoneWithoutDaylight(); return getTimezone(hour, timestamp, TimeZone.getDefault()); } private static int getTimezone(int timezoneWithoutDaylight, long timestamp, TimeZone dbTimezone) { int hour = timezoneWithoutDaylight; if(isSummer(timestamp, dbTimezone)) hour ++; return hour; } private static int getJVMTimezoneWithoutDaylight() { TimeZone timeZoneCurrent = TimeZone.getDefault(); int offset = timeZoneCurrent.getRawOffset(); int hour = offset / 1000 / 60 / 60; return hour; } public static boolean isSummer(long timestamp, TimeZone targetTimezone){ Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(timestamp); calendar.setTimeZone(targetTimezone); int targetDaylightOffset = calendar.get(Calendar.DST_OFFSET); return targetDaylightOffset != 0; } public static Date timezoneJvmToDbWhenInsertAndUpdate(Date date, int dbZoneWithoutDaylight, TimeZone dbTimezone) { int offset = getTimezone(dbZoneWithoutDaylight, date.getTime(), dbTimezone) - getJVMTimezone(date.getTime()); return dealTimeZone(date, offset); } public static Date timezoneDbToJvmWhenQuery(Date date, int dbZone, TimeZone dbTimezone) { int offsetWithoutDaylight = dbZone - getJVMTimezoneWithoutDaylight(); Date dateWithoutDaylight = dealTimeZone(date, -offsetWithoutDaylight); int offsetDaylight = getTimezone(dbZone, dateWithoutDaylight.getTime(), dbTimezone) - getJVMTimezone(dateWithoutDaylight.getTime()); return dealTimeZone(date, -offsetDaylight); } private static Date dealTimeZone(Date date, int offset) { Calendar cal = Calendar.getInstance(); cal.setTime(date); cal.add(Calendar.HOUR, offset); return cal.getTime(); }

      

    public class TimezoneNewYorkTypeHandler extends NullableTimezoneTypeHandler {

    private static final int DB_ZONE_NEW_YORK = -5;
    private static final TimeZone newYorkTimeZone = TimeZone.getTimeZone("America/New_York");

    public TimezoneNewYorkTypeHandler() {
    super(DB_ZONE_NEW_YORK, newYorkTimeZone);
    }
    }
    abstract public class TimezoneTypeHandler extends BaseTypeHandler<Date> {
    
        private static final Logger logger = LoggerFactory.getLogger(TimezoneTypeHandler.class);
        protected int dbZoneWithoutDaylight;
        protected TimeZone dbTimezone;
    
        public TimezoneTypeHandler(int dbZoneWithoutDaylight, TimeZone dbTimezone) {
            this.dbZoneWithoutDaylight = dbZoneWithoutDaylight;
            this.dbTimezone = dbTimezone;
        }
    
        @Override
        public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date dateJvm, JdbcType jdbcType) throws SQLException {
            if(dateJvm != null) {
    
                if(!dealWithTimezone()) {
                    preparedStatement.setTimestamp(i, new java.sql.Timestamp(dateJvm.getTime()));
                    return;
                }
    
                Date dateDb = TimezoneManager.timezoneJvmToDbWhenInsertAndUpdate(dateJvm, this.dbZoneWithoutDaylight, this.dbTimezone);
                preparedStatement.setTimestamp(i, new java.sql.Timestamp(dateDb.getTime()));
            }
        }
    
    
        private Date getJvmDateByDbDate(java.sql.Timestamp dateDb) {
            if(dateDb == null) return null;
    
            if(!dealWithTimezone()) {
                return new Date(dateDb.getTime());
            }
    
            return TimezoneManager.timezoneDbToJvmWhenQuery(new Date(dateDb.getTime()), this.dbZoneWithoutDaylight, this.dbTimezone);
        }
    

      

    完美,顺便提一下,TimeZone.getDefault,运行期修改操作系统时区,对其不生效,但chrome浏览器立即生效

    2020.10.30 

    7)前端

    前端处于伦敦,当前处于伦敦冬令时,时区0,输入2020.8.1 00:00:00,浏览器传过来的时区为0,然后8.1这个日期处于夏令时,应处于-1,此时用0作为该日期的时区错误

    浏览器不可调和的问题,除非在前端从浏览器获取系统时区,计算该输入日期应处于哪个时区,直接在前端转换

  • 相关阅读:
    jQuery Ajax 实例 全解析
    用Javascript评估用户输入密码的强度
    常用网址
    常用的107条Javascript
    根据键盘操作表格
    HTML5吧
    css3动画简介以及动画库animate.css的使用
    jquery插件下载地址
    CEO、COO、CFO、CTO
    springboot与shiro配置
  • 原文地址:https://www.cnblogs.com/silyvin/p/12696872.html
Copyright © 2020-2023  润新知