Файлы не содержат символов, они содержат байты. Чтобы получить символы из байтов, вам нужно применить некоторую кодировку. Если вы применяете разные кодировки к одним и тем же байтам, вы можете получить разные символы в результатах.
Взяв в качестве примера вашу строку байтов:
PS> $ByteArray = [Byte[]]('24-4D-65-73-73-61-67-65-20-3D-20-5B-49-4F-2E-46-69-6C-65-5D-3A-3A-52-65-61-64-41-6C-6C-54-65-78-74-28-22-24-50-53-53-63-72-69-70-74-52-6F-6F-74-5C-F0-F3-F1-F1-EA-E8-E9-2E-74-78-74-22-2C-20-5B-53-79-73-74-65-6D-2E-54-65-78-74-2E-45-6E-63-6F-64-69-6E-67-5D-3A-3A-44-65-66-61-75-6C-74-29-0D-0A-24-4D-65-73-73-61-67-65' -split '-' | % { [Byte]::Parse( Файлы не содержат символов, они содержат байты. Чтобы получить символы из байтов, вам нужно применить некоторую кодировку. Если вы применяете разные кодировки к одним и тем же байтам, вы можете получить разные символы в результатах.
Взяв в качестве примера вашу строку байтов:
[110] Важно использовать правильную кодировку при чтении файлов. И еще одна важная вещь: ваш файл скрипта использует кодовую страницу 1251, а не UTF-8. Также обратите внимание, что последовательность байтов F0-F3-F1-F1-EA-E8-E9
(которая представляет мир русский
в кодовой странице 1251) является недопустимой последовательностью байтов в соответствии с UTF-8, поэтому вместо этого вы получаете семь заменяющих символов (U+FFFD
).
Поскольку PowerShell Core по умолчанию использует UTF-8, а в вашем файле сценария нет спецификации для указания иного (хотя нет спецификации, которая позволяет PowerShell распознавать кодовую страницу 1251), PowerShell Core читает ваш файл с использованием кодировки UTF-8, таким образом, он пытается получить доступ к �������.txt
(которого у вас нет) вместо русский.txt
.
Вы можете легко наблюдать это сами, если измените сценарий на запись файла вместо чтения.
PS> $ByteArray2 = [Byte[]](91, 73, 79, 46, 70, 105, 108, 101, 93, 58, 58, 87, 114, 105, 116, 101, 65, 108, 108, 84, 101, 120, 116, 40, 34, 36, 80, 83, 83, 99, 114, 105, 112, 116, 82, 111, 111, 116, 92, 240, 243, 241, 241, 234, 232, 233, 46, 116, 120, 116, 34, 44, 32, 91, 68, 97, 116, 101, 84, 105, 109, 101, 93, 58, 58, 85, 116, 99, 78, 111, 119, 41)
PS> # Representing `[IO.File]::WriteAllText("$PSScriptRoot\русский.txt", [DateTime]::UtcNow)` in codepage 1251
PS> [IO.File]::WriteAllBytes("$(Convert-Path .)\write.ps1", $ByteArray2)
PS> .\write.ps1
Теперь вы можете прочитать файл обратно с вашим оригинальным скриптом:
PS> [IO.File]::WriteAllBytes("$(Convert-Path .)\asd.ps1", $ByteArray)
PS> .\asd.ps1
01/18/2019 17:13:15
Вызов обоих скриптов с помощью PowerShell Core:
PS> pwsh -Command ".\write.ps1; .\asd.ps1"
01/18/2019 17:21:02
Как видите, ваш скрипт успешно выполнен в PowerShell Core. Если вы просматриваете текущий каталог, то можете увидеть, что в нем есть и русский.txt
, и �������.txt
, и их содержимое совпадает с тем, что было напечатано на консоли.
На самом деле проблема не в том, чтобы делать чтение / запись файлов (кроме самого файла скрипта). Это можно продемонстрировать с помощью простого скрипта, который просто печатает коды символов строкового литерала:
PS> $ByteArray3 = [Byte[]](40, 39, 240, 243, 241, 241, 234, 232, 233, 39, 46, 71, 101, 116, 69, 110, 117, 109, 101, 114, 97, 116, 111, 114, 40, 41, 32, 124, 32, 37, 32, 84, 111, 73, 110, 116, 51, 50, 32, 36, 110, 117, 108, 108, 32, 124, 32, 37, 32, 84, 111, 83, 116, 114, 105, 110, 103, 32, 88, 52, 41, 32, 45, 106, 111, 105, 110, 32, 39, 45, 39)
PS> # Representing `('русский'.GetEnumerator() | % ToInt32 $null | % ToString X4) -join '-'` in codepage 1251
PS> [IO.File]::WriteAllBytes("$(Convert-Path .)\test.ps1", $ByteArray3)
Вызов его в Windows PowerShell даст один результат:
PS> .\test.ps1
0440-0443-0441-0441-043A-0438-0439
В то время как PowerShell Core даст другой:
PS> pwsh -Command ".\test.ps1"
FFFD-FFFD-FFFD-FFFD-FFFD-FFFD-FFFD
Одним из способов решения этой проблемы является использование UTF-8 с спецификацией, которая гарантирует, что и Windows PowerShell, и PowerShell Core будут использовать одну и ту же кодировку при чтении файлов сценариев.
Ответ написан с допущением, что [Text.Encoding]::Default.CodePage
вернет 1251, как это выглядит для ОП.
, 'HexNumber') })
PS> [Text.Encoding]::UTF8.GetString($ByteArray)
$Message = [IO.File]::ReadAllText("$PSScriptRoot\�������.txt", [System.Text.Encoding]::Default)
$Message
PS> [Text.Encoding]::GetEncoding(1251).GetString($ByteArray)
$Message = [IO.File]::ReadAllText("$PSScriptRoot\русский.txt", [System.Text.Encoding]::Default)
$Message
PS> [Text.Encoding]::GetEncoding(1252).GetString($ByteArray)
$Message = [IO.File]::ReadAllText("$PSScriptRoot\ðóññêèé.txt", [System.Text.Encoding]::Default)
$Message
Важно использовать правильную кодировку при чтении файлов. И еще одна важная вещь: ваш файл скрипта использует кодовую страницу 1251, а не UTF-8. Также обратите внимание, что последовательность байтов F0-F3-F1-F1-EA-E8-E9
(которая представляет мир русский
в кодовой странице 1251) является недопустимой последовательностью байтов в соответствии с UTF-8, поэтому вместо этого вы получаете семь заменяющих символов (U+FFFD
).
Поскольку PowerShell Core по умолчанию использует UTF-8, а в вашем файле сценария нет спецификации для указания иного (хотя нет спецификации, которая позволяет PowerShell распознавать кодовую страницу 1251), PowerShell Core читает ваш файл с использованием кодировки UTF-8, таким образом, он пытается получить доступ к �������.txt
(которого у вас нет) вместо русский.txt
.
Вы можете легко наблюдать это сами, если измените сценарий на запись файла вместо чтения.
PS> $ByteArray2 = [Byte[]](91, 73, 79, 46, 70, 105, 108, 101, 93, 58, 58, 87, 114, 105, 116, 101, 65, 108, 108, 84, 101, 120, 116, 40, 34, 36, 80, 83, 83, 99, 114, 105, 112, 116, 82, 111, 111, 116, 92, 240, 243, 241, 241, 234, 232, 233, 46, 116, 120, 116, 34, 44, 32, 91, 68, 97, 116, 101, 84, 105, 109, 101, 93, 58, 58, 85, 116, 99, 78, 111, 119, 41)
PS> # Representing `[IO.File]::WriteAllText("$PSScriptRoot\русский.txt", [DateTime]::UtcNow)` in codepage 1251
PS> [IO.File]::WriteAllBytes("$(Convert-Path .)\write.ps1", $ByteArray2)
PS> .\write.ps1
Теперь вы можете прочитать файл обратно с вашим оригинальным скриптом:
PS> [IO.File]::WriteAllBytes("$(Convert-Path .)\asd.ps1", $ByteArray)
PS> .\asd.ps1
01/18/2019 17:13:15
Вызов обоих скриптов с помощью PowerShell Core:
PS> pwsh -Command ".\write.ps1; .\asd.ps1"
01/18/2019 17:21:02
Как видите, ваш скрипт успешно выполнен в PowerShell Core. Если вы просматриваете текущий каталог, то можете увидеть, что в нем есть и русский.txt
, и �������.txt
, и их содержимое совпадает с тем, что было напечатано на консоли.
На самом деле проблема не в том, чтобы делать чтение / запись файлов (кроме самого файла скрипта). Это можно продемонстрировать с помощью простого скрипта, который просто печатает коды символов строкового литерала:
PS> $ByteArray3 = [Byte[]](40, 39, 240, 243, 241, 241, 234, 232, 233, 39, 46, 71, 101, 116, 69, 110, 117, 109, 101, 114, 97, 116, 111, 114, 40, 41, 32, 124, 32, 37, 32, 84, 111, 73, 110, 116, 51, 50, 32, 36, 110, 117, 108, 108, 32, 124, 32, 37, 32, 84, 111, 83, 116, 114, 105, 110, 103, 32, 88, 52, 41, 32, 45, 106, 111, 105, 110, 32, 39, 45, 39)
PS> # Representing `('русский'.GetEnumerator() | % ToInt32 $null | % ToString X4) -join '-'` in codepage 1251
PS> [IO.File]::WriteAllBytes("$(Convert-Path .)\test.ps1", $ByteArray3)
Вызов его в Windows PowerShell даст один результат:
PS> .\test.ps1
0440-0443-0441-0441-043A-0438-0439
В то время как PowerShell Core даст другой:
PS> pwsh -Command ".\test.ps1"
FFFD-FFFD-FFFD-FFFD-FFFD-FFFD-FFFD
Одним из способов решения этой проблемы является использование UTF-8 с спецификацией, которая гарантирует, что и Windows PowerShell, и PowerShell Core будут использовать одну и ту же кодировку при чтении файлов сценариев.
Ответ написан с допущением, что [Text.Encoding]::Default.CodePage
вернет 1251, как это выглядит для ОП.
Для тех, кто хочет сохранить наносекундную часть даты, вам нужно использовать DateTime2 в качестве типа столбца sql, а также тип Datehime2 Nhibernate.
Вот мое соглашение по настройке (используя свободное владение)
public class DateTimeConvention : IPropertyConvention, IPropertyConventionAcceptance
{
public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
{
criteria.Expect(x => x.Type == typeof(DateTime) || x.Type == typeof(DateTime?));
}
public void Apply(IPropertyInstance instance)
{
instance.CustomSqlType("DateTime2"); //specify that the sql column is DateTime2
instance.CustomType("DateTime2"); //set the nhib type as well
}
}
И чтобы активировать соглашение:
var v = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(d => d.FromConnectionStringWithKey("connstring"))
.ShowSql())
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<IRepository>()
.Conventions.AddFromAssemblyOf<IRepository>()) //this adds your convention
.BuildSessionFactory();
Используя это, вы сохраните наносекунды при хранении вашего DateTimes.
В моем домене допустимо терять миллисекунды с даты и времени в SQL Server. Поэтому я допускаю допуск в моих тестерах устойчивости, использующих этот статический помощник (реализация nunit):
public static class AssertDateTime
{
/// <summary>
/// Checks that the DateTimes are no more than second apart
/// </summary>
/// <param name="Expected"></param>
/// <param name="Actual"></param>
public static void AreWithinOneSecondOfEachOther(DateTime Expected, DateTime Actual)
{
var timespanBetween = Actual.Subtract(Expected);
if (timespanBetween > TimeSpan.FromSeconds(1))
Assert.Fail(string.Format("The times were more than a second appart. They were out by {0}. Expected {1}, Actual {2}.", timespanBetween, Expected, Actual));
}
}
Я столкнулся с такой же проблемой с полем аудита CreatedDate на моих бизнес-классах. Я поработал над ним, установив время, используя значение из утилитного метода. Надеюсь, это поможет.
/// <summary>
/// Return a DateTime with millisecond resolution to be used as the timestamp. This is needed so that DateTime of an existing instance
/// will equal one that has been persisted and returned from the database. Without this, the times differ due to different resolutions.
/// </summary>
/// <returns></returns>
private DateTime GetTime()
{
var now = DateTime.Now;
var ts = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, now.Millisecond, DateTimeKind.Local);
return ts;
}
На самом деле ссылка NHibernate утверждает, что тип DateTime nhibernate будет хранить .NET DateTime как SQL datetime, усеченный на втором уровне (без миллисекундной детализации)
Таким образом, он предоставляет тип Timestamp
NHibernate ( type = «Timestamp»
в сопоставлении), который будет хранить .NET DateTime
как SQL datetime
без усечения. Обратите внимание, что тип данных SQL timestamp
не необходим и фактически прервется, если у вас будет более одного столбца timestamp
в одной таблице. Таким образом, важно различать атрибуты sql-type
и type
в отображении NHibernate.
Кроме того, обратите внимание, что если вы работаете с фильтрами, то же правило применяется к определению фильтра: если вы укажете параметр DateTime
, значение параметра будет усечено без миллисекунд.
См. главу 5.2.2. Основные типы значений , Таблица 5.3 Типы сопоставления System.ValueType .