【问题标题】:Displaying the build date显示构建日期
【发布时间】:2010-12-08 17:28:34
【问题描述】:

我目前有一个应用程序在其标题窗口中显示内部版本号。这很好,只是对于大多数想知道自己是否拥有最新版本的用户来说毫无意义——他们倾向于将其称为“上周四的”而不是版本 1.0.8.4321。

计划是把构建日期放在那里 - 例如“应用程序构建于 2009 年 10 月 21 日”。

我正在努力寻找一种编程方式来将构建日期作为文本字符串提取出来,以便像这样使用。

对于内部版本号,我使用了:

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

在定义了这些是如何出现的之后。

我想要类似的东西作为编译日期(和时间,以获得奖励积分)。

非常感谢这里的指针(如果合适,请原谅双关语),或者更简洁的解决方案......

【问题讨论】:

  • 我尝试了提供的方法来获取在简单场景中工作的程序集的构建数据,但如果两个程序集合并在一起,我得到的构建时间不正确,它是未来一小时......任何建议?

标签: c# date time compilation


【解决方案1】:

Jeff Atwood 在Determining Build Date the hard way 中就这个问题发表了一些看法。

事实证明,最可靠的方法是从嵌入在可执行文件中的PE header 中检索链接器时间戳——从 cmets 到 Jeff 的文章的一些 C# 代码(由 Joe Spivey 编写):

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 范围内的随机年份)

【讨论】:

  • 我已经稍微改变了我的语气,在深入研究实际 PE 标头时我仍然会非常小心。但据我所知,这个 PE 的东西比使用版本号可靠得多,而且我不想分配与构建日期分开的版本号。
  • 我喜欢这个并且正在使用它,但是与.AddHours() 的倒数第二行相当老套,并且(我认为)不会考虑 DST。如果你想在当地时间,你应该改用清洁剂dt.ToLocalTime();。中间部分也可以用using() 块大大简化。
  • 是的,这对我来说也不再适用于 .net 核心(1940 年代、1960 年代等)
  • 虽然今天使用 PE 标头似乎是一个不错的选择,但值得注意的是,MS 正在尝试确定性构建(这会使该标头变得无用),甚至可能在未来的编译器版本中将其设为默认值C#(有充分的理由)。好读:blog.paranoidcoding.com/2016/04/05/…,这是与 .NET Core 相关的答案(TLDR:“这是设计使然”):developercommunity.visualstudio.com/content/problem/35873/…
  • 对于那些发现这不再有效的人,问题不是 .NET Core 问题。请参阅下面关于从 Visual Studio 15.4 开始的新构建参数默认值的回答。
【解决方案2】:

在预构建事件命令行中添加以下内容:

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

将此文件添加为资源, 现在您的资源中有“BuildDate”字符串。

要创建资源,请参阅How to create and use resources in .NET

【讨论】:

  • +1 来自我,简单而有效。我什至设法使用如下代码从文件中获取值: String buildDate = .Properties.Resources.BuildDate
  • 另一个选项是创建一个类:(必须在第一次编译后包含在项目中)--> echo namespace My.app.namespace { public static class Build { public static string Timestamp = "%DATE% %TIME%".Substring(0,16);}} > "$(ProjectDir)\BuildTimestamp.cs" - - - --> 然后可以用 Build.Timestamp 调用它
  • 这是一个很好的解决方案。唯一的问题是 %date% 和 %time% 命令行变量是本地化的,因此输出会根据用户的 Windows 语言而有所不同。
  • +1,这是比读取 PE 标头更好的方法 - 因为有几种情况根本不起作用(例如 Windows Phone 应用程序)
  • 聪明。您还可以使用 powershell 来更精确地控制格式,例如获取格式化为 ISO8601 的 UTC 日期时间: powershell -Command "((Get-Date).ToUniversalTime()).ToString(\"s\") | Out-File '$(ProjectDir)Resources\BuildDate.txt'"
【解决方案3】:

方式

正如@c00000fd 在comments 中指出的那样。微软正在改变这一点。虽然许多人不使用最新版本的编译器,但我怀疑这种变化使这种方法无疑是糟糕的。虽然这是一个有趣的练习,但如果跟踪二进制文件本身的构建日期很重要,我建议人们通过任何其他必要的方式将构建日期简单地嵌入到他们的二进制文件中。

这可以通过一些简单的代码生成来完成,这可能已经是构建脚本的第一步。这一点,以及 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

老办法

那么,您如何生成内部版本号?如果您将 AssemblyVersion 属性更改为例如,Visual Studio(或 C# 编译器)实际上会提供自动构建和修订号。 1.0.*

将会发生的是,构建将等于自当地时间 2000 年 1 月 1 日以来的天数,而修订等于自当地时间午夜以来的秒数除以 2。

查看社区内容,Automatic Build and Revision numbers

例如AssemblyInfo.cs

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

示例代码.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)

【讨论】:

  • 如果TimeZone.CurrentTimeZone.IsDaylightSavingTime(buildDateTime) == true,我刚加了一小时
  • 不幸的是,我在没有彻底审查的情况下使用了这种方法,它在生产中对我们不利。问题是当 JIT 编译器启动时,PE 标头信息发生了变化。因此投反对票。现在我要进行不必要的“研究”来解释为什么我们将安装日期视为构建日期。
  • @JasonD 你的问题在什么领域会变成我的问题?您如何仅仅因为遇到此实施未考虑的问题而证明否决票是合理的。你免费得到了这个,但你测试它很糟糕。还有什么让您相信 JIT 编译器正在重写标头?您是从进程内存还是从文件中读取这些信息?
  • 我注意到,如果您在 Web 应用程序中运行,.Codebase 属性似乎是一个 URL (file://c:/path/to/binary.dll)。这会导致 File.Exists 调用失败。使用“assembly.Location”而不是 CodeBase 属性为我解决了这个问题。
  • @JohnLeidegren:不要依赖 Windows PE 标头。 Since Windows 10reproducible buildsIMAGE_FILE_HEADER::TimeDateStamp 字段设置为随机数,不再是时间戳。
【解决方案4】:

在预构建事件命令行中添加以下内容:

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

将此文件添加为资源,现在您的资源中有“BuildDate”字符串。

将文件插入资源(作为公共文本文件)后,我通过

访问它
string strCompTime = Properties.Resources.BuildDate;

要创建资源,请参阅How to create and use resources in .NET

【讨论】:

  • @DavidGorsline - 评论降价是正确的,因为它引用了this other answer。我没有足够的声誉来回滚您的更改,否则我会自己完成。
  • @Wai Ha Lee - a)您引用的答案没有给出实际检索编译日期/时间的代码。 b)当时我没有足够的声誉来对该答案添加评论(我会这样做),只能发布。所以 c)我发布了完整的答案,以便人们可以在一个区域中获得所有详细信息..
  • 如果您看到 Úte% 而不是 %date%,请在此处查看:developercommunity.visualstudio.com/content/problem/237752/… 简而言之,请执行以下操作:echo %25date%25 %25time%25
【解决方案5】:

我很惊讶没有人提到的一种方法是使用T4 Text Templates 进行代码生成。

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

优点:

  • 与区域无关
  • 允许的不仅仅是编译时间

缺点:

【讨论】:

  • 所以,这是现在最好的答案。在它成为最高投票答案之前还有 324 分 :)。 Stackoverflow 需要一种方法来展示最快的登山者。
  • @pauldendulk,不会有太大帮助,因为最多支持的答案和接受的答案几乎总是最快地获得选票。自从我发布此答案以来,此问题的公认答案为 +60/-2
  • 我相信你需要在你的 Ticks 中添加一个 .ToString() (否则我会得到一个编译错误)。也就是说,我在这里遇到了一个陡峭的学习曲线,你能在主程序中展示如何使用它吗?
  • 如果其他人想知道,这就是让它在 VS 2017 上运行所需要的:我必须将它设为 Design Time T4 模板(我花了一段时间才弄清楚,我添加了一个预处理器首先是模板)。我还必须包含这个程序集:Microsoft.VisualStudio.TextTemplating.Interfaces.10.0 作为对项目的引用。最后,我的模板必须包含“使用系统;”在命名空间之前,否则对 DateTime 的引用失败。
  • 抱歉,关于设计模板与预处理器的比较,我可能弄错了。所以问题:我正在使用自定义工具:TextTemplatingPreProcessor,但输出文件并未在每次构建时更新。我是否缺少其他东西来强制它在每次编译时更新?
【解决方案6】:

这里有很多很棒的答案,但我觉得我可以添加自己的答案,因为它简单、性能(与资源相关的解决方案相比)跨平台(也可与 Net Core 一起使用)以及避免使用任何第 3 方工具。只需将此 msbuild 目标添加到 csproj。

<Target Name="Date" BeforeTargets="BeforeBuild">
    <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,例如:

var compileTime = new DateTime(Builtin.CompileTime, DateTimeKind.Utc);

ReSharper 不会喜欢它。您也可以忽略他或将部分类添加到项目中,但它仍然有效。

UPD:如今 ReSharper 在选项的第一页中有一个选项:“MSBuild 访问”、“每次编译后从 MSBuild 获取数据”。这有助于生成代码的可见性。

【讨论】:

  • 我可以使用它构建并在 ASP.NET Core 2.1 中本地开发(运行网站),但是从 VS 2017 发布的 Web 部署失败并出现错误“当前上下文中不存在名称‘Builtin’ ”。补充:如果我从 Razor 视图访问Builtin.CompileTime
  • 在这种情况下,我认为您只需要BeforeTargets="RazorCoreCompile",但前提是它在同一个项目中
  • @Matteo,如答案中所述,您可以使用“Builtin.CompileTime”或“new DateTime(Builtin.CompileTime, DateTimeKind.Utc)”。 Visual Studio IntelliSense 能够立即看到这一点。旧的 ReSharper 可能会在设计时抱怨,但看起来他们在新版本中修复了这个问题。 clip2net.com/s/46rgaaO
  • 我使用了这个版本,所以不需要额外的代码来获取日期。 resharper 也不会抱怨其最新版本。
  • 在 Visual Studio 2022 上,我必须将 ItemGroup 部分移出 Target 块才能正常工作。
【解决方案7】:

关于从程序集 PE 标头的字节中提取构建日期/版本信息的技术,Microsoft 从 Visual Studio 15.4 开始更改了默认构建参数。新的默认值包括确定性编译,它使有效的时间戳和自动递增的版本号成为过去。时间戳字段仍然存在,但它填充了一个永久值,该值是某事物或其他事物的哈希值,但没有任何构建时间的指示。

Some detailed background here

对于那些将有用的时间戳优先于确定性编译的人,有一种方法可以覆盖新的默认值。您可以在感兴趣的程序集的 .csproj 文件中包含一个标记,如下所示:

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

更新: 我赞同此处另一个答案中描述的 T4 文本模板解决方案。我用它干净地解决了我的问题,而不会失去确定性编译的好处。关于它的一个警告是,Visual Studio 仅在保存 .tt 文件时运行 T4 编译器,而不是在构建时运行。如果您从源代码管理中排除 .cs 结果(因为您希望生成它)并且另一个开发人员检查代码,这可能会很尴尬。如果不重新保存,他们将没有 .cs 文件。 nuget 上有一个包(我认为称为 AutoT4),它使 T4 编译成为每个构建的一部分。在生产部署期间,我还没有遇到过这个问题的解决方案,但我希望有类似的东西可以让它正确。

【讨论】:

  • 这解决了我在使用最旧答案的 sln 中的问题。
  • 您对 T4 的谨慎是完全公平的,但请注意它已经出现在我的回答中。
【解决方案8】:

对于 .NET Core 项目,我调整了 Postlagerkarte 的答案,以使用构建日期更新程序集版权字段。

直接编辑csproj

以下内容可以直接添加到csproj中的第一个PropertyGroup

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

替代方案:Visual Studio 项目属性

或者将内部表达式直接粘贴到 Visual Studio 中项目属性的 Package 部分的 Copyright 字段中:

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

这可能有点令人困惑,因为 Visual Studio 将评估表达式并在窗口中显示当前值,但它也会在幕后适当地更新项目文件。

通过 Directory.Build.props 解决方案范围

您可以将上面的 &lt;Copyright&gt; 元素放入解决方案根目录中的 Directory.Build.props 文件中,并使其自动应用于目录中的所有项目,假设每个项目不提供自己的版权值。

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

Directory.Build.props:Customize your build

输出

示例表达式会给你这样的版权:

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

检索

您可以在Windows的文件属性中查看版权信息,也可以在运行时抓取:

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

Console.WriteLine(version.LegalCopyright);

【讨论】:

    【解决方案9】:

    我只是 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,但 Window Explorer 的日期显示为 2012-05-23。在搜索了这种差异后,我发现该文件可能是在 2012 年 5 月 23 日创建的(如 Windows 资源管理器所示),但在 2012 年 5 月 29 日复制到当前文件夹(如 File.GetCreationTime 命令所示) - 所以为了安全起见,我正在使用 File.GetLastWriteTime 命令。

    扎莱克

    【讨论】:

    • 我不确定这是否是跨驱动器/计算机/网络复制可执行文件的防弹。
    • 这是首先想到的,但你知道它不可靠,有很多软件用于通过网络移动文件,下载后不会更新属性,我会选择 @Abdurrahim 的答案.
    • 我知道这是旧的,但我刚刚发现一些类似的代码,安装过程(至少在使用 clickonce 时)会更新程序集文件时间。不是很有用。不过,不确定它是否适用于此解决方案。
    • 您可能真的想要LastWriteTime,因为它准确地反映了可执行文件实际更新的时间。
    • 抱歉,可执行文件写入时间并不是构建时间的可靠指示。由于您的影响范围之外的各种事情,文件时间戳可能会被重写。
    【解决方案10】:

    在 2018 年,上述一些解决方案不再适用或不适用于 .NET Core。

    我使用以下简单的方法,适用于我的 .NET Core 2.0 项目。

    将以下内容添加到 PropertyGroup 内的 .csproj 中:

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

    这定义了一个PropertyFunction,您可以在预构建命令中访问它。

    您的预构建看起来像这样

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

    将 BuildTimeStamp.txt 的属性设置为 Embedded 资源。

    现在你可以像这样读取时间戳

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

    【讨论】:

    • 从预构建事件 using batch script commands 生成 BuildTimeStamp.txt 也可以。请注意您在此处犯了一个错误:您应该用引号将您的目标括起来(例如"$(ProjectDir)BuildTimeStamp.txt"),否则当文件夹名称中有空格时它会中断。
    • 也许使用文化不变的时间格式是有意义的。像这样:$([System.DateTime]::Now.tostring("MM/dd/yyyy HH:mm:ss")) 而不是 $([System.DateTime]::Now)
    • stackoverflow.com/a/11336754/4675770去掉echo命令产生的换行符,让txt文件只有一行而不是两行。
    【解决方案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))
    • 这在构建 Debug-release 时确实有效,但在构建为 Release 时会杀死我的应用程序(不抛出异常)。不幸的是,我不知道为什么。我在 Windows 10 20H2 中使用 Visual Studio 2019。
    【解决方案12】:

    我只是这样做:

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

    【讨论】:

    • 有趣的是,如果从调试运行,“真实”日期是 GetLastAccessTime()
    • 请注意,您添加using System.IO; 并将其放入类的构造函数中,以便GetType() 在实例上工作。
    【解决方案13】:

    对于需要在 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;
    • 如果你想在两个系统上运行你的代码怎么办? - 这些方法之一是否适用于两个平台?
    【解决方案14】:

    此处未讨论的选项是将您自己的数据插入到 AssemblyInfo.cs 中,“AssemblyInformationalVersion”字段似乎合适 - 我们有几个项目,我们正在执行类似于构建步骤的操作(但我并不完全对这种工作方式感到满意,所以不想复制我们所拥有的)。

    codeproject上有一篇关于这个主题的文章:http://www.codeproject.com/KB/dotnet/Customizing_csproj_files.aspx

    【讨论】:

      【解决方案15】:

      我需要一个可以在任何平台(iOS、Android 和 Windows)上与 NETStandard 项目配合使用的通用解决方案。为了实现这一点,我决定通过 PowerShell 脚本自动生成一个 CS 文件。这是 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 并将其添加到您的项目中。最后,将以下行添加到您的 Pre-Build 事件:

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

      确保 BuildDate.cs 包含在您的项目中。在任何操作系统上都能像冠军一样工作!

      【讨论】:

      • 你也可以通过svn命令行工具来获取SVN版本号。我已经用那个做了类似的事情。
      【解决方案16】:

      另一种对 PCL 友好的方法是使用 MSBuild 内联任务将构建时间替换为应用程序属性返回的字符串。我们在具有 Xamarin.Forms、Xamarin.Android 和 Xamarin.iOS 项目的应用中成功地使用了这种方法。

      编辑:

      通过将所有逻辑移动到 SetBuildDate.targets 文件中并使用 Regex 而不是简单的字符串替换进行简化,以便每次构建都可以修改文件而无需“重置”。

      MSBuild 内联任务定义(在此示例中保存在 Xamarin.Forms 项目本地的 SetBuildDate.targets 文件中):

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

      在目标 BeforeBuild 中的 Xamarin.Forms csproj 文件中调用上述内联任务:

        <!-- 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 属性设置为 Xamarin.Forms 项目中的 BuildMetadata.cs 文件,该文件包含一个带有字符串属性 BuildDate 的简单类,生成时间将被替换为该类:

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

      将此文件BuildMetadata.cs 添加到项目中。每次构建都会对其进行修改,但以允许重复构建(重复替换)的方式进行修改,因此您可以根据需要在源代码管理中包含或省略它。

      【讨论】:

        【解决方案17】:

        你可以使用这个项目:https://github.com/dwcullop/BuildInfo

        它利用 T4 自动化构建日期时间戳。有几个版本(不同的分支),其中一个可以为您提供当前签出分支的 Git 哈希,如果您喜欢这种类型的话。

        披露:我编写了模块。

        【讨论】:

          【解决方案18】:

          关于 Jhon 的“新方式”答案的小更新。

          在使用 ASP.NET/MVC 时,您需要构建路径而不是使用 CodeBase 字符串

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

          【讨论】:

          • 请注意,从单个文件包加载的程序集不支持 CodeBase
          【解决方案19】:

          您可以使用项目构建后事件以当前日期时间将文本文件写入目标目录。然后您可以在运行时读取该值。这有点 hacky,但它应该可以工作。

          【讨论】:

            【解决方案20】:

            我不确定,但Build Incrementer 可能会有所帮助。

            【讨论】:

              【解决方案21】:

              我使用了 Abdurrahim 的建议。但是,它似乎给出了一种奇怪的时间格式,并且还添加了日期的缩写作为构建日期的一部分;示例:2017 年 12 月 24 日星期日 13:21:05.43。我只需要日期,所以我必须使用子字符串消除其余部分。

              echo %date% %time% &gt; "$(ProjectDir)\Resources\BuildDate.txt"添加到预构建事件后,我只是做了以下事情:

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

              好消息是它奏效了。

              【讨论】:

              【解决方案22】:

              Visual Studio 2019 的完整解决方案,就像我多年前开始时希望找到的那样。

              添加文本资源文件

              访问您项目的属性:从解决方案资源管理器中选择您的项目,然后右键单击 -> 属性,或 Alt+Enter。在资源选项卡中,选择文件 (Ctrl+5)。然后添加资源/添加新文本文件。在弹出消息中,输入您的资源名称,例如BuildDate:这将在您的项目/资源文件夹中创建一个新的文本文件BuildDate.txt,将其包含为项目文件,并将其注册为资源,这可以然后通过 C# 中的 Properties.Resources 或 VB 中的 My.Resources 访问。

              每次构建时自动更新资源文件

              现在您可以告诉 Visual Studio 在每次构建或重新构建项目时将日期写入此文件。为此,请转到 Project Properties 的 Compile 选项卡,选择 Build Events,然后将以下内容复制/粘贴到“Pre-Build event command line”文本框中:

              powershell -Command "((Get-Date).ToUniversalTime()).ToString(\"s\") | Out-File '$(ProjectDir)Resources\BuildDate.txt'"
              

              这一行会定位到BuildDate.txt,并在ISO8601格式下写入today/NowUtc的日期时间,如2021-09-07T16:08:35

              通过读取文件在运行时获取构建日期

              然后,您可以在运行时通过以下帮助程序 (C#) 从代码中检索此日期:

              DateTime CurrentBuildDate = DateTime.Parse(Properties.Resources.BuildDate, null, System.Globalization.DateTimeStyles.RoundtripKind);
              

              学分

              【讨论】:

                【解决方案23】:

                您可以在构建过程中启动一个额外的步骤,将日期戳写入文件,然后可以显示该文件。

                在项目属性选项卡上查看构建事件选项卡。可以选择执行构建前或构建后的命令。

                【讨论】:

                  【解决方案24】:

                  我刚刚添加了预构建事件命令:

                  powershell -Command Get-Date -Format 'yyyy-MM-ddTHH:mm:sszzz' > Resources\BuildDateTime.txt
                  

                  在项目属性中生成一个资源文件,然后很容易从代码中读取。

                  【讨论】:

                    【解决方案25】:

                    对于我的项目(一个 .Net Core 2.1 Web 应用程序)的建议解决方案,我遇到了困难。我结合了上面的各种建议并进行了简化,并将日期转换为我需要的格式。

                    回显命令:

                    echo Build %DATE:~-4%/%DATE:~-10,2%/%DATE:~-7,2% %time% > "$(ProjectDir)\BuildDate.txt"
                    

                    代码:

                    Logger.Info(File.ReadAllText(@"./BuildDate.txt").Trim());
                    

                    它似乎工作。输出:

                    2021-03-25 18:41:40,877 [1] INFO Config - Build 2021/03/25 18:41:37.58
                    

                    没什么原创的,我只是结合了这里的建议和其他相关问题,并简化了。

                    【讨论】:

                      【解决方案26】:

                      对于 .NET 5,我已成功使用此方法。 (找到here)。

                      将此添加到 .csproj 文件中:

                      <SourceRevisionId>build$([System.DateTime]::UtcNow.ToString("yyyyMMddHHmmss"))</SourceRevisionId>
                      

                      获取构建日期的方法:

                      private static DateTime GetBuildDate(Assembly assembly)
                      {
                          const string BuildVersionMetadataPrefix = "+build";
                      
                          var attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
                          if (attribute?.InformationalVersion != null)
                          {
                              var value = attribute.InformationalVersion;
                              var index = value.IndexOf(BuildVersionMetadataPrefix);
                              if (index > 0)
                              {
                                  value = value.Substring(index + BuildVersionMetadataPrefix.Length);
                                  if (DateTime.TryParseExact(value, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var result))
                                  {
                                      return result;
                                  }
                              }
                          }
                      
                          return default;
                      }
                      

                      用法:

                       var buildTime = GetBuildDate(Assembly.GetExecutingAssembly());
                       buildTime = buildTime.ToLocalTime();
                      

                      【讨论】:

                        【解决方案27】:

                        如果您将程序集复制到另一个位置,GetLastWriteTime 不会更改。

                        public static class AssemblyExtensions
                        {
                            public static DateTime GetLinkerTime(this Assembly assembly)
                            {
                                return File.GetLastWriteTime(assembly.Location).ToLocalTime();
                            }
                        }
                        

                        【讨论】:

                          【解决方案28】:

                          如果这是一个 Windows 应用程序,您可以只使用应用程序的可执行路径: 新 System.IO.FileInfo(Application.ExecutablePath).LastWriteTime.ToString("yyyy.MM.dd")

                          【讨论】:

                          • 已经用这个回答了,但也不完全是防弹的。
                          【解决方案29】:

                          可能是 Assembly execAssembly = Assembly.GetExecutingAssembly(); var creationTime = new FileInfo(execAssembly.Location).CreationTime; // "2019-09-08T14:29:12.2286642-04:00"

                          【讨论】:

                          猜你喜欢
                          • 2012-07-31
                          • 1970-01-01
                          • 1970-01-01
                          • 2014-06-24
                          • 2012-10-02
                          • 2011-08-02
                          • 1970-01-01
                          相关资源
                          最近更新 更多