NSDate、NSTimeZone、NSLocale、NSDateComponents、NSCalendar、NSDateFormatter详解

2,372 阅读14分钟

本文主要是介绍了iOS中关于时间转化和处理。


文章分六部分介绍:

  • NSDate -- 表示一个绝对的时间点
  • NSTimeZone -- 时区信息
  • NSLocale -- 本地化信息
  • NSDateComponents -- 一个封装了具体年月日、时秒分、周、季度等的类
  • NSCalendar -- 日历类,它提供了大部分的日期计算接口,并且允许您在NSDate和NSDateComponents之间转换
  • NSDateFormatter -- 用来在日期和字符串之间转换

一、NSDate

用来表示公历的GMT时间(格林威治时间)

GMT 时间基准是1970-01-01 08:00:00, 而标准时间基准是2021-01-01 08:00:00

1.1 初始化

  1. 默认初始化,返回当前时间
- (instancetype)init;
+ (instancetype)date;
  1. 以当前时间的偏移秒数来初始化 eg:当前时间是2021-06-25 16:55:07 +0000,初始化是secs传20, 得到的时间就是2021-06-25 16:55:27 +0000
- (instancetype)initWithTimeIntervalSinceNow:(NSTimeInterval)secs;
+ (instancetype)dateWithTimeIntervalSinceNow:(NSTimeInterval)secs;
  1. 以GMT时间的偏移秒数来初始化, 得到的时间是格林威治时间往前\color{red}{往前}secs秒 eg:初始化时secs = 20,得到的时间就是1970-1-1 07:59:40 +0000
- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)secs;
+ (instancetype)dateWithTimeIntervalSince1970:(NSTimeInterval)secs;
  1. 以基准时间2001-1-1 08:00:00的偏移秒数来初始化 eg:初始化时ti = 80, 得到的时间就是Mon Jan 1 08:01:20 2001
- (instancetype)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti;
+ (instancetype)dateWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti;
  1. 以sinceDate为基准时间的偏移秒数来初始化
- (instancetype)initWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;
+ (instancetype)dateWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;

1.2 常用属性和方法

  1. 两个只读属性, 分别用来返回一个极早的时间点和一个极晚的时间点
// 极晚的时间 4001-01-01 08:00:00 +0000
@property (class, readonly, copy) NSDate *distantFuture;
// 极早的时间 0000-12-30 08:05:43 +0000
@property (class, readonly, copy) NSDate *distantPast;
  1. 返回以当前NSDate对象为基准,偏移多少秒后得到的新NSDate对象
- (instancetype)dateByAddingTimeInterval:(NSTimeInterval)ti;
  1. 将当前对象与参数传递的对象进行比较,根据是否相同返回BOOL值
- (BOOL)isEqualToDate:(NSDate *)otherDate;
  1. 比较两个NSDate对象,返回较早/较晚的时间点,并以新NSDate对象的形式返回
- (NSDate *)earlierDate:(NSDate *)anotherDate;
- (NSDate *)laterDate:(NSDate *)anotherDate;
  1. 将当前对象与参数传递的对象进行比较,如果相同,返回0(NSOrderedSame);对象时间早于参数时间,返回-1(NSOrderedAscending);对象时间晚于参数时间,返回1(NSOrderedDescending)
- (NSComparisonResult)compare:(NSDate *)other;

eg:output: 1 , 也就是降序, date9 值比date10 大, 即date9 比date10 更晚

NSDate *date9 = [NSDate dateWithTimeIntervalSince1970:0];
NSDate *date10 = [NSDate dateWithTimeInterval:-50 sinceDate:date9];

NSInteger result = [date9 compare:date10];
NSLog(@"%ld",(long)result);
  1. 返回当前对象时间与1970-1-1 08:00:00的相隔秒数,也可以这样理解:从1970-1-1 08:00:00开始,经过多少秒到达对象指定时间
@property (readonly) NSTimeInterval timeIntervalSince1970;
  1. 返回当前对象时间与客户端时间的相隔秒数,也可以这样理解:从客户端当前时间开始,经过多少秒到达对象指定时间
@property (readonly) NSTimeInterval timeIntervalSinceNow;
  1. 返回当前对象时间与2001-1-1 08:00:00的相隔秒数,也可以这样理解:从2001-1-1 08:00:00开始,经过多少秒到达对象指定时间
@property (class, readonly) NSTimeInterval timeIntervalSinceReferenceDate;
  1. 返回当前对象时间与参数传递的对象时间的相隔秒数,也可以这样理解:从参数时间开始,经过多少秒到达对象执行时间
- (NSTimeInterval)timeIntervalSinceDate:(NSDate *)anotherDate;

二、NSTimeZone

表示时区信息

2.1 初始化

  1. 根据时区名称初始化
- (nullable instancetype)initWithName:(NSString *)tzName;
+ (nullable instancetype)timeZoneWithName:(NSString *)tzName;

eg:output: America/Chicago (GMT-5) offset -18000 (Daylight) (时差5个小时, 56060)

NSTimeZone *zone = [[NSTimeZone alloc] initWithName:@"America/Chicago"];
NSLog(@"%@",zone);
  1. 根据时区缩写初始化。例如:EST(美国东部标准时间)、HKT(香港标准时间)
+ (nullable instancetype)timeZoneWithAbbreviation:(NSString *)abbreviation;

eg:output: America/New_York (GMT-4) offset -14400 (Daylight)

NSTimeZone *zone2 = [NSTimeZone timeZoneWithAbbreviation:@"EST"];    NSLog(@"%@",zone2);
  1. 返回系统时区
@property (class, readonly, copy) NSTimeZone *systemTimeZone;

eg;假如时区是上海,打印出的时区信息将会是:Asia/Shanghai (CST (China)) offset 28800,28800代表相对于GMT时间偏移的秒数,即8个小时。(86060)

NSTimeZone *zone3 = [NSTimeZone systemTimeZone];
NSLog(@"%@",zone3);
  1. 返回本地时区: 与systemTimeZone的区别在于本地时区可以被修改,而系统时区不能修改
@property (class, readonly, copy) NSTimeZone *localTimeZone;
  1. 根据零时区的秒数偏移返回一个新时区对象
+ (instancetype)timeZoneForSecondsFromGMT:(NSInteger)seconds;

2.2 常用属性和方法

  1. 以数组的形式返回所有已知的时区名称
@property (class, readonly, copy) NSArray<NSString *> *knownTimeZoneNames;
NSArray *zoneArray = [NSTimeZone knownTimeZoneNames];
for(NSString *str in zoneArray) {
    NSLog(@"%@",str);
}
  1. 返回时区对象的名称或缩写
@property (readonly, copy) NSString *name;
@property (nullable, readonly, copy) NSString *abbreviation;
NSTimeZone *zone = [NSTimeZone localTimeZone];

NSString *strZoneName = [zone name];
NSString *strZoneAbbreviation = [zone abbreviation];

NSLog(@"name is %@",strZoneName);
NSLog(@"abbreviation is %@",strZoneAbbreviation);

output: name is Asia/Shanghai - abbreviation is GMT+8

  1. 得到当前时区与零时区的间隔秒数
@property (readonly) NSInteger secondsFromGMT;

三、NSLoale

返回本地化信息,主要体现在"语言"和"区域格式"这两个设置项

3.1 初始化

  1. 返回系统初始本地化信息
@property (class, readonly, copy) NSLocale *systemLocale;

eg:output: gregorian, 代表当前日历 NSCalendarIdentifierGregorian 公历

NSLocale *locale = [NSLocale systemLocale];
NSLog(@"%@",[[locale objectForKey:NSLocaleCalendar] calendarIdentifier]);
  1. 返回当前客户端的本地化信息
@property (class, readonly, copy) NSLocale *currentLocale;
@property (class, readonly, strong) NSLocale *autoupdatingCurrentLocale

注:这两个类方法都将返回当前客户端的本地化信息,区别在于:currentLocale取得的值会一直保持在cache中,第一次用此方法实例化对象后,即使修改了本地化设定,这个对象也不会改变。而使用autoupdatingCurrentLocale,当每次修改本地化设定,其实例化的对象也会随之改变。 eg:下面的代码演示了区别所在,假设初始本地化信息为en_CN,先用这两个函数分别初始化两个对象,然后修改本地化设定语言为台湾繁体中文,再重新打印这两个对象的信息:

NSLocale *locale1 = [NSLocale currentLocale];
NSLocale *locale2 = [NSLocale autoupdatingCurrentLocale];;

NSLog(@"before:%@",locale1.localeIdentifier);
NSLog(@"before:%@",locale2.localeIdentifier);

// output: before:en_CN
// output: before:en_CN

// --> 支持多语言且手机修改语言后

// output: before:en_CN
// output: before:zh_TW
  1. 用标示符初始化本地化信息
- (instancetype)initWithLocaleIdentifier:(NSString *)string;

eg:output: ¥ , 代码用"zh_CN"来初始化对象,然后再打印出对象的货币符号,得到的结果是人民币符号¥

NSLocale *locale3 = [[NSLocale alloc] initWithLocaleIdentifier:@"zh_CN"];
NSString *strSymbol = [locale3 objectForKey:NSLocaleCurrencySymbol];
NSLog(@"%@",strSymbol);

3.2 常用对象方法与类方法

  1. 根据不同的key返回各种本地化信息
- (nullable id)objectForKey:(NSLocaleKey)key;
// 返回了当前货币符号
NSString *strSymbol = [locale objectForKey:NSLocaleCurrencySymbol];

// 返回了当前日历
NSCalendar *calendar = [[NSLocale currentLocale] objectForKey:NSLocaleCalendar];
  1. 显示特定地区代号下相应键的显示名称
- (nullable NSString *)displayNameForKey:(NSLocaleKey)key value:(id)value;
NSLocale *locale1 = [[NSLocale alloc] initWithLocaleIdentifier:@"zh_CN"];
NSString *str = [locale1 displayNameForKey:NSLocaleIdentifier value:@"en_US"];
NSLog(@"%@",str);
// output: 英语(美国)
// 第一句代码代表以中文来实例化对象,然后得到"en_US"的NSLocaleIdentifier键的显示名称。最后输出的结果是"英文(美国)"

四、NSDateComponents

NSDateComponents封装了具体年月日、时秒分、周、季度等, 可以通过设置这些属性来制指定一个具体的时间

NSDateComponents *compt = [[NSDateComponents alloc] init];
[compt setEra:1];
[compt setYear:2013];
[compt setMonth:3];
[compt setDay:15];
[compt setHour:11];
[compt setMinute:20];
[compt setSecond:55];
[compt setQuarter:2];
[compt setTimeZone:[NSTimeZone systemTimeZone]];
[compt setWeek:3];
[compt setWeekday:4];
[compt setWeekOfMonth:3];
[compt setWeekOfYear:2];
[compt setCalendar:[NSCalendar currentCalendar]];

五、NSCalendar

5.1 初始化

  1. 返回当前客户端的逻辑日历, 区别在于:

    currentCalendar取得的值会一直保持在cache中,第一次用此方法实例化对象后,即使修改了系统日历设定,这个对象也不会改变

    而使用autoupdatingCurrentCalendar,当每次修改系统日历设定,其实例化的对象也会随之改变

@property (class, readonly, copy) NSCalendar *currentCalendar;
@property (class, readonly, strong) NSCalendar *autoupdatingCurrentCalendar
  1. 根据提供的日历标示符初始化
- (nullable id)initWithCalendarIdentifier:(NSCalendarIdentifier)ident;
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierChinese];
NSLog(@"%@",calendar.calendarIdentifier);
// output: chinese
  1. 设置本地化信息
@property (nullable, copy) NSLocale *locale;
  1. 设置时区信息
@property (copy) NSTimeZone *timeZone;

5.2 NSCalendar & NSDateComponents

  1. 取得一个NSDate对象的1个或多个部分,用NSDateComponents来封装
- (NSDateComponents *)components:(NSCalendarUnit)unitFlags fromDate:(NSDate *)date;
NSCalendar *calendar = [NSCalendar currentCalendar];

NSDate *date = [NSDate date];

NSDateComponents *compt = [calendar components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:date];

NSLog(@"%ld, %@",(long)[compt year], date);
NSLog(@"%ld, %@",(long)[compt month], date);
NSLog(@"%ld, %@",(long)[compt day], date);

// output: 2021, Mon Jun 28 11:33:50 2021
// output: 6, Mon Jun 28 11:33:50 2021
// output: 28, Mon Jun 28 11:33:50 2021

//需要注意的是,只有明确指定了unitFlags,NSDateComponents相应的那一部分才有值

NSCalendarUnit包含的值有:

NSEraCalendarUnit -- 纪元单位。对于NSGregorianCalendar(公历)来说,只有公元前(BC)和公元(AD);而对于其它历法可能有很多,例如日本和历是以每一代君王统治来做计算。
NSYearCalendarUnit -- 年单位。值很大,相当于经历了多少年,未来多少年。
NSMonthCalendarUnit -- 月单位。范围为1-12
NSDayCalendarUnit -- 天单位。范围为1-31
NSHourCalendarUnit -- 小时单位。范围为0-24
NSMinuteCalendarUnit -- 分钟单位。范围为0-60
NSSecondCalendarUnit -- 秒单位。范围为0-60
NSWeekCalendarUnit -- 周单位。范围为1-53
NSWeekdayCalendarUnit -- 星期单位,每周的7天。范围为1-7
NSWeekdayOrdinalCalendarUnit -- 没完全搞清楚
NSQuarterCalendarUnit -- 几刻钟,也就是15分钟。范围为1-4
NSWeekOfMonthCalendarUnit -- 月包含的周数。最多为6个周
NSWeekOfYearCalendarUnit -- 年包含的周数。最多为53个周
NSYearForWeekOfYearCalendarUnit -- 没完全搞清楚
NSTimeZoneCalendarUnit -- 没完全搞清楚

  1. 取得两个NSDate对象的时间间隔,用NSDateComponents来封装
- (NSDateComponents *)components:(NSCalendarUnit)unitFlags fromDate:(NSDate *)startingDate toDate:(NSDate *)resultDate options:(NSCalendarOptions)opts;

eg:

NSCalendar *calendar2 = [NSCalendar currentCalendar];

NSDate *date1 = [NSDate date];
NSDate *date2 = [NSDate dateWithTimeInterval:5*60*60+75 sinceDate:date];

NSDateComponents *compt2 = [calendar2 components:(NSCalendarUnitMinute | NSCalendarUnitSecond) fromDate:date1 toDate:date2 options:0];

NSLog(@"%ld",(long)[compt2 minute]); // 301
NSLog(@"%ld",(long)[compt2 second]); // 14 ??? 可能在比较的时候消耗了1s

有两点需要注意:

     ① 得到的NSDateComponents对象可能会包含负数。例如:当toDate比fromDate晚10秒,second部分返回10;当toDate比fromDate早10秒,second部分返回-10

     ② 当指定unitFlags返回多个部分时,相隔的时间由多个部分共同组成。例如:上面的例子时间相差5小时1分15秒,如果指定只返回second部分,将得到18075秒;如果指定返回minute和second部分,将得到301分15秒;如果指定返回hour、minute和second,将得到5小时1分15秒。

  1. 根据NSDateComponents对象得到一个NSDate对象
- (nullable NSDate *)dateFromComponents:(NSDateComponents *)comps;
NSDateComponents *compt3 = [[NSDateComponents alloc] init];
[compt3 setYear:2021];
[compt3 setMonth:7];
[compt3 setDay:11];

NSCalendar *calendar3 = [NSCalendar currentCalendar];
NSDate *date3 = [calendar3 dateFromComponents:compt3];
// 2021-07-10 16:00:00 +0000

// 得到本地时区
NSTimeZone *zone3 = [NSTimeZone systemTimeZone];

// 和GMT时间相差多少秒 28800 = 60 * 60 * 8
NSInteger interval3 = [zone3 secondsFromGMTForDate:date3];
NSDate *localeDate3 = [date3 dateByAddingTimeInterval:interval3];
NSLog(@"%@",localeDate3);
// output: 2021-07-11 00:00:00 +0000
  1. 在参数date基础上,增加一个NSDateComponents类型的时间增量
- (nullable NSDate *)dateByAddingComponents:(NSDateComponents *)comps toDate:(NSDate *)date options:(NSCalendarOptions)opts;

5.3 NSCalendar 其他方法

系统日历为6行 * 7 列, 星期排列为:1.Sun. 2.Mon. 3.Thes. 4.Wed. 5.Thur. 6.Fri. 7.Sat.

  1. 设置每周的第一天从星期几开始,比如:1代表星期日开始,2代表星期一开始,以此类推。默认值是1
@property NSUInteger firstWeekday;
  1. 设置每年及每月第一周必须包含的最少天数,比如:设定第一周最少包括3天,则value传入3
@property NSUInteger minimumDaysInFirstWeek;
  1. 目标date 所在大的时间单位的的第几个小的时间单位
- (NSUInteger)ordinalityOfUnit:(NSCalendarUnit)smaller inUnit:(NSCalendarUnit)larger forDate:(NSDate *)date;
// 目标date 所在月份的第几周
NSInteger count1 = [calendar ordinalityOfUnit:NSCalendarUnitWeekOfMonth inUnit:NSCalendarUnitMonth forDate:[NSDate date]];

// 目标date 所在年份的第几周
NSInteger count2 = [calendar ordinalityOfUnit:NSCalendarUnitWeekOfYear inUnit:NSCalendarUnitYear forDate:[NSDate date]];

// 目标date 所在周内的第几天 
NSInteger count3 = [calendar ordinalityOfUnit:NSCalendarUnitWeekday inUnit:NSCalendarUnitWeekOfMonth forDate:[NSDate date]];
NSInteger count4 = [calendar ordinalityOfUnit:NSCalendarUnitWeekday inUnit:NSCalendarUnitWeekOfYear forDate:[NSDate date]];

// 取"天"(Day)这个单位在这一年(Year)的取值范围  (1 -- 365)
NSRange range1 = [calendar rangeOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitYear forDate:[NSDate date]];

// "天"(Day)的取值范围。根据参数时间的月份不同,值也不同。例如2月是1--28、3月是1--31、4月是1--30。
NSRange range2 = [calendar rangeOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitMonth forDate:[NSDate date]];

// "周"(Week)的取值范围。需要注意的是结果会受到minimumDaysInFirstWeek属性的影响。在默认minimumDaysInFirstWeek情况下,取得的范围值一般是"1--5",从日历上可以看出来这个月包含5排,即5个周。
NSRange range3 = [calendar rangeOfUnit:NSCalendarUnitWeekOfMonth inUnit:NSCalendarUnitMonth forDate:[NSDate date]];
  1. 返回该日期月数第一周开始的第一天, 也就是日历的本月左上角第一天
- (NSDate *)beginningOfMonth:(NSDate *)date{

    NSCalendar *calendar =[[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];

    

    NSDateComponents *componentsCurrentDate =[calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute fromDate:date];

    NSDateComponents *componentsNewDate = [NSDateComponents new];

    componentsNewDate.year = componentsCurrentDate.year;
    componentsNewDate.month = componentsCurrentDate.month;
    componentsNewDate.weekOfMonth = 1;//本月第一周
    componentsNewDate.weekday = calendar.firstWeekday;//本月第一周开始的日期
    componentsNewDate.hour = componentsCurrentDate.hour;
    componentsNewDate.minute = componentsCurrentDate.minute;

    return [calendar dateFromComponents:componentsNewDate];
}

eg: 使用如下:

NSDateComponents *dayComponent = [NSDateComponents new];
dayComponent.month = -1;
NSDate *monthDate = [calendar dateByAddingComponents:dayComponent toDate:[NSDate date] options:0];
NSLog(@"%@,%ld", monthDate, (long)dayComponent.month);  // Fri May 28 18:12:16 2021,-1
NSLog(@"月初:%@", [self beginningOfMonth:monthDate]);    // 月初:2021-04-25 10:12:00 +0000

dayComponent.month = 0;
NSDate *monthDate2 = [calendar dateByAddingComponents:dayComponent toDate:[NSDate date] options:0];
NSLog(@"%@,%ld", monthDate2, (long)dayComponent.month); // Mon Jun 28 18:12:16 2021,0
NSLog(@"月初:%@", [self beginningOfMonth:monthDate2]);   // 月初:2021-05-30 10:12:00 +0000

dayComponent.month = 1;
NSDate *monthDate3 = [calendar dateByAddingComponents:dayComponent toDate:[NSDate date] options:0];
NSLog(@"%@,%ld", monthDate3, (long)dayComponent.month); // Wed Jul 28 18:12:16 2021,1
NSLog(@"月初:%@", [self beginningOfMonth:monthDate3]);   // 月初:2021-06-27 10:12:00 +0000
  1. date所在当前月日历的第几周
- (void)testWeekOfMonth {

    NSDate *date = [NSDate dateWithTimeIntervalSinceNow:60 * 60 * 24 * -20];

    NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
    NSDateComponents *comps = [calendar components:NSCalendarUnitWeekOfMonth fromDate:date];
    NSLog(@"%ld", (long)comps.weekOfMonth);
}

iOS 系统日历就是一个月6周 (横7天 * 竖6周)

注意: 周数不同于编程思想, 不是从零开始, 是从1开始, 也就是第一行就是第一周.

weekOfMonth 代表当前时间所在当前月分日历的第几周, 通俗来讲就是第几行.

六、NSDateFormatter

用来在日期和字符串之间转换

6.1 NSDateFormatter的日期格式

G -- 纪元
一般会显示公元前(BC)和公元(AD)

y -- 年
假如是2013年,那么yyyy=2013,yy=13

M -- 月
假如是3月,那么M=3,MM=03,MMM=Mar,MMMM=March
假如是11月,那么M=11,MM=11,MMM=Nov,MMMM=November

w -- 年包含的周
假如是1月8日,那么w=2(这一年的第二个周)

W -- 月份包含的周(与日历排列有关)
假如是2013年4月21日,那么W=4(这个月的第四个周)

F -- 月份包含的周(与日历排列无关)
和上面的W不一样,F只是单纯以7天为一个单位来统计周,例如7号一定是第一个周,15号一定是第三个周,与日历排列无关。

D -- 年包含的天数
假如是1月20日,那么D=20(这一年的第20天)
假如是2月25日,那么D=31+25=56(这一年的第56天)

d -- 月份包含的天数
假如是5号,那么d=5,dd=05
假如是15号,那么d=15,dd=15

E -- 星期
假如是星期五,那么E=Fri,EEEE=Friday

a -- 上午(AM)/下午(PM)

H -- 24小时制,显示为0--23
假如是午夜00:40,那么H=0:40,HH=00:40

h -- 12小时制,显示为1--12
假如是午夜00:40,那么h=12:40

K -- 12小时制,显示为0--11
假如是午夜00:40,那么K=0:40,KK=00:40

k -- 24小时制,显示为1--24
假如是午夜00:40,那么k=24:40

m -- 分钟
假如是5分钟,那么m=5,mm=05
假如是45分钟,那么m=45,mm=45

s -- 秒
假如是5秒钟,那么s=5,ss=05
假如是45秒钟,那么s=45,ss=45

S -- 毫秒
一般用SSS来显示

z -- 时区
表现形式为GMT+08:00

Z -- 时区
表现形式为+0800

NSDateFormatter的两个最实用的方法是dateFromString和stringFromDate,前者将一个字符串经过格式化后变成NSDate对象,后者将NSDate对象格式化成字符串

- (NSString *)stringFromDate:(NSDate *)date;
- (nullable NSDate *)dateFromString:(NSString *)string;

eg: 在调用setDateFormat设置格式化字符串时,可以加入一些别的字符串,用单引号来引入

NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd 'some ''special'' string' HH:mm:ss"];

七、结语

路漫漫其修远兮,吾将上下而求索~

作者简书

作者GitHub

.End