当前位置:
首页 > Python基础教程 >
-
C#教程之Monthly数据类型
Monthly由来
最近在做关于智能财税的项目,大量用到了账期相关的数据操作。项目已有两年历史了,对于账期数据,前辈们用的是DateTime数据类型,即每个月的最后一天就是账期。而用DateTime来表达账期数据,确实让我人很困惑:
- 概念不统一:
DateTime是时间类型,而账期只跟年月相关,DateTime用在这里确实有点杀鸡用了宰牛刀,而且给人的理解和沟通造成了额外的误解。- 格式不统一:
为了在数据传输和存储中达到数据的统一性,需要大量的字符串与日期的转换、日期格式的转换。- 浪费性能:
DateTime的精确度是可以到毫秒级的,而我们的账期数据只需要精确到月,如:2018年1月账期。 所以DateTime是很影响运算性能和存储空间的。- 操作异常:
由于账期是取月末日期,所以对每次接收了账期参数都要取月末值,以确保数据的准确性。而在实际开发中,任何一个疏忽都会引发表达偏差。
Monthly简介
Monthly是一个跟Datetime类似的,与月份相关的数据类型,适用于表达年月数据,如账单、账期、月刊等信息。
Monthly源码
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
namespace System
{
/// <summary>
/// 与月份相关的对象,如账单、账期、月刊、月报等
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Auto)]
public struct Monthly : IComparable<Monthly>, IEquatable<Monthly>
{
private int _year;
private int _month;
#region Property
/// <summary>
/// 获取当前实例的年
/// </summary>
public int Year => _year;
/// <summary>
/// 获取当前实例的月
/// </summary>
public int Month => _month;
/// <summary>
/// 获取当前实例的年月标记值,如2018年1月记为 : 201801
/// </summary>
public int Dot => this._year * 100 + this._month;
/// <summary>
/// 获取当前实例从公元零年一月开始的月份累计值
/// </summary>
public int Tickes => this._year * 12 + this._month;
/// <summary>
/// 获取当前实例所在的季度
/// </summary>
public int Quarter => (this._month - 1) / 3 + 1;
#endregion
#region Ctor
/// <summary>
/// 以指定的年和月初始化Monthly实例。
/// </summary>
/// <param name="year"> 年(0 到 9999)</param>
/// <param name="month"> 月(1 到 12)</param>
public Monthly(int year, int month)
{
CheckYear(year);
CheckMonth(month);
this._year = year;
this._month = month;
}
/// <summary>
/// 获取以当前时间点为依据的新实例
/// </summary>
public static Monthly Current => new Monthly() { _year = DateTime.Now.Year, _month = DateTime.Now.Month };
/// <summary>
/// 获取当前时间点的上月为依据的新实例
/// </summary>
public Monthly Previous => Monthly.fromTickes(this.Tickes - 1);
/// <summary>
/// 获取当前时间点的下月为依据的新实例
/// </summary>
public Monthly Next => Monthly.fromTickes(this.Tickes + 1);
/// <summary>
/// 获取当前年份的一月为依据的新实例
/// </summary>
public Monthly First => new Monthly() { _year = this._year, _month = 1 };
/// <summary>
/// 获取当前年份的十二月为依据的新实例
/// </summary>
public Monthly Last => new Monthly() { _year = this._year, _month = 12 };
/// <summary>
/// 获取Monthly的最小值实例
/// </summary>
public static Monthly MinValue => new Monthly() { _year = 0, _month = 1 };
/// <summary>
/// 获取Monthly的最大值实例
/// </summary>
public static Monthly MaxValue => new Monthly() { _year = 9999, _month = 12 };
#endregion
#region Method
private static int yearOfDot(int dot) => dot / 100;
private static int monthOfDot(int dot) => dot % 100;
/// <summary>
/// 获取当前实例的年月标记值,如2018年1月记为 : 201801
/// </summary>
/// <returns></returns>
public int ToDot() => this.Dot;
/// <summary>
/// 以当前实例与years的和值为依据创建一个新实例
/// </summary>
public Monthly AddYears(int years) => Monthly.FromTickes(this.Tickes + years * 12);
/// <summary>
/// 以当前实例与months的和值为依据创建一个新实例
/// </summary>
public Monthly AddMonths(int months) => Monthly.FromTickes(this.Tickes + months);
/// <summary>
/// 判断当前实例的值与给定实例的值是否相等
/// </summary>
public bool Equals(Monthly other) => this.Tickes == other.Tickes;
/// <summary>
/// 获取当前实例与给定实例的月份差值
/// </summary>
public int SpanMonths(Monthly other) => this - other;
/// <summary>
/// 获取当前实例与DateTime实例的月份差值
/// </summary>
public int SpanMonths(DateTime date) => this.Tickes - date.Year * 12 - date.Month;
/// <summary>
/// 获取当前实例与给定实例的大小比较的结果标识
/// </summary>
/// <param name="other"></param>
/// <returns>-1:小于other实例值 ; 0 等于other实例值 ; 1:大于other实例值</returns>
public int CompareTo(Monthly other)
{
if (this.Tickes < other.Tickes) return -1;
if (this.Tickes > other.Tickes) return 1;
else return 0;
}
/// <summary>
/// 以年月标记值创建一个Monthly新实例
/// </summary>
/// <param name="dot">格式:201801</param>
/// <returns></returns>
public static Monthly FromDot(int dot)
{
var year = yearOfDot(dot);
var month = monthOfDot(dot);
if (year < 0 || year > 9999 || month < 1 || month > 12)
throw new ArgumentOutOfRangeException("dot", dot, "Please enter correct dot format such as \'201801\'.");
return new Monthly
{
_year = yearOfDot(dot),
_month = monthOfDot(dot)
};
}
private static Monthly fromTickes(int tickes)
{
return new Monthly
{
_year = (tickes - 1) / 12,
_month = tickes % 12 == 0 ? 12 : tickes % 12
};
}
/// <summary>
/// 以年月累计值创建一个Monthly新实例
/// </summary>
/// <param name="tickes">以公元零年一月为起点的月份计数值(1-120000)</param>
public static Monthly FromTickes(int tickes)
{
if (tickes < 1 || tickes > 120000)
throw new ArgumentOutOfRangeException("tickes", tickes, "The tickes must beteen 1 and 120000 .");
return fromTickes(tickes);
}
/// <summary>
/// 以DateTime实例创建一个Monthly新实例
/// </summary>
public static Monthly FromDate(DateTime time) => new Monthly() { _year = time.Year, _month = time.Month };
/// <summary>
/// 以诸如"2018/01"格式的字符串创建一个Monthly新实例
/// </summary>
/// <param name="s">"2018/01"格式的字符串</param>
/// <param name="spliter">分隔符</param>
public static Monthly FromString(string s)
{
if (string.IsNullOrEmpty(s))
throw new Exception("The parameter cannot be null or empty.");
var nums = Regex.Matches(s, "[0-9]+");
if (nums.Count == 0)
throw new Exception("Please give the correct parameters, such as '2018/01' .");
if (nums.Count == 1)
return new Monthly(0, Convert.ToInt32(nums[0].ToString().TrimStart('0')));
else
return new Monthly(Convert.ToInt32(nums[0].ToString().TrimStart('0')), Convert.ToInt32(nums[1].ToString().TrimStart('0')));
}
/// <summary>
/// 获取一段时间内的Monthly数轴(包含开始与结束月份)
/// </summary>
/// <param name="from">开始月份</param>
/// <param name="to">结束月份</param>
/// <returns></returns>
public static List<Monthly> Axis(Monthly from, Monthly to)
{
var result = new List<Monthly>();
var span = from - to;
var len = (span ^ (span >> 31)) - (span >> 31) + 1;
for (int i = 0; i < len; i++)
{
if (span > 0) result.Add(from - i);
else result.Add(from + i);
}
return result;
}
/// <summary>
/// 获取给定时间段内的Monthly集合(包含开始与结束月份)
/// </summary>
/// <param name="from">开始月份</param>
/// <param name="to">结束月份</param>
/// <returns></returns>
public static List<Monthly> Axis(int from, int to)
{
return Axis(Monthly.FromDot(from), Monthly.FromDot(to));
}
/// <summary>
/// 检查year的合法性
/// </summary>
private static void CheckYear(int year)
{
if (year < 0 || year > 9999)
throw new ArgumentOutOfRangeException("year", year, "The year must beteen 0 and 9999 .");
}
/// <summary>
/// 检查month的合法性
/// </summary>
private static void CheckMonth(int month)
{
if (month < 1 || month > 12)
throw new ArgumentOutOfRangeException("month", month, "The month must beteen 1 and 12 .");
}
#endregion
#region Operator
/// <summary>
/// 以给定实例与months的和值创建一个新实例
/// </summary>
/// <param name="months">月分数</param>
public static Monthly operator +(Monthly m, int months) => FromTickes(m.Tickes + months);
/// <summary>
/// 以给定实例与months的差值创建一个新实例
/// </summary>
/// <param name="months">月分数</param>
public static Monthly operator -(Monthly m, int months) => FromTickes(m.Tickes - months);
/// <summary>
/// 获取当前实例与给定实例的月份差值
/// </summary>
public static int operator -(Monthly m1, Monthly m2) => m1.Tickes - m2.Tickes;
/// <summary>
///获取当前实例与DateTime实例的月份差值
/// </summary>
public static int operator -(Monthly m, DateTime d) => m.SpanMonths(d);
public static Monthly operator ++(Monthly m) => m + 1;
public static Monthly operator --(Monthly m) => m - 1;
/// <summary>
///判断m1是否等于m2
/// </summary>
public static bool operator ==(Monthly m1, Monthly m2) => m1.Tickes == m2.Tickes;
/// <summary>
/// 判断m1是否不等于m2
/// </summary>
public static bool operator !=(Monthly m1, Monthly m2) => m1.Tickes != m2.Tickes;
/// <summary>
/// 判断m1是否小于m2
/// </summary>
public static bool operator <(Monthly m1, Monthly m2) => m1.Tickes < m2.Tickes;
/// <summary>
/// 判断m1是否大于m2
/// </summary>
public static bool operator >(Monthly m1, Monthly m2)
{
return m1.Tickes > m2.Tickes; ;
}
/// <summary>
/// 判断m1是否小于等于m2
/// </summary>
public static bool operator <=(Monthly m1, Monthly m2)
{
return m1.Tickes <= m2.Tickes; ;
}
/// <summary>
/// 判断m1是否大于等于m2
/// </summary>
public static bool operator >=(Monthly m1, Monthly m2)
{
return m1.Tickes >= m2.Tickes; ;
}
/// <summary>
/// 以年月标识的Monthly实例
/// </summary>
/// <param name="dot">格式:201801</param>
public static implicit operator Monthly(int dot)
{
return Monthly.FromDot(dot);
}
#endregion
#region Override
/// <summary>
/// 获取包含"Y、y、M、m"字符格式的自定义Monthly字符串
/// </summary>
/// <param name="format">
/// 如:yyyy/mm ; yy/mm ; yyyy年mm月 ;YYYY-Mm...
/// 不区分大小写
/// </param>
/// <returns></returns>
public string ToString(string format = "yyyy/mm")
{
return Format(this, format);
}
/// <summary>
/// 判断当前实例的值与给定实例的转换值是否相等
/// </summary>
public override bool Equals(object obj)
{
if (obj is null) throw new ArgumentNullException("obj", "The parameter cannot be null.");
if (obj is Monthly) return this == (Monthly)obj;
if (obj is DateTime) return this == Monthly.FromDate((DateTime)obj);
throw new ArgumentException("The parameter must be System.DateTime type or System.Monthly type .", "obj");
}
public override int GetHashCode()
{
Int64 ticks = Tickes;
return unchecked((int)ticks) ^ (int)(ticks >> 32);
}
private static string Format(Monthly m, string format)
{
string _y = m.Year.ToString();
string _m = m.Month.ToString();
format = format.ToLower();
if (!(format.Contains("yyyy") || format.Contains("yyyy")) && !(format.Contains("mm") || format.Contains("m")))
throw new ArgumentException("The format expression error. ", nameof(format));
if (format.Contains("yyyy"))
format = format.Replace("yyyy", m.Year < 10 ? $"0{_y}" : _y);
else if (format.Contains("yy"))
format = format.Replace("yy", m.Year < 10 ? $"0{_y}" : _y.PadLeft(4, '0').Substring(2));
if (format.Contains("mm"))
format = format.Replace("mm", m.Month < 10 ? $"0{_m}" : _m);
else if (format.Contains("m"))
format = format.Replace("m", _m.TrimStart('0'));
return format;
}
#endregion
}
}
测试
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Pilipa.Utility.Test
{
[TestClass]
public class MonthlyTest
{
[TestMethod]
public void TestProps()
{
var tar = DateTime.Now;
Monthly plan = Monthly.FromDate(tar);
Assert.AreEqual(Monthly.Current, new Monthly(tar.Year, tar.Month));
Assert.AreEqual(plan.Year, tar.Year);
Assert.AreEqual(plan.Month, tar.Month);
Assert.AreEqual(plan.Dot, tar.Year * 100 + tar.Month);
Assert.AreEqual(plan.Tickes, tar.Year * 12 + tar.Month);
Assert.AreEqual(plan.First.ToDot(), tar.Year * 100 + 1);
Assert.AreEqual(plan.Last.ToDot(), tar.Year * 100 + 12);
Assert.AreEqual(plan.First.Previous.ToDot(), tar.AddYears(-1).Year * 100 + 12);
Assert.AreEqual(plan.Last.Next.ToDot(), tar.AddYears(1).Year * 100 + 1);
Assert.AreEqual(plan.Quarter, GetQuarter(tar.Month));
Assert.AreEqual(Monthly.MinValue, new Monthly(0, 1));
Assert.AreEqual(Monthly.MaxValue, new Monthly(9999, 12));
}
private int GetQuarter(int q)
{
if (new System.Collections.Generic.List<int>() { 1, 2, 3 }.Contains(q)) return 1;
if (new System.Collections.Generic.List<int>() { 4, 5, 6 }.Contains(q)) return 2;
if (new System.Collections.Generic.List<int>() { 7, 8, 9 }.Contains(q)) return 3;
if (new System.Collections.Generic.List<int>() { 10, 11, 12 }.Contains(q)) return 4;
return 0;
}
[TestMethod]
栏目列表
最新更新
nodejs爬虫
Python正则表达式完全指南
爬取豆瓣Top250图书数据
shp 地图文件批量添加字段
爬虫小试牛刀(爬取学校通知公告)
【python基础】函数-初识函数
【python基础】函数-返回值
HTTP请求:requests模块基础使用必知必会
Python初学者友好丨详解参数传递类型
如何有效管理爬虫流量?
SQL SERVER中递归
2个场景实例讲解GaussDB(DWS)基表统计信息估
常用的 SQL Server 关键字及其含义
动手分析SQL Server中的事务中使用的锁
openGauss内核分析:SQL by pass & 经典执行
一招教你如何高效批量导入与更新数据
天天写SQL,这些神奇的特性你知道吗?
openGauss内核分析:执行计划生成
[IM002]Navicat ODBC驱动器管理器 未发现数据
初入Sql Server 之 存储过程的简单使用
这是目前我见过最好的跨域解决方案!
减少回流与重绘
减少回流与重绘
如何使用KrpanoToolJS在浏览器切图
performance.now() 与 Date.now() 对比
一款纯 JS 实现的轻量化图片编辑器
关于开发 VS Code 插件遇到的 workbench.scm.
前端设计模式——观察者模式
前端设计模式——中介者模式
创建型-原型模式