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

时间:2016-12-07 00:36:04

标签: oracle sqlite date date-difference

我想知道是否有人见过这个,如果有解决方案,或者我是不是做对了。我试图获得现在和创建日期之间的小时差异"在数据库记录中 - 不是试图获得总小时数,而是在您消除总天数后剩余的小时数,因此您可以输出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中,您必须将日期存储为格式为' yyyy-MM-dd HH:mm的字符串:SS&#39 ;.但每个表的值都是相同的。

我开始得到"现在"之间的总天数差异。和日期。我可以从小数天中减去整数天数并得到我需要的小时数。

总天数 - Oracle

如果我在Oracle中这样做,给我总天数差异:
SELECT (SYSDATE - CREATED_DATE) FROM MyTable

第一个获得686.211284...,第二个获得180.144976...

总天数 - SQLite

如果我使用SQLite来给我总天差,第一个非常接近,但第二个真的是关闭:
SELECT (julianday('now') - julianday(CREATED_DATE, 'utc')) FROM MyTable

第一个获得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,它完全适用于分钟。

屏幕截图:并排比较

这是在12/6/2016 7:20 PM进行的,左边是我的应用程序中显示的SQLite,右边是Oracle SQL Developer中的Oracle查询:

enter image description here

3 个答案:

答案 0 :(得分:2)

实际上您错过了一个重要信息:您认为哪个值是正确的?您是否需要考虑夏令时?

Oracle

开始

我假设列CREATED_DATE的数据类型为DATESYSDATE还返回DATE值。 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天。

如果您必须考虑夏令时,则必须使用数据类型TIMESTAMP WITH TIME ZONE(或TIMESTAMP WITH LOCAL TIME ZONE),请参阅此示例:

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小时。

这取决于您的要求您必须使用哪一个。一般情况下,我建议使用TIMESTAMP,resp。 TIMESTAMP WITH TIME ZONE而不是DATE,因为您可以使用EXTRACT(datetime)来获取时间,而不必使用FLOOR这样的东西:

 SELECT 
    EXTRACT(HOUR FROM SYSTIMESTAMP - CREATED_DATE) AS diff_hours 
FROM MyTable;

注意,LOCALTIMESTAMP返回TIMESTAMP值,使用SYSTIMESTAMP,resp。 CURRENT_TIMESTAMP将当前时间设为TIMESTAMP WITH TIME ZONE值。

现在考虑 SQLite

<强>更新

实际上julianday('now') - julianday(CREATED_DATE, 'utc')会给出正确的结果 - 或者称之为“精确结果”。它需要考虑夏令时的变化。例如,“2016-10-31 00:00:00” - “2016-10-30 00:00:00”(欧洲时间)的差异是25小时 - 而不是24小时!

现在,您希望忽略计算中的夏令时变化。对于Oracle来说,这很简单,使用DATETIMESTAMP数据类型而不是TIMESTAMP WITH TIME ZONE,然后就完成了。

SQLite总是考虑时区和夏令时的转变,你必须做一些黑客来绕过它。 我有时间做一些测试,我发现了几种方法。

以下方法都在我的机器上工作(瑞士时间采用夏令时设置,+ 01:00或+02:00)。

  • julianday('now', 'localtime') - julianday(CREATED_DATE)
  • julianday(datetime('now', 'localtime')||'Z') - julianday(CREATED_DATE||'Z')

查看测试用例:

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

我使用strftime('%j %H:%M:%S', datetime('0000-01-01T00:00:00', ..., '-1 day'))仅用于格式化目的,它不适用于超过1年的增量。

答案 1 :(得分:0)

就我的问题而言,似乎SQLite的日光变化,或者确切地说,您需要在保存时指定此更改(因为它是字符串而不是 true date field / transparent-timestamp)。

当您生成(?如果您这样做)日期时,请将其设为完整的UTC,时区显式而非本地隐含:

  

SQLite Date / Time

     

格式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时区,它只会做一个简单的减法,而不是给你一个关于你的日光的 icus

你的julianday('now')也不能'UTC'吗? (对不起,如果我没有得到它,明天我会再看一下)

答案 2 :(得分:0)

我知道这根本不是理想的方法,但我自己在我的C#应用​​程序中使用补偿函数来解决这个问题,该函数正在提取SQLite值。在我写完这篇文章之后,实际上我发现我可以在C#中重新完成日期减法并覆盖我的Age字段!但是因为我只是需要修改给定条件的小时数(以及天数,如果小时数为0且它是DST日期),我只是使用了它。

所以我有一个类,它将根据我提供给该类中的公共静态字符串变量的查询以及我调用的另一个函数提供DataTable结果。然后,我调用BindTable()将该表绑定到我的WPF应用中的ListView以显示信息。

我通过调用ADOClass.adoDataTable来调用我的DataTable。一旦我有DataTable,我只是遍历行,将Created Date存储为变量,Age字符串(我使用函数根据需要更新)作为变量。如果创建日期符合.IsDaylightSavingTime()条件,则必须减去一小时。如果说Age,0小时(以及几分钟),我们必须将天数设置为一天,将小时设置为23天。

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
}

注意:我后来意识到,因为我在非DST日测试了这个功能,这是为了修复DST日期的问题,当它再次成为DST时,我相信事情会发生变化。我想我最终还是要在非DST日期的结果中添加一个小时才能使其正常工作。所以如果DateTime.Now符合要求,则必须另外检查一下.IsDaylightSavingTime()以便知道该怎么做。我当时可能会重新访问这篇文章。)

万一有人好奇..... 以下是我在Oracle中运行的完整查询:

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`

这是我对我的应用程序中SQLite的最终完整查询:

    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";

新截图,显示所有匹配项(除了总天数,我可以通过从我的C#函数中减去1/24并以相同的方式更新DST日期来改变):

enter image description here

<强>更新

由于Wernfried在SQLite中发现了2个查询,否定了对此函数的需求,我将接受如何真正解决此问题的答案:

对于Oracle -

  • SELECT (SYSDATE - CREATED_DATE) FROM MyTable

或使用to_date格式语法有助于获取days.hours和进行转换。取小数部分并乘以24可以持续数小时,并且与DST无关,就像我想要的那样。请参阅上面的完整查询,我将其用于格式化为年,月,日,小时和分钟。

对于SQLite -

正如韦尔弗里德发现的那样,其中任何一个都可行:

julianday('now', 'localtime') - julianday(CREATED_DATE)

julianday(datetime('now', 'localtime')||'Z') - julianday(CREATED_DATE||'Z')

这样可以避免上述功能的需要。

如果您使用:

julianday('now') - julianday(CREATED_DATE, 'utc')

就像我在上面的代码中所说的那样,你需要我的DST补偿器功能,更进一步。