Re: Excel 29.02.1900 и .NET/C#
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 01.07.22 17:34
Оценка: 103 (3)
Здравствуйте, fortnum, Вы писали:

F>Как можно, не изобретая велосипед, в C# преобразовать такую дату в соответствующую строку, используя множество всех уже доступных строк форматирования?

Вы прям заинтриговали... Спасибо, оказалось очень интересным, заставило покопаться...

В общем, что у меня получилось...
Да, вы абсолютно правы, смотря в сторону календарей.
В целом, DateTime — это просто число тиков от условной стартовой точки.
А вот в Calendar (ну и его потомки), должны отвечать за то:
— что это за точка,
— как из тиков получать год, день, месяц, эру, часы, минуты, ... (и т.д. — что там заложено в каждой системе лето/время исчисления)
— и наоборот — из элементов даты/времени конкретной системы получать тики
— как выполнять всевозможные операции над датами (например, прибавить X дней скорее всего будет везде одинаково, т.к. дни имеют фиксированную продолжительность, а вот прибавить Y месяцев в том же григорианском календаре зависит от конкретной даты, т.к. месяцы нужно перевести в дни с учетом разной длины месяцев в году, а также с учетом високосных лет, ...)
— ...

Всё выглядит довольно красиво:
— получаешь дату в определенном календаре (с его трактовкой, что есть, например 1.01.1987 20:23:00 например в том же григорианском)
— все манипуляции выполняешь с нужным тебе календарем (например, в юлианском)
— и под конец выводишь или в том календаре, в котором манипулировал (в нашем случае — юлианском) или в том, котором нужно тебе (получая автоматом перевод в нужный календарь)

Но(!!!!) весь .Net "захардкожен" на использование григорианского календаря.
В частности:
— если вы создаете DateTime без явного указания календаря — он создается по грегорианскому.
— все операции в самом DateTime делаются без учета календаря, т.е. строго по григорианскому. Хочешь другой календарь — вызывай явно его методы.
— все объекты глобализации, связанные с датой и временем (DateTimeFormatInfo, сам CultureInfo, ...), по-умолчанию используют григорианский календарь и не допускают (по крайней мере, через публичный интерфейс) никаких других календарей (например, у того же DateTimeFormatInfo свойство Calendar имеет и getter, и setter но в setter идет проверка, что вы выставляете календарь из списка разрешенных для той культуры, с которой связан текущий DateTimeFormatInfo — и всё, приехали). Т.е. без хаков не обойдешься

Поэтому ваша задача по идее сводится к 2-м:
— создать такой календарь, который будет работать как привычный нам григорианский, но допускать дату 29.02.1900
— заставить .Net окружение работать с этим календарем.

Давайте начнем с конца — как подсунуть свой календарь (например, попробуем везде подсовывать юлианский календарь).

Создание:
var calendar = new JulianCalendar();         

var date1 = new DateTime(1900, 2, 29, calendar);
var date2 = calendar.ToDateTime(1900, 2, 29, 0, 0, 0, 0);

Console.WriteLine(date1 == date2); // True


Модификация (как видите, по юлианскому календарю 1900 — високосный, поэтому результат прибавления (а мы при печати возвращаемся к григорианскому, который "захордкожен") — на 1 день больше):
var calendar1 = new GregorianCalendar();
var calendar2 = new JulianCalendar();

var date = new DateTime(1900, 1, 31);
var date1 = date.AddYears(1);
var date2 = calendar1.AddYears(date, 1);
var date3 = calendar2.AddYears(date, 1);

Console.WriteLine(date1); // 01/31/1901 00:00:00
Console.WriteLine(date2); // 01/31/1901 00:00:00
Console.WriteLine(date3); // 02/01/1901 00:00:00


Парсинг/Вывод (здесь пришлось обходит setter от DateTimeFormatInfo и напрямую устанавливать приватное поле calendar — после этого, DateTimeFormatInfo можно передавать как IFormatProvider):
var calendar = new JulianCalendar();

var formatInfo = new DateTimeFormatInfo();
var calendarField = formatInfo.GetType().GetField(
    "calendar",
    System.Reflection.BindingFlags.NonPublic
    | System.Reflection.BindingFlags.Instance);
calendarField.SetValue(formatInfo, calendar);

var date = new DateTime(1900, 2, 29, calendar);
Console.WriteLine(date.ToString(formatInfo));

var date1 = DateTime.Parse("02/29/1900", formatInfo);
Console.WriteLine(date1.ToString(formatInfo));
Отредактировано 01.07.2022 17:37 Михаил Романов . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.