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

开发中总会在各种场景下遇到需要显示时间的情况,显示的格式要求又往往五花八门,正常的譬如: “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 hugoyuusing 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 stringreturn 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 occurreturn null;}}}return null;}
}

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

参考资料

  • DateTime.ToString的格式说明

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

编程小知识之时间显示相关推荐

  1. c语言代码游戏跳一跳,微信小程序《跳一跳》游戏里的编程小知识,你知道吗?...

    微信小程序<跳一跳>游戏里的编程小知识,你知道吗? 今日你跳了吗? 玩过的朋友都知道,跳一跳里的游戏操作非常简单,就用手指按住屏幕按住施放进行跳跃,整个游戏是个人都可以很轻松玩起来! 游戏 ...

  2. Linux网络编程小知识(字节序、IP格式、函数、子网掩码、DNS域名解析代码实现)

    参考:网络编程前的一些小知识–Linux笔记 作者:一只青木呀 发布时间: 2021-04-12 23:19:10 网址:https://blog.csdn.net/weixin_45309916/a ...

  3. 编程小知识之 Dithering

    本文简单介绍了 Dithering(抖动) 的一些知识 图形后处理有一种操作称为 Dithering(抖动),所谓 Dithering,就是一种能够在较小色彩空间上"模拟出"较大色 ...

  4. python编程小知识_分享Python开发中要注意的十个小贴士

    大家请注意:这篇文中假设我们都用的是Python 3 1. 列表推导式 你有一个list:bag = [1, 2, 3, 4, 5] 现在你想让所有元素翻倍,让它看起来是这个样子:[2, 4, 6, ...

  5. 编程小知识 之 序列 rotate

    本文简述了 序列 rotate 的一些知识 基础 本篇文章中所说的 序列 rotate(旋转) 可能跟我们平常理解的 图像 rotate(旋转) 不太相同,所谓 序列 rotate,其实就是一种调整序 ...

  6. 编程小知识:文件扩展名的作用是什么?通俗易懂的文件扩展名详解

    介绍 文件扩展名(filename extension)也称为文件的后缀名,是操作系统用来标记文件类型的一种机制,在Windows系统下,扩展名还可以告诉操作系统默认用什么软件打开文件.通常来说,一个 ...

  7. 编程小知识之 CanvasScaler 的一点知识

    本文简述了 Unity 中 CanvasScaler 的一点知识 制作 UI 时,一般都需要进行多分辨率适配,基本的方法大概有以下几种: UI 参照单一的分辨率(参考分辨率)进行制作,实际显示时按照某 ...

  8. 8条嵌入式C语言编程小知识总结

    1. 流水线被指令填满时才能发挥最大效能,即每时钟周期完成一条指令的执行(仅指单周期指令).如果程序发生跳转,流水线会被清空,这将需要几个时钟才能使流水线再次填满.因此,尽量少的使用跳转指令可以提高程 ...

  9. 时间小知识对于时间转换可能有帮助

    那么UTC与世界各地的时间应如何换算呢?它是将全世界分为24个时区,地球的东.西经各180°(共360°)被24个时区平分,每个时区各占15°.以经度0°(即本初子午线)为基准,东经7°30′与西经7 ...

最新文章

  1. 官宣!英雄联盟、王者荣耀、街霸……这些电子竞技入选杭州亚运会
  2. 《系统集成项目管理工程师》必背100个知识点-02项目组织方式和特点
  3. okgo 缓存html,okhttp-OkGo OkHttpUtils-2.0.0 升级后改名 OkGo,全新完美支持 RxJava,比 Retro @codeKK Android开源站...
  4. [LeetCode]--71. Simplify Path
  5. 网页设计图片向上浮动_果冻公开课第六课:5分钟理解浮动布局
  6. UNION ALL vs UNION
  7. C++将double类型小数以16进制格式打印出
  8. windows知识点2
  9. Atitit 图像处理之理解卷积attilax总结
  10. c语言编程创意表白,C语言和图形界面编程打造——浪漫的表白程序
  11. java栈中存放_java栈存放什么?java堆存放什么?
  12. 增强版在线LEFSe分析和可视化鉴定标志性基因或物种
  13. HoloLens忘记开机密码,并重新安装HoloLens系统
  14. ubuntu加装固态硬盘设置
  15. 15 个问题自查真的了解 java 编译优化吗
  16. 全国高级计算机高新技术考证合格证书能申请人才入户吗?
  17. 2015去哪儿、大众点评、搜狗、小米校园招聘笔试题
  18. kafka权限认证ssl
  19. 安卓Bugly使用教程
  20. 云服务器显示内存不足怎么调整,云服务器扩充内存

热门文章

  1. 表格制作过程html,HTML进行表格制作
  2. Android定制属于你自己的导航栏
  3. 数据结构之寻找下标和相等的数字方阵
  4. 学习-定义和调用求x的n次幂的函数
  5. XlsReadWriteII 版本声明操作要点
  6. word2016:使用多级列表+样式表,自动生成插图清单
  7. 先行试点,创新改造:中信期货关键业务系统自主可控的实践之路
  8. mysql连接navicat premium 15的具体方法 附navicat的官网连接
  9. 英文文本大小写的转换
  10. Pion流媒体服务测试