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

Я ищу стандарты для добавления даты/времени. Я не смог найти никого. В частности, я надеюсь найти спецификацию, которая определяет, что должно произойти, когда вы добавляете месяц к дате, подобной 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

Oracle

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

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

Java

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

Ответы

Ответ 1

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

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

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

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

Ссылки:

Ответ 2

Я считаю, что стандартом 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. Я удивлен, что больше внимания не уделялось этой проблеме, поскольку это действительно важно, иногда критично, во многих приложениях. Спасибо за вопрос!

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

Ответ 3

Query:

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';

Вывод:

    Dec + Month  Jan + Month  Feb + Month   Mar + Month
    2011-01-31   2011-02-28   2011-03-28    2011-04-30

Мой вывод:

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

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

источник

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

Ответ 4

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 обрабатывает эту здесь.

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

Ответ 5

Первый день месяца + 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 год - високосный год).

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

Ответ 6

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

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

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

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

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

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

Ответ 7

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

SELECT ADDDATE ('2011-01-31', INTERVAL 1 MONTH)//2011-02-28

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

SELECT ADDDATE ('2012-01-31', INTERVAL 1 MONTH)//2012-02-29

Ответ 8

В среде .NET поведение 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
    */