Affichage de la date de construction

260

J'ai actuellement une application affichant le numéro de build dans sa fenêtre de titre. C'est bien beau, sauf que cela ne signifie rien pour la plupart des utilisateurs, qui veulent savoir s'ils ont la dernière version - ils ont tendance à s'y référer comme "jeudi dernier" plutôt qu'à la version 1.0.8.4321.

Le plan est de mettre la date de construction à la place - Donc, "Application construite le 21/10/2009" par exemple.

J'ai du mal à trouver un moyen programmatique pour extraire la date de construction sous forme de chaîne de texte à utiliser comme ceci.

Pour le numéro de build, j'ai utilisé:

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

après avoir défini comment ceux-ci sont venus.

J'aimerais quelque chose comme ça pour la date de compilation (et l'heure, pour les points bonus).

Des pointeurs ici très appréciés (excuse jeu de mots si approprié), ou des solutions plus soignées ...

Mark Mayo
la source
2
J'ai essayé les moyens fournis pour obtenir les données de construction des assemblys qui fonctionnent dans des scénarios simples, mais si deux assemblages sont fusionnés, je n'ai pas le bon temps de construction, c'est une heure dans le futur .. des suggestions?

Réponses:

356

Jeff Atwood avait quelques choses à dire sur ce problème dans Déterminer la date de construction à la dure .

La méthode la plus fiable s'avère être la récupération de l'horodatage de l'éditeur de liens à partir de l'en- tête PE intégré dans le fichier exécutable - du code C # (par Joe Spivey) pour cela dans les commentaires de l'article de Jeff:

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;
}

Exemple d'utilisation:

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

MISE À JOUR: La méthode fonctionnait pour .Net Core 1.0, mais a cessé de fonctionner après la sortie de .Net Core 1.1 (donne des années aléatoires dans la plage 1900-2020)

mdb
la source
8
J'ai un peu changé mon ton à ce sujet, je serais toujours très prudent lorsque je creuserais dans l'en-tête PE acutal. Mais pour autant que je sache, ce truc PE est beaucoup plus fiable que d'utiliser les numéros de version, en plus je ne veux pas attribuer les numéros de version séparément de la date de construction.
John Leidegren
6
J'aime ça et je l'utilise, mais cette avant-dernière ligne avec le .AddHours()est plutôt hackish et (je pense) ne tiendra pas compte de l'heure d'été. Si vous le souhaitez à l'heure locale, vous devez utiliser le nettoyeur à la dt.ToLocalTime();place. La partie centrale pourrait également être grandement simplifiée avec un using()bloc.
JLRishe
6
Oui, cela a également cessé de fonctionner pour moi avec le noyau .net (années 1940, 1960, etc.)
eoleary
7
Bien que l'utilisation de l'en-tête PE puisse sembler une bonne option aujourd'hui, il convient de noter que MS expérimente des constructions déterministes (ce qui rendrait cet en-tête inutile) et peut-être même en faisant par défaut dans les futures versions de compilateur de C # (pour de bonnes raisons). Bonne lecture: blog.paranoidcoding.com/2016/04/05/… et voici la réponse relative à .NET Core (TLDR: "it's by design"): developercommunity.visualstudio.com/content/problem/35873/…
Paweł Bulwan
13
Pour ceux qui trouvent que cela ne fonctionne plus, le problème n'est pas un problème .NET Core. Voir ma réponse ci-dessous à propos des nouveaux paramètres par défaut des paramètres de construction à partir de Visual Studio 15.4.
Tom
107

Ajoutez ci-dessous à la ligne de commande d'événement de pré-génération:

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

Ajoutez ce fichier en tant que ressource, vous avez maintenant la chaîne 'BuildDate' dans vos ressources.

Pour créer des ressources, consultez Comment créer et utiliser des ressources dans .NET .

Abdurrahim
la source
4
+1 de moi, simple et efficace. J'ai même réussi à obtenir la valeur du fichier avec une ligne de code comme celle-ci: String buildDate = <MyClassLibraryName> .Properties.Resources.BuildDate
davidfrancis
11
une autre option est de créer une classe: (devez inclure dans le projet après la première compilation) -> echo namespace My.app.namespace {public static class Build {public static string Timestamp = "% DATE%% TIME%" .Substring (0,16);}}> "$ (ProjectDir) \ BuildTimestamp.cs" - - - -> peut ensuite l'appeler avec Build.Timestamp
FabianSilva
9
Ceci est une excellente solution. Le seul problème est que les variables de ligne de commande% date% et% time% sont localisées, donc la sortie variera en fonction de la langue Windows de l'utilisateur.
VS
2
+1, c'est une meilleure méthode que de lire les en-têtes PE - car il existe plusieurs scénarios où cela ne fonctionnera pas du tout (application Windows Phone par exemple)
Matt Whitfield
17
Intelligent. Vous pouvez également utiliser powershell pour obtenir un contrôle plus précis sur le format, par exemple pour obtenir un datetime UTC au format ISO8601: powershell -Command "((Get-Date) .ToUniversalTime ()). ToString (\" s \ ") | Out-File '$ (ProjectDir) Resources \ BuildDate.txt' "
déconnexion
90

Le chemin

Comme l'a souligné @ c00000fd dans les commentaires . Microsoft change cela. Et bien que beaucoup de gens n'utilisent pas la dernière version de leur compilateur, je soupçonne que ce changement rend cette approche incontestablement mauvaise. Et bien que ce soit un exercice amusant, je recommanderais aux gens d'incorporer simplement une date de construction dans leur binaire par tout autre moyen nécessaire s'il est important de suivre la date de construction du binaire lui-même.

Cela peut être fait avec une génération de code triviale qui est probablement déjà la première étape de votre script de construction. Cela, et le fait que les outils ALM / Build / DevOps aident beaucoup à cela et devraient être préférés à toute autre chose.

Je laisse le reste de cette réponse ici à des fins historiques uniquement.

La nouvelle façon

J'ai changé d'avis à ce sujet et j'utilise actuellement cette astuce pour obtenir la bonne date de construction.

#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

L'ancienne façon

Eh bien, comment générer des numéros de build? Visual Studio (ou le compilateur C #) fournit en fait des numéros de construction et de révision automatiques si vous changez l'attribut AssemblyVersion par exemple1.0.*

Ce qui se passera, c'est que la génération sera égale au nombre de jours depuis le 1er janvier 2000, heure locale, et que la révision soit égale au nombre de secondes depuis minuit, heure locale, divisé par 2.

voir les numéros de contenu communautaire, de génération automatique et de révision

par exemple 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)
John Leidegren
la source
3
Je viens d'ajouter une heure siTimeZone.CurrentTimeZone.IsDaylightSavingTime(buildDateTime) == true
e4rthdog
2
Malheureusement, j'ai utilisé cette approche sans l'examiner minutieusement, et cela nous mord en production. Le problème est que lorsque le compilateur JIT démarre dans les informations d'en-tête PE est modifié. D'où le downvote. Maintenant, je vais faire des «recherches» inutiles pour expliquer pourquoi nous considérons la date d'installation comme la date de construction.
Jason D
8
@JasonD Dans quel univers votre problème devient-il en quelque sorte mon problème? Comment justifiez-vous un downvote simplement parce que vous avez rencontré un problème que cette implémentation n'a pas pris en considération. Vous l'avez obtenu gratuitement et vous l'avez mal testé. De plus, qu'est-ce qui vous fait croire que l'en-tête est en cours de réécriture par le compilateur JIT? Lisez-vous ces informations depuis la mémoire du processus ou depuis un fichier?
John Leidegren
6
J'ai remarqué que si vous exécutez dans une application Web, la propriété .Codebase semble être une URL (fichier: // c: /path/to/binary.dll). Cela provoque l'échec de l'appel File.Exists. L'utilisation de "assembly.Location" au lieu de la propriété CodeBase a résolu le problème pour moi.
mdryden
2
@JohnLeidegren: Ne comptez pas sur l'en-tête Windows PE pour cela. Depuis Windows 10 et les versions reproductibles , le IMAGE_FILE_HEADER::TimeDateStampchamp est défini sur un nombre aléatoire et n'est plus un horodatage.
c00000fd
51

Ajoutez ci-dessous à la ligne de commande d'événement de pré-génération:

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

Ajoutez ce fichier en tant que ressource, vous avez maintenant la chaîne 'BuildDate' dans vos ressources.

Après avoir inséré le fichier dans la ressource (en tant que fichier texte public), je l'ai accédé via

string strCompTime = Properties.Resources.BuildDate;

Pour créer des ressources, consultez Comment créer et utiliser des ressources dans .NET .

brewmanz
la source
1
@DavidGorsline - la démarque du commentaire était correcte car elle cite cette autre réponse . J'ai une réputation insuffisante pour annuler votre changement, sinon je l'aurais fait moi-même.
Wai Ha Lee
1
@Wai Ha Lee - a) la réponse que vous citez ne donne pas de code pour récupérer réellement la date / heure de compilation. b) à l'époque je n'avais pas assez de réputation pour ajouter un commentaire à cette réponse (ce que j'aurais fait), seulement pour poster. donc c) J'ai posté en donnant une réponse complète afin que les gens puissent obtenir tous les détails dans un seul domaine ..
brewmanz
Si vous voyez Úte% au lieu de% date%, vérifiez ici: developercommunity.visualstudio.com/content/problem/237752/… En résumé, faites ceci: echo% 25date% 25% 25time% 25
Qodex
41

Une approche que je suis étonné que personne n'a encore mentionnée consiste à utiliser des modèles de texte T4 pour la génération de code.

<#@ 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); } }
    }
}

Avantages:

  • Indépendant des paramètres régionaux
  • Permet bien plus que le temps de la compilation

Les inconvénients:

Peter Taylor
la source
1
C'est donc maintenant la meilleure réponse. 324 points avant qu'il ne devienne la réponse la plus votée :). Stackoverflow a besoin d'un moyen de montrer le grimpeur le plus rapide.
pauldendulk
1
@pauldendulk, n'aiderait pas beaucoup, car la réponse la plus votée et la réponse acceptée recueillent presque toujours les votes le plus rapidement. La réponse acceptée à cette question a + 60 / -2 depuis que j'ai posté cette réponse.
Peter Taylor
Je pense que vous devez ajouter un .ToString () à vos ticks (sinon j'obtiens une erreur de compilation). Cela dit, je suis confronté à une courbe d'apprentissage abrupte ici, pourriez-vous montrer comment L'UTILISER également dans le programme principal?
Andy
@Andy, vous avez raison sur la ToString (). L'utilisation est juste Constants.CompilationTimestampUtc. Si VS ne génère pas de fichier C # avec la classe, vous devez trouver comment le faire, mais la réponse dépend (au moins) de la version de VS et du type de fichier csproj, il est donc trop de détails pour ce post.
Peter Taylor
1
Au cas où d'autres se poseraient la question, voici ce qu'il a fallu pour le faire fonctionner sur VS 2017: j'ai dû en faire un modèle Design Time T4 (il m'a fallu un certain temps pour comprendre, j'ai d'abord ajouté un modèle de préprocesseur). J'ai également dû inclure cet assemblage: Microsoft.VisualStudio.TextTemplating.Interfaces.10.0 comme référence au projet. Enfin mon modèle devait inclure "using System;" avant l'espace de noms, sinon la référence à DateTime a échoué.
Andy
20

En ce qui concerne la technique d'extraction des informations de date / version de génération à partir des octets d'un en-tête PE d'assembly, Microsoft a modifié les paramètres de génération par défaut à partir de Visual Studio 15.4. La nouvelle valeur par défaut inclut la compilation déterministe, ce qui fait qu'un horodatage valide et des numéros de version incrémentés automatiquement appartiennent au passé. Le champ d'horodatage est toujours présent mais il est rempli avec une valeur permanente qui est un hachage de quelque chose ou autre, mais pas d'indication du temps de construction.

Quelques informations détaillées ici

Pour ceux qui priorisent un horodatage utile sur la compilation déterministe, il existe un moyen de remplacer la nouvelle valeur par défaut. Vous pouvez inclure une balise dans le fichier .csproj de l'assemblage d'intérêt comme suit:

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

Mise à jour: J'approuve la solution de modèle de texte T4 décrite dans une autre réponse ici. Je l'ai utilisé pour résoudre mon problème proprement sans perdre l'avantage de la compilation déterministe. Une mise en garde à ce sujet est que Visual Studio n'exécute le compilateur T4 que lorsque le fichier .tt est enregistré, pas au moment de la génération. Cela peut être gênant si vous excluez le résultat .cs du contrôle de code source (car vous vous attendez à ce qu'il soit généré) et qu'un autre développeur extrait le code. Sans réenregistrement, ils n'auront pas le fichier .cs. Il y a un paquet sur nuget (je pense appelé AutoT4) qui fait de la compilation T4 une partie de chaque build. Je n'ai pas encore confronté la solution à cela lors du déploiement de la production, mais j'attends quelque chose de similaire pour faire les choses correctement.

À M
la source
Cela a résolu mon problème dans un sln qui utilise la réponse la plus ancienne.
pauldendulk
Votre prudence au sujet du T4 est parfaitement juste, mais notez qu'elle est déjà présente dans ma réponse.
Peter Taylor
15

Je suis juste un débutant en C #, alors peut-être que ma réponse semble idiote - j'affiche la date de construction à partir de la date à laquelle le fichier exécutable a été écrit pour la dernière fois:

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());

J'ai essayé d'utiliser la méthode File.GetCreationTime mais j'ai obtenu des résultats étranges: la date de la commande était 2012-05-29, mais la date de l'explorateur de fenêtres montrait 2012-05-23. Après avoir recherché cette différence, j'ai constaté que le fichier a probablement été créé le 2012-05-23 (comme indiqué par l'Explorateur Windows), mais copié dans le dossier actuel le 2012-05-29 (comme indiqué par la commande File.GetCreationTime) - donc pour être du bon côté, j'utilise la commande File.GetLastWriteTime.

Zalek

Zalek Bloom
la source
4
Je ne suis pas sûr que ce soit à l'épreuve des balles en copiant l'exécutable sur des lecteurs / ordinateurs / réseaux.
Rabbin furtif du
c'est la première chose qui vient à l'esprit mais vous savez que ce n'est pas fiable, il existe de nombreux logiciels utilisés pour déplacer les fichiers sur le réseau qui ne mettent pas à jour les attributs après le téléchargement, j'irais avec la réponse de @ Abdurrahim.
Mubashar
Je sais que c'est ancien, mais je viens de découvrir avec un code similaire que le processus d'installation (au moins lors de l'utilisation de clickonce) met à jour l'heure du fichier d'assemblage. Pas très utile. Je ne sais pas si cela s'appliquerait à cette solution.
bobwki
Vous voulez probablement vraiment le LastWriteTime, car cela reflète précisément l'heure à laquelle le fichier exécutable a été réellement mis à jour.
David R Tribble
Désolé mais un temps d'écriture de fichier exécutable n'est pas une indication fiable du temps de construction. L'horodatage du fichier peut être réécrit en raison de toutes sortes de choses qui sont en dehors de votre sphère d'influence.
Tom
15

Beaucoup de bonnes réponses ici, mais je pense pouvoir ajouter les miennes en raison de la simplicité, des performances (par rapport aux solutions liées aux ressources) multiplateforme (fonctionne également avec Net Core) et de l'évitement de tout outil tiers. Ajoutez simplement cette cible msbuild au 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>

et maintenant vous en avez Builtin.CompileTimeou new DateTime(Builtin.CompileTime, DateTimeKind.Utc)si vous en avez besoin.

ReSharper ne va pas aimer ça. Vous pouvez également l'ignorer ou ajouter une classe partielle au projet, mais cela fonctionne quand même.

Dmitry Gusarov
la source
Je peux construire avec cela et développer localement (exécuter des sites Web) dans ASP.NET Core 2.1 mais la publication de déploiement Web à partir de VS 2017 échoue avec l'erreur "Le nom 'Builtin' n'existe pas dans le contexte actuel". ADDITION: Si j'accède à Builtin.CompileTimepartir d'une vue Razor.
Jeremy Cook
Dans ce cas, je pense que vous avez juste besoin, BeforeTargets="RazorCoreCompile"mais seulement pendant que c'est dans le même projet
Dmitry Gusarov
cool, mais comment se réfère-t-on à l'objet généré? il me semble que la réponse manque un élément clé ...
Matteo
1
@Matteo, comme mentionné dans la réponse, vous pouvez utiliser "Builtin.CompileTime" ou "new DateTime (Builtin.CompileTime, DateTimeKind.Utc)". Visual Studio IntelliSense est capable de le voir immédiatement. Un ancien ReSharper peut se plaindre au moment du design, mais il semble qu'il ait corrigé cela dans les nouvelles versions. clip2net.com/s/46rgaaO
Dmitry Gusarov
J'ai utilisé cette version, donc pas besoin de code supplémentaire pour obtenir la date. Resharper ne se plaint pas non plus de sa dernière version. <WriteLinesToFile File = "$ (IntermediateOutputPath) BuildInfo.cs" Lines = "utilisant la classe partielle statique interne System% 3B BuildInfo {public static long DateBuiltTicks = $ ([System.DateTime] :: UtcNow.Ticks)% 3B public static DateTime DateBuilt => nouveau DateTime (DateBuiltTicks, DateTimeKind.Utc)% 3B} "Overwrite =" true "/>
Softlion
13

Pour les projets .NET Core, j'ai adapté la réponse de Postlagerkarte pour mettre à jour le champ Copyright de l'assembly avec la date de construction.

Modifier directement csproj

Les éléments suivants peuvent être ajoutés directement au premier PropertyGroupdans le csproj:

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

Alternative: Propriétés du projet Visual Studio

Ou collez l'expression interne directement dans le champ Copyright de la section Package des propriétés du projet dans Visual Studio:

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

Cela peut être un peu déroutant, car Visual Studio évaluera l'expression et affichera la valeur actuelle dans la fenêtre, mais il mettra également à jour le fichier de projet de manière appropriée dans les coulisses.

À l'échelle de la solution via Directory.Build.props

Vous pouvez placer l' <Copyright>élément ci-dessus dans un Directory.Build.propsfichier dans la racine de votre solution et le faire appliquer automatiquement à tous les projets du répertoire, en supposant que chaque projet ne fournit pas sa propre valeur de copyright.

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

Directory.Build.props: Personnalisez votre build

Production

L'exemple d'exemple vous donnera un copyright comme celui-ci:

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

Récupération

Vous pouvez afficher les informations de copyright à partir des propriétés du fichier dans Windows ou les récupérer lors de l'exécution:

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

Console.WriteLine(version.LegalCopyright);
Travis Troyer
la source
11

La méthode ci-dessus peut être modifiée pour les assemblys déjà chargés dans le processus en utilisant l'image du fichier en mémoire (par opposition à la relire à partir du stockage):

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;
    }
}
tcostin
la source
Celui-ci fonctionne très bien, même pour le framework 4.7 Utilisation: Utils.GetLinkerDateTime (Assembly.GetExecutingAssembly (), null))
real_yggdrasil
Fonctionne très bien! Merci!
bobwki
10

Pour tous ceux qui ont besoin d'obtenir le temps de compilation sous 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);
            }
        }
    }

Pour tous ceux qui ont besoin d'obtenir le temps de compilation dans 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;
    }

REMARQUE: Dans tous les cas, vous exécutez dans un bac à sable, vous ne pourrez donc obtenir que l'heure de compilation des assemblys que vous déployez avec votre application. (c'est-à-dire que cela ne fonctionnera sur rien dans le GAC).

Matt Dotson
la source
Voici comment vous obtenez l'Assemblée dans WP 8.1:var assembly = typeof (AnyTypeInYourAssembly).GetTypeInfo().Assembly;
André Fiedler
Et si vous souhaitez exécuter votre code sur les deux systèmes? - l'une de ces méthodes est-elle applicable aux deux plates-formes?
bvdb
10

En 2018, certaines des solutions ci-dessus ne fonctionnent plus ou ne fonctionnent pas avec .NET Core.

J'utilise l'approche suivante qui est simple et fonctionne pour mon projet .NET Core 2.0.

Ajoutez ce qui suit à votre .csproj dans le PropertyGroup:

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

Cela définit une PropertyFunction à laquelle vous pouvez accéder dans votre commande de pré-génération.

Votre pré-build ressemble à ceci

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

Définissez la propriété de BuildTimeStamp.txt sur Ressource intégrée.

Vous pouvez maintenant lire l'horodatage comme ceci

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();
            }
        }
    }
Postlagerkarte
la source
Il suffit également de générer ce BuildTimeStamp.txt à partir des événements de pré-génération à l' aide de commandes de script batch . Notez que vous avez fait une erreur là-bas: vous devez entourer votre cible entre guillemets (par exemple "$(ProjectDir)BuildTimeStamp.txt") ou elle se brisera lorsqu'il y aura des espaces dans les noms de dossier.
Nyerguds
Il est peut-être judicieux d'utiliser le format temporel invariant de la culture. Comme ceci: $([System.DateTime]::Now.tostring("MM/dd/yyyy HH:mm:ss"))au lieu de$([System.DateTime]::Now)
Ivan Kochurkin
9

L'option non discutée ici est d'insérer vos propres données dans AssemblyInfo.cs, le champ "AssemblyInformationalVersion" semble approprié - nous avons quelques projets où nous faisions quelque chose de similaire en tant qu'étape de construction (mais je ne suis pas entièrement satisfait du manière qui fonctionne, donc je ne veux pas vraiment reproduire ce que nous avons).

Il y a un article sur le sujet sur codeproject: http://www.codeproject.com/KB/dotnet/Customizing_csproj_files.aspx

Murph
la source
6

J'avais besoin d'une solution universelle qui fonctionnait avec un projet NETStandard sur n'importe quelle plate-forme (iOS, Android et Windows.) Pour ce faire, j'ai décidé de générer automatiquement un fichier CS via un script PowerShell. Voici le script 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

Enregistrez le fichier PowerScript sous GenBuildDate.ps1 et ajoutez-le à votre projet. Enfin, ajoutez la ligne suivante à votre événement Pre-Build:

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

Assurez-vous que BuildDate.cs est inclus dans votre projet. Fonctionne comme un champion sur n'importe quel OS!

David Tosi
la source
1
Vous pouvez également l'utiliser pour obtenir le numéro de révision SVN à l'aide de l'outil de ligne de commande svn. J'ai fait quelque chose de similaire à cela avec ça.
user169771
5

Je fais juste:

File.GetCreationTime(GetType().Assembly.Location)
Rui Santos
la source
1
Fait intéressant, si vous exécutez à partir du débogage, la `` vraie '' date est GetLastAccessTime ()
balint
4

Vous pouvez utiliser ce projet: https://github.com/dwcullop/BuildInfo

Il utilise T4 pour automatiser l'horodatage de la date de construction. Il existe plusieurs versions (différentes branches) dont une qui vous donne le Git Hash de la branche actuellement extraite, si vous êtes dans ce genre de chose.

Divulgation: j'ai écrit le module.

Darrin Cullop
la source
3

Une approche différente et conviviale pour PCL consisterait à utiliser une tâche en ligne MSBuild pour remplacer le temps de génération dans une chaîne renvoyée par une propriété de l'application. Nous utilisons cette approche avec succès dans une application qui a des projets Xamarin.Forms, Xamarin.Android et Xamarin.iOS.

ÉDITER:

Simplifié en déplaçant toute la logique dans le SetBuildDate.targetsfichier et en utilisant à la Regexplace de la simple chaîne de remplacement pour que le fichier puisse être modifié par chaque build sans "reset".

La définition de tâche en ligne MSBuild (enregistrée dans un fichier SetBuildDate.targets local au projet Xamarin.Forms pour cet exemple):

<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>

Appel de la tâche en ligne ci-dessus dans le fichier csproj Xamarin.Forms dans BeforeBuild cible:

  <!-- 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>

La FilePathpropriété est définie sur un BuildMetadata.csfichier dans le projet Xamarin.Forms qui contient une classe simple avec une propriété de chaîne BuildDate, dans laquelle le temps de génération sera substitué:

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

Ajoutez ce fichier BuildMetadata.csau projet. Il sera modifié par chaque génération, mais d'une manière qui autorise les générations répétées (remplacements répétés), vous pouvez donc l'inclure ou l'omettre dans le contrôle de code source comme vous le souhaitez.

Mark Larter
la source
2

Vous pouvez utiliser un événement post-génération de projet pour écrire un fichier texte dans votre répertoire cible avec l'heure de date actuelle. Vous pouvez ensuite lire la valeur au moment de l'exécution. C'est un peu hacky, mais ça devrait marcher.

MikeWyatt
la source
2

Une petite mise à jour sur la réponse "New Way" de Jhon.

Vous devez créer le chemin d'accès au lieu d'utiliser la chaîne CodeBase lorsque vous travaillez avec ASP.NET/MVC

    var codeBase = assembly.GetName().CodeBase;
    UriBuilder uri = new UriBuilder(codeBase);
    string path = Uri.UnescapeDataString(uri.Path);
Thadeu Antonio Ferreira Melo
la source
1

Vous pouvez lancer une étape supplémentaire dans le processus de génération qui écrit un tampon de date dans un fichier qui peut ensuite être affiché.

Dans l'onglet Propriétés des projets, regardez l'onglet Événements de génération. Il y a une option pour exécuter une commande avant ou après construction.

Guy van den Berg
la source
1

J'ai utilisé la suggestion d'Abdurrahim. Cependant, il semblait donner un format d'heure étrange et a également ajouté l'abréviation du jour dans le cadre de la date de construction; exemple: dim. 24/12/2017 13: 21: 05.43. Je n'avais besoin que de la date, j'ai donc dû éliminer le reste en utilisant une sous-chaîne.

Après avoir ajouté l' echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"événement à la pré-génération, je viens de faire ce qui suit:

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

La bonne nouvelle ici est que cela a fonctionné.

DemarcPoint
la source
Solution très simple. Je l'aime. Et si le format est un problème, il existe des moyens d'obtenir un meilleur format à partir de la ligne de commande.
Nyerguds
0

S'il s'agit d'une application Windows, vous pouvez simplement utiliser le chemin exécutable de l'application: new System.IO.FileInfo (Application.ExecutablePath) .LastWriteTime.ToString ("yyyy.MM.dd")

John Clark
la source
2
Déjà et répondez en utilisant cela, et aussi pas exactement à l'épreuve des balles.
crashmstr
0

il pourrait être Assembly execAssembly = Assembly.GetExecutingAssembly(); var creationTime = new FileInfo(execAssembly.Location).CreationTime; // "2019-09-08T14:29:12.2286642-04:00"

Sasha Bond
la source
Cela ne fait-il pas la même chose que cette autre réponse ?
Wai Ha Lee