Стандарты для добавления даты / времени?

Я ищу стандарты для добавления даты / времени. Я не смог найти никого. В частности, я надеюсь найти спецификацию, которая определяет, что должно произойти, когда вы добавляете месяц к дате, подобной 31 января. Правильный ответ 28 февраля (/ 29)? 1 марта? 2 марта?

Я видел противоречивые реализации между различными инструментами (в этом случае PHP и MySQL), и я пытаюсь найти какие-то стандарты для моей работы.

Разные результаты:

PHP

$end = strtotime("+1 month", 1314835200); //1317513600 Sat, 01 Oct 2011 20:00:00 -0400 

MySQL

 SELECT UNIX_TIMESTAMP(DATE_ADD(FROM_UNIXTIME(1314835200), INTERVAL 1 MONTH)); #1317427200 Fri, 30 Sep 2011 20:00:00 -0400 

оракул

 SELECT ADD_MONTHS('31-Aug-11', 1) FROM dual; #30-SEP-11 

(извините за изменение формата, мой оракул foo слаб)

Ява

 Calendar c = Calendar.getInstance(); c.clear(); c.set( 2011, Calendar.AUGUST, 31 ); c.add( Calendar.MONTH, 1 ); c.getTime() #Fri Sep 30 00:00:00 EDT 2011 

Согласно стандарту POSIX.1-2001, следующий месяц (как при увеличении tm_mon до вызова mktime ) выполняется путем корректировки значений до их соответствия. Так, например, в следующем месяце с 31 января 2001 года – 3 марта 2001 года. Это связано с тем, что tm_mday из 31 недействителен с tm_mon из 1 (февраль), поэтому он нормализуется до tm_mon из 2 (март) и tm_mday из 3.

Следующий месяц с 31 января 2000 года – 2 марта 2000 года, потому что февраль имеет 29 дней в этом году. В следующем месяце с 1 января 2038 года не существует, в зависимости.

Самое замечательное в отношении стандартов – их так много . Проверьте стандарт SQL, я уверен, вы можете найти другое значение следующего месяца. Я подозреваю, что ISO 8601 может дать вам еще один выбор. Дело в том, что существует много разных способов поведения, значение «следующего месяца» очень специфично для домена.

Мне кажется, я нашел, как SQL-92 справляется с этим, по-видимому, запрашивая следующий месяц с 31 января, является ошибкой.

Ссылки:

Я считаю, что стандартом defacto является ISO 8601. К сожалению, существует множество двусмысленностей, например:

Арифметика даты не определена

 2001-03-30 + P1M = 2001-04-29 (Add 30 days) 2001-03-30 + P1M = 2001-04-30 (Add 1 mon.) 

Добавление не является коммутативным или ассоциативным

 2001-03-30 + P1D + P1M = 2001-04-30 2001-03-30 + P1M + P1D = 2001-05-01 

Вычитание не является инверсией добавления.

Точность десятичных дробей может варьироваться.

Полную спецификацию можно найти по адресу http://www.iso.org/iso/catalogue_detail.htm?csnumber=26780.

Я думаю, что каждый продукт пытается придерживаться невозможного внедрения стандарта. Неоднозначные части открыты для интерпретации, поэтому каждый интерпретирует. Это тот же стандарт, который открыл нас до ошибки Y2K!

Я выступаю за реализацию, которая преобразует дату / время в число на основе 1970 года (временная метка UNIX), выполняет вычисления и конвертирует обратно. Я считаю, что это подход Oracle / MySQL. Я удивлен, что больше внимания не уделялось этой проблеме, поскольку это действительно важно, иногда критично, во многих приложениях. Спасибо за вопрос!

Редактирование: делая еще несколько чтений, я нашел мысли Джо Селко о разных представлениях и стандартах даты и времени ЗДЕСЬ .

Запрос:

 SELECT ADDDATE(DATE('2010-12-31'), INTERVAL 1 MONTH) 'Dec + Month', ADDDATE(DATE('2011-01-31'), INTERVAL 1 MONTH) 'Jan + Month', ADDDATE(DATE('2011-02-28'), INTERVAL 1 MONTH) 'Feb + Month', ADDDATE(DATE('2011-03-31'), INTERVAL 1 MONTH) 'Mar + Month'; 

Вывод:

     Дек + Месяц Янв + Месяц Фев + Месяц Мар + Месяц
     2011-01-31 2011-02-28 2011-03-28 2011-04-30

Мой вывод:

  1. Рассчитайте количество дней в месяце входной даты.
  2. Добавьте много дней к дате ввода.
  3. Проверьте, превышает ли день в итоговой дате максимальное количество дней в результирующем месяце.
  4. Если да, то измените итоговый день на максимальный день месяца.

Если вы добавите MONTH, YEAR_MONTH или YEAR, а итоговая дата имеет день, который больше максимального дня для нового месяца, день корректируется до максимальных дней в новом месяце

источник

Проблема здесь в том, что он не упоминает, что месяц – это месяц с даты ввода.

Joda-Time в Java выбирает предыдущую действительную дату, когда создается недопустимый. Например, 2011-01-31 + P1M = 2011-02-28 . Я считаю, что это наиболее широко используемый выбор по умолчанию в библиотеках времени и, следовательно, стандарт де-факто.

ThreeTen / JSR-310 предоставляет стратегический шаблон для этого, с четырьмя вариантами, см. Код .

Более забавным является вопрос о том, какой ответ на 2011-01-31 + P1M-1D . Если вы добавите месяц, то разрешите недействительную дату, а затем вычтите день, вы получите 2011-02-27. Но я думаю, что большинство пользователей ожидают 2011-02-28, потому что период добавляется в единый кусок. Посмотрите, как здесь работает ThreeTen.

Я подумал о том, чтобы попытаться написать передовые практики общей цели в расчетах даты / времени или фактической спецификации, но на самом деле не было времени!

Первый день месяца + 1 месяц должен быть равен первому из следующего месяца. Попытка этого на SQL Server

  SELECT CAST ('01/01/2012' AS DateTime), DATEADD (m, 1, '01/01/2012') UNION ALL SELECT CAST ('02/01/2012' AS DateTime), DATEADD (m, 1, '02/01/2012') UNION ALL SELECT CAST ('03/01/2012' AS DateTime), DATEADD (m, 1, '03/01/2012') UNION ALL SELECT CAST ('04/01/2012' AS DateTime), DATEADD (m, 1, '04/01/2012') UNION ALL SELECT CAST ('05/01/2012' AS DateTime), DATEADD (m, 1, '05/01/2012') 

Это приводит к

 ----------------------- ----------------------- 2012-01-01 2012-02-01 2012-02-01 2012-03-01 2012-03-01 2012-04-01 2012-04-01 2012-05-01 2012-05-01 2012-06-01 

Последний день этого месяца + 1 месяц должен быть равен последнему дню следующего месяца. Это должно продолжаться в следующем месяце, текущем месяце, 10 месяцах и т. Д.

  SELECT CAST ('01/31/2012' AS DateTime), DATEADD (m, 1, '01/31/2012') UNION ALL SELECT CAST ('01/30/2012' AS DateTime), DATEADD (m, 1, '01/30/2012') UNION ALL SELECT CAST ('01/29/2012' AS DateTime), DATEADD (m, 1, '01/29/2012') UNION ALL SELECT CAST ('01/28/2012' AS DateTime), DATEADD (m, 1, '01/28/2012') UNION ALL SELECT CAST ('01/27/2012' AS DateTime), DATEADD (m, 1, '01/27/2012') UNION ALL SELECT CAST ('01/26/2012' AS DateTime), DATEADD (m, 1, '01/26/2012') 

Это приводит к

 ----------------------- ----------------------- 2012-01-31 2012-02-29 2012-01-30 2012-02-29 2012-01-29 2012-02-29 2012-01-28 2012-02-28 2012-01-27 2012-02-27 2012-01-26 2012-02-26 

Посмотрите, как 31, 30, 29 все станут 29 (2012 год – високосный год).

ps Я снял временные части (все нули), чтобы сделать его более читаемым

Нет общепринятого стандарта. Причина различных реализаций заключается в том, что люди не могут согласиться с тем, что должен быть в стандарте. Многие популярные программные системы дают ответы, которых никто не ожидал. Поэтому документация всегда необходима, чтобы сообщить пользователю, что ваша система будет доставлять. Однако вы выбираете методологию, основываясь на том, что, по вашему мнению, ожидает большинство людей.

Я думаю, что большинство людей на улице согласятся с тем, что:

  1. Вы не можете определить «месяц» на определенное количество дней. Так…
  2. Когда 31 января, и кто-то говорит: «Я встречу тебя месяц с сегодняшнего дня», это последний день февраля. В основном, это добавляет к месяцу, а затем найдет номер дня, который ближе всего к сегодняшнему дню, не превышая его.

Исключение составляет бухгалтерский учет, где иногда месяц означает 30 дней.

EDIT: Я спросил некоторых людей здесь: «Если это 31 марта, и кто-то говорит, что я встречу тебя с вами сегодня, в какой день ты собираешься встретиться с ними?» Большинство сказали, 30 апреля, но некоторые сказали 28 апреля, потому что это четыре недели. Некоторые из них интерпретировали графики работы и думали: «Если бы мы встретились в этот будний день, мы встретимся снова в тот же день недели». В принципе, если они встречались в последний четверг месяца, и они должны встретиться через месяц, это будет в последний четверг этого месяца.

Итак, я пойду. : \

Попробуйте функцию даты mysql:

SELECT ADDDATE ('2011-01-31', ИНТЕРВАЛ 1 МЕСЯЦ) // 2011-02-28

Дата ввода в високосный год

SELECT ADDDATE ('2012-01-31', INTERVAL 1 МЕСЯЦ) // 2012-02-29

В .NET Framework поведение System.DateTime.AddMonths выглядит следующим образом:

Метод AddMonths вычисляет итоговый месяц и год, принимая во внимание високосные годы и количество дней в месяце, а затем корректирует дневную часть результирующего объекта DateTime. Если результирующий день не является действительным днем ​​в результирующем месяце, используется последний действительный день месяца. Например, 31 марта + 1 месяц = ​​30 апреля [а не 31 апреля].

Я тестировал, как это работает:

 Console.WriteLine(new DateTime(2008,2,27).AddMonths(1)); Console.WriteLine(new DateTime(2008,2,28).AddMonths(1)); Console.WriteLine(new DateTime(2008,2,29).AddMonths(1)); Console.WriteLine(new DateTime(2011,2,27).AddMonths(1)); Console.WriteLine(new DateTime(2011,2,28).AddMonths(1)); Console.WriteLine(new DateTime(2008,1,30).AddMonths(1)); Console.WriteLine(new DateTime(2008,1,31).AddMonths(1)); Console.WriteLine(new DateTime(2011,1,30).AddMonths(1)); Console.WriteLine(new DateTime(2011,1,31).AddMonths(1)); /* output 3/27/2008 12:00:00 AM 3/28/2008 12:00:00 AM 3/29/2008 12:00:00 AM 3/27/2011 12:00:00 AM 3/28/2011 12:00:00 AM 2/29/2008 12:00:00 AM 2/29/2008 12:00:00 AM 2/28/2011 12:00:00 AM 2/28/2011 12:00:00 AM */