本文中的时区通过TimeZoneInfo.FindSystemTimeZoneById获取的,其中的参数Id是windows下特有的,因此其Id在不同的操作系统下不一定存在,为标准起见可采用IANA时区库,可以使用三方库NodaTime来处理时间,本文暂不对NodaTime讲解。


  • DateTime 不储存时区信息
  • DateTimeOffset 可以储存时区信息

代码:

/// <summary>
/// 转换字符串为DateTime
/// </summary>
/// <param name="sourceTimeString">
///     源时间字符串
/// </param>
/// <param name="sourceFormat">
///     源时间的字符串格式<br/>
///     1. 例如: "yyyyMMddHHmmss","yyyy-MM-dd HH:mm:ss","yyyy-MM-ddTHH:mm:ssZ"<br/>
///     2. 本参数仅用于解析源时间时指定的字符串格式,不会对字符串中是否存在时区信息进行处理和转换<br/>
/// </param>
/// <param name="sourceIsUtc">
///     源时间是否UTC时间
/// </param>
/// <param name="sourceTimeZoneInfoId">
///     时区信息ID<br/>
///     1. 如果sourceIsUtc为true,该参数可以为空<br/>
///     2. 可通过<seealso cref="GetAllTimeZoneId"/>获取所有时区查看需要的时区ID<br/>
///     3. 中国时区ID为: "China Standard Time"
/// </param>
/// <param name="isToUtc">
///     是否转换为UTC时间<br/>
///     1. 如果为false,则转换为本地时间(本地时间取决于操作系统时区)<br/>
///     2. 如果需要转换为其他时区的时间,请使"isToUtc"为"true"后得到UTC时间再自行进行转换
/// </param>
/// <returns></returns>
public static DateTime? FormatDate(
    this string sourceTimeString,
    string sourceFormat,
    bool sourceIsUtc,
    string sourceTimeZoneInfoId,
    bool isToUtc
    )
{
    // 将指定时间转换为无时区的时间
    var sourceProvider = CultureInfo.InvariantCulture;// 不指定区域文化
    var srouceStyle = DateTimeStyles.None;// 不指定时区 System.DateTimeKind.Unspecified

    // 这个时间是默认建立在操作系统所在地区的时区上,因此不能直接使用,需要进一步转换为带时区和偏移的时间
    var sourceLocalTime = DateTime.ParseExact(sourceTimeString, sourceFormat, sourceProvider, srouceStyle);

    // 获取源时间对应的国家时区信息,以便找到时间偏移量
    var sourceTimeZoneInfo = GetTimeZoneOffsetHours(sourceIsUtc, sourceTimeZoneInfoId);
    var sourceOffsetHours = sourceTimeZoneInfo.BaseUtcOffset.Hours;

    // 将无时区时间转换为带时区的时间
    var dateTimeOffset = new DateTimeOffset(sourceLocalTime, TimeSpan.FromHours(sourceOffsetHours));

    if (isToUtc)
    {
        return dateTimeOffset.UtcDateTime;
    }
    else
    {
        return dateTimeOffset.LocalDateTime;
    }
}

/// <summary>
/// 根据时区ID获取时区信息
/// </summary>
/// <param name="isUtc"></param>
/// <param name="timeZoneInfoId"></param>
/// <returns></returns>
private static TimeZoneInfo GetTimeZoneOffsetHours(bool isUtc, string timeZoneInfoId = "China Standard Time")
{
    if (isUtc)
    {
        return TimeZoneInfo.Utc;
    }
    else
    {
        return TimeZoneInfo.FindSystemTimeZoneById(timeZoneInfoId);
    }
}

/// <summary>
/// 获取系统中所有可用的时区信息
/// </summary>
/// <returns></returns>
public static List<Tuple<int, string>> GetAllTimeZoneId()
{
    // 获取系统中所有可用的时区信息
    var timeZones = TimeZoneInfo.GetSystemTimeZones();
    return timeZones
        .Select(t => new Tuple<int, string>(t.BaseUtcOffset.Hours, t.Id))
        .OrderBy(t => t.Item1)
        .ThenBy(t => t.Item2)
        .ToList();
}

单元测试:

{
    // 获取当前系统所在时区的时间
    var dateTime = DateTime.Now;
    // 转换为日本时间
    TimeZoneInfo tokyoTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
    DateTime tokyoTime = TimeZoneInfo.ConvertTime(dateTime, tokyoTimeZone);
}

{
    var timeZoneIds = StringExtension.GetAllTimeZoneId();

    // 假设日本时间为 2024-08-16 21:00:00,对应utc时间为 12:00,对应北京时间(本机)为 20:00
    var inputTimeString = "20240816210000";
    string sourceFormat = "yyyyMMddHHmmss";
    bool sourceIsUtc = false;
    string sourceTimeZoneInfoId = "Tokyo Standard Time";
    bool isToUtc = true;
    var outDateTime = inputTimeString.FormatDate(sourceFormat, sourceIsUtc, sourceTimeZoneInfoId, isToUtc);

    var beijingTime = DateTime.Parse("2024-08-16 20:00:00");
    Assert.AreEqual(
        outDateTime,
        beijingTime.ToUniversalTime()
    );
}

标签: C#, 扩展类

评论已关闭