LINUX.ORG.RU

Дата. Нумерация месяцев и недель.

 , , ,


0

1

Во многих языках (Java, JS, C/C++…) если возникает необходимость получить текущую дату, то мы будем иметь что-то типа такой структуры:

struct rtc_time {
    int tm_sec;
    int tm_min;
    int tm_hour;
    int tm_mday;
    int tm_mon;
    int tm_year;
    int tm_wday;     /* не используется */
    int tm_yday;     /* не используется */
    int tm_isdst;    /* не используется */
};

При этом, Январю и воскресенью соответствует число 0. Я задался вопросом: «почему так?». Вопрос этот возник потому, что я знаком со многими RTC контроллерами, в том числе и со встроенными в микроконтроллеры. Смотрите что получается:

man RTC - русский man по /dev/rtc Отсюда узнаем, что:

Все ПК i386 и системы с ACPI содержат RTC, которые совместимы с микросхемой Motorola MC146818 из первоначальной модели PC/AT. Сегодня такие RTC обычно встраивают в чипсет материнской платы (в южный мост), и они используют заменяемую резервную батарею (типа «таблетки»).

Открываем даташит на MC146818, и видим таблицу 3 на странице 12, из которой узнаем, что диапазон месяцев находится в переделе от 1 до 12. То есть MC146818 знает, что Январю соответствует число 1.

Включаем компьютер. На каком-то этапе (на каком?) через BIOS считываются данные из RTC контроллера, складываются в вышеописанную структуру rtc_time, вычисляется epoch, устанавливается системный таймер по вычисленному epoch. И мы имеем текущее время в системе.

Пишем программу на Си(любом языке). Используем time.h. Как я понимаю, при использовании любой функции из библиотеки для получения текущей даты/времени, будет использован системный вызов time, который вернет epoch. Посмотрим на функции для работы со временем. Есть функции, которые преобразуют время в строку для вывода на экран, а есть функция gmtime, которая позволяет получить структуру tm, в которой будет все, что касается времени и даты, но только месяц и день недели будут идти с 0 . Теперь, если мы хотим работать с этим форматом, то мы должны всегда помнить об этом нюансе. Да, работать с массивом удобно: нулевой элемент не пустой, и вроде как экономия памяти. Но очень многие программисты наступают на грабли, когда работают с датой в разных ЯП из-за того, что Январь - нулевой месяц.

Причем, эти грабли кладутся так: RTC -> (JAN(01)->epoch) -> Kernel -> SysCall -> epoch -> App -> JAN(00). И, не дай бог, придется где-то работать с этим Январем (месяцем) в виде числа и отдельно в виде строки (вывод). Кстати, если посмотреть как работает /dev/rtc ,то можно увидеть, что после чтения из RTC контроллера происходит декремент месяца. А при записи в контроллер происходит инкремент. Основной вопрос: зачем сделаны такие мучения над временем? Для совместимости с чем? Почему бы через /dev/rtc не выдавать дату без изменений, считанную с RTC? Ведь я, как электронщик, ожидаю получить от RTC модуля именно то, что есть в даташите.

В итоге, в JS и JAVA часто приходится вертеть этими числами прибавляя и убавляя единицу. В Си/Си++ аналогично. Я всю голову сломал, пытаясь понять зачем все это так сделано. Разъясните.



Последнее исправление: msin87 (всего исправлений: 1)

Ведь я, как электронщик, ожидаю получить от RTC модуля именно то, что есть в даташите.

чего за бред, причем тут электронщики с даташитами ? /dev/rtc напрямую вообще никто не использует, системное время и аппаратные часы реального времени вещи ортогональные.

anonymous
()
Ответ на: комментарий от anonymous

Да не вопрос. Системное время, окей. Но за каким чертом все либы делают декремент месяца и недели после пересчета из epoch?

msin87
() автор топика

Я просто оставляю нулевой элемент массива пустым. Ну и фиг с ним, зато меньше шансов где-то что-то прошляпить...

А насчет дней недели, есть еще косяк в том, что каким-то раком в русском языке неделя начинается не с воскресенья, как у всех, а с понедельника!

Eddy_Em ☆☆☆☆☆
()

P.S. а насчет справедливого негодования, никто это менять не будет! Это такое жуткое legacy, что если один раз поменять, замучишься потом в тысячах других мест костыли вправлять.

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от Eddy_Em

А насчет дней недели, есть еще косяк в том, что каким-то раком в русском языке неделя начинается не с воскресенья, как у всех, а с понедельника!

Нехристи, что с них взять!

Алсо вот это:

The ISO prescribes Monday as the first day of the week with ISO-8601 for software date formats.

https://en.wikipedia.org/wiki/ISO_8601

hateyoufeel ★★★★★
()
Ответ на: комментарий от Eddy_Em

Все-таки иногда от legacy отказываются и делают deprecated

msin87
() автор топика

Подкинули немного StackOverflow:

Раз

Два

Но и там ответа нет.

msin87
() автор топика

Просто вопрос соглашения.

Делать printf("Сейчас %s", monthNames[month]) и printf("С начала месяца прошло %d часов", day * 24 + hour) удобнее когда месяц и числа нумеруются с 0, а не 1 и можно ошибиться в противном случае.

Делать printf("%02d.%02d.%04d", day, month, year) удобнее когда месяц и числа нумеруются с 1, а не 0 и можно ошибиться в противном случае.

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

я знаком со многими RTC контроллерами

А кому какое до них дело? Есть POSIX который предписывает что должно быть в struct tm, и его написали исходя из каких-то соображений, в число которых «сделать точно так как в даташите на какой-то там сраный RTC» едва ли входило с ненулевым весом. Тем более что когда он разрабатывался может и тех RTC что вы «знаете» не существовало.

Сами контроллеры тоже проектировали исходя из каких-то соображений, но с другими приоритетами, и мне кажется логичным то что в RTC, основное применение которым испокон веков было показать часики в приборах, где цифровой логики кроме этих часов могло вообще не быть, приоритетом было отдавать время в human readable формате, а POSIX всё-таки писали для разработчиков полноценного ПО, у которых во-первых, с нумерацией с нуля проблем нет и найдётся пара байт кода на ±1, во-вторых, которым намного больнее сделать ошибку забыв ±1 при индексации массива или расчётов со временем, чем при отображении пользователю (что несёт намного менее серьёзные последствия и быстрее обнаруживается). Правда, почему tm_mday начинается не с 0 у меня предположений нет.

А rtc_time, очевидно, делали совместимым с struct tm независимо от того что там отдаёт RTC.

slovazap ★★★★★
()
Последнее исправление: slovazap (всего исправлений: 4)
Ответ на: комментарий от slovazap

Сами контроллеры тоже проектировали исходя из каких-то соображений

Я даже больше скажу: если брать микроконтроллеры, то там везде все по-разному со встроенным RTC. У одних Январь - это нулевой месяц, у других - первый месяц.

В принципе, тоже самое и с отдельными RTC контроллерами. Есть как совместимые с MC146818, так и не совместимые.

Но, в целом, как ты и написал - у двух подходов свои плюсы и минусы. Но, в итоге, программист практически во всех случаях делает month+1 и month-1 . Все остальное не важно. Важен сам факт, что библиотеки именно так работают с месяцами, выдавая 0. И, даже, если будешь ковырять ядро, то ты там увидишь тоже самое.

Да, c /dev/rtc типа никто не работает. Но от rtc ты можешь, как я понимаю, получить точное прерывание по заданному периоду. С минимальной задержкой. И вот ты решаешься воспользоваться этой фишкой, ну или по другим соображениям, и система тянет month из RTC, потом в драйвере делает month-1, через ioctl тянешь данные и делаешь month+1.

Что-то мне это все кажется очень странным. Особенно тот факт, что если используешь сишную либу time, то фактически ты делаешь системный вызов, получаешь epoch, и либа ручками высчитывает тебе месяц.

А теперь смотри такой прикол: MFC, QT, R

Разные языки, разные либы. Если использовать в C++ наследие от C, то : ctime

Понимаешь? Нет никакой логики в упрощении работы с массивами и прочее. Просто тянется старое наследие, которое перекочевывает в Java и JS, и еще может быть куда-то. Но почему его тянут, но не фиксят? Почему QT дает 1-Январь, а Java 0-Январь? Где кто согрешил? И почему в ядре такие же приколы? Ради чего? POSIX? POSIX разве где-то оговаривает стандарт нумерации месяцев? В стандарте говориться (стр.451) лишь о расширении стандарта C. И далее, если ковыряться в стандарте, идут ссылки на C, что те или иные функции и структуры соответствуют стандарту C Если глянуть в C, то там по какой-то причине месяцы с нуля идут. А потом в либах типа QT уже с 1. Да, ок, может быть какой-то программный интерфейс для ОС должен быть единым, поддерживаемым. Но тогда не очень понятно это решение. Возможно, раньше, во времена радиоламп, лучше надо было выдавать на перфокарту месяцы с нуля. Ну да, ну ок. Но уже 2019 год. Да, я докопался, но почему бы и нет?

msin87
() автор топика
Ответ на: комментарий от msin87

Ты же вроде всё понял

У одних Январь - это нулевой месяц, у других - первый месяц.

но всё равно продолжаешь тупить

система тянет month из RTC, потом в драйвере делает month-1, через ioctl тянешь данные и делаешь month+1.

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

Значит что если в одном случае

система тянет month из RTC, потом в драйвере делает month-1, через ioctl тянешь данные и делаешь month+1.

в другом мне не надо делать month+1 и я использую month из ioctl в неизменном виде. А в третьем случае, железка выдаёт 0-based, и оказывается что ничего не надо делать и в драйвере. А по итогу совершенно похрен какую нумерацию использовать.

Почему QT дает 1-Январь, а Java 0-Январь?

А почему нет-то? Я же написал, и ты согласился что оба варианта применимы. Все выбирают более удобный для своего случая. Нет тут никакого наследия и фиксить нечего.

slovazap ★★★★★
()
Ответ на: комментарий от slovazap

Окей, но если ты считаешь такты? Ты прям супероптимизатор. Хм.. ты не будешь использовать ,/dev/rtc. Ты напишешь свой драйвер. Ну да. Согласен.

msin87
() автор топика
Ответ на: комментарий от Harald

время из rtc в норме читается один раз, при загрузке системы

причём опционально. Не знаю почему пациента заклинило на /dev/rtc, его вообще может не быть в системе - можно системное время проинициализировать из сети при загрузке.

anonymous
()
Ответ на: комментарий от Harald

или оставить 1970 год по дефолту

на одноплатниках без батарейки так и бывает. В общем понятно что он просто не понимает - системное время изменяется тиками системного таймера, а часы реального времени это один из вариантов проинициализировать начальное значение системного времени.

anonymous
()
Ответ на: комментарий от slovazap

Правда, почему tm_mday начинается не с 0 у меня предположений нет.

На самом деле как раз под вывод: printf(«%s %s %d %02d:%02d:%02d %s %04d», weekdayNames[weekday], monthNames[month], day, hour, minute, sec, tz, year)

monk ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.