SQLite与Oracle-计算日期差异-小时数

SQLite与Oracle-计算日期差异-小时数,oracle,sqlite,date,date-difference,Oracle,Sqlite,Date,Date Difference,我想知道是否有人看到了这一点,是否有解决办法,或者我只是做得不对。我试图得到现在和数据库记录中“创建日期”之间的小时差——不是试图得到总小时数,而是去掉总天数后剩下的小时数,这样你就可以输出某物是x天,x小时 初始给定值 让我们使用一个SYSDATE,或12/6/2016 6:41 PM的“现在” 假设我有一个Oracle表和一个SQLite表,我们称之为MyTable。在其中,我有一个CREATED_DATE字段,其中日期存储在本地时间: CREATED_DATE ------------ 1

我想知道是否有人看到了这一点,是否有解决办法,或者我只是做得不对。我试图得到现在和数据库记录中“创建日期”之间的小时差——不是试图得到总小时数,而是去掉总天数后剩下的小时数,这样你就可以输出某物是x天,x小时

初始给定值

让我们使用一个
SYSDATE
,或
12/6/2016 6:41 PM
的“现在”

假设我有一个Oracle表和一个SQLite表,我们称之为
MyTable
。在其中,我有一个
CREATED_DATE
字段,其中日期存储在本地时间:

CREATED_DATE
------------
1/20/2015 1:35:17 PM
6/9/2016 3:10:46 PM
这两个表都是相同的,只是Oracle中的日期类型是
DATE
,但在SQLite中,必须将日期存储为格式为“yyy-MM-dd HH:MM:ss”的字符串。但每个表的值都是相同的

我开始计算“现在”和日期之间的总天数差。我可以从十进制天数中减去整数天数,得到我需要的小时数

总天数-Oracle

如果我在Oracle中这样做,给我总天数差:
从MyTable中选择(SYSDATE-CREATED_DATE)

我得到第一个的
686.211284…
,第二个的
180.144976…

总天数-SQLite

如果我使用SQLite来计算总天数差异,第一个非常接近,但第二个非常接近:
从MyTable中选择(julianday('now')-julianday(创建日期,'utc'))

我得到第一个的
686.212924…
,第二个的
180.188283…

问题

我在SQLite查询中添加了
'utc'
,因为我知道
julianday()
使用GMT。否则,时间大约为6小时。问题是他们现在休息1小时,但不是所有时间。第一个结果给出了正确的小时数差:5,在这两种情况下:

.211284 x 24 = 5.07 hours
.212924 x 24 = 5.11 hours
当我确定这些值的下限时,它会给出我需要的结果

然而,对于第二个问题,我得到的是:

.144976 x 24 = 3.479 hours
.188283 x 24 = 4.519 hours
一个巨大的不同——一个小时的不同!有人能帮我解释一下为什么会这样,以及是否有办法修正/使其准确

获取工作时间

这是我用来计算小时数的代码。我用计算器仔细检查了一下,确认了我使用Oracle时的小时数是正确的。为此,我使用:

SELECT FLOOR(((SYSDATE - CREATED_DATE)-(FLOOR(SYSDATE - CREATED_DATE)))*24) FROM MyTable
我目前正在尝试使用类似的设置获取SQLite中的小时数:

(((julianday('now') - julianday(CREATED_DATE, 'utc')) - 
CAST ((julianday('now') - julianday(CREATED_DATE, 'utc')) AS INTEGER))*24)
现在,我故意省略了SQLite结果的“地板”或整数转换。这两个查询基本上都是以总天数减去整数总天数得到小数点余数(这是一天中表示小时的部分)并将其乘以24

不过,这很有趣,因为我对整个小时使用了上面相同的查询,而对整数小时使用了它的转换版本,将小数部分保留几分钟,然后将其乘以60,分钟的结果就完美了

屏幕截图:并排比较

这张照片拍摄于2016年6月12日下午7:20,左边是我的应用程序中显示的SQLite,右边是Oracle SQL Developer中执行的Oracle查询:


就我所知,SQLite的日光更改似乎丢失了,或者确切地说,您需要在保存时指定此更改(因为它是一个字符串,而不是一个真正的日期字段/透明时间戳)

生成日期(如果生成)时,将其设为完整UTC,时区为显式,而非本地隐式:

格式2到10可以可选地后跟时区 形式为“[+-]HH:MM”或仅为“Z”的指示器。日期和时间 函数在内部使用UTC或“zulu”时间,因此“Z”后缀是 不可操作。任何非零的“HH:MM”后缀将从指示的值中减去 计算祖鲁时间的日期和时间。例如,所有 以下时间字符串是等效的:

2013-10-07 08:23:19.120
2013-10-07T08:23:19.120Z
2013-10-07 04:23:19.120-04:00
老实说,使用
utc
的SQLite在“忘记”日光变化时并没有错,因为utc不会移动(这是计时物理时间)。如果你告诉他一切都在同一个UTC时区,只需做一个简单的减法运算,就不会给出你的日光

你的
julianday('now')
不也使用
'UTC'
吗?
(对不起,如果我不明白,我明天再看一次)

实际上你错过了一个重要的信息:你认为哪一个值是正确的?你必须考虑夏时制吗?< /P> 从Oracle开始:

我假设列
CREATED\u DATE
的数据类型是
DATE
SYSDATE
还返回一个
DATE
值<代码>日期值没有任何时区(即夏令时设置)信息

假设现在是2016-12-06 06:00:00:

SELECT 
   TO_DATE('2016-12-06 06:00:00','YYYY-MM-DD HH24:MI:SS') 
   - TO_DATE('2016-06-09 06:00:00','YYYY-MM-DD HH24:MI:SS') 
FROM dual;
返回正好180天

如果你必须考虑夏令时,你必须使用数据类型<代码>时间戳,带时区< /代码>(或<代码>本地本地时区时间戳),请参见这个例子:

SELECT 
   TO_TIMESTAMP_TZ('2016-12-06 06:00:00 Europe/Zurich','YYYY-MM-DD HH24:MI:SS TZR') 
   - TO_TIMESTAMP_TZ('2016-06-09 06:00:00 Europe/Zurich','YYYY-MM-DD HH24:MI:SS TZR') 
FROM dual;
结果是
+180 01:00:00.000000
,即180天零1小时

这取决于你的要求,你必须使用哪一个。一般来说,我建议分别使用
时间戳
<代码>带有时区的时间戳而不是
日期
,因为在那里你可以简单地用它来计算小时数,而不必摆弄
楼层
之类的东西:

 SELECT 
    EXTRACT(HOUR FROM SYSTIMESTAMP - CREATED_DATE) AS diff_hours 
FROM MyTable;
注意,
LOCALTIMESTAMP
返回一个
TIMESTAMP
值,分别使用
SYSTIMESTAMP
CURRENT_TIMESTAMP
获取当前时间作为带时区的时间戳

现在考虑<强> SQLite < /强>:

更新create table t (CREATED_DATE DATE); insert into t values (datetime('2015-06-01 00:00:00')); insert into t values (datetime('2015-12-01 00:00:00')); insert into t values (datetime('2016-06-01 00:00:00')); insert into t values (datetime('2016-12-01 00:00:00')); select datetime('now', 'localtime') as now, created_date, julianday('now') - julianday(CREATED_DATE, 'utc') as wrong_delta_days, strftime('%j %H:%M:%S', datetime('0000-01-01T00:00:00', '+'||(julianday('now') - julianday(CREATED_DATE, 'utc'))||' day', '-1 day')) as wrong_delta, strftime('%j %H:%M:%S', datetime('0000-01-01T00:00:00', '+'||(julianday('now', 'localtime') - julianday(CREATED_DATE))||' day', '-1 day')) as delta_1, strftime('%j %H:%M:%S', datetime('now', 'localtime', '-'||strftime('%Y', CREATED_DATE)||' year', '-'||strftime('%j', CREATED_DATE)||' day', '-'||strftime('%H', CREATED_DATE)||' hour', '-'||strftime('%M', CREATED_DATE)||' minute', '-'||strftime('%S', CREATED_DATE)||' second' )) as delta_2, strftime('%j %H:%M:%S', datetime('0000-01-01T00:00:00', '+'||(julianday(datetime('now', 'localtime')||'Z') - julianday(CREATED_DATE||'Z'))||' day', '-1 day')) as delta_3 from t; now | CREATED_DATE | wrong_delta_days | wrong_delta | delta_1 | delta_2 | delta_3 2016-12-08 08:34:08 | 2015-06-01 00:00:00 | 556.398711088113 | 190 09:34:08 | 190 08:34:08 | 190 08:34:08 | 190 08:34:08 2016-12-08 08:34:08 | 2015-12-01 00:00:00 | 373.357044421136 | 007 08:34:08 | 007 08:34:08 | 007 08:34:08 | 007 08:34:08 2016-12-08 08:34:08 | 2016-06-01 00:00:00 | 190.398711088113 | 190 09:34:08 | 190 08:34:08 | 190 08:34:08 | 190 08:34:08 2016-12-08 08:34:08 | 2016-12-01 00:00:00 | 7.35704442113638 | 007 08:34:08 | 007 08:34:08 | 007 08:34:08 | 007 08:34:08
private void BindTable()
{            
    DataTable dt = ADOClass.adoDataTable;

    if (!Oracle_DAL.ConnTest()) // if no Oracle connection, SQLite is running and it needs DST correction
    {
        int oldHours = 0;
        int newHours = 0;
        int oldDays = 0;
        int newDays = 0;
        string ageCell = String.Empty;
        string hoursString = String.Empty;
        string daysString = String.Empty;
        string crDate = String.Empty;

        if (dt != null && dt.Rows != null)
        {
            if (dt.Rows.Count > 0)
            {
                foreach (DataRow dr in dt.Rows)
                {
                    crDate = dr["CREATED_DATE"] != null ? dr["CREATED_DATE"].ToString() : String.Empty;
                    if (!String.IsNullOrEmpty(crDate))
                    {
                        DateTime createdDate = DateTime.Parse(crDate);
                        if (createdDate.IsDaylightSavingTime())
                        {
                            ageCell = dr["AGE"] != null ? dr["AGE"].ToString() : String.Empty;
                            if (!String.IsNullOrEmpty(ageCell))
                            {
                                hoursString = ageCell.Split(',')[3];
                                hoursString = hoursString.TrimStart(' ');
                                oldHours = int.Parse(hoursString.Split(' ')[0]);

                                if (oldHours == 0)
                                {
                                    newHours = 23;
                                    daysString = ageCell.Split(',')[2];
                                    daysString = daysString.TrimStart(' ');
                                    oldDays = int.Parse(daysString.Split(' ')[0]);
                                    oldDays--;
                                    newDays = oldDays;
                                    ageCell = ageCell.Replace(daysString, newDays.ToString() + " days");
                                    dr["AGE"] = ageCell;
                                }
                                else
                                {
                                    oldHours--;
                                    newHours = oldHours;                                    
                                }
                                dr["AGE"] = ageCell.Replace(hoursString, newHours.ToString() + " hours");
                            }
                        }
                    }
                }                        
            }
        }
    }

    lstData.DataContext = dt;  // binds to my ListView's grid
}
SELECT CREATED_DATE, (SYSDATE - CREATED_DATE) AS TOTALDAYS, 
FLOOR(ABS(MONTHS_BETWEEN(CREATED_DATE, SYSDATE)) / 12) || ' years, '  
|| (FLOOR(ABS(MONTHS_BETWEEN(CREATED_DATE, SYSDATE))) - 
   (FLOOR(ABS(MONTHS_BETWEEN(CREATED_DATE, SYSDATE)) / 12)) * 12) || ' months, '  
-- we take total days - years(as days) - months(as days) to get remaining days
|| FLOOR((SYSDATE - CREATED_DATE) -      -- total days
   (FLOOR((SYSDATE - CREATED_DATE)/365)*12)*(365/12) -      -- years, as days
   -- this is total months - years (as months), to get number of months, 
   -- then multiplied by 30.416667 to get months as days (and remove it from total days)
   FLOOR(FLOOR(((SYSDATE - CREATED_DATE)/365)*12 - (FLOOR((SYSDATE - CREATED_DATE)/365)*12)) * (365/12)))  
|| ' days, '   
-- Here, we can just get the remainder decimal from total days minus 
-- floored total days and multiply by 24       
|| FLOOR(
     ((SYSDATE - CREATED_DATE)-(FLOOR(SYSDATE - CREATED_DATE)))*24
   )
|| ' hours, ' 
-- Minutes just use the unfloored hours equation minus floored hours, 
-- then multiply by 60
|| ROUND(
       (
         (
           ((SYSDATE - CREATED_DATE)-(FLOOR(SYSDATE - CREATED_DATE)))*24
         ) - 
         FLOOR((((SYSDATE - CREATED_DATE)-(FLOOR(SYSDATE - CREATED_DATE)))*24))
       )*60
    )
|| ' minutes'  
AS AGE FROM MyTable`
    private static readonly string mainqueryCommandTextSQLite = "SELECT " + 
        "CREATED_DATE, " +
        " (julianday('now') - julianday(CREATED_DATE, 'utc')) AS TOTALDAYS, " +
        //            " (((julianday('now') - julianday(CREATED_DATE))/365)*12) || ' total months, ' || " +
        //            " ((CAST ((julianday('now') - julianday(CREATED_DATE))/365 AS INTEGER))*12) || ' years as months, ' || " +

        // Provide years, months
        " CAST ((julianday('now') - julianday(CREATED_DATE, 'utc'))/365 AS INTEGER) || ' years, ' || " +
        " CAST (((((julianday('now') - julianday(CREATED_DATE, 'utc'))/365)*12) - (CAST ((julianday('now') - julianday(CREATED_DATE, 'utc'))/365 AS INTEGER)*12)) AS INTEGER)  || ' months, ' " +

        // Provide days
        "|| ((CAST ((julianday('now') - julianday(CREATED_DATE, 'utc')) AS INTEGER) - " +  // total number of days
        " (CAST ((julianday('now') - julianday(CREATED_DATE, 'utc'))/365 AS INTEGER)*365) ) -" + // years in days  
        " CAST((30.41667 * ((CAST ((((julianday('now') - julianday(CREATED_DATE, 'utc'))/365)*12) AS INTEGER)) - ((CAST ((julianday('now') - julianday(CREATED_DATE, 'utc')) / 365 AS INTEGER)) * 12))) AS INTEGER)) " + // days of remaining months using total months - months from # of floored years * (365/12)
        " || ' days, ' " +

        // BUG:  These next two do not get accurate hours during DST months (March - Nov)
        // This gives hours
        "|| CAST ((((julianday('now') - julianday(CREATED_DATE, 'utc')) - " +
        " CAST ((julianday('now') - julianday(CREATED_DATE, 'utc')) AS INTEGER))*24) AS INTEGER) " +

        // This gives hours.minutes
        //"|| (((julianday('now') - julianday(CREATED_DATE, 'utc')) - CAST ((julianday('now') - julianday(CREATED_DATE, 'utc')) AS INTEGER))*24) " +

        // This gives days.hours, but taking the decimal and multiplying by 24 to get actual hours 
        // gives an incorrect result
        //"|| ((" +
        //        "(0.0 + strftime('%S', 'now', 'localtime') " +
        //        "+ 60*strftime('%M', 'now', 'localtime') " +
        //        "+ 24*60*strftime('%H', 'now', 'localtime') " +
        //        "+ 24*60*60*strftime('%j', 'now', 'localtime')) - " +
        //        "(strftime('%S', CREATED_DATE) " +
        //        "+ 60*strftime('%M', CREATED_DATE) " +
        //        "+ 24*60*strftime('%H', CREATED_DATE) " +
        //        "+ 24*60*60*strftime('%j', CREATED_DATE)) " +
        //    ")/60/60/24) " +
        "|| ' hours, ' " +

        // Provide minutes
        "|| CAST (ROUND(((((julianday('now') - julianday(CREATED_DATE, 'utc')) - CAST ((julianday('now') - julianday(CREATED_DATE, 'utc')) AS INTEGER))*24) - " +
        "(CAST((((julianday('now') - julianday(CREATED_DATE, 'utc')) - CAST((julianday('now') - julianday(CREATED_DATE, 'utc')) AS INTEGER)) * 24) AS INTEGER)))*60) AS INTEGER)" +
        "|| ' minutes' " +
        " AS AGE FROM MyTable";