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


Вот, например, у меня на компьютере сегодняшняя дата пишется так — 06.01.2023, а на одном из моих серверов в уголке экрана светится 01/06/23. Это американский формат, и я каждый раз мучительно вспоминаю, 01/06 это первое июня или всё таки шестое января.


Ещё одно национальное отличие — символ отделения целой и дробной части в числах. В России по традиции и в соответствии со стандартами надо отделять дробную часть запятой (Пи = 3,14), а американцы (и вслед за ними программисты) используют десятичную точку (Pi = 3.14). Из-за этого различия иногда случаются разные казусы.


В русском Excel в разделе "Сохранить как" есть формат файла "CSV (разделитель — запятая)". Если вы сохраните таблицу в этом формате, то получите текстовый файл, в котором разделителем колонок будет… Нет, не запятая, как было обещано, а точка с запятой, поскольку запятая уже занята как разделитель дробной части. Если же вы попробуете открыть "американский" CSV-файл (с разделителем запятой и десятичной точкой), скачанный из интернета, то у вас возникнет сразу несколько проблем. Во-первых, все столбцы сольются в один, поскольку не будет распознан разделитель столбцов. Это можно преодолеть, предварительно вставив в начало файла магическую строку sep=,, но тут вы нарветесь на вторую проблему — теперь ваши числа с десятичными точками будут восприняты не как числа, а как текст. В принципе, можно выделить колонки с числами и сделать во всех ячейках замену точки на запятую, но у себя я наткнулся на третью проблему — при открытии файла Excel попытался угадать формат ячеек и безвозвратно заменил небольшие числа вида "1.2" на даты — "1 февраля 2023".


Для того, чтобы упростить работу с национальными форматами, программисты разработали специальные базы данных, автоматизирующие процесс локализации программ (по-английскии кратко пишется L10N поскольку L[ocalizatio]N). На программистском жаргоне эти базы называются локалями и обозначаются либо названием языка ("Русский"), либо аббревиатурой, составленной из названия языка и названия страны (en_US — американский английский). В Windows можно выбрать стандартную локаль (ищем в настройках Windows 10 слово "Регион") и (при желании) поправить отдельные ее параметры ("Панель управления\Часы и регион"). В командной строке Linux локаль в целом выбирается установкой значения переменной LANG или LC_ALL, а частичные изменения настраиваются через переменные LC_NUMERIC, LC_TIME и т.д. У меня, например, в Windows отдельно настроена десятичная точка, а в Linux установлен программистский языковой стандарт для чисел LC_NUMERIC=C. В Excel в дополнительных параметрах присутствует отдельный пункт для разделителя целой и дробной частей числа — можно выбирать между разделителем из системной локали и произвольным символом, например, точкой.


А теперь примеры программистских ошибок.


В 2000-х годах в суперкомпьютерном центра МГУ эксплуатировался NUMA сервер от IBM. Сейчас не могу вспомнить, какой именно модели, поскольку в последний раз получал к нему доступ десять лет назад в 2013 году. На сервере использовался родной компилятор от IBM и этот компилятор имел замечательную ошибку. При установленной русской локали ru_RU он не мог распознать вещественные числа в программах, поскольку считал, что разделителем десятичных знаков должна быть запятая. Программы не собирались, а компилятор выдавал сообщение о недопустимом символе в позициях, в которых стояли десятичные точки. Когда моя коллега впервые наткнулась на эту ошибку у меня ушло минут 40 для того, чтобы осознать, что дело не в кодировке и не в невидимых символах в окрестности десятичных точек, а именно в локали. Ради интереса, я поменял все десятичные точки на запятые, но это не помогло: в зависимости от контекста, эти запятые интерпретировались либо как разделители параметров функции, либо как оператор «запятая», что, в общем-то, было вполне ожидаемо.


Недавно наткнулся в собственной программе на то, что при установленной русской локали вывод вещественных чисел в C++ двумя разными способами дает разные результаты:


#include <locale.h>
#include <unistd.h>
#include <stdio.h>
#include <iostream>
int main(int argc, char *argv[]){
  float pi=3.14;

  // Укажем, что мы хотим форматировать числа, даты, валюты и пр.
  // по правилам русского языка
  setlocale(LC_ALL, "Russian");

  // libc по умолчанию использует пользовательские настройки
  printf(«%f\n», pi); // 3,14

  // Стандартные потоки по умолчанию используют локаль POSIX
  std::cout << pi << std::endl; // 3.14

  // Для вывода в cout по национальным правилам
  // надо проделать не совсем очевидные вещи:

  // Создание копии пользовательской локали
  std::locale loc("");
  std::cout << "Locale name = " << loc.name() << std::endl; //Russian

  // Установка формата вывода в соответствии с указанной локалью
  std::cout.imbue(loc);

  // Вот теперь можно выводить числа в национальном формате
  std::cout << pi << std::endl; // 3,14

  return 0;
}

Давно хотел написать эту заметку, но объём материала казался каким-то незначительным. Сегодня наткнулся в блоге у Евгения Степанищева на ещё один интересный пример, на этот раз из ранних версий PHP4, и решил, что пора писать обобщающий материал. Хотя бы для того, чтобы обратить на эту проблему внимание начинающих программистов.


Пример от Евгения (можно поиграться в песочнице). Установка локали в PHP 4.2.3 влияет на конвертацию строки в число:


<?php
var_dump('1,4' + '0,7'); // int(1)
var_dump('1,4' + '0.7'); // float(1.7)
setlocale(LC_ALL, 'ru_RU.koi8r');
var_dump('1,4' + '0,7'); //float(2,1)
var_dump('1,4' + '0.7'); // float(1.4)

Комментарии (50)


  1. AndreyDmitriev
    06.01.2023 13:18

    Я ещё со времён работы на ДВК (что есть клон DEC PDP-11) приучился показывать даты как "06-Jan-2023", снимает сразу много вопросов:

    А точка и запятая как разделитель отличаются да. Я по воле случая работаю в Германии, но в американской компании, так что софт используется и так и сяк, и мы берём разделитель из региональных настроек, чтоб пользователю было комфортно. В принципе особых проблем нет, если всё аккуратно делать. CSV cтараемся по возможности избегать.


    1. aragont Автор
      06.01.2023 13:53
      +25

      Даты я предпочитаю в формате ISO 8601 т.е. YYYY-MM-DD. В таком формате они в правильно сортируются как строки любой локали.


      1. MrNutz
        08.01.2023 03:53

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


    1. slonopotamus
      06.01.2023 17:58
      +3

      У вас на фото проблема 2000 года. Ну и +1 к комментатору выше, ISO8601 рулез.


  1. savostin
    06.01.2023 14:50
    -3

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


    1. Naf2000
      06.01.2023 15:06
      +2

      А что должен читать и писать человек?


      1. GospodinKolhoznik
        06.01.2023 15:08
        +5

        Люди предпочитают смайлики, стикеры и эмодзи.


      1. savostin
        06.01.2023 15:17
        -3

        Человек-то конечно кроме текста писать ничего не может. А вот программы (и веб) давно должны перейти на двоичный язык/протокол. А то у нас одна программа пишет в JSON/XML/etc. потом другая программа парсит его. Зачем? Кому этот текст нужен, кроме человека, который уже давно не участвует в передаче и обработке.

        Для начала нужно хранить/передавать дату в utc timestamp.


    1. KongEnGe
      06.01.2023 15:21
      +5

      Человеки уже сильно наелись бинарной передачей и давно решили, что надежнее текста, который можно распарсить, ничего не придумали.


    1. aragont Автор
      06.01.2023 15:22

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

      Что касается взаимодействия компьютеров, то мне постоянно приходится переносить данные между bash и Excel. Для моих задач оптимальным оказался текстовый файл с разделителем в виде символа табуляции. Формат CSV, на самом деле, очень сложен в генерации и разборе и ужасно читается как человеком, так и компьютером.


    1. aragont Автор
      06.01.2023 15:31
      +2

      Если же говорить о двоичных форматах при обмене между компьютерами, то учтите, что вам придётся как минимум отслеживать различия архитектур BIG ENDIAN и LITLE ENDIAN, и это тоже не очень просто.


      1. savostin
        06.01.2023 15:36

        Все же меньше проблем, чем с парсингом той же даты.


        1. aragont Автор
          06.01.2023 16:38
          +2

          Чтобы оценить сложность корректного кодирования/декодирования универсальных двоичных форматов попробуйте как-нибудь распарсить сертификат X.509 в формате der или посмотреть на 100 000 строк библиотеки libasn1 (https://github.com/adirkuhn/libasn1), которая с этим форматом работать умеет.


          1. aborouhin
            06.01.2023 17:23
            +1

            Справедливости ради, "универсальный двоичный формат" - это всё же не DER, а Avro, Parquet или Protobuf, и вряд ли сложность программной работы с ними существенно превосходит таковую с XML или JSON.

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


            1. aragont Автор
              06.01.2023 17:47
              +1

              На самом деле, сложность работы с различными форматами мало зависит от самих форматов, скорее от удобства библиотек, которые кто-то уже написал для вас. Хотя в маленькой программке для себя при работе с текстовым форматом можно "срезать угол" и, например, выдернуть из XML нужный параметр простым поиском по тексту не заморачиваясь с разбором и валидацией всей структуры.


        1. acsent1
          06.01.2023 17:47
          +3

          В двоичном формате тоже нужно определяться как дату передавать: аля юникс дата - число секунд / милисекунд / дней начиная с 01-01-1970 (а почему именно это, а если меньше нужно) или как то по другому


    1. thegriglat
      06.01.2023 17:55
      +1

      «Философия Unix гласит:

      Пишите программы, которые делают что-то одно и делают это хорошо.

      Пишите программы, которые бы работали вместе.

      Пишите программы, которые бы поддерживали текстовые потоки, поскольку это универсальный интерфейс».


      1. savostin
        06.01.2023 17:59

        Откуда тогда этот топик взялся, если все так просто?


      1. aragont Автор
        06.01.2023 19:22

        Как только в Юниксе разрешили переводы строки в именах файлов, так с текстовыми потоками начались косяки.

        Мне кажется, что если бы разработчики Юникса придумали объектные потоки как в PowerShell, то это с одной стороны увеличило бы мощь Юникса на порядок, а с другой стороны, убило бы его, поскольку Юникс взлетел именно как очень простая и лёгкая ОС для слабых машин.


        1. garwall
          07.01.2023 12:05

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


          1. aragont Автор
            08.01.2023 08:31

            Мэйнфреймы 1980-х это многопроцессорные системы с расслоением доступа к памяти, виртуализацией, контейнерами и т.п. — всем тем, до чего персоналки (и Linux вместе сними) доросли спустя тридцать лет.

            Юникс была очень простой ОС и стала популярна в первую очередь как ОС для рабочих станций, которые по сути были корпоративными персоналками для инженеров (SUN Solaris, SGI Irix) и для серверов младшего уровня (IBM AIX, HP UX). Microsoft даже пыталась продавать Юникс для персоналок на Intel 286 (Xenix), но потом сосредоточилась на MS DOS.

            В начале 1990-х на рынок серверов нижнего ценового уровня вышла Microsoft со своей Windows NT и задавила оригинальный Юникс практически полностью.


    1. konst90
      06.01.2023 20:33

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


      1. aragont Автор
        07.01.2023 09:15

        Видел подобное в какой-то важной оснастке Windows, чуть ли не в просмотре событий, — было очень неудобно. Не помню версию, возможно, Win2000.


  1. gdt
    06.01.2023 18:07

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


    1. aragont Автор
      06.01.2023 19:12

      Кавычками экранируется текст, который содержит переводы строк, разделители колонок и, кажется, сами кавычки. Вот маленький пример из одной строки и двух колонок. Разделитель колонок — точка с запятой.

      """Abc"";
      """"Def""""";"""Abc"",
      """"Def"""""


      1. gdt
        06.01.2023 19:16

        Согласен, я скорее к тому, что не совсем понятно, что мешает экселю просто взять в кавычки значение с запятой.


        1. aragont Автор
          06.01.2023 19:30

          Типизация. То, что в кавычках — строки, без кавычек — числа и даты.


    1. AndreyDmitriev
      06.01.2023 19:47

      смотрю csv в специализированных утилитах типа Tad

      а что такое "Tad", если не секрет?


      1. gdt
        06.01.2023 19:49
        +2

        Отличная вещь. Рекомендую - https://www.tadviewer.com/

        Tad is a free (MIT Licensed) desktop application for viewing and analyzing tabular data.

        A fast viewer for CSV and Parquet files and SQLite and DuckDb databases that supports large files.

        Там кажется даже SQL подобные запросы можно писать.


        1. savostin
          06.01.2023 20:36
          +1

          215 Мб... Электрон чтоль?


          1. gdt
            06.01.2023 22:03
            +1

            Похоже на то, да


          1. aborouhin
            06.01.2023 22:20

            Электрон и свой движок БД (duckDB) внутри. Но для данного класса инструментов это, честно говоря, уж совсем не критично.


    1. aborouhin
      06.01.2023 22:17

      В сторонних CSV (чаще всего сталкиваюсь с данными каких-нибудь госорганов, финансовой информацей; в других сферах, надеюсь, всё лучше) обычно такие ужасы, что тяну их сразу в OpenRefine, где их можно хорошенько почистить и привести к удобоваримому виду.

      Но за наводку на Tad спасибо, гляну, обещают работу с большими файлами, а вот как раз OpenRefine на миллионах строк уже имеет дурную привычку съедать все 192 Гб памяти, которые я ему максимум могу выделить на доступном железе...

      P.S. Глянул Tad. Работает умеренно шустро (памяти ест немного, но скорость отклика при любых манипуляциях с данными так себе, и никакого прогресса в процентах не показывает, чтобы хоть понимать, скоро закончит, или можно идти кофе наливать...) Но главное - те самые русские CSV с точками с запятой не понимает вообще :(


      1. gdt
        07.01.2023 12:05

        Странно. Я сам работал в основном с выгрузками чего-то по типу Zeppelin, там вроде были вообще tsv, поэтому с такими файлами не сталкивался. Однако, в ченджлоге есть:

        Tad 0.8.5 - June 28, 2017

        New Features

        • European CSV support - support for ; instead of , as field separator.


        1. aborouhin
          07.01.2023 14:20

          Ну я для пробы взял CSVшки Росстата, у которых точка с запятой, - в дереве слева их показывает, а в основной части окна никаких данных не появляется. Взял CSV от ФССП, где запятые - сразу подгрузилась. Может, там в каких-то других факторах дело, в тех же кавычках, детально не копал.


  1. konst90
    06.01.2023 20:40
    +2

    Здесь можно поблагодарить разработчиков, программы которых едят оба формата. Компас, например: след ввести размер с точкой, можно с запятой, и оба варианта он обработает, изменив в одном из случаев (в зависимости от системных настроек) знак.

    А жирный минус - тем, у кого в одном большом пакете (Ansys) некоторые модули принимают строго точку (потому что писали те, для кого точка это норма), а некоторые - системную настройку (потому что предусмотрели). Ладно хоть между модулями числа нормально ходят.


    1. grafdezimal
      07.01.2023 16:33

      Тоже так делал когда-то, но потом приходят «специалисты» и вводят 2.001 толи это два и одна тысячная, толи две тысячи один. Поэтому нет, только настройки системы для ввода. И только «InvariantCulture» для сохранения и обмена между модулями.


      1. konst90
        07.01.2023 18:10

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


  1. Sergey_zx
    06.01.2023 22:20
    +1

    Это для слабаков. Которые еще не искал ошибку, когда в коде вместо английской "с" стоит кириллица и компилятор валится без указания номера строки с Unknown error.


    1. enabokov
      06.01.2023 23:14

      Наверное, только древние компиляторы валятся от этого с Unknown error. Современные понимают имена на любом языке, и если в одном месте будет другая "с", то он напишет что такой идентификатор не определён. Хорошая IDE подсветит идентификатор с русской "с" в названии. Так что эта проблема позади.


      1. Sergey_zx
        06.01.2023 23:35
        +1

        Да, современные этим не страдают.

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

        Следующий шаг развития, когда на компиляцию будут влиять эмодзи которые будет допустимо вставлять в имена.


      1. sav13
        07.01.2023 11:31
        +1

        Ну еще неизвестно что хуже. Неизвестная ошибка в непонятной строке, которую все же можно отловить последовательными комментариями текста или когда компилятор/интерпретатор "хавает все" и записав значение в Срусское долго пытаешься найти его в Санглийском (если добавить туда локальные, глобальные переменные и области видимости, то задача еще усложняется)

        А ведь еще есть и кодировки исходных текстовых файлов ASCII, UTF8, UTF16 etc. и послав текстовую строку в том же JSON не факт что получишь именно ее )))

        На заре развития компьютеров в текстовые знакогенераторы зашивали разное начертание похожих букв. Классика - это перечеркнутый 0, отличающийся от O.

        Но увы, теперь в мире правят менеджеры и дизайнеры ...


    1. Furriest
      07.01.2023 20:05

      Ох, сразу вспомнилась СМ1420, на которой в компиляторе Фортрана были "русская кавычка" и "латинская кавычка", естественно, не отличающиеся визуально.


      1. Sergey_zx
        07.01.2023 21:08

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


  1. NeoCode
    07.01.2023 01:56
    +4

    А ведь на заре IT разработчики ОС и офисного софта могли бы просто принудить весь мир пользоваться стандартной системой форматов (точка, формат даты год-месяц-день-час-минута-секунда), и никто бы сейчас не возмущался. Наоборот все радовались бы, как все просто и унифицированно. Но вместо этого в угоду непонятно кому сделали дурацкую локализацию.


    1. MainBelia
      07.01.2023 20:52

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


  1. Stormbringer-s
    08.01.2023 09:06

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


    1. slonopotamus
      08.01.2023 11:21

      Конечно не решатся. <Картинка про 14 стандартов>


    1. aragont Автор
      08.01.2023 13:06
      +1

      Cимвол разделения колонок есть даже в ASCII — называется символ табуляции. А вот уже с символом разделения строк три основные ОС для персональных компьютеров не смогли договориться — Unix (LF), Windows (CR LF) и старая MacOS (CR)


  1. Squoworode
    08.01.2023 21:01

    Это можно преодолеть, предварительно вставив в начало файла магическую строку sep=,, но тут вы нарветесь на вторую проблему — теперь ваши числа с десятичными точками будут восприняты не как числа, а как текст. В принципе, можно выделить колонки с числами и сделать во всех ячейках замену точки на запятую, но у себя я наткнулся на третью проблему — при открытии файла Excel попытался угадать формат ячеек и безвозвратно заменил небольшие числа вида "1.2" на даты — "1 февраля 2023".

    Вообще, для удобного открытия файла с разделителями другой системы нужно всего лишь открыть параметры Excel, и на вкладке "Дополнительно" снять галочку "Использовать системные разделители: целой и дробной части; разрядов".