Расчет перехода на летнее время только с даты - PullRequest
17 голосов
/ 08 апреля 2011

Я работаю с Arduino и чипом часов реального времени. Чип компенсирует високосные годы и т. Д., Поэтому он всегда будет иметь правильную дату, но он не поддерживает летнее время, я полагаю, из-за региональных сложностей. Часы могут показывать мне день, месяц и год (на основе 1) и день недели (с воскресенья = 0 до субботы = 6).

Поскольку мне нужно сравнивать введенные пользователем даты и время, мне нужно знать дату и время, скорректированные на летнее время. Если текущая дата относится к летнему времени, я могу просто добавить время к часам на часах, и у меня есть то, что мне нужно.

Сложнее всего определить, нахожусь ли я в летнее время или нет, потому что оно меняется из года в год. Меня волнует только то, что это работает в моем месте (Mountain Time). Похоже, что для моей платформы нет исчерпывающих библиотек дат, и я чувствую, что в любом случае это будет излишним. Есть ли простая формула, чтобы определить, нахожусь ли я в DST или нет?

Ответы [ 8 ]

40 голосов
/ 08 апреля 2011

Это на самом деле обманчиво просто. Есть несколько фактов, которые помогут нам:

  1. В большинстве США летнее время начинается во второе воскресенье марта и заканчивается в первое воскресенье ноября, в 2 часа ночи оба раза.
  2. Второе воскресенье марта всегда будет между 8 и 14 включительно.
  3. Первое воскресенье ноября всегда будет между 1 и 7 включительно.
  4. Нумерация дней недели очень удобна, потому что день - день недели даст вам предыдущее воскресенье.

Эти факты приводят к следующему коду (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;
    }

Оказывается, вам даже не нужно знать год, чтобы сделать это, если вы можете доверять значению дня недели.

Я написал быстрый модульный тест и убедился, что этот код соответствует TimeZone.IsDayLightSavingsTime() для всех дат с 1800 до 2200. Я не учел правило 2 часов утра, но вы могли бы легко сделать эту проверку, если день недели Воскресенье и дата между 8 и 14 (в марте) или 1 и 7 (в ноябре).

11 голосов
/ 31 марта 2014

Код для Центральной Европы (тестируется на каждый день в диапазоне 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();

}

2 голосов
/ 08 апреля 2011

Несмотря на то, что легко подсчитать, находится ли конкретная дата в летнее время для определенного местоположения в соответствии с текущими правилами, учтите, что летнее время находится на прихоти политиков и может измениться в любой момент.У меня есть часы, выпущенные до 2007 года, которые автоматически настраиваются на летнее время, и теперь я должен менять их четыре раза в год: дважды, когда происходит реальное изменение, и дважды, когда оно сейчас - неправильно меняет себя.по старым датам.

В этом случае вы можете полностью игнорировать летнее время, просто введя пользовательский часовой пояс вместе с датой и временем.Или вы можете сделать то же самое, что и большинство потребительских устройств, и позволить пользователю настраивать время в соответствии с местным часовым поясом два раза в год.

Но если вам действительно нужно обрабатывать летнее время и вы действительно хотите делать все правильно, используйте zoneinfo database и убедитесь, что она может быть обновлена ​​каким-либо образом.Если вы не можете сделать это по какой-то причине, по крайней мере, позвольте пользователю переопределить правила.И если даже это слишком сложно, по крайней мере, дайте пользователю возможность отключить автоматические настройки (в отличие от моего глупого будильника).

0 голосов
/ 08 июля 2019

Я нашел это все очень полезным.У Бразилии есть некоторые особые проблемы, южное полушарие, и иногда Карнавал частично совпадает с датой смены осени.

В этих случаях законодательный орган откладывает ТЛЧ на одну неделю.Расчет обсерватории ВМС США может найти Пасху (в http://aa.usno.navy.mil/faq/docs/easter.php,, полученную 1/3/2017), а карнавал - это точное число дней раньше, в выходные перед пепельной средой (Mardi Gras означает «толстый вторник»).).

Итак, в 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;
    }
}
0 голосов
/ 02 апреля 2018

этот код использует mktime, чтобы получить день недели.Он использовал день недели для расчета летнего времени.Если вы не хотите использовать mktime, вы можете использовать программу second_sunday.Начните с 14.03.2007, которая является средой.День недели продвигается на 1 день для каждого года и 2 дня для каждого скачка после 2004 года.

#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
   /8016671/raschet-perehoda-na-letnee-vremya-tolko-s-daty
***************/



/*****
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
***************/
0 голосов
/ 03 января 2016

Вот мой ответ, и я приветствую любые исправления. Предполагается, что годы между 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++;
  }

Ссылка для расчета дня недели: Как определить день недели с учетом месяца, дня и года
Ссылка на тест DST приведена в этой статье, на которую ответил captncraig и мои собственные аргументы и интерпретация его ответа.

0 голосов
/ 10 ноября 2015

Я пытаюсь использовать этот подход и считаю его простым и точным:

// для первого воскресенья марта, как будто DoW = 1 для воскресенья if (month == 3 && day> = 8 && day<= 14 && DoW = 1) вернуть True </p>

// для второго воскресенья ноября, как если бы DoW = 1 для воскресенья if (month == 11 && day> = 1 && day <= 7 && DoW = 1) вернуть True </p>

0 голосов
/ 30 сентября 2015

14 марта и 7 ноября всегда являются частью недели, когда в Соединенных Штатах происходит переход на летнее время. Они могут быть воскресеньем, субботой или днем ​​недели, но они всегда являются частью этого.неделю.Приведенный ниже код будет определять воскресенье, в котором эти две даты являются частью года, в котором происходит указанная дата. Затем к этой дате добавляется 2 часа, чтобы получить время, в которое фактически происходит переход на летнее время.Затем вы сравниваете соответствующую дату с датами начала и окончания перехода на летнее время и корректируете время по смещению gmt.Этот процесс может работать для других стран даты начала и окончания.Вы можете настроить таблицу, в которой есть код страны и почтовый индекс, а также даты окончания и начала летнего времени и смещение gmt для обоих периодов.даты будут 7-го, 14-го, 21-го и 28-го числа с первого по четвертое воскресенье месяца.вы бы указали максимальный день в последнее воскресенье месяца, например, 30 сентября или 31 октября.

2-е воскресенье марта:

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

1-е воскресенье ноября:

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

СЛУЧАЙ, КОГДА [date2check] <DATEADD (чч, 2, CAST ('3/14 /' + CAST (DATEPART (гггг, [date2check]) как AS nvarchar (4)) как дата / время) + 1 - DATEPART (w, CAST('3/14 /' + CAST (DATEPART (гггг, [date2check]) AS nvarchar (4)) AS datetime))) THEN dateadd (чч, - DST_GMT_TM_ZN_DIFF, [date2check]) Иначе CASE WHEN [date2check] <DATEADD (чч, 2, CAST ('11 / 7 / '+ CAST (DATEPART (гггг, [date2check]) как AS nvarchar (4)) AS datetime) + 1 - DATEPART (w, CAST ('11 / 7 /' + CAST (DATEPART (гггг, [date2check]) AS nvarchar (4)) AS datetime))) THEN dateadd (чч, - STD_GMT_TM_ZN_DIFF, [date2check]) ELSE dateadd (чч, - DST_GMT_TM_ZN_DIFF]] * date2 [date]

...