【问题标题】:Check if timestamp (milli) belongs to a specific day检查时间戳(毫)是否属于特定日期
【发布时间】:2014-08-26 21:33:50
【问题描述】:

在我的 SQLite 数据库表中有一个时间戳 (毫秒) 列。这个表真的很大,我想要一种有效的方法来查询时间戳列引用特定日期的所有行(在这种情况下,我想获取属于 TODAY 的所有条目)。

我想出了这个,但它不起作用而且速度很慢。任何想法都会被应用。 (不,我不能在数据库中进行任何更改,例如添加 DATETIME 列...我必须使用时间戳)。

public boolean isTimestampInToday(long timestamp){

    //get a timestamp from today
    long todayStamp = System.currentTimeMillis();

    Calendar c = Calendar.getInstance();
    c.setTimeInMillis(todayStamp);
    c.setTimeZone(TimeZone.getDefault());

    c.set(Calendar.HOUR_OF_DAY, 0);
    c.set(Calendar.MINUTE, 0);
    c.set(Calendar.SECOND, 0);
    c.set(Calendar.MILLISECOND, 0);

    Date startDate = c.getTime(); //START OF DAY

    c.set(Calendar.HOUR_OF_DAY, 23);
    c.set(Calendar.MINUTE, 59);
    c.set(Calendar.SECOND, 59);
    c.set(Calendar.MILLISECOND, 999);

    Date endDate = c.getTime(); //END OF DAY

    Long startStamp = startDate.getTime();
    Long endStamp = endDate.getTime();

    if(timestamp >= startStamp && timestamp < endStamp) {
        return true;            
    } else {
        return false;
    }      
}

我感谢有经验的人的所有答案,但老实说,我能理解的是@iaune 的答案。现在我已经在查询本身内部完成了这项工作:

Calendar gc = new GregorianCalendar();
gc.setTimeZone(TimeZone.getDefault());
long toBeg = gc.getTimeInMillis();
toBeg -= toBeg % (24*60*60*1000);
long toEnd = toBeg + 24*60*60*1000;


Cursor cursor = mDb.rawQuery(
        "SELECT * FROM "
                + DbHelper.TABLE_PA_DATA
                + " WHERE "
                + DbHelper.COLUMN_TIMESTAMP
                + " BETWEEN "
                + toBeg
                + " AND "
                + toEnd
        , null);

这种方法有什么问题还是可以进一步改进?

更新

我意识到使用INTEGER 作为时间戳列类型是错误的。我添加了一个DATETIME 列...我现在应该使用直接 SQLite 查询函数。有什么想法吗?

这是我转储表格时得到的结果:

ID  DATETIME                    TIMESTAMP      STEPS    CALORIES
1   2014-07-06 07:18:55         169629539           3   0
2   2014-07-06 07:19:10         169644509           4   0
3   2014-07-06 08:15:36         173030229           1   0
4   2014-07-06 08:16:04         173058397           4   0
5   2014-07-06 08:31:39         173993598           2   0
6   2014-07-06 08:33:20         174094714           5   0
7   2014-07-06 09:31:54         177609142           1   0
8   2014-07-06 09:42:24         178238517           1   0
9   2014-07-06 10:37:37         181551849           1   0
10  2014-07-06 11:00:31         182925950           1   0
11  2014-07-06 12:17:42         187557035           1   0
12  2014-07-06 14:14:39         194573993           2   0

好的,我设法在@laune 解决方案中添加了一些内容:

Calendar cal = Calendar.getInstance();
// offset to add since we're not UTC
long offset = cal.get(Calendar.ZONE_OFFSET) +
        cal.get(Calendar.DST_OFFSET);
long toBeg = cal.getTimeInMillis();
toBeg -= (toBeg + offset) % (24*60*60*1000);
long toEnd = (toBeg) + 24*60*60*1000;

使用这个查询:

Cursor cursor = mDb.rawQuery(
        "SELECT * FROM "
                + Database.TABLE_PA_DATA
                + " WHERE "
                + Database.COLUMN_TIMESTAMP
                + " BETWEEN "
                + toBeg
                + " AND "
                + toEnd, null
);

【问题讨论】:

  • 你的 SQL 代码在哪里?你不是要查询你的数据库吗?
  • SQLite 不提供 Timestamp milliseconds-since-epoch 数据类型。那么究竟是什么data type of your column
  • @BasilBourque 它只是一个integer 专栏
  • 那么你存储的是秒还是毫秒?你的问题说毫秒,但SQLite doc 说秒。
  • @BasilBourque 我将TIMESTAMP 列创建为INTEGER,并将System.currenttimemili 存储在其中,所以我猜它的毫秒数

标签: java android sql sqlite datetime


【解决方案1】:

简单的算术运算可以避免昂贵的字符串和格式化操作。不确定大卫是否是我建议的意思,但也许代码更清楚地表达了它:

Calendar gc = new GregorianCalendar();
long toBeg = gc.getTimeInMillis();
toBeg -= toBeg % (24*60*60*1000);
long toEnd = toBeg + 24*60*60*1000;

long now = gc.getTimeInMillis();
if( toBeg <= now && now < toEnd ){
    System.out.println( "today" );
}

根据 if ,预先计算的 toBeg 和 toEnd 值可用于运行查询。 (准确地说,注意&lt;=&lt;的使用。)

【讨论】:

  • 此解决方案假定您想要 GMT 的“今天”。
  • @DavidWallace gc.setTimeZone(TimeZone.getDefault()); 不能解决问题吗?
  • @DavidWallace 我想 OP 可以使用自己的建议或使用 GregorianCalendar(TimeZone zone) 创建日历。
  • @Sean87 不,它没有。
  • 如果时间戳不是 GMT s.o.做了一个糟糕的决定。如果这个应用程序被移动到另一个 TZ 怎么办?如果运行 APP 的用户来自一个超过 TZ 的国家怎么办?如果您所在地区的 TZ 发生变化(我知道,这种情况很少见,但是...)怎么办?
【解决方案2】:

我会像这样使用SimpleDateFormatnew Date(long)

// four digit year, two digit month, two digit day. For example, 20140706
private final DateFormat sdf = new SimpleDateFormat("yyyyMMdd");
public boolean isTimestampInToday(final long timestamp){
  return sdf.format(new Date(timestamp)).equals(sdf.format(new Date()));
}

【讨论】:

    【解决方案3】:

    使用PreparedStatement 选择您的数据,其中where 子句包括

    date_column between ? and ?
    

    一旦您计算出 startDateendDate - 并且您在上面执行的方式完全可以 - 将它们设置为 PreparedStatement 中的参数并运行它。

    【讨论】:

      【解决方案4】:

      半开

      日期时间比较最好使用“半开放”方法完成,其中时间跨度的开始包含在内,结束时间不包含在内。对于一天,这意味着您希望查询大于或等于相关当天第一时刻且小于第二天第一时刻的日期时间。也就是说,最多不包括第二天。这种方法避免了无限分割一天中最后一刻的问题。

      乔达时间

      java、util.Date 和 .Calendar 类是出了名的麻烦。避开他们。请改用 Joda-Time 库。它适用于 Android。

      时区

      时区至关重要。一天的定义取决于时区。

      指定所需的时区,而不是依赖隐式默认值。使用proper time zone names,而不是 3 或 4 字母代码。

      如果你真的想使用JVM的默认时区,我建议显式调用getDefault而不是依赖隐式默认。这种依赖通常会导致日期时间工作的混乱。

      示例代码

      DateTimeZone timeZone = DateTimeZone.getDefault();  // Or DateTimeZone.forID( "Europe/Amsterdam" )
      DateTime now = DateTime.now( timeZone );
      DateTime todayStartOfDay = now.withTimeAtStartOfDay():
      DateTime tomorrowStartOfDay = today.plusDays( 1 ).withTimeAtStartOfDay();
      

      您可以从那里调用getMillis 来获取一个long,您可以使用它来为您的SQL 查询构造一个java.sql.Timestamp 对象。

      SQL 逻辑

      对于半开方法,我们不能使用 SQL 命令BETWEEN。该命令包含在两个比较器上。

      相反,我们需要编写两个比较器,就像这个伪代码一样。注意 &gt;=&lt;(没有等号)。

      SELECT * 
      FROM some_table_ 
      WHERE target >= todayStartOfDay.getMillis() 
      AND target < tomorrowStartOfDay.getMillis();
      

      【讨论】:

      • 我也真的看不懂你的代码。我不知道如何将它链接到我的 SQL 查询。你能检查我更新的问题吗?
      • @Sean87 因此,您需要一对数字来表示时间跨度的每一端,每个数字都是自 epoch 以来的秒数或毫秒数。最后一句告诉你如何从 Joda-Time 中的 DateTime 对象中提取这样一个数字。
      【解决方案5】:

      我会这样做

      boolean isTimestampInToday(long timestamp) {
          Calendar c1 =  Calendar.getInstance();
          Calendar c2 =  Calendar.getInstance();
          c2.setTimeInMillis(timestamp);
          return c1.get(Calendar.DATE) == c2.get(Calendar.DATE);
      }
      

      【讨论】:

      • 我真的看不懂这段代码。我不知道如何将它链接到我的 SQL 查询。你能检查我更新的问题吗?
      • 但是你自己说...我必须用时间戳来做......所以我提出了一种用时间戳来做到这一点的方法
      • 是的,但我仍然可以查询数据库并访问时间戳列,所以我想知道如何让您的代码在查询中工作
      【解决方案6】:
      DateUtils.isToday(timeStampInMilli)
      

      您可以使用此方法检查时间戳是否为今天。

      【讨论】:

        猜你喜欢
        • 2010-12-14
        • 2017-03-13
        • 1970-01-01
        • 2012-05-27
        • 2011-12-27
        • 2021-12-18
        • 1970-01-01
        • 2011-12-20
        • 2013-06-08
        相关资源
        最近更新 更多