【问题标题】:Getting Start & End of Month for 12 Months (A Year)开始和结束 12 个月(一年)
【发布时间】:2018-01-13 02:42:18
【问题描述】:

我实际上是在创建一种方法来根据某个日期时间范围获取我的 asp 图表的详细信息。我看过这两个链接,

First Link , Second Link

我的数据检索方法,

//Monthly Statistics
public int janLogin(String username)
{
    int result = 0;

    StringBuilder sqlCmd = new StringBuilder();
    sqlCmd.AppendLine("SELECT COUNT(*) FROM AuditActivity WHERE Username = @getUsername AND DateTimeActivity BETWEEN @getFirstDT AND @getLastDT AND ActivityType = @getType");

    try
    {
        SqlConnection myConn = new SqlConnection(DBConnectionStr);

        myConn.Open();

        SqlCommand cmd = new SqlCommand(sqlCmd.ToString(), myConn);

        //DateTime
        DateTime currentDT = DateTime.Today;

        //Code Below gets only current Start and End of Current month
        DateTime FirstDT = currentDT.AddDays(1 - currentDT.Day);
        DateTime SecondDT = FirstDT.AddMonths(1).AddSeconds(-1);

        cmd.Parameters.AddWithValue("@getUsername", username);
        cmd.Parameters.AddWithValue("@getFirstDT", FirstDT);
        cmd.Parameters.AddWithValue("@getLastDT", SecondDT);
        cmd.Parameters.AddWithValue("@getType", "Login");

        result = Convert.ToInt16(cmd.ExecuteScalar());

        myConn.Close();

        return result;
    }
    catch (SqlException ex)
    {
        logManager log = new logManager();
        log.addLog("AuditNLoggingDAO.janLogin", sqlCmd.ToString(), ex);
        return 0;
    }
}

从方法名称 janLogin 中可以看出,我实际上正在尝试做的事情基本上是为了获取从 2017 年 1 月 1 日 00:00:00 到 2017 年 1 月 31 日 登录的人数。当前月份实际上是八月。我如何才能真正获得 2017 年所有十二个月(1 月至 12 月)的开始和结束月份(来自粗体示例)?

如果我没记错的话,它必须涉及一个 for 循环吗?最终计数是 12。但我很确定该怎么做.. 可能有重复的问题,但我似乎找不到符合我要求的问题..

感谢任何帮助。谢谢!

【问题讨论】:

  • 开始不使用 AddWithValue 特别是日期 Can we Stop using addWithValue already?
  • 第二你能解释一下你的数据库中两列的数据类型是什么吗?如果他们还存储时间部分,您的代码可能会被窃听
  • @Steve 感谢您的建议和文章,将来会修改! DateTime 是 firstDT 和 lastD 的数据类型

标签: c# asp.net date datetime


【解决方案1】:

整个 2017 日历年

整个日历年:

int year = 2017
DateTime FirstDT = new DateTime(year, 1, 1); // jan 2017
DateTime SecondDT = FirstDT.AddYear(1).AddSeconds(-1); // last second of dec 2017

上面的示例将 01 Jan 2017 00:00:00 返回到 31 Dec 2017 23:59:59
请注意此答案底部的精度问题 31 DEC 2017 23:59:59.999999


一年中的特定月份

为了获得不同的月份,而不是使用当前月份,您可以这样做:

int year = 2017;
int month = 12;
DateTime FirstDT = new DateTime(year, month , 1); // december 2017
DateTime SecondDT = FirstDT.AddMonths(1).AddSeconds(-1); // last second of dec 2017

上面的示例将 01 Dec 2017 00:00:00 返回到 31 Dec 2017 23:59:59
请注意此答案底部的精度问题 31 DEC 2017 23:59:59.999999

然后您可以更改方法以将月份作为参数,如下所示:

public int LoginsByMonth(int year, int month, String username)
{
    if (month < 1 || month > 12)
    {
        throw new ArgumentOutOfRangeException("month must be between 1 and 12.");
    }

    ...
    
    DateTime FirstDT = new DateTime(year, month, 1);
    DateTime SecondDT = FirstDT.AddMonths(1).AddSeconds(-1);

    ...
}

您可以像这样将您的方法称为单身:

 int loginsNovember2017 = LoginsByMonth(2017, 11, "userA");
 int loginsDecember2017 = LoginsByMonth(2017, 12, "userA");

精度:使用刻度而不是秒

如下面的 cmets 所述。您可以使用.AddTicks(-1L) 而不是.AddSeconds(-1) 来获得更准确的过滤器。但是,根据您的要求,您可能更愿意在下一个选项中完全避免这种情况。

避免精确

也如下面的 cmets 所述。您可以通过将SELECT 语句切换为在WHERE 语句中包含&lt; (less than) 条件,而不用担心使用BETWEEN 条件时的精度,从而完全避免这种精确度问题。

更改如下所示

SELECT COUNT(*)
FROM AuditActivity
WHERE Username = @getUsername 
    AND DateTimeActivity >= @getFirstDT
    AND DateTimeActivity < @getLastDT
    AND ActivityType = @getType

这意味着您可以删除AddSeconds(-1)AddTicks(-1L)。例如:

// entire year
DateTime SecondDT = FirstDT.AddYear(1);
// month
DateTime SecondDT = FirstDT.AddMonths(1);

【讨论】:

  • .AddMonths(1).AddSeconds(-1)有点差劲。您真的应该使用.AddMonths(1).AddTicks(-1L) 来获取新月份开始前的最后可能表示的时间。
  • @Enigmativity -- OP 在他的问题中专门询问了1 Jan 2017 00:00:00(秒)。但在你的情况下,你是对的,Ticks 是正确的方法。我会在答案上注明。
  • @domster - 是的,事情可能会在一天的最后一秒发生。购买 .AddSeconds(-1) 你会想念那些的。
  • @domster - 它将包括 59 秒和 00 秒之间的秒数,以使用 DateTime 结构可以找到的最高精度
  • @domster 我们使用的DateTime 构造函数是new DateTime(year, month, day)。所以,通过 month = 12 会给你 12 月。
【解决方案2】:

这应该以表示每个月的整数数组的形式获取当前年份的登录信息):

public int[] yearLogin(String username)
{
    string sqlCmd = @"SELECT COUNT(*)
FROM AuditActivity
WHERE Username = @getUsername AND DateTimeActivity
BETWEEN @getFirstDT AND @getLastDT AND ActivityType = @getType";

    try
    {
        Func<SqlConnection, DateTime, DateTime, int> fetch = (c, f, t) =>
        {
            using (SqlCommand cmd = new SqlCommand(sqlCmd.ToString(), c))
            {
                cmd.Parameters.AddWithValue("@getUsername", username);
                cmd.Parameters.AddWithValue("@getFirstDT", f);
                cmd.Parameters.AddWithValue("@getLastDT", t);
                cmd.Parameters.AddWithValue("@getType", "Login");

                return Convert.ToInt16(cmd.ExecuteScalar());
            }
        };
        using (SqlConnection myConn = new SqlConnection(DBConnectionStr))
        {
            myConn.Open();

            DateTime currentDT = DateTime.Today;
            DateTime FirstDT = currentDT.AddMonths(1 - currentDT.Month).AddDays(1 - currentDT.Day);

            int[] result =
                Enumerable
                    .Range(0, 12)
                    .Select(x => fetch(myConn, FirstDT.AddMonths(x), FirstDT.AddMonths(x + 1).AddTicks(-1L)))
                    .ToArray();

            return result;
        }
    }
    catch (SqlException ex)
    {
        logManager log = new logManager();
        log.addLog("AuditNLoggingDAO.janLogin", sqlCmd.ToString(), ex);
        return null;
    }
}

【讨论】:

    【解决方案3】:

    为什么不直接使用 SQL 提供的功能呢?

    @"SELECT MONTH(DateTimeActivity), COUNT(*) FROM AuditActivity 
      WHERE Username = @getUsername AND ActivityType = @getType
      GROUP BY MONTH(DateTimeActivity)
      ORDER BY MONTH(DateTimeActivity)"
    

    这将为您提供每个月的 12 条记录(假设您有全年月份的日期),只需一次调用数据库,而不是循环内的 12 次不同调用。而且 WHERE 语句要简单得多,不需要对 DateTimeActivity 字段进行计算

    这种方法只有一个问题。如果 WHERE 语句没有找到任何具有给定约束的记录,它不会返回零值记录。

    但在代码中处理最终丢失的月份相对容易

        cmd.Parameters.AddWithValue("@getUsername", username);
        cmd.Parameters.AddWithValue("@getType", "Login");
    
        SqlDataReader reader = cmd.ExecuteReader();
        int[] monthCount = new int[12];
        while(reader.Read())
            // -1 because arrays start at zero and January has value 1.
            monthCount[reader.GetInt32(0) - 1] = reader.GetInt32(1);
    

    最后,您有一个整数数组,其中每个元素表示每个月的计数,在应用 WHERE 语句中的给定约束后没有记录的月份为零。

    【讨论】:

    • 嘿史蒂夫,它返回所有 12 个月?
    • 如果每个月至少有一个DateTimeActivity,否则当月没有记录。不过,它可以很容易地在代码中处理。还有其他方法可以直接从 sql server 获取月份为零,但我现在手头没有示例。
    • 我尝试将语句放入 sql 查询中,但根本没有返回任何行。我知道它应该只有一行。与月份和计数。我用这个:SELECT MONTH(DateTimeActivity) AS 'DateTimeActivity', COUNT(*) AS 'CountTimes' FROM AuditActivity WHERE Username = 'x@gmail.com' AND ActivityType = 'Login Failuire' GROUP BY MONTH(DateTimeActivity) ORDER BY MONTH (日期时间活动)
    • 登录失败?
    • 可能的错字的一部分。我已经直接使用 SQL Server 对我的表进行了测试,它返回了按月分组的所有记录。我无法确切地说出问题所在。当然 DateTimeActivity 是一个 DateTime 列吧?
    【解决方案4】:

    所以,我想这里有两个不同的问题。

    第一个是“如何逐年逐月获取特定月份的日期范围?”

    这是一种方法:

    var year = 2017;
    for (int month = 1; month <= 12; month++)
    {
        var firstDayOfMonth = new DateTime(year, month, 1);
        var lastDayOfMonth = new DateTime(year, month, DateTime.DaysInMonth(year, month));
        Console.WriteLine($"{firstDayOfMonth} - {lastDayOfMonth}");
    }
    

    请记住,DateTime 没有仅限 Date 的值。如果你像我一样省略构造函数中的时间部分,它将被设置为当天的午夜。当使用这些值过滤某些日期时,您应该先调用.Date

    var datesInThisMonth = someDates.Where(x => x >= firstDayOfMonth && x.Date <= lastDayOfMonth);
    

    ...或者使用 strict less 以下个月的第一天为上限:

    var datesInThisMonth = someDates.Where(x => x >= firstDayOfMonth && x <= lastDayOfMonth.AddDays(1));
    

    第二个是如何使用该值来构建 SQL 查询。

    你不应该在这里使用BETWEEN。根据您为该列使用的数据类型,它要么难以消化,要么就是完全错误。

    这是您的选择:

    • 您的专栏是datetime 类型

    然后是 rounded.000.003.007 秒。因此,您将在BETWEEN 语句中使用的上限将四舍五入为第二天的23:59:59.99700:00。您可能会错过一些记录,或者多次包含一些记录(它将出现在本月和下个月的结果中)。

    • 您的专栏是datetime2 类型

    然后可以编写正确的查询,但是为了正确执行此操作,您必须考虑在 SQL 中使用的精度(可以针对该特定表的特定列进行自定义)和您应该从 C# 传递什么值才能在 SQL 中获得正确的值。

    我不明白这有多大的麻烦。

    只需使用x.Date &gt; firstDayOfMonth AND x.Date &lt; firstDayOfNextMonth 并忘记它。

    你也可以看看this answer

    【讨论】:

    • 嘿,我会试试看,看看我能得到什么,谢谢!
    • 这将错过每个月最后一天的所有登录。
    • @Enigmativity 这取决于您如何使用此代码过滤登录;我会写.Where(x =&gt; x.LoginDate.Date &lt;= lastDayOfMonth),不会错过任何东西
    • @vorou - 但它会像那样错过。您定义的变量lastDayOfMonth 是该月最后一天的午夜。由于午夜是一天的开始,因此您错过了该月的最后 24 小时 - 即最后一天发生的任何时间。
    • @Enigmativity 请重新阅读我的评论:我正在使用DateTime.Date 属性。它也返回午夜,因此对于该月最后一天的任何DateTime,它将等于我在lastDayOfMonth 中的值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-11
    • 2019-08-02
    • 1970-01-01
    相关资源
    最近更新 更多