仅从日期计算夏令时
Posted
技术标签:
【中文标题】仅从日期计算夏令时【英文标题】:Calculating daylight saving time from only date 【发布时间】:2011-08-01 05:13:16 【问题描述】:我正在使用 Arduino 和实时时钟芯片。该芯片补偿闰年等,所以它总是有正确的日期,但它不处理夏令时,我认为由于地区的复杂性。时钟可以给我日期、月份和年份(从 1 开始)和星期几(星期日 = 0 到星期六 = 6)。
因为我需要与用户输入的日期和时间进行比较,所以我需要知道为夏令时调整的日期和时间。如果当前日期是夏令时,我可以简单地从时钟时间增加一个小时,我就有了我需要的东西。
困难的部分是确定我是否处于夏令时,因为它每年都在变化。我只关心它在我的位置(山地时间)是否有效。我的平台似乎没有任何全面的日期库,而且我觉得无论如何这都会是矫枉过正。是否有一个简单的公式可以确定我是否在夏令时?
【问题讨论】:
【参考方案1】:这实际上看起来很简单。有一些事实可以帮助我们:
-
在美国大部分地区,夏令时从 3 月的第二个星期日开始,到 11 月的第一个星期日结束,两次都是凌晨 2 点。
3 月的第二个星期日将始终介于 8 日和 14 日之间。
11 月的第一个星期日将始终介于 1 日和 7 日之间。
星期编号非常方便,因为星期几将给您上一个星期日。
这些事实导致以下代码(C#,但可以轻松移植到您的平台):
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 月)之间。
【讨论】:
如果该代码在 1800 到 2006 年的所有日期中确实与TimeZone.IsDayLightSavingsTime()
一致,则 TimeZone.IsDayLightSavingsTime()
已损坏。直到 1966 年(除了两次世界大战期间的某些时期),美国甚至在全国范围内都不存在夏令时。直到 2007 年,它从 4 月的第一个星期日开始,到 10 月的最后一个星期日结束,除了 1974 年至 1975 年期间全年都有夏令时。
文档说它不是历史性的。它仅适用于当前文化的规则。还有另一个工具可以获取准确的历史数据。
我认为您的一张支票中可能存在不合格的情况。如果 3 月的第二个星期日是 8 号,那么我们在 8 号星期日是 DST。 8-1 将是 7。那么,假设星期天是 DOW=1,支票不应该是 previousSunday >= 7
吗?如果 Sunday DOW 为 0,那么 11 月的支票将是 previousSunday <= 1;
,对吧?
@dannysauer 很好,但我认为 dow
的值是从 0-6 而不是从 1-7 才能正常工作。
请不要犯我犯的同样愚蠢的错误,并传递一个 C 风格的 struct tm
tm_mon
成员作为月份; struct tm
成员都是从零开始的,除了tm_mday
(一个月中的某天)。如果你这样做,当夏令时真正到来时,你将有一个月的假期。【参考方案2】:
中欧代码(针对 2014-3000 年范围内的每一天进行测试)
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
测试功能
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();
【讨论】:
【参考方案3】:虽然根据当前规则很容易计算特定日期是否在特定地点的夏令时,但请注意,夏令时是政客的心血来潮,随时可能发生变化。我有一个 2007 年之前制造的时钟,可以自动调整夏令时,现在我必须每年更改 四次:实际更改发生时两次,现在更改不正确时两次自己在旧日期。
在这种情况下,您可以通过让用户输入时区以及日期和时间的简单权宜之计来完全忽略 DST。或者您可以像大多数消费类设备一样,让用户每年两次将时间调整到当地时区。
但如果你真的需要处理 DST 并且真的想把事情做好,请使用zoneinfo database 并确保它可以以某种方式进行更新。如果由于某种原因您不能这样做,至少允许用户覆盖规则。如果这太难了,至少让用户可以选择关闭自动调整(不像我愚蠢的闹钟)。
【讨论】:
请注意,我正在使用嵌入式平台,因此内存非常宝贵。该数据库比我没有硬件扩展的可用内存大几个数量级。这个项目是单一安装的,所以我真的只关心它在一个时区内保持准确。如果 DST 规则发生变化,我可以重新编程,但如果可以避免的话,我宁愿不要每年重新编程两次以手动调整时间。【参考方案4】:此代码使用 mktime 来获取一周中的某一天。它使用一周中的一天来计算夏令时。如果不想使用 mktime,可以使用程序 second_sunday。从 2007 年 3 月 14 日开始,也就是星期三。星期几将每年提前 1 天,2004 年之后的每个闰年将提前 2 天。
#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("\nsyntax: %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\n", 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://***.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("\nsyntax: %s year, with year >= 2007.\n", argv[0]);
return -1;
year = atoi(argv[1]);
if( year < 2007 )
printf("\nsyntax: %s year, with year >= 2007.\n", argv[0]);
return -1;
second_sunday = secondSunday(year);
printf("second_sunday=%d\n", 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\n",
//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
***************/
【讨论】:
【参考方案5】:3 月 14 日和 11 月 7 日始终是美国实行夏令时的一周中的一部分。它们可能是星期日或星期六,或者介于两者之间的一周中的某一天,但它们始终是其中的一部分星期。下面的代码将找到这两个日期是相关日期所在年份的一部分的星期日。然后,它会在该日期上增加 2 小时,以获得夏令时实际发生的时间。然后,您将相关日期与夏令时开始日期和结束日期进行比较,并通过 gmt 偏移量调整时间。此过程适用于其他国家/地区的开始和结束日期。您可以设置一个表格,其中包含每个国家/地区代码和邮政编码,以及夏令时结束和开始日期以及两个时期的 gmt 偏移量。日期将是每月第一个到第四个星期日的 7 日、14 日、21 日和 28 日。您将在 9 月 30 日或 10 月 31 日之前的当月最后一个星期日输入最大天数。
3 月的第二个星期日:
cdate("3/14/" & format(now(),"yyyy"))-format(cdate("3/14/" & format(now(),"yyyy")),"W")+1+2/24
11 月的第一个星期日:
cdate("11/7/" & format(now(),"yyyy"))-format(cdate("11/7/" & format(now(),"yyyy")),"W")+1+2/24
例如
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 示例
CASE WHEN [date2check]
【讨论】:
请添加一些文字来描述这里发生的事情。谢谢! 您应该将解释放在答案本身中。此外,您可能希望格式化您的最后一个代码 sn-p 使其可读性模糊。【参考方案6】:我正在尝试这种方法,我认为它简单而准确:
// 对于 3 月的第一个星期日,好像 DoW = 1 代表星期日 if (month==3 && day>=8 && day
// 11 月的第二个星期日,好像 DoW = 1 代表星期日 if (month==11 && day>=1 && day
【讨论】:
【参考方案7】:这是我的回答,欢迎大家指正。假设年份介于 2000 年和 2099 年之间。更多详细信息可通过参考链接获得。
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++;
星期计算参考:How to determine the day of the week, given the month, day and year DST 测试参考是通过captncraig 回答的这篇文章以及我自己对他的回答的推理和解释。
【讨论】:
【参考方案8】:我发现这一切都非常有帮助。巴西有一些特殊问题,南半球,有时狂欢节与秋季变化日期重叠。
在这些情况下,立法机构会将夏令时推迟一周。美国海军天文台计算可以找到复活节,(http://aa.usno.navy.mil/faq/docs/easter.php,检索 2017 年 1 月 3 日),狂欢节是准确的天数,即灰烬星期三之前的周末(狂欢节的意思是“胖星期二”)。
所以,在 C 中:
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;
【讨论】:
【参考方案9】:如果有人在 python 中寻找这个,这里有一篇很棒的文章: http://code.activestate.com/recipes/425607-findng-the-xth-day-in-a-month/
它包含以下代码:
from calendar import monthrange
def dow_date_finder(which_weekday_in_month=FIRST,day=MONDAY,month=JANUARY,year=2000):
bom, days = monthrange(year, month)
firstmatch = (day - bom) % 7 + 1
return xrange(firstmatch, days+1, 7)[which_weekday_in_month]
请记住,您需要在上面链接的页面顶部显示所有变量,以使其更加用户友好。
以下内容适用于 2007 年或以后的任何一年:
def find_dt_of_daylight_savings_time(yr=date.today().year):
dst =
spring = dow_date_finder(SECOND, SUNDAY, MARCH, yr)
spring_dt = datetime(int(yr), 3, spring, 3, 0)
dst['spring'] = spring_dt
fall = dow_date_finder(FIRST, SUNDAY, NOVEMBER, yr)
fall_dt = datetime(int(yr), 11, fall, 3, 0)
dst['fall'] = fall_dt
return dst
【讨论】:
以上是关于仅从日期计算夏令时的主要内容,如果未能解决你的问题,请参考以下文章