前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >编程小知识之时间显示

编程小知识之时间显示

作者头像
用户2615200
发布2019-01-03 11:58:04
5170
发布2019-01-03 11:58:04
举报

本文简述了编程中常见的时间显示问题

开发中总会在各种场景下遇到需要显示时间的情况,显示的格式要求又往往五花八门,正常的譬如: “2018年12月29日20点30分15秒”, 简洁一些的则有: “2018-12-29 20:30:15”, 等等种类,不一而足.

其实各种显示方式都可以使用诸如 String.Format 等方法来实现,灵活性也比较高,但是中间的格式细节却比较繁琐,基本库中的 DateTime 类型同样提供了 ToString 方法来帮助我们实现时间日期的格式化显示,但是同样存在格式细节繁复,不便记忆使用的问题.

解决上述问题的一个方法就是将各种显示规则抽象为函数参数,拿上面的 “2018年12月29日20点30分15秒”“2018-12-29 20:30:15” 两种显示格式来说,区别其实就是各个中间的分隔符不同(第一种格式的分隔符为 “年” “月” “日” “点” “分” “秒”,第二种格式的分隔符为"-" “-” " " “:” “:” “”),据此,我们可以编写下面的时间格式化函数:

string GetDateStr(DateTime date, string yearSep, string monthSep, string daySep, string hourSep, string minuteSep, string secondSep)
{
    // implementation
}

虽然参数不少,但是借助缺省参数等方式,使用起来还算OK,一般的时间显示需求也足够应付(在我的实际开发工作中,该(类型)接口也确实使用了很长时间).

但是当后面遇到更细致的时间显示需求时,上面的接口便显得有些"无力"了,其中最普遍的需求之一可能就是省略年份的显示了(“2018年12月29日20点30分15秒” 省略年份显示为 “12月29日20点30分15秒”)

沿用之前抽象显示参数的方法,我们可能会扩展出这样的接口:

string GetDateStr(DateTime date, string yearSep, string monthSep, string daySep, string hourSep, string minuteSep, string secondSep, bool yearDisplay, bool monthDisplay, bool dayDisplay, bool hourDisplay, bool minuteDisplay, bool secondDisplay)
{
    // implementation
}

再考虑到后续可能还有补全显示时间(前导零补全,例如 “1秒” 显示为 “01秒”)等需求,很显然这里我们遇到了参数组合爆炸的问题.

实际上,我们需要的是一个简化的时间 Format 函数,支持且仅支持必要的控制格式,并且控制格式统一,方便记忆使用,下面的表格列出了可能的一种控制格式设计:

格式

说明

y 或 Y

年份显示

连续两个(包括)以上的 y 或 Y

两位数的年份显示(例如 2018 显示为 18)

M

月份显示

连续两个(包括)以上的 M

两位数的月份显示(例如 5 显示为 05)

d 或 D

天数显示

连续两个(包括)以上的 d 或 D

两位数的天数显示(例如 9 显示为 09)

h 或 H

小时显示

连续两个(包括)以上的 h 或 H

两位数的小时显示(例如 8 显示为 08)

m

分钟显示

连续两个(包括)以上的 m

两位数的秒钟显示(例如 6 显示为 06)

s 或 S

秒钟显示

连续两个(包括)以上的 s 或 S

两位数的秒钟显示(例如 16 显示为 16)

\

转义符

实现的示例代码如下:

// time format util
// maintainer hugoyu

using System;
using System.Text;

/*
time format desc :
y|Y : whole year display, e.g. 2018
(y|Y)(y|Y)(y|Y)* : short year display, e.g. 18
M : whole month display, e.g. 5
MMM* : whole month display with leading zero(if necessary), e.g. 05
d|D : whole day display, e.g. 9
(d|D)(d|D)(d|D)* : whole day display with leading zero(if necessary), e.g. 09
h|H : whole hour display, e.g. 8
(h|H)(h|H)(h/H)* : whole hour display with leading zero(if necessary), e.g. 08
m : whole minute display, e.g. 6
mmm* : whole minute display with leading zero(if necessary), e.g. 06
s|S : whole second display, e.g. 16
(s|S)(s|S)(s|S)* : whole second display with leading zero(if necessary), e.g. 16
'\' is escape character
*/

public static class TimeFormatUtil
{
    enum Token
    {
        None = 0,
        Year,
        YearPadding,
        Month,
        MonthPadding,
        Day,
        DayPadding,
        Hour,
        HourPadding,
        Minute,
        MinutePadding,
        Second,
        SecondPadding,
        Literal,
    }

    static StringBuilder s_strBuffer = new StringBuilder();

    static bool IsEscapeChar(char c)
    {
        return c == '\\';
    }

    static bool IsYearChar(char c)
    {
        return c == 'y' || c == 'Y';
    }

    static bool IsMonthChar(char c)
    {
        return c == 'M';
    }

    static bool IsDayChar(char c)
    {
        return c == 'd' || c == 'D';
    }

    static bool IsHourChar(char c)
    {
        return c == 'h' || c == 'H';
    }

    static bool IsMinuteChar(char c)
    {
        return c == 'm';
    }

    static bool IsSecondChar(char c)
    {
        return c == 's' || c == 'S';
    }

    static bool IsLiteralChar(char c)
    {
        return !IsEscapeChar(c) &&
               !IsYearChar(c) &&
               !IsMonthChar(c) &&
               !IsDayChar(c) &&
               !IsHourChar(c) &&
               !IsMinuteChar(c) &&
               !IsSecondChar(c);
    }

    static bool GetNextToken(string format, ref int curIndex, out Token token, out string tokenStr)
    {
        if (format != null)
        {
            if (curIndex >= 0 && curIndex < format.Length)
            {
                var curChar = format[curIndex];
                if (IsEscapeChar(curChar))
                {
                    // escape character
                    ++curIndex;
                    if (curIndex < format.Length)
                    {
                        token = Token.Literal;
                        tokenStr = format[curIndex].ToString();
                        ++curIndex;
                        return true;
                    }
                }
                else if (IsYearChar(curChar))
                {
                    var lastIndex = curIndex;
                    ++curIndex;

                    while (curIndex < format.Length && IsYearChar(format[curIndex]))
                    {
                        ++curIndex;
                    }

                    if (curIndex - lastIndex == 1)
                    {
                        token = Token.Year;
                        tokenStr = format.Substring(lastIndex, 1);
                        return true;
                    }
                    else
                    {
                        token = Token.YearPadding;
                        tokenStr = format.Substring(lastIndex, curIndex - lastIndex);
                        return true;
                    }
                }
                else if (IsMonthChar(curChar))
                {
                    var lastIndex = curIndex;
                    ++curIndex;

                    while (curIndex < format.Length && IsMonthChar(format[curIndex]))
                    {
                        ++curIndex;
                    }

                    if (curIndex - lastIndex == 1)
                    {
                        token = Token.Month;
                        tokenStr = format.Substring(lastIndex, 1);
                        return true;
                    }
                    else
                    {
                        token = Token.MonthPadding;
                        tokenStr = format.Substring(lastIndex, curIndex - lastIndex);
                        return true;
                    }
                }
                else if (IsDayChar(curChar))
                {
                    var lastIndex = curIndex;
                    ++curIndex;

                    while (curIndex < format.Length && IsDayChar(format[curIndex]))
                    {
                        ++curIndex;
                    }

                    if (curIndex - lastIndex == 1)
                    {
                        token = Token.Day;
                        tokenStr = format.Substring(lastIndex, 1);
                        return true;
                    }
                    else
                    {
                        token = Token.DayPadding;
                        tokenStr = format.Substring(lastIndex, curIndex - lastIndex);
                        return true;
                    }
                }
                else if (IsHourChar(curChar))
                {
                    var lastIndex = curIndex;
                    ++curIndex;

                    while (curIndex < format.Length && IsHourChar(format[curIndex]))
                    {
                        ++curIndex;
                    }

                    if (curIndex - lastIndex == 1)
                    {
                        token = Token.Hour;
                        tokenStr = format.Substring(lastIndex, 1);
                        return true;
                    }
                    else
                    {
                        token = Token.HourPadding;
                        tokenStr = format.Substring(lastIndex, curIndex - lastIndex);
                        return true;
                    }
                }
                else if (IsMinuteChar(curChar))
                {
                    var lastIndex = curIndex;
                    ++curIndex;

                    while (curIndex < format.Length && IsMinuteChar(format[curIndex]))
                    {
                        ++curIndex;
                    }

                    if (curIndex - lastIndex == 1)
                    {
                        token = Token.Minute;
                        tokenStr = format.Substring(lastIndex, 1);
                        return true;
                    }
                    else
                    {
                        token = Token.MinutePadding;
                        tokenStr = format.Substring(lastIndex, curIndex - lastIndex);
                        return true;
                    }
                }
                else if (IsSecondChar(curChar))
                {
                    var lastIndex = curIndex;
                    ++curIndex;

                    while (curIndex < format.Length && IsSecondChar(format[curIndex]))
                    {
                        ++curIndex;
                    }

                    if (curIndex - lastIndex == 1)
                    {
                        token = Token.Second;
                        tokenStr = format.Substring(lastIndex, 1);
                        return true;
                    }
                    else
                    {
                        token = Token.SecondPadding;
                        tokenStr = format.Substring(lastIndex, curIndex - lastIndex);
                        return true;
                    }
                }
                else
                {
                    var lastIndex = curIndex;
                    ++curIndex;

                    while (curIndex < format.Length && IsLiteralChar(format[curIndex]))
                    {
                        ++curIndex;
                    }

                    token = Token.Literal;
                    tokenStr = format.Substring(lastIndex, curIndex - lastIndex);
                    return true;
                }
            }
            else if (curIndex >= format.Length)
            {
                // end of string
                // reuse Token.None here ?
                token = Token.None;
                tokenStr = null;
                return true;
            }
        }

        token = Token.None;
        tokenStr = null;
        return false;
    }

    static string GetPaddingString(int value)
    {
        if (value >= 100)
        {
            value %= 100;
        }

        if (value < 10)
        {
            // [0, 10)
            return "0" + value.ToString();
        }
        else
        {
            // [10, 100)
            return value.ToString();
        }
    }

    public static string GetDateStrFormat(DateTime time, string format)
    {
        if (format != null)
        {
            s_strBuffer.Length = 0;
            var curIndex = 0;
            while (true)
            {
                Token token = Token.None;
                string tokenStr = null;
                var getTokenResult = GetNextToken(format, ref curIndex, out token, out tokenStr);
                if (getTokenResult)
                {
                    if (token == Token.None)
                    {
                        // end of string
                        return s_strBuffer.ToString();
                    }
                    else
                    {
                        switch (token)
                        {
                            case Token.Year:
                                s_strBuffer.Append(time.Year);
                                break;
                            case Token.YearPadding:
                                s_strBuffer.Append(GetPaddingString(time.Year));
                                break;
                            case Token.Month:
                                s_strBuffer.Append(time.Month);
                                break;
                            case Token.MonthPadding:
                                s_strBuffer.Append(GetPaddingString(time.Month));
                                break;
                            case Token.Day:
                                s_strBuffer.Append(time.Day);
                                break;
                            case Token.DayPadding:
                                s_strBuffer.Append(GetPaddingString(time.Day));
                                break;
                            case Token.Hour:
                                s_strBuffer.Append(time.Hour);
                                break;
                            case Token.HourPadding:
                                s_strBuffer.Append(GetPaddingString(time.Hour));
                                break;
                            case Token.Minute:
                                s_strBuffer.Append(time.Minute);
                                break;
                            case Token.MinutePadding:
                                s_strBuffer.Append(GetPaddingString(time.Minute));
                                break;
                            case Token.Second:
                                s_strBuffer.Append(time.Second);
                                break;
                            case Token.SecondPadding:
                                s_strBuffer.Append(GetPaddingString(time.Second));
                                break;
                            case Token.Literal:
                                s_strBuffer.Append(tokenStr);
                                break;
                        }
                    }
                }
                else
                {
                    // error occur
                    return null;
                }
            }
        }

        return null;
    }
}

小结 : GetDateStrFormat 方法平衡了接口灵活性和复杂性

参考资料

时间倏忽而过,转眼间 2018 年都到了最后一天,说来也巧,今年这最后一篇博文竟也是关于时间的,说来也可算作是一种纪念了,希冀在即将到来的 2019 年中,大家(包括自己)都继续砥砺前行吧~

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018年12月31日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档