Відображення дати складання


260

Наразі у своєму заголовковому вікні є додаток із номером збірки. Це добре і добре, за винятком того, що нічого не означає для більшості користувачів, які хочуть знати, чи є у них остання збірка - вони, як правило, називають це "минулим четвергом", а не збіркою 1.0.8.4321.

План полягає в тому, щоб замість цього дати встановити дату створення - наприклад, "Додаток побудовано 21.10.2009".

Я намагаюся знайти програмний спосіб вивести дату складання у вигляді текстового рядка для такого використання.

Для номера збірки я використав:

Assembly.GetExecutingAssembly().GetName().Version.ToString()

після визначення того, як вони з'явилися.

Мені б хотілося щось подібне для дати складання (і часу, для бонусних балів).

Покажчики тут високо оцінені (виправданий каламбур, якщо це доречно), або більш акуратні рішення ...


2
Я спробував надані способи отримати дані збірки збірок, які працюють у простих сценаріях, але якщо дві збірки об'єднані разом, я отримаю не правильний час збирання, це буде одна година в майбутньому .. будь-які пропозиції?

Відповіді:


356

Джеффу Етвуду було важко сказати про цю проблему в « Визначення дати складання» на важкому шляху .

Найбільш надійним методом виявляється отримання часової позначки лінкера з заголовка PE, вбудованого у виконуваний файл - якийсь код C # (Джо Джой Співі) для цього з коментарів до статті Джеффа:

public static DateTime GetLinkerTime(this Assembly assembly, TimeZoneInfo target = null)
{
    var filePath = assembly.Location;
    const int c_PeHeaderOffset = 60;
    const int c_LinkerTimestampOffset = 8;

    var buffer = new byte[2048];

    using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
        stream.Read(buffer, 0, 2048);

    var offset = BitConverter.ToInt32(buffer, c_PeHeaderOffset);
    var secondsSince1970 = BitConverter.ToInt32(buffer, offset + c_LinkerTimestampOffset);
    var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

    var linkTimeUtc = epoch.AddSeconds(secondsSince1970);

    var tz = target ?? TimeZoneInfo.Local;
    var localTime = TimeZoneInfo.ConvertTimeFromUtc(linkTimeUtc, tz);

    return localTime;
}

Приклад використання:

var linkTimeLocal = Assembly.GetExecutingAssembly().GetLinkerTime();

ОНОВЛЕННЯ: Метод працював для .Net Core 1.0, але перестав працювати після випуску .Net Core 1.1 (дає випадкові роки в діапазоні 1900-2020)


8
Я дещо змінив свій тон з цього приводу, я все ще буду дуже обережним, коли копаюся в загострений заголовок PE. Але наскільки я можу сказати, цей матеріал PE набагато надійніший, ніж використання номерів версій, до того ж я не хочу призначати номери версій окремо від дати складання.
Джон Лейдегрен

6
Мені це подобається і я його використовую, але цей другий останній рядок із .AddHours()символом є досить хакітським і (я думаю) не братиме до уваги DST. Якщо ви хочете, щоб він був у місцевий час, dt.ToLocalTime();замість цього скористайтеся очищувачем . Середня частина також може бути значно спрощена за допомогою using()блоку.
JLRishe

6
Так, для мене це перестало працювати і з ядром .net (1940-ті, 1960-ті тощо)
eoleary

7
Хоча використання заголовка PE може здатися хорошим варіантом сьогодні, варто відзначити, що MS експериментує з детермінованими збірками (що зробить цей заголовок марним) і, можливо, навіть зробить його за замовчуванням у майбутніх версіях компілятора C # (з поважних причин). Добре читайте: blog.paranoidcoding.com/2016/04/05/…, і ось відповідь, що стосується .NET Core (TLDR: "це за дизайном"): developercommunity.visualstudio.com/content/problem/35873/…
Paweł Bulwan

13
Для тих, хто вважає, що це більше не працює, проблема не є проблемою .NET Core. Дивіться мою відповідь нижче про нові налаштування параметрів збірки, починаючи з Visual Studio 15.4.
Том

107

Додайте нижче до командного рядка попередньої збірки подій:

echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"

Додайте цей файл як ресурс, тепер у ваших ресурсах є рядок "BuildDate".

Щоб створити ресурси, див. Як створювати та використовувати ресурси в .NET .


4
+1 від мене, просто і ефективно. Мені навіть вдалося отримати значення з файлу з таким рядком коду: String buildDate = <MyClassLibraryName> .Properties.Resources.BuildDate
davidfrancis

11
інший варіант - зробити клас: (повинні включити в проект після першого разу, коли ви його компілюєте) -> ехо-простір імен My.app.namespace {public static class Build {public static string Timestamp = "% DATE%% TIME%" .Substring (0,16);}}> "$ (ProjectDir) \ BuildTimestamp.cs" - - - -> тоді можна назвати це за допомогою Build.Timestamp
FabianSilva

9
Це відмінне рішення. Єдина проблема полягає в тому, що змінні командного рядка% date% та% time% локалізовані, тому вихід буде змінюватися залежно від мови Windows користувача.
VS

2
+1, це кращий метод, ніж читання заголовків PE - адже існує кілька сценаріїв, коли це взагалі не буде працювати (наприклад, програма Windows Phone)
Метт Вітфілд

17
Розумний. Ви також можете використовувати powershell для отримання більш точного контролю над форматом, наприклад, для отримання часу з датою UTC, відформатованого як ISO8601: powershell -Command "((Get-Date) .ToUniversalTime ()). ToString (\" s \ ") | Out-File '$ (ProjectDir) Resources \ BuildDate.txt' "
квітня 1515

90

Шлях

Як вказував @ c00000fd у коментарях . Microsoft це змінює. І хоча багато людей не використовують останню версію свого компілятора, я підозрюю, що ця зміна робить цей підхід безсумнівно поганим. І хоча це весела вправа, я б рекомендував людям просто вставляти дату складання у свій двійковий файл будь-якими іншими необхідними засобами, якщо важливо відстежувати дату складання бінарного файлу.

Це можна зробити за допомогою тривіального генерації коду, який, ймовірно, є першим кроком у вашому сценарії збірки. Це та факт, що інструменти ALM / Build / DevOps дуже допомагають у цьому і слід віддавати перевагу будь-якому іншому.

Решту цієї відповіді я залишаю тут лише для історичних цілей.

Новий шлях

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

#region Gets the build date and time (by reading the COFF header)

// http://msdn.microsoft.com/en-us/library/ms680313

struct _IMAGE_FILE_HEADER
{
    public ushort Machine;
    public ushort NumberOfSections;
    public uint TimeDateStamp;
    public uint PointerToSymbolTable;
    public uint NumberOfSymbols;
    public ushort SizeOfOptionalHeader;
    public ushort Characteristics;
};

static DateTime GetBuildDateTime(Assembly assembly)
{
    var path = assembly.GetName().CodeBase;
    if (File.Exists(path))
    {
        var buffer = new byte[Math.Max(Marshal.SizeOf(typeof(_IMAGE_FILE_HEADER)), 4)];
        using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read))
        {
            fileStream.Position = 0x3C;
            fileStream.Read(buffer, 0, 4);
            fileStream.Position = BitConverter.ToUInt32(buffer, 0); // COFF header offset
            fileStream.Read(buffer, 0, 4); // "PE\0\0"
            fileStream.Read(buffer, 0, buffer.Length);
        }
        var pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        try
        {
            var coffHeader = (_IMAGE_FILE_HEADER)Marshal.PtrToStructure(pinnedBuffer.AddrOfPinnedObject(), typeof(_IMAGE_FILE_HEADER));

            return TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1) + new TimeSpan(coffHeader.TimeDateStamp * TimeSpan.TicksPerSecond));
        }
        finally
        {
            pinnedBuffer.Free();
        }
    }
    return new DateTime();
}

#endregion

Старий шлях

Ну як ви генеруєте номери побудови? Visual Studio (або компілятор C #) насправді надає автоматичні номери збирання та перегляду, якщо змінити атрибут AssemblyVersion на напр.1.0.*

Що відбудеться, так це те, що збірка буде дорівнює кількості днів з 1 січня 2000 року за місцевим часом, а для доопрацювання дорівнює кількості секунд з часу півночі за місцевим часом, поділеною на 2.

див. номери вмісту спільноти, номери автоматичної збірки та перегляду

наприклад AssemblyInfo.cs

[assembly: AssemblyVersion("1.0.*")] // important: use wildcard for build and revision numbers!

SampleCode.cs

var version = Assembly.GetEntryAssembly().GetName().Version;
var buildDateTime = new DateTime(2000, 1, 1).Add(new TimeSpan(
TimeSpan.TicksPerDay * version.Build + // days since 1 January 2000
TimeSpan.TicksPerSecond * 2 * version.Revision)); // seconds since midnight, (multiply by 2 to get original)

3
Я щойно додав годину, якщоTimeZone.CurrentTimeZone.IsDaylightSavingTime(buildDateTime) == true
e4rthdog

2
На жаль, я застосував цей підхід, не ретельно перевіряючи його, і він кусає нас у виробництві. Проблема полягає в тому, що коли компілятор JIT запускає інформацію в заголовку PE, змінюється. Звідси і низхідний рух. Тепер я можу робити зайві «дослідження», щоб пояснити, чому ми бачимо дату встановлення як дату збирання.
Джейсон Д

8
@JasonD У якому Всесвіті твоя проблема якось стає моєю проблемою? Як виправдовуєте скоромовки лише тому, що ви зіткнулися з проблемою, яку ця реалізація не врахувала. Ви отримали це безкоштовно, і ви його погано перевірили. Крім того, чому ви вважаєте, що заголовок переписується компілятором JIT? Читаєте ви цю інформацію з пам'яті процесу або з файлу?
Джон Лейдегрен

6
Я помітив, що якщо ви працюєте у веб-програмі, властивість .Codebase видається URL-адресою (файл: // c: /path/to/binary.dll). Це призводить до відмови виклику File.Exists. Використання "Assembly.Location" замість властивості CodeBase вирішило проблему для мене.
mdryden

2
@JohnLeidegren: Не варто для цього покладатися на заголовок Windows PE. Так як Windows 10 і відтворена збірка , то IMAGE_FILE_HEADER::TimeDateStampполе не встановлено на випадкове число , і більше не є тимчасовою штамп.
c00000fd

51

Додайте нижче до командного рядка попередньої збірки подій:

echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"

Додайте цей файл як ресурс, тепер у ваших ресурсах є рядок "BuildDate".

Після вставки файлу в Ресурс (як публічний текстовий файл) я отримав доступ до нього через

string strCompTime = Properties.Resources.BuildDate;

Щоб створити ресурси, див. Як створювати та використовувати ресурси в .NET .


1
@DavidGorsline - розмітка коментарів була правильною, оскільки вона цитує цю іншу відповідь . У мене недостатньо репутації, щоб відмовити вашу зміну, інакше я зробив би це сам.
Вай Ха Лі

1
@ Вай Ха Лі - а) відповідь, яку ви цитуєте, не дає коду для фактичного отримання дати / часу складання. б) на той час мені не вистачило репутації, щоб додати коментар до цієї відповіді (що я б зробив), аби лише опублікувати. так в) я розмістив повну відповідь, щоб люди могли отримати всі деталі в одній області ..
brewmanz

Якщо ви бачите Úte% замість% date%, перевірте тут: developercommunity.visualstudio.com/content/problem/237752/… Якщо коротко, зробіть це: echo% 25date% 25% 25time% 25
Qodex

41

Один із підходів, про який я дивуюсь, ще ніхто не згадував, - це використовувати текстові шаблони T4 для створення коду.

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System" #>
<#@ output extension=".g.cs" #>
using System;
namespace Foo.Bar
{
    public static partial class Constants
    {
        public static DateTime CompilationTimestampUtc { get { return new DateTime(<# Write(DateTime.UtcNow.Ticks.ToString()); #>L, DateTimeKind.Utc); } }
    }
}

Плюси:

  • Незалежно від місцевості
  • Дозволяє набагато більше, ніж просто час складання

Мінуси:


1
Отже, це зараз найкраща відповідь. 324 бали, щоб пройти, перш ніж вона стане найкращою відповіді :). Stackoverflow потребує способу показати найшвидшого альпініста.
pauldendulk

1
@pauldendulk, це не дуже допоможе, оскільки відповідь за найвищу перевагу та прийняту відповідь майже завжди набирає голоси швидше. Прийнята відповідь на це питання має + 60 / -2, оскільки я опублікував цю відповідь.
Пітер Тейлор

Я вважаю, що вам потрібно додати .ToString () до своїх Ticks (в іншому випадку я отримую помилку компіляції). Це означає, що я тут стикаюся з крутою кривою навчання, чи можете ви показати, як це використовувати В основному програмі?
Енді

@Andy, ти маєш рацію щодо ToString (). Використання простоConstants.CompilationTimestampUtc . Якщо VS не генерує файл C # з класом, то вам потрібно розібратися, як змусити це зробити, але відповідь залежить як мінімум від версії VS та типу файлу csproj, тож це занадто багато деталей для цієї публікації.
Пітер Тейлор

1
У випадку, якщо інші задаються питанням, це те, що потрібно, щоб він працював над VS 2017: я повинен був зробити цей шаблон дизайну Time T4 (знадобився деякий час, щоб розібратися, я додав попередньо шаблон препроцесора). Я також повинен був включити цю збірку: Microsoft.VisualStudio.TextTemplating.Interfaces.10.0 як посилання на проект. Нарешті мій шаблон повинен був містити "використання системи;" перед простором імен, інакше посилання на DateTime не вдалося.
Енді

20

Що стосується техніки витягування інформації про дату збірки / версії з байтів заголовка PE, то Microsoft змінила параметри збірки за замовчуванням, починаючи з Visual Studio 15.4. Новий за замовчуванням включає детерміновану компіляцію, яка робить дійсною часовою позначкою та автоматично примноженими номерами версій минуле. Поле часової позначки все ще присутнє, але воно заповнюється постійним значенням, яке є хеш-то чи іншого, але не будь-яким вказівкою часу збирання.

Деякі детальні передумови тут

Для тих, хто надає пріоритет корисній позначці часу детермінованій компіляції, є спосіб змінити новий дефолт. Ви можете включити тег у файл .csproj цікавої збірки таким чином:

  <PropertyGroup>
      ...
      <Deterministic>false</Deterministic>
  </PropertyGroup>

Оновлення: Я схвалюю рішення шаблону тексту T4, описане в іншій відповіді тут. Я використовував це для чистого вирішення своєї проблеми, не втрачаючи переваги детермінованої компіляції. Одним із застережень є те, що Visual Studio запускає компілятор T4 лише тоді, коли файл .tt зберігається, а не під час збирання. Це може бути незручно, якщо виключити результат .cs з керування джерелом (оскільки ви очікуєте, що він буде створений) і інший розробник перевірить код. Без відновлення у них не буде файлу .cs. Є пакунок на nuget (я думаю, називається AutoT4), який робить компіляцію T4 частиною кожної збірки. Я ще не стикався з рішенням цього питання під час розгортання виробництва, але очікую, що щось подібне може зробити це правильним.


Це вирішило мою проблему в sln, який використовує найдавнішу відповідь.
pauldendulk

Ваша обережність щодо Т4 цілком справедлива, але зауважте, що він вже присутній у моїй відповіді.
Пітер Тейлор

15

Я просто C # новачок, тому, можливо, моя відповідь звучить нерозумно - я показую дату збірки з дати останнього запису виконуваного файлу:

string w_file = "MyProgram.exe"; 
string w_directory = Directory.GetCurrentDirectory();

DateTime c3 =  File.GetLastWriteTime(System.IO.Path.Combine(w_directory, w_file));
RTB_info.AppendText("Program created at: " + c3.ToString());

Я спробував використовувати метод File.GetCreationTime, але отримав дивні результати: дата команди була 2012-05-29, але дата в Провіднику вікон показала 2012-05-23. Після пошуку цієї невідповідності я виявив, що файл, ймовірно, створений в 2012-05-23 (як показав Windows Explorer), але скопійований у поточну папку в 2012-05-29 (як показано командою File.GetCreationTime) - так щоб бути в безпечній стороні, я використовую команду File.GetLastWriteTime.

Залек


4
Я не впевнений, чи це захист від копіювання виконуваного файлу на диски / комп’ютери / мережі.
Стелс-раббі

це перше, що має на увазі, але ви знаєте, що це не надійно, існує багато програмного забезпечення, яке використовується для переміщення файлів по мережі, які не оновлюють атрибути після завантаження, я б пішов з відповіддю @ Абдуррахіма.
Мубашар

Я знаю, що це давнє, але я просто виявив, що за допомогою подібного коду процес INSTALL (принаймні при використанні clickkonce) оновлює час складання файлу. Не дуже корисно. Не впевнений, що це стосуватиметься цього рішення.
bobwki

Ви, мабуть, дуже хочете цього LastWriteTime, оскільки це точно відображає час фактичного оновлення виконуваного файлу.
David R

Вибачте, але час запису виконуваного файлу не є надійною ознакою часу збирання. Штамп часу файлу може бути переписаний через усілякі речі, які знаходяться поза вашою сферою впливу.
Том

15

Тут багато чудових відповідей, але я відчуваю, що можу додати свої власні через простоту, продуктивність (порівняно з рішеннями, пов’язаними з ресурсами) крос-платформу (також працює з Net Core) та уникнення будь-якого стороннього інструменту. Просто додайте цю цільову програму msbuild до csproj.

<Target Name="Date" BeforeTargets="CoreCompile">
    <WriteLinesToFile File="$(IntermediateOutputPath)gen.cs" Lines="static partial class Builtin { public static long CompileTime = $([System.DateTime]::UtcNow.Ticks) %3B }" Overwrite="true" />
    <ItemGroup>
        <Compile Include="$(IntermediateOutputPath)gen.cs" />
    </ItemGroup>
</Target>

і тепер у вас є Builtin.CompileTimeабоnew DateTime(Builtin.CompileTime, DateTimeKind.Utc) якщо вам це потрібно.

ReSharper це не сподобається. Ви можете його проігнорувати або додати частковий клас до проекту, але він все одно працює.


Я можу створювати це і розвивати локально (запускати веб-сайти) в ASP.NET Core 2.1, але публікація веб-розгортань з VS 2017 не вдається з помилкою "Назва" Вбудований "не існує в поточному контексті". ДОПОЛНЕННЯ: Якщо я отримую доступ Builtin.CompileTimeіз виду Бритви.
Джеремі Кук

У цьому випадку я думаю, що вам просто потрібно, BeforeTargets="RazorCoreCompile"але лише тоді, коли це в тому ж проекті
Дмитро Гусаров

круто, але як ми ставимося до створеного об’єкта? мені здається, у відповіді відсутня ключова частина ...
Маттео

1
@Matteo, як зазначено у відповіді, ви можете використовувати "Builtin.CompileTime" або "новий DateTime (Builtin.CompileTime, DateTimeKind.Utc)". Visual Studio IntelliSense здатна побачити це відразу. Старий ReSharper може скаржитися на час розробки, але схоже, що вони виправили це в нових версіях. clip2net.com/s/46rgaaO
Дмитро Гусаров

Я використовував цю версію, тому для отримання дати не потрібен додатковий код. Також resharper не скаржиться на останню версію. <WriteLinesToFile File = "$ (IntermediateOutputPath) BuildInfo.cs" Lines = "з використанням внутрішнього статичного часткового класу BuildInfo System% 3B BuildInfo {public static long DateBuiltTicks = $ ([System.DateTime] :: UtcNow.Ticks)% 3B public static DateTime DateBuilt => новий DateTime (DateBuiltTicks, DateTimeKind.Utc)% 3B} "Перезаписати =" true "/>
Softlion

13

Для проектів .NET Core я адаптував відповідь Postlagerkarte для оновлення поля авторських прав на збірку з датою створення.

Безпосередньо редагуйте csproj

До першого PropertyGroupв csproj можна додати:

<Copyright>Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))</Copyright>

Альтернатива: Властивості проекту Visual Studio

Або вставити внутрішнє вираження безпосередньо в поле «Авторські права» в розділі «Пакет» властивостей проекту в Visual Studio:

Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))

Це може бути трохи заплутано, оскільки Visual Studio оцінить вираз і відобразить поточне значення у вікні, але також оновить файл проекту відповідним чином за кадром.

Загальне рішення через Directory.Build.props

Ви можете спланувати <Copyright>елемент вище у Directory.Build.propsфайл у вашому корені рішення та автоматично застосувати його до всіх проектів у каталозі, якщо кожен проект не надає власного значення авторського права.

<Project>
 <PropertyGroup>
   <Copyright>Copyright © $([System.DateTime]::UtcNow.Year) Travis Troyer ($([System.DateTime]::UtcNow.ToString("s")))</Copyright>
 </PropertyGroup>
</Project>

Directory.Build.props: налаштувати збірку

Вихідні дані

Приклад виразу дасть вам таке авторське право:

Copyright © 2018 Travis Troyer (2018-05-30T14:46:23)

Пошук

Ви можете переглянути інформацію про авторські права із властивостей файлу в Windows або схопити її під час виконання:

var version = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location);

Console.WriteLine(version.LegalCopyright);

11

Вищеописаний метод можна змінити для збірок, які вже завантажені в процесі , використовуючи зображення файлу в пам'яті (на відміну від повторного читання його зі сховища):

using System;
using System.Runtime.InteropServices;
using Assembly = System.Reflection.Assembly;

static class Utils
{
    public static DateTime GetLinkerDateTime(this Assembly assembly, TimeZoneInfo tzi = null)
    {
        // Constants related to the Windows PE file format.
        const int PE_HEADER_OFFSET = 60;
        const int LINKER_TIMESTAMP_OFFSET = 8;

        // Discover the base memory address where our assembly is loaded
        var entryModule = assembly.ManifestModule;
        var hMod = Marshal.GetHINSTANCE(entryModule);
        if (hMod == IntPtr.Zero - 1) throw new Exception("Failed to get HINSTANCE.");

        // Read the linker timestamp
        var offset = Marshal.ReadInt32(hMod, PE_HEADER_OFFSET);
        var secondsSince1970 = Marshal.ReadInt32(hMod, offset + LINKER_TIMESTAMP_OFFSET);

        // Convert the timestamp to a DateTime
        var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        var linkTimeUtc = epoch.AddSeconds(secondsSince1970);
        var dt = TimeZoneInfo.ConvertTimeFromUtc(linkTimeUtc, tzi ?? TimeZoneInfo.Local);
        return dt;
    }
}

Цей відмінно працює навіть у рамках 4.7 Використання: Utils.GetLinkerDateTime (Assembly.GetExecutingAssembly (), null))
real_yggdrasil

Чудово працює! Дякую!
bobwki

10

Для всіх, кому потрібно отримати час компіляції в Windows 8 / Windows Phone 8:

    public static async Task<DateTimeOffset?> RetrieveLinkerTimestamp(Assembly assembly)
    {
        var pkg = Windows.ApplicationModel.Package.Current;
        if (null == pkg)
        {
            return null;
        }

        var assemblyFile = await pkg.InstalledLocation.GetFileAsync(assembly.ManifestModule.Name);
        if (null == assemblyFile)
        {
            return null;
        }

        using (var stream = await assemblyFile.OpenSequentialReadAsync())
        {
            using (var reader = new DataReader(stream))
            {
                const int PeHeaderOffset = 60;
                const int LinkerTimestampOffset = 8;

                //read first 2048 bytes from the assembly file.
                byte[] b = new byte[2048];
                await reader.LoadAsync((uint)b.Length);
                reader.ReadBytes(b);
                reader.DetachStream();

                //get the pe header offset
                int i = System.BitConverter.ToInt32(b, PeHeaderOffset);

                //read the linker timestamp from the PE header
                int secondsSince1970 = System.BitConverter.ToInt32(b, i + LinkerTimestampOffset);

                var dt = new DateTimeOffset(1970, 1, 1, 0, 0, 0, DateTimeOffset.Now.Offset) + DateTimeOffset.Now.Offset;
                return dt.AddSeconds(secondsSince1970);
            }
        }
    }

Для всіх, кому потрібно отримати час компіляції в Windows Phone 7:

    public static async Task<DateTimeOffset?> RetrieveLinkerTimestampAsync(Assembly assembly)
    {
        const int PeHeaderOffset = 60;
        const int LinkerTimestampOffset = 8;            
        byte[] b = new byte[2048];

        try
        {
            var rs = Application.GetResourceStream(new Uri(assembly.ManifestModule.Name, UriKind.Relative));
            using (var s = rs.Stream)
            {
                var asyncResult = s.BeginRead(b, 0, b.Length, null, null);
                int bytesRead = await Task.Factory.FromAsync<int>(asyncResult, s.EndRead);
            }
        }
        catch (System.IO.IOException)
        {
            return null;
        }

        int i = System.BitConverter.ToInt32(b, PeHeaderOffset);
        int secondsSince1970 = System.BitConverter.ToInt32(b, i + LinkerTimestampOffset);
        var dt = new DateTimeOffset(1970, 1, 1, 0, 0, 0, DateTimeOffset.Now.Offset) + DateTimeOffset.Now.Offset;
        dt = dt.AddSeconds(secondsSince1970);
        return dt;
    }

ПРИМІТКА. У всіх випадках ви працюєте в пісочниці, тому ви зможете отримати лише час складання збірок, які ви розгортаєте зі своїм додатком. (тобто це не працюватиме ні на що в GAC).


Ось як ви отримуєте асамблею в WP 8.1:var assembly = typeof (AnyTypeInYourAssembly).GetTypeInfo().Assembly;
Андре Фідлер,

Що робити, якщо ви хочете запустити свій код в обох системах? - чи застосовується один із цих методів для обох платформ?
bvdb

10

У 2018 році деякі з перерахованих вище рішень більше не працюють або не працюють з .NET Core.

Я використовую такий підхід, який простий і працює для мого проекту .NET Core 2.0.

Додайте до свого .csproj всередині PropertyGroup:

    <Today>$([System.DateTime]::Now)</Today>

Це визначає PropertyFunction якого можна отримати доступ у команді попереднього збирання.

Ваша попередня збірка виглядає приблизно так

echo $(today) > $(ProjectDir)BuildTimeStamp.txt

Встановіть властивість BuildTimeStamp.txt на Вбудований ресурс.

Тепер ви можете прочитати позначку часу так

public static class BuildTimeStamp
    {
        public static string GetTimestamp()
        {
            var assembly = Assembly.GetEntryAssembly(); 

            var stream = assembly.GetManifestResourceStream("NamespaceGoesHere.BuildTimeStamp.txt");

            using (var reader = new StreamReader(stream))
            {
                return reader.ReadToEnd();
            }
        }
    }

Так само працює генерація BuildTimeStamp.txt з подій перед збіркою за допомогою команд пакетного сценарію . Зверніть увагу, що ви помилилися там: ви повинні оточити свою ціль у лапках (наприклад "$(ProjectDir)BuildTimeStamp.txt"), інакше вона зламається, коли в іменах папки будуть пробіли.
Nyerguds

Можливо, має сенс використовувати інваріантний формат часу культури. $([System.DateTime]::Now.tostring("MM/dd/yyyy HH:mm:ss"))$([System.DateTime]::Now)
Ось так

9

Тут не обговорюється варіант - вставити власні дані у AssemblyInfo.cs, поле "AssemblyInformationalVersion" видається відповідним - у нас є кілька проектів, де ми робили щось подібне, як крок збирання (однак я не зовсім задоволений Таким чином, це не дуже хочеться відтворювати те, що у нас є).

Стаття з цього питання про codeproject: http://www.codeproject.com/KB/dotnet/Customizing_csproj_files.aspx


6

Мені потрібно було універсальне рішення, яке працювало з проектом NETStandard на будь-якій платформі (iOS, Android та Windows.) Для цього я вирішив автоматично генерувати CS-файл за допомогою сценарію PowerShell. Ось сценарій PowerShell:

param($outputFile="BuildDate.cs")

$buildDate = Get-Date -date (Get-Date).ToUniversalTime() -Format o
$class = 
"using System;
using System.Globalization;

namespace MyNamespace
{
    public static class BuildDate
    {
        public const string BuildDateString = `"$buildDate`";
        public static readonly DateTime BuildDateUtc = DateTime.Parse(BuildDateString, null, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
    }
}"

Set-Content -Path $outputFile -Value $class

Збережіть файл PowerScript як GenBuildDate.ps1 та додайте його у свій проект. Нарешті, додайте наступний рядок до своєї події перед збіркою:

powershell -File $(ProjectDir)GenBuildDate.ps1 -outputFile $(ProjectDir)BuildDate.cs

Переконайтеся, що BuildDate.cs включений у ваш проект. Працює як чемпіон на будь-якій ОС!


1
Ви також можете скористатися цим, щоб отримати номер редакції SVN за допомогою інструмента командного рядка svn. З цим я зробив щось подібне.
користувач169771

5

Я просто роблю:

File.GetCreationTime(GetType().Assembly.Location)

1
Цікаво, що якщо ви працюєте з налагодження, "справжньою" датою є GetLastAccessTime ()
балінт

4

Ви можете використовувати цей проект: https://github.com/dwcullop/BuildInfo

Він використовує T4 для автоматизації часових позначок дати складання. Існує кілька версій (різних гілок), включаючи таку, яка дає вам Git Hash поточно перевіреної гілки, якщо ви займаєтесь такою справою.

Розкриття: Я написав модуль.


3

Іншим підходом до PCL-підходу буде використання вбудованого завдання MSBuild для заміни часу складання на рядок, який повертається властивістю у додатку. Ми успішно використовуємо цей підхід у програмі, яка має проекти Xamarin.Forms, Xamarin.Android та Xamarin.iOS.

Редагувати:

Спрощено переміщенням усієї логіки у SetBuildDate.targetsфайл, а Regexзамість простої строки заміни, щоб файл міг змінювати кожну збірку без "скидання".

Вбудоване визначення завдання MSBuild (для цього прикладу зберігається у файлі SetBuildDate.targets, локальному для проекту Xamarin.Forms):

<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' ToolsVersion="12.0">

  <UsingTask TaskName="SetBuildDate" TaskFactory="CodeTaskFactory" 
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll">
    <ParameterGroup>
      <FilePath ParameterType="System.String" Required="true" />
    </ParameterGroup>
    <Task>
      <Code Type="Fragment" Language="cs"><![CDATA[

        DateTime now = DateTime.UtcNow;
        string buildDate = now.ToString("F");
        string replacement = string.Format("BuildDate => \"{0}\"", buildDate);
        string pattern = @"BuildDate => ""([^""]*)""";
        string content = File.ReadAllText(FilePath);
        System.Text.RegularExpressions.Regex rgx = new System.Text.RegularExpressions.Regex(pattern);
        content = rgx.Replace(content, replacement);
        File.WriteAllText(FilePath, content);
        File.SetLastWriteTimeUtc(FilePath, now);

   ]]></Code>
    </Task>
  </UsingTask>

</Project>

Викликати вищезазначене вбудоване завдання у файл Xamarin.Forms csproj у цільовій команді BeforeBuild:

  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
       Other similar extension points exist, see Microsoft.Common.targets.  -->
  <Import Project="SetBuildDate.targets" />
  <Target Name="BeforeBuild">
    <SetBuildDate FilePath="$(MSBuildProjectDirectory)\BuildMetadata.cs" />
  </Target>

FilePathВластивість встановлено в BuildMetadata.csфайл в проекті Xamarin.Forms , який містить простий клас з властивістю рядка BuildDate, в якій час збирання буде замінено:

public class BuildMetadata
{
    public static string BuildDate => "This can be any arbitrary string";
}

Додайте цей файл BuildMetadata.csдо проекту. Він буде модифікований кожною збіркою, але таким чином, що дозволяє повторювати збірки (повторні заміни), тому ви можете включити або опустити його в керування джерелом за бажанням.


2

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



2

Невелике оновлення відповіді "Новий шлях" від Джона.

Вам потрібно побудувати шлях замість використання рядка CodeBase під час роботи з ASP.NET/MVC

    var codeBase = assembly.GetName().CodeBase;
    UriBuilder uri = new UriBuilder(codeBase);
    string path = Uri.UnescapeDataString(uri.Path);

1

Ви можете запустити додатковий крок у процесі збирання, який записує штамп дати у файл, який потім може бути відображений.

На вкладці "Властивості проектів" дивіться на вкладку "Події збирання". Є можливість виконати команду до або після збірки.


1

Я використав пропозицію Абдуррахіма. Однак, здавалося, він надає дивний формат часу, а також додав абревіатуру для дня як частину дати складання; приклад: Нд 24.12.2017 13: 21: 05.43. Мені потрібна була лише дата, тому мені довелося усунути решту за допомогою підрядки.

Після додавання echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"до події перед збіркою я просто зробив наступне:

string strBuildDate = YourNamespace.Properties.Resources.BuildDate;
string strTrimBuildDate = strBuildDate.Substring(4).Remove(10);

Хороша новина тут - це спрацювало.


Дуже просте рішення. Мені це подобається. І якщо формат турбує, є способи отримати кращий формат з командного рядка.
Nyerguds

0

Якщо це програма для Windows, ви можете просто скористатися виконуваним шляхом програми: новий System.IO.FileInfo (Application.ExecutablePath) .LastWriteTime.ToString ("yyyy.MM.dd")


2
Вже і відповідайте, використовуючи це, а також не зовсім бронежилет.
crashmstr

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.