Mostrar la fecha de construcción

260

Actualmente tengo una aplicación que muestra el número de compilación en su ventana de título. Eso está muy bien, excepto que no significa nada para la mayoría de los usuarios, que quieren saber si tienen la última compilación; tienden a referirse a ella como "el jueves pasado" en lugar de compilar 1.0.8.4321.

El plan es poner la fecha de compilación allí, por lo tanto, "Aplicación creada el 21/10/2009".

Estoy luchando por encontrar una forma programática para extraer la fecha de compilación como una cadena de texto para usar de esta manera.

Para el número de compilación, utilicé:

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

después de definir cómo surgieron esos.

Me gustaría algo así para la fecha de compilación (y la hora, para los puntos de bonificación).

Los punteros aquí son muy apreciados (disculpen el juego de palabras si corresponde), o soluciones más ordenadas ...

Mark Mayo
fuente
2
Intenté las formas proporcionadas para obtener los datos de compilación de ensamblajes que funcionan en escenarios simples, pero si dos ensamblajes se fusionan, no obtengo el tiempo de compilación correcto, es una hora en el futuro ... ¿Alguna sugerencia?

Respuestas:

356

Jeff Atwood tenía algunas cosas que decir sobre este problema en Determinar la fecha de compilación de la manera difícil .

Resulta que el método más confiable es recuperar la marca de tiempo del enlazador desde el encabezado PE incrustado en el archivo ejecutable: algún código C # (por Joe Spivey) para eso de los comentarios al artículo 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;
}

Ejemplo de uso:

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

ACTUALIZACIÓN: El método estaba funcionando para .Net Core 1.0, pero dejó de funcionar después de la versión .Net Core 1.1 (da años aleatorios en el rango 1900-2020)

mdb
fuente
8
He cambiado un poco mi tono sobre esto, todavía sería muy cuidadoso al excavar en el encabezado de PE aguda. Pero por lo que puedo decir, este material de educación física es mucho más confiable que usar los números de versión, además de que no quiero asignar los números de versión por separado desde la fecha de compilación.
John Leidegren
66
Me gusta y lo estoy usando, pero esa penúltima línea con el .AddHours()es bastante hack y (creo) no tendrá en cuenta el horario de verano. Si lo desea en la hora local, debe usar el limpiador en su dt.ToLocalTime();lugar. La parte central también podría simplificarse enormemente con un using()bloque.
JLRishe
66
Sí, esto dejó de funcionar para mí con .net core también (1940, 1960, etc.)
eoleario
77
Si bien el uso del encabezado PE puede parecer una buena opción hoy en día, vale la pena señalar que MS está experimentando con compilaciones deterministas (lo que haría que este encabezado sea inútil) y tal vez incluso lo haga predeterminado en futuras versiones de compilador de C # (por buenas razones). Buena lectura: blog.paranoidcoding.com/2016/04/05/… y aquí está la respuesta relacionada con .NET Core (TLDR: "es por diseño"): developercommunity.visualstudio.com/content/problem/35873/…
Paweł Bulwan
13
Para aquellos que encuentran que esto ya no funciona, el problema no es un problema de .NET Core. Vea mi respuesta a continuación sobre los nuevos valores predeterminados de los parámetros de compilación que comienzan con Visual Studio 15.4.
Tom
107

Agregue a continuación la línea de comando del evento previo a la compilación:

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

Agregue este archivo como recurso, ahora tiene la cadena 'BuildDate' en sus recursos.

Para crear recursos, vea Cómo crear y usar recursos en .NET .

Abdurrahim
fuente
44
+1 de mi parte, simple y efectivo. Incluso logré obtener el valor del archivo con una línea de código como esta: String buildDate = <MyClassLibraryName>
.Properties.Resources.BuildDate
11
otra opción es hacer una clase: (debe incluir en el proyecto después de la primera vez que lo compila) -> echo namespace My.app.namespace {public static class Build {public static string Timestamp = "% DATE%% TIME%" .Substring (0,16);}}> "$ (ProjectDir) \ BuildTimestamp.cs" - - - -> luego puede llamarlo con Build.Timestamp
FabianSilva
9
Esta es una excelente solución. El único problema es que las variables de línea de comando% date% y% time% están localizadas, por lo que la salida variará según el idioma de Windows del usuario.
VS
2
+1, este es un método mejor que leer encabezados de PE, porque hay varios escenarios en los que eso no funcionará en absoluto (por ejemplo, la aplicación de Windows Phone)
Matt Whitfield
17
Inteligente. También puede usar powershell para obtener un control más preciso sobre el formato, por ejemplo, para obtener una fecha y hora UTC formateada como ISO8601: powershell -Command "((Get-Date) .ToUniversalTime ()). ToString (\" s \ ") | Out-File '$ (ProjectDir) Resources \ BuildDate.txt' "
dbruning
90

La manera

Como señaló @ c00000fd en los comentarios . Microsoft está cambiando esto. Y aunque muchas personas no usan la última versión de su compilador, sospecho que este cambio hace que este enfoque sea indudablemente malo. Y aunque es un ejercicio divertido, recomendaría a las personas que simplemente incorporen una fecha de compilación en su binario a través de cualquier otro medio necesario si es importante rastrear la fecha de compilación del propio binario.

Esto se puede hacer con una generación de código trivial que probablemente ya sea el primer paso en su script de compilación. Eso y el hecho de que las herramientas ALM / Build / DevOps ayudan mucho con esto y deberían preferirse a cualquier otra cosa.

Dejo el resto de esta respuesta aquí solo con fines históricos.

La nueva forma

Cambié de opinión sobre esto, y actualmente uso este truco para obtener la fecha de compilación correcta.

#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

La vieja manera

Bueno, ¿cómo generas números de compilación? Visual Studio (o el compilador de C #) en realidad proporciona números de compilación y revisión automáticos si cambia el atributo AssemblyVersion a, p. Ej.1.0.*

Lo que sucederá es que la compilación será igual a la cantidad de días desde el 1 de enero de 2000, hora local, y que la revisión sea igual a la cantidad de segundos desde la medianoche, hora local, dividida por 2.

ver Contenido de la comunidad, Números automáticos de compilación y revisión

eg 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
fuente
3
Acabo de agregar una hora siTimeZone.CurrentTimeZone.IsDaylightSavingTime(buildDateTime) == true
e4rthdog
2
Desafortunadamente, utilicé este enfoque sin examinarlo a fondo y nos está afectando en la producción. El problema es que cuando el compilador JIT patea la información del encabezado PE se cambia. De ahí el voto negativo. Ahora estoy haciendo una 'investigación' innecesaria para explicar por qué vemos la fecha de instalación como la fecha de compilación.
Jason D
8
@JasonD ¿En qué universo tu problema se convierte de alguna manera en mi problema? ¿Cómo justifica un voto negativo simplemente porque se encontró con un problema que esta implementación no tuvo en cuenta? Obtuviste esto gratis y lo probaste mal. Además, ¿qué te hace creer que el compilador JIT reescribe el encabezado? ¿Está leyendo esta información de la memoria del proceso o del archivo?
John Leidegren
66
Me di cuenta de que si está ejecutando en una aplicación web, la propiedad .Codebase parece ser una URL (archivo: // c: /path/to/binary.dll). Esto hace que la llamada File.Exists falle. El uso de "assembly.Location" en lugar de la propiedad CodeBase resolvió el problema por mí.
mdryden
2
@JohnLeidegren: No confíes en el encabezado de Windows PE para eso. Desde Windows 10 y compilaciones reproducibles , el IMAGE_FILE_HEADER::TimeDateStampcampo se establece en un número aleatorio y ya no es una marca de tiempo.
c00000fd
51

Agregue a continuación la línea de comando del evento previo a la compilación:

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

Agregue este archivo como recurso, ahora tiene la cadena 'BuildDate' en sus recursos.

Después de insertar el archivo en el Recurso (como archivo de texto público), accedí a él a través de

string strCompTime = Properties.Resources.BuildDate;

Para crear recursos, vea Cómo crear y usar recursos en .NET .

brewmanz
fuente
1
@DavidGorsline: la reducción de comentarios fue correcta ya que está citando esta otra respuesta . No tengo suficiente reputación para deshacer su cambio, de lo contrario lo habría hecho yo mismo.
Wai Ha Lee
1
@Wai Ha Lee: a) la respuesta que cita no proporciona código para recuperar la fecha / hora de compilación. b) en ese momento no tenía suficiente reputación para agregar comentarios a esa respuesta (lo que hubiera hecho), solo para publicar. c) publiqué dando una respuesta completa para que la gente pudiera obtener todos los detalles en un área ..
brewmanz
Si ve Úte% en lugar de% date%, marque aquí: developercommunity.visualstudio.com/content/problem/237752/… En pocas palabras, haga esto: echo% 25date% 25% 25time% 25
Qodex
41

Un enfoque que me sorprende que nadie haya mencionado todavía es usar las plantillas de texto T4 para la generación de código.

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

Pros:

  • Independiente de la localidad
  • Permite mucho más que solo el momento de la compilación

Contras:

Peter Taylor
fuente
1
Entonces, esta es ahora la mejor respuesta. 324 puntos antes de que se convierta en la respuesta más votada :). Stackoverflow necesita una forma de mostrar al escalador más rápido.
pauldendulk
1
@pauldendulk, no ayudaría mucho, porque la respuesta más votada y la respuesta aceptada casi siempre obtienen los votos más rápido. La respuesta aceptada a esta pregunta tiene + 60 / -2 desde que publiqué esta respuesta.
Peter Taylor
Creo que necesita agregar un .ToString () a sus Ticks (de lo contrario, obtengo un error de compilación). Dicho esto, me encuentro con una curva de aprendizaje empinada aquí, ¿podría mostrar cómo USAR esto también en el programa principal?
Andy
@ Andy, tienes razón sobre ToString (). El uso es justo Constants.CompilationTimestampUtc. Si VS no está generando un archivo C # con la clase, entonces debe descubrir cómo hacerlo, pero la respuesta depende (al menos) de la versión de VS y el tipo de archivo csproj, por lo que es demasiados detalles para esta publicación.
Peter Taylor
1
En caso de que otros se pregunten, esto es lo que se necesitó para que funcione en VS 2017: tuve que hacer de esto una plantilla Design Time T4 (me tomó un tiempo descubrirlo, primero agregué una plantilla de preprocesador). También tuve que incluir este ensamblado: Microsoft.VisualStudio.TextTemplating.Interfaces.10.0 como referencia al proyecto. Finalmente mi plantilla tenía que incluir "usar el sistema"; antes del espacio de nombres, o bien la referencia a DateTime falló.
Andy
20

Con respecto a la técnica de extraer información de la fecha / versión de compilación de los bytes de un encabezado PE de ensamblaje, Microsoft ha cambiado los parámetros de compilación predeterminados a partir de Visual Studio 15.4. El nuevo valor predeterminado incluye una compilación determinista, que hace que una marca de tiempo válida y los números de versión incrementados automáticamente sean cosa del pasado. El campo de marca de tiempo todavía está presente, pero se llena con un valor permanente que es un hash de algo u otro, pero no es una indicación del tiempo de construcción.

Algunos antecedentes detallados aquí

Para aquellos que priorizan una marca de tiempo útil sobre la compilación determinista, hay una manera de anular el nuevo valor predeterminado. Puede incluir una etiqueta en el archivo .csproj del conjunto de interés de la siguiente manera:

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

Actualización: Respaldo la solución de plantilla de texto T4 descrita en otra respuesta aquí. Lo usé para resolver mi problema limpiamente sin perder el beneficio de la compilación determinista. Una advertencia al respecto es que Visual Studio solo ejecuta el compilador T4 cuando se guarda el archivo .tt, no en el momento de la compilación. Esto puede ser incómodo si excluye el resultado .cs del control de código fuente (ya que espera que se genere) y otro desarrollador comprueba el código. Sin volver a guardar, no tendrán el archivo .cs. Hay un paquete en nuget (creo que se llama AutoT4) que hace que la compilación T4 sea parte de cada compilación. Todavía no he confrontado la solución a esto durante el despliegue de producción, pero espero que algo similar lo haga bien.

Tom
fuente
Esto resolvió mi problema en una sln que usa la respuesta más antigua.
pauldendulk
Su precaución sobre T4 es perfectamente justa, pero tenga en cuenta que ya está presente en mi respuesta.
Peter Taylor
15

Solo soy un novato de C #, así que tal vez mi respuesta suene tonta: muestro la fecha de compilación a partir de la fecha en que se escribió por última vez el archivo ejecutable:

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

Traté de usar el método File.GetCreationTime pero obtuve resultados extraños: la fecha del comando fue 2012-05-29, pero la fecha del Explorador de ventanas mostró 2012-05-23. Después de buscar esta discrepancia, descubrí que el archivo probablemente se creó el 23/05/2012 (como se muestra en el Explorador de Windows), pero se copió a la carpeta actual el 29/05/2012 (como se muestra en el comando File.GetCreationTime). para estar seguro, estoy usando el comando File.GetLastWriteTime.

Zalek

Zalek Bloom
fuente
44
No estoy seguro de si esto es a prueba de balas al copiar el ejecutable en unidades / computadoras / redes.
Stealth Rabbi
Esto es lo primero que viene a la mente, pero sabes que no es confiable, hay muchos programas utilizados para mover los archivos a través de la red que no actualizan los atributos después de la descarga, iría con la respuesta de @ Abdurrahim.
Mubashar
Sé que esto es antiguo, pero acabo de encontrar con un código similar que el proceso de INSTALACIÓN (al menos cuando se usa clickonce) actualiza el tiempo del archivo de ensamblaje. No muy útil Sin embargo, no estoy seguro de si se aplicaría a esta solución.
bobwki
Probablemente realmente desee LastWriteTime, ya que eso refleja con precisión el tiempo en que el archivo ejecutable se actualizó realmente.
David R Tribble
Lo sentimos, pero el tiempo de escritura de un archivo ejecutable no es una indicación confiable del tiempo de compilación. La marca de tiempo del archivo puede reescribirse debido a todo tipo de cosas que están fuera de su esfera de influencia.
Tom
15

Muchas respuestas geniales aquí, pero siento que puedo agregar las mías debido a la simplicidad, el rendimiento (en comparación con las soluciones relacionadas con los recursos) multiplataforma (también funciona con Net Core) y eludir cualquier herramienta de terceros. Simplemente agregue este objetivo de msbuild al 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>

y ahora lo tienes Builtin.CompileTimeo new DateTime(Builtin.CompileTime, DateTimeKind.Utc)si lo necesitas de esa manera.

A ReSharper no le va a gustar. Puede ignorarlo o agregar una clase parcial al proyecto también, pero funciona de todos modos.

Dmitry Gusarov
fuente
Puedo construir con esto y desarrollar localmente (ejecutar sitios web) en ASP.NET Core 2.1, pero la publicación de implementación web de VS 2017 falla con el error "El nombre 'Builtin' no existe en el contexto actual". ADICION: si estoy accediendo Builtin.CompileTimedesde una vista Razor.
Jeremy Cook, el
En este caso, creo que solo necesitas, BeforeTargets="RazorCoreCompile"pero solo mientras esto esté en el mismo proyecto
Dmitry Gusarov
genial, pero ¿cómo nos referimos al objeto generado? me parece que a la respuesta le falta una parte clave ...
Matteo
1
@Matteo, como se menciona en la respuesta, puede usar "Builtin.CompileTime" o "new DateTime (Builtin.CompileTime, DateTimeKind.Utc)". Visual Studio IntelliSense es capaz de ver esto de inmediato. Un viejo ReSharper puede quejarse en tiempo de diseño, pero parece que lo arreglaron en nuevas versiones. clip2net.com/s/46rgaaO
Dmitry Gusarov
Usé esta versión, así que no necesito código adicional para obtener la fecha. Además resharper no se queja con su última versión. <WriteLinesToFile File = "$ (IntermediateOutputPath) BuildInfo.cs" Lines = "utilizando System% 3B clase parcial estática interna BuildInfo {public static long DateBuiltTicks = $ ([System.DateTime] :: UtcNow.Ticks)% 3B public static DateTime DateBuilt => nuevo DateTime (DateBuiltTicks, DateTimeKind.Utc)% 3B} "Overwrite =" true "/>
Softlion
13

Para los proyectos .NET Core, adapté la respuesta de Postlagerkarte para actualizar el campo Copyright de ensamblado con la fecha de compilación.

Editar directamente csproj

Lo siguiente se puede agregar directamente al primero PropertyGroupen csproj:

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

Alternativa: Propiedades del proyecto de Visual Studio

O pegue la expresión interna directamente en el campo Copyright en la sección Paquete de las propiedades del proyecto en Visual Studio:

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

Esto puede ser un poco confuso, porque Visual Studio evaluará la expresión y mostrará el valor actual en la ventana, pero también actualizará el archivo del proyecto de manera adecuada detrás de escena.

Toda la solución a través de Directory.Build.props

Puede colocar el <Copyright>elemento anterior en un Directory.Build.propsarchivo en la raíz de su solución y aplicarlo automáticamente a todos los proyectos dentro del directorio, suponiendo que cada proyecto no proporcione su propio valor de Copyright.

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

Directory.Build.props: personaliza tu construcción

Salida

La expresión de ejemplo le dará un copyright como este:

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

Recuperación

Puede ver la información de copyright de las propiedades del archivo en Windows, o tomarla en tiempo de ejecución:

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

Console.WriteLine(version.LegalCopyright);
Travis Troyer
fuente
11

El método anterior se puede ajustar para ensamblajes ya cargados dentro del proceso mediante el uso de la imagen del archivo en la memoria (en lugar de volver a leerlo desde el almacenamiento):

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
fuente
Este funciona muy bien, incluso para Framework 4.7 Uso: Utils.GetLinkerDateTime (Assembly.GetExecutingAssembly (), null))
real_yggdrasil
¡Funciona genial! ¡Gracias!
bobwki
10

Para cualquiera que necesite obtener el tiempo de compilación en 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);
            }
        }
    }

Para cualquiera que necesite obtener el tiempo de compilación en 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;
    }

NOTA: en todos los casos, se está ejecutando en un entorno limitado, por lo que solo podrá obtener el tiempo de compilación de los ensamblados que implementa con su aplicación. (es decir, esto no funcionará en nada en el GAC).

Matt Dotson
fuente
Así es como se obtiene la Asamblea en WP 8.1:var assembly = typeof (AnyTypeInYourAssembly).GetTypeInfo().Assembly;
André Fiedler
¿Qué sucede si desea ejecutar su código en ambos sistemas? ¿Es uno de estos métodos aplicable para ambas plataformas?
bvdb
10

En 2018, algunas de las soluciones anteriores ya no funcionan o no funcionan con .NET Core.

Utilizo el siguiente enfoque, que es simple y funciona para mi proyecto .NET Core 2.0.

Agregue lo siguiente a su .csproj dentro del PropertyGroup:

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

Esto define una función de propiedad que puede acceder en su comando de compilación previa.

Su precompilación se ve así

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

Establezca la propiedad de BuildTimeStamp.txt en Recurso incorporado.

Ahora puedes leer la marca de tiempo como esta

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
fuente
Simplemente generar ese BuildTimeStamp.txt a partir de los eventos previos a la compilación utilizando comandos de script por lotes también funciona. Tenga en cuenta que cometió un error allí: debe rodear su objetivo entre comillas (por ejemplo "$(ProjectDir)BuildTimeStamp.txt") o se romperá cuando haya espacios en los nombres de las carpetas.
Nyerguds
Tal vez tenga sentido usar el formato de tiempo invariante de la cultura. Así: en $([System.DateTime]::Now.tostring("MM/dd/yyyy HH:mm:ss"))lugar de$([System.DateTime]::Now)
Ivan Kochurkin el
9

La opción que no se discute aquí es insertar sus propios datos en AssemblyInfo.cs, el campo "AssemblyInformationalVersion" parece apropiado: tenemos un par de proyectos en los que estábamos haciendo algo similar como un paso de compilación (sin embargo, no estoy completamente satisfecho con el que funciona así que no quiero reproducir lo que tenemos).

Hay un artículo sobre el tema en codeproject: http://www.codeproject.com/KB/dotnet/Customizing_csproj_files.aspx

Murph
fuente
6

Necesitaba una solución universal que funcionara con un proyecto NETStandard en cualquier plataforma (iOS, Android y Windows). Para lograr esto, decidí generar automáticamente un archivo CS a través de un script de PowerShell. Aquí está el script de 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

Guarde el archivo PowerScript como GenBuildDate.ps1 y agréguelo a su proyecto. Finalmente, agregue la siguiente línea a su evento Pre-Build:

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

Asegúrese de que BuildDate.cs esté incluido en su proyecto. Funciona como un campeón en cualquier sistema operativo!

David Tosi
fuente
1
También puede usar esto para obtener el número de revisión SVN usando la herramienta de línea de comando svn. He hecho algo similar a esto con eso.
user169771
5

Solamente lo hago:

File.GetCreationTime(GetType().Assembly.Location)
Rui Santos
fuente
1
Curiosamente, si se ejecuta desde la depuración, la fecha 'verdadera' es GetLastAccessTime ()
balint
4

Puede usar este proyecto: https://github.com/dwcullop/BuildInfo

Aprovecha T4 para automatizar la marca de tiempo de la fecha de compilación. Hay varias versiones (diferentes ramas), incluida una que le proporciona el Git Hash de la rama actualmente desprotegida, si le gusta ese tipo de cosas.

Divulgación: escribí el módulo.

Darrin Cullop
fuente
3

Un enfoque diferente y compatible con PCL sería utilizar una tarea en línea de MSBuild para sustituir el tiempo de compilación en una cadena que devuelve una propiedad de la aplicación. Estamos utilizando este enfoque con éxito en una aplicación que tiene proyectos Xamarin.Forms, Xamarin.Android y Xamarin.iOS.

EDITAR:

Simplificado moviendo toda la lógica al SetBuildDate.targetsarchivo y usando en Regexlugar de una simple sustitución de cadena para que el archivo pueda ser modificado por cada compilación sin un "reinicio".

La definición de tarea en línea de MSBuild (guardada en un archivo SetBuildDate.targets local para el proyecto Xamarin.Forms para este ejemplo):

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

Invocando la tarea en línea anterior en el archivo csproj Xamarin.Forms en Target 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>

La FilePathpropiedad se establece en un BuildMetadata.csarchivo en el proyecto Xamarin.Forms que contiene una clase simple con una propiedad de cadenaBuildDate , en la que se sustituirá el tiempo de compilación:

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

Agregue este archivo BuildMetadata.csal proyecto. Será modificado por cada compilación, pero de una manera que permita compilaciones repetidas (reemplazos repetidos), por lo que puede incluirlo u omitirlo en el control de origen según lo desee.

Mark Larter
fuente
2

Puede usar un evento posterior a la compilación del proyecto para escribir un archivo de texto en su directorio de destino con la fecha y hora actual. Luego puede leer el valor en tiempo de ejecución. Es un poco hacky, pero debería funcionar.

MikeWyatt
fuente
2

Una pequeña actualización de la respuesta "New Way" de Jhon.

Debe crear la ruta en lugar de usar la cadena CodeBase cuando trabaje con ASP.NET/MVC

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

Puede iniciar un paso adicional en el proceso de compilación que escribe una marca de fecha en un archivo que luego se puede mostrar.

En la pestaña de propiedades del proyecto, mira la pestaña de eventos de construcción. Hay una opción para ejecutar un comando de compilación previa o posterior.

Guy van den Berg
fuente
1

Usé la sugerencia de Abdurrahim. Sin embargo, parecía dar un formato de tiempo extraño y también agregó la abreviatura para el día como parte de la fecha de compilación; ejemplo: dom 24/12/2017 13: 21: 05.43. Solo necesitaba la fecha, así que tuve que eliminar el resto usando la subcadena.

Después de agregar el echo %date% %time% > "$(ProjectDir)\Resources\BuildDate.txt"al evento previo a la compilación, acabo de hacer lo siguiente:

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

La buena noticia aquí es que funcionó.

DemarcPoint
fuente
Solución muy simple. Me gusta. Y si el formato es una molestia, hay formas de obtener un mejor formato desde la línea de comandos.
Nyerguds
0

Si se trata de una aplicación de Windows, puede usar la ruta ejecutable de la aplicación: nuevo System.IO.FileInfo (Application.ExecutablePath) .LastWriteTime.ToString ("aaaa.MM.dd")

John Clark
fuente
2
Ya y responda usando esto, y tampoco exactamente a prueba de balas.
crashmstr
0

podría ser Assembly execAssembly = Assembly.GetExecutingAssembly(); var creationTime = new FileInfo(execAssembly.Location).CreationTime; // "2019-09-08T14:29:12.2286642-04:00"

Sasha Bond
fuente
¿No está haciendo lo mismo que esta otra respuesta ?
Wai Ha Lee