关于日期时间:仅从日期开始计算夏令时

Calculating daylight saving time from only date

我正在使用Arduino和实时时钟芯片。 芯片可以补偿闰年等,所以它总是有正确的日期,但它不能处理夏令时,我认为由于区域复杂性。 时钟可以给我一天,一个月和一年(基于1)和星期几(星期日= 0到星期六= 6)。

因为我需要与用户输入的日期和时间进行比较,我需要知道为夏令时调整的日期和时间。 如果当前日期是夏令时我可以简单地从时钟添加一个小时,我有我需要的东西。

困难的部分是确定我是否在夏令时,因为它每年都在变化。 我只关心它在我的位置(山区时间)有效。 我的平台似乎没有任何全面的日期库,我觉得无论如何都会有点过头了。 是否有一个简单的公式来确定我是否在使用DST?


这实际上看似简单。有一些事实可以帮助我们:

  • 在美国的大部分地区,夏令时从3月的第二个星期日开始,到11月的第一个星期日,凌晨2点结束。
  • 3月的第二个星期日将始终在8日至14日之间。
  • 11月的第一个星期日将始终在1日至7日之间。
  • 星期几的编号非常方便,因为星期几将为您提供上周日。
  • 这些事实导致以下代码(C#,但可以轻松地移植到您的平台):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
        public bool IsDST(int day, int month, int dow)
        {
            //January, february, and december are out.
            if (month < 3 || month > 11) { return false; }
            //April to October are in
            if (month > 3 && month < 11) { return true; }
            int previousSunday = day - dow;
            //In march, we are DST if our previous sunday was on or after the 8th.
            if (month == 3) { return previousSunday >= 8; }
            //In november we must be before the first sunday to be dst.
            //That means the previous sunday must be before the 1st.
            return previousSunday <= 0;
        }

    事实证明,你甚至不需要知道这一年的时间,只要你可以相信你的一周中的价值。

    我写了一个快速的单元测试并验证了这段代码与1800到2200的所有日期的TimeZone.IsDayLightSavingsTime()一致。我没有考虑到凌晨2点的规则,但你可以很容易地检查一周中的星期几和星期日日期是8到14(3月)或1和7(11月)。


    中欧代码(2014-3000年范围内每天测试)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
        public static bool IsDst(int day, int month, int dow)
        {
            if (month < 3 || month > 10)  return false;
            if (month > 3 && month < 10)  return true;

            int previousSunday = day - dow;

            if (month == 3) return previousSunday >= 25;
            if (month == 10) return previousSunday < 25;

            return false; // this line never gonna happend
        }

    测试功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
        static void Main(string[] args)
        {
            TimeZoneInfo tzf2 = TimeZoneInfo.FindSystemTimeZoneById("Central Europe Standard Time");

            var date = new DateTime(2014, 01, 1, 5, 0,0);
            bool wasSummer = false;

            while (date <= new DateTime(3000,1,1))
            {                                        
                var dow = (int) date.DayOfWeek;

                var isDst = IsDst(date.Day, date.Month, dow);              

                DateTime f2 = TimeZoneInfo.ConvertTime(date, tzf2);
                var isSummer = f2.IsDaylightSavingTime();

                if (isSummer != isDst)
                {
                    Console.WriteLine("ERROR");
                    Console.WriteLine(date);
                }

                if (isSummer != wasSummer)
                {
                    Console.WriteLine(date.AddDays(-1).ToShortDateString());
                }

                date = date.AddDays(1);
                wasSummer = isSummer;
            }

            Console.ReadKey();

    }


    虽然根据现行规则很容易计算特定日期是否在特定地点的DST中,但请注意DST是政治家的心血来潮,并且可能随时改变。我有一个2007年之前制造的时钟,它可以自动调整夏令时,现在我必须每年更换四次:实际发生变化时发生两次,现在发生两次 - 在旧日期不正确地改变自己。

    在这种情况下,您可以通过让用户输入时区以及日期和时间的简单权宜来完全忽略DST。或者您可以像大多数消费者设备一样,让用户每年两次调整到当地时区的时间。

    但是如果你真的需要处理DST并且真的想要做正确的事情,请使用zoneinfo数据库并确保它可以以某种方式更新。如果由于某种原因无法做到这一点,至少允许用户覆盖规则。即使这太难了,至少可以让用户选择关闭自动调整(不像我的愚蠢闹钟)。


    我发现这一切都很有帮助。巴西有一些特殊问题,南半球,有时狂欢节与秋季更改日期重叠。

    在这些情况下,立法机关将DST延迟一周。美国海军天文台计算可以找到复活节,(http://aa.usno.navy.mil/faq/docs/easter.php,检索1/3/2017),嘉年华是一个确切的天数,星期三之前的周末(狂欢节意味着"胖子星期二")。

    所以,在C:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
        static const uint8_t carnival[] = {
        0x04, 0x24, 0x24, 0x21, // 2000... 2031
        0x01, 0x09, 0x48, 0x09, // 2032... 2063
        0x4a, 0x40, 0x4a, 0x52, // 2064... 2095
        0x02, 0x90, 0x12, 0x94  // 2096... 2127
        }

    /* Returns the current time offset. */

    int dst(struct tm *tm_ptr)
    {

        int st = 0;
        int dst = 60;
        int mon = tm_ptr->tm_mon;
        int mday, previous_sunday;
        int gmt_offset = tm_ptr->gmt_offset;

        // If not Brasilia or Amazon time, no DST.
        if(gmt_offset != -240 && gmt_offset != -300)
            return st;

        if(NOV < mon || FEB > mon) // Summer?
            return dst;
        else if(NOV > mon && FEB < mon) // Winter?
            return st;

        mday = tm_ptr->tm_mday;

        previous_sunday = mday - tm_ptr->tm_wday;
        // Begin DST on first Sunday of November.
        if(NOV == mon) // If it's November... i.e. spring, so forward
        {
            if(previous_sunday < 1) // Before Sunday, week 1?
            {
                return st;
            } else { // After or during Sunday, week 1
                return dst;
            }
            // End DST in February, accounting for Carnival.
        } else { // It has to be February, i.e. fall, so backward.
            int year, week_start;

            year = tm_ptr->tm_year;
            if(0 == (carnival[year/8] & (1 << (year%8))))
                week_start = 15; // 3rd Sunday is not in Carnival.
            else
                week_start = 22; // Use 4th Sunday, 1 week after Carnival.

            if(previous_sunday < (week_start-1))
                return dst;
            if(previous_sunday < week_start) {
                if(tm_ptr->tm_isdst == st) // if it already fell backward, stay.
                    return st;
                return dst;
            }
            // On or after the correct Sunday?
            return st;
        }
    }

    此代码使用mktime来获取一周中的某一天。它使用一周中的某一天来计算夏令时。如果您不想使用mktime,可以使用程序second_sunday。从2007年3月14日开始,即星期三。一周中的每一天将每年提前1天,并且在2004年之后的每一次飞跃将提前2天。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    #include <stdio.h>
    #include <string.h>
    #include <time.h>
    #include <sys/timeb.h>


    int isDst(int month, int dayOfMonth, int hour, int dayOfWeek);

    int main(int argc, char *argv[])
    {
       int isdst, dayOfWeek;
       char buf[80];
       struct tm tmData;

       if( argc == 1 )
       {
          printf("
    syntax: %s mm/dd/yyyy_hh:mm:00", argv[0]);
          return -1;
       }

       // 0123456789A12
       // 03/12/2018_12
       strcpy(buf, argv[1]);
       tmData.tm_mon   = atoi(&buf[0]) - 1;  //month -1
       tmData.tm_mday  = atoi(&buf[3]); //day of month
       tmData.tm_year  = atoi(&buf[6]) - 1900; // year - 1900
       tmData.tm_hour  = atoi(&buf[11]); // hour
       tmData.tm_min   = 0; //minutes (not used)
       tmData.tm_sec   = 0; //seconds (not used)
       //tmData.tm_min   = atoi(&buf[14]);
       //tmData.tm_sec   = atoi(&buf[27]);

       //day light saving time variable.
       //NOT used in this calculation.
       //Tells mktime the input date is in day light saving time
       tmData.tm_isdst = 0; //

       mktime(&tmData);
       dayOfWeek = tmData.tm_wday;

       printf("%02d/%02d/%2d_%02d dayWk=%d",
          tmData.tm_mon+1, tmData.tm_mday, tmData.tm_year, tmData.tm_hour, dayOfWeek);
       isdst = isDst(tmData.tm_mon+1, tmData.tm_mday, tmData.tm_hour, dayOfWeek);
       printf("isdst=%d
    ", isdst);

       return 0;
    }


    int isDst(int month, int dayOfMonth, int hour, int dayOfWeek)
    {
       int second_sunday, first_sunday;

       if( month  > 3 && month < 11  ) return 1; //4,5,6,7,8,9,10
       if( month  < 3 || month == 12 ) return 0; //1, 2 or 12
       if( month == 3 )
       {
          //The 2nd Sunday in March is 8,9,10,11,12,13,14
          if( dayOfMonth < 8  ) return 0;
          if( dayOfMonth > 14 ) return 1;

          //To get here dayOfMonth >= 8 && dayOfMonth <= 14
          second_sunday = dayOfMonth - dayOfWeek;
          if( second_sunday < 8 ) second_sunday += 7;
    printf("2nd_Sunday=%2d", second_sunday);
          if( dayOfMonth > second_sunday ) return 1;
          if( dayOfMonth < second_sunday ) return 0;

          //To get here dayOfMonth = second_sunday
          if( hour >= 2 ) return 1;
          else return 0;
       }

       if( month == 11 )
       {
          //The 1st Sunday in Nov is 1,2,3,4,5,6,7
          if( dayOfMonth > 7 ) return 0;

          //To get here dayOfMonth >= 1 && dayOfMonth <= 7
          first_sunday = dayOfMonth - dayOfWeek;
          if( first_sunday < 1 ) first_sunday += 7;
    printf("1st_Sunday=%2d", first_sunday);
          if( dayOfMonth > first_sunday ) return 0;
          if( dayOfMonth < first_sunday ) return 1;

          //To get here dayOfMonth = first_sunday
          if( hour >= 2 ) return 0;
          else return 1;
       }
       return -1;
    }
    /**************
       Compile via   cl.exe  isDst.c

       Begin and End dates for day light saving time
       03/11/2007_01:00:00    11/04/2007_01:00:00
       03/09/2008_01:00:00    11/02/2008_01:00:00
       03/08/2009_01:00:00    11/01/2009_01:00:00
       03/14/2010_01:00:00    11/07/2010_01:00:00
       03/13/2011_01:00:00    11/06/2011_01:00:00
       03/11/2012_01:00:00    11/04/2012_01:00:00
       03/10/2013_01:00:00    11/03/2013_01:00:00
       03/09/2014_01:00:00    11/02/2014_01:00:00
       03/08/2015_01:00:00    11/01/2015_01:00:00
       03/13/2016_01:00:00    11/06/2016_01:00:00
       03/12/2017_01:00:00    11/05/2017_01:00:00
       03/11/2018_01:00:00    11/04/2018_01:00:00
       03/10/2019_01:00:00    11/03/2019_01:00:00
       03/08/2020_01:00:00    11/01/2020_01:00:00
       03/14/2021_01:00:00    11/07/2021_01:00:00
       03/13/2022_01:00:00    11/06/2022_01:00:00
       03/12/2023_01:00:00    11/05/2023_01:00:00
       03/10/2024_01:00:00    11/03/2024_01:00:00
       03/09/2025_01:00:00    11/02/2025_01:00:00
       03/08/2026_01:00:00    11/01/2026_01:00:00
       03/14/2027_01:00:00    11/07/2027_01:00:00
       03/12/2028_01:00:00    11/05/2028_01:00:00
       03/11/2029_01:00:00    11/04/2029_01:00:00
       03/10/2030_01:00:00    11/03/2030_01:00:00
       03/09/2031_01:00:00    11/02/2031_01:00:00
       03/14/2032_01:00:00    11/07/2032_01:00:00

       isDst.exe 03/11/2007_02:00:00  >> dst.txt
       isDst.exe 03/09/2008_02:00:00  >> dst.txt
       isDst.exe 03/08/2009_02:00:00  >> dst.txt
       isDst.exe 03/14/2010_02:00:00  >> dst.txt
       isDst.exe 03/13/2011_02:00:00  >> dst.txt
       isDst.exe 03/11/2012_02:00:00  >> dst.txt
       isDst.exe 03/10/2013_02:00:00  >> dst.txt
       isDst.exe 03/09/2014_02:00:00  >> dst.txt
       isDst.exe 03/08/2015_02:00:00  >> dst.txt
       isDst.exe 03/13/2016_02:00:00  >> dst.txt
       isDst.exe 03/12/2017_02:00:00  >> dst.txt
       isDst.exe 03/11/2018_02:00:00  >> dst.txt
       isDst.exe 03/10/2019_02:00:00  >> dst.txt
       isDst.exe 03/08/2020_02:00:00  >> dst.txt
       isDst.exe 03/14/2021_02:00:00  >> dst.txt
       isDst.exe 03/13/2022_02:00:00  >> dst.txt
       isDst.exe 03/12/2023_02:00:00  >> dst.txt
       isDst.exe 03/10/2024_02:00:00  >> dst.txt
       isDst.exe 03/09/2025_02:00:00  >> dst.txt
       isDst.exe 03/08/2026_02:00:00  >> dst.txt
       isDst.exe 03/14/2027_02:00:00  >> dst.txt
       isDst.exe 03/12/2028_02:00:00  >> dst.txt
       isDst.exe 03/11/2029_02:00:00  >> dst.txt
       isDst.exe 03/10/2030_02:00:00  >> dst.txt
       isDst.exe 03/09/2031_02:00:00  >> dst.txt
       isDst.exe 03/14/2032_02:00:00  >> dst.txt
       isDst.exe 11/04/2007_02:00:00  >> dst.txt
       isDst.exe 11/02/2008_02:00:00  >> dst.txt
       isDst.exe 11/01/2009_02:00:00  >> dst.txt
       isDst.exe 11/07/2010_02:00:00  >> dst.txt
       isDst.exe 11/06/2011_02:00:00  >> dst.txt
       isDst.exe 11/04/2012_02:00:00  >> dst.txt
       isDst.exe 11/03/2013_02:00:00  >> dst.txt
       isDst.exe 11/02/2014_02:00:00  >> dst.txt
       isDst.exe 11/01/2015_02:00:00  >> dst.txt
       isDst.exe 11/06/2016_02:00:00  >> dst.txt
       isDst.exe 11/05/2017_02:00:00  >> dst.txt
       isDst.exe 11/04/2018_02:00:00  >> dst.txt
       isDst.exe 11/03/2019_02:00:00  >> dst.txt
       isDst.exe 11/01/2020_02:00:00  >> dst.txt
       isDst.exe 11/07/2021_02:00:00  >> dst.txt
       isDst.exe 11/06/2022_02:00:00  >> dst.txt
       isDst.exe 11/05/2023_02:00:00  >> dst.txt
       isDst.exe 11/03/2024_02:00:00  >> dst.txt
       isDst.exe 11/02/2025_02:00:00  >> dst.txt
       isDst.exe 11/01/2026_02:00:00  >> dst.txt
       isDst.exe 11/07/2027_02:00:00  >> dst.txt
       isDst.exe 11/05/2028_02:00:00  >> dst.txt
       isDst.exe 11/04/2029_02:00:00  >> dst.txt
       isDst.exe 11/03/2030_02:00:00  >> dst.txt
       isDst.exe 11/02/2031_02:00:00  >> dst.txt
       isDst.exe 11/07/2032_02:00:00  >> dst.txt
       https://stackoverflow.com/questions/5590429/calculating-daylight-saving-time-from-only-date
    ***************/



    /*****
    The previous programs used mktime to compute day_of_week.
    It used day_of_week to compute 2nd_sunday in march and
    1st_sunday in Nov.
    If you don't want to use mktime, you can use this program to
    compute 2nd_sunday.  The same technique will compute 1st_sunday.

    On 03/14/2007, the day of the week is Wed, or 3.
    Every year after 2007, the day of the week advances 1 day.
    on leap years, the day of the week advances 2 days.
    Must include the no. of leap years sinc 2004.
    ******/

    #include <stdio.h>
    #include <string.h>
    #include <time.h>

    int secondSunday(year);

    int main(int argc, char *argv[])
    {
       int year, second_sunday;

       if( argc == 1 )
       {
          printf("
    syntax: %s year, with year >= 2007.
    ", argv[0]);
          return -1;
       }

       year = atoi(argv[1]);
       if( year < 2007 )
       {
          printf("
    syntax: %s year, with year >= 2007.
    ", argv[0]);
          return -1;
       }

       second_sunday = secondSunday(year);
       printf("second_sunday=%d
    ", second_sunday);

       return 0;
    }


    int secondSunday(year)
    {
       //On 03/14/2007, the day of the week is Wed, or 3.
       int no_years, no_leaps, day_of_week, second_sunday;

       no_years = year - 2007;
       no_leaps = (year - 2004)/4;
       day_of_week = 3 + (no_years + no_leaps) % 7;
       second_sunday = 14 - day_of_week;
       if( second_sunday < 8 ) second_sunday += 7;

       //printf("no_years=%d,no_leaps=%d,day_of_week=%d, second_sunday=%d
    ",
       //no_years, no_leaps, day_of_week, second_sunday);

       return second_sunday;
    }
    /**************
       Compile via   cl.exe  second_sunday.c

       second_sunday.exe 2007
       second_sunday.exe 2008
       second_sunday.exe 2009
       second_sunday.exe 2010
       second_sunday.exe 2011
       second_sunday.exe 2012
       second_sunday.exe 2013
       second_sunday.exe 2014
       second_sunday.exe 2015
       second_sunday.exe 2016
       second_sunday.exe 2017
       second_sunday.exe 2018
       second_sunday.exe 2019
       second_sunday.exe 2020
       second_sunday.exe 2021
       second_sunday.exe 2022
       second_sunday.exe 2023
       second_sunday.exe 2024
       second_sunday.exe 2025
       second_sunday.exe 2026
       second_sunday.exe 2027
       second_sunday.exe 2028
       second_sunday.exe 2029
       second_sunday.exe 2030
       second_sunday.exe 2031
       second_sunday.exe 2032
    ***************/

    这是我的答案,我欢迎任何更正。假设这些年份在2000年至2099年之间。通过参考链接提供更多详细信息。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
      int timezone = 0;  // Set to correct initial value depending on where you are (or via GPS if you like).
      // Calculate day of week for Daylight savings time.
      int day_of_week = (day_of_month + int(2.6 * (((month + 12 - 3) % 12) + 1) - 0.2) - 40 +
                  (month < 3 ? year-1 : year) + int((month < 3 ? year-1 : year)/4) + 5) % 7;
      // Adjust timezone based on Daylight savings time for northern hemisphere, USA
      if ((month  >  3 && month < 11 ) ||
          (month ==  3 && day_of_month >= 8 && day_of_week == 0 && hour >= 2) ||  // DST starts 2nd Sunday of March;  2am
          (month == 11 && day_of_month <  8 && day_of_week >  0) ||
          (month == 11 && day_of_month <  8 && day_of_week == 0 && hour < 2)) {   // DST ends 1st Sunday of November; 2am
        timezone++;
      }

    星期几计算参考:如何确定星期几,给定月,日和年
    DST测试参考是通过本文作为回答,由captncraig和我自己的推理和解释他的答案。


    我正在尝试这种方法,我认为它简单而准确:

    //对于3月的第一个星期日,好像DoW = 1表示周日
    if(月= = 3&amp;&amp; day> = 8&amp;&amp; day <= 14&amp;&amp; DoW = 1)返回True

    //对于11月的第二个星期日,好像DoW = 1表示周日
    if(月== 11&amp;&amp; day> = 1&amp;&amp; day <= 7&amp;&amp; DoW = 1)返回True


    3月14日和11月7日总是美国一天节约光线的一周的一部分......它们可能是星期日或星期六或星期几,但它们总是那一周的一部分。下面的代码将找到星期日,即这两个日期是相关日期发生的年份的一部分。然后它会在此日期增加2小时以获得实际发生夏令时的时间。然后,您将有问题的日期与夏令时开始日期和结束日期进行比较,并通过gmt偏移量调整时间。此过程适用于其他国家/地区的开始和结束日期。您可以设置一个包含每个国家/地区代码和邮政编码的表格,其中包括夏令时结束和开始日期以及两个期间的gmt偏移量。日期是一个月的第一个到第四个星期日的第7天,第14天,第21天和第28天。你会在9月30日或10月31日之前的最后一个星期日放入最长的一天。

    3月的第2个星期天:

    1
    cdate("3/14/" & format(now(),"yyyy"))-format(cdate("3/14/" & format(now(),"yyyy")),"W")+1+2/24

    11月1日星期日:

    1
    cdate("11/7/" & format(now(),"yyyy"))-format(cdate("11/7/" & format(now(),"yyyy")),"W")+1+2/24

    防爆。

    1
    If(now() < cdate("3/14/" & format(now(),"yyyy"))-format(cdate("3/14/" & format(now(),"yyyy")),"W")+1+2/24, dateadd("H",-5,now()), if(now() < cdate("11/7/" & format(now(),"yyyy"))-format(cdate("11/7/" & format(now(),"yyyy")),"W")+1+2/24, dateadd("H",-6,now()), dateadd("H",-5,now())))

    t_SQL示例

    情况[date2check]