理解得越多,需要记忆的越少

2016-09-06
TimeZoneId 在 Windows 和 非Windows 平台下值不同

自从 .NET Core 跨平台以来,终于可以在我的 Mac OS 系统里愉快地开发了,但是遇到一个很无奈的问题:对于同一个时区,TimeZoneInfoId属性值在 Windows 平台下和在 Mac OS/Linux 平台下的值是不同的。

比如如下代码:

1
Console.WriteLine(TimeZoneInfo.Local.Id);

在 Mac OS/Linux 里结果是:Asia/Shanghai.

在 Windows 里结果是:China Standard Time.

而我们系统里作为用户时区首选项保存的都是 Windows 下的时区 Id 值,所以在 Mac OS 下通过时区 Id 获取TimeZoneInfo的方法就悲剧了:“The time zone ID ‘China Standard Time’ was not found on the local computer”

这个跨平台的小坑怎么填?暂时没有好办法,只能想方设法建立两个平台下的时区 Id 对照表。那么对照信息从哪里来呢?不要愁,unicode.org 上就有!两个平台的值可以通过 TZID 对应上。

这里再附上我创建的针对 Sql Server 的 TimeZoneInfo 表脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[TimeZoneInfo]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[TimeZoneInfo](
[TimeZoneId] [varchar](128) NOT NULL,
[LinuxTimeZoneId] [varchar](256) NOT NULL,
[DisplayName] [varchar](256) NOT NULL,
[BaseUtcOffsetMinutes] [int] NOT NULL,
CONSTRAINT [PK_TimeZoneInfo] PRIMARY KEY CLUSTERED
(
[TimeZoneId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
END
GO
SET ANSI_PADDING OFF
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Afghanistan Standard Time', N'Asia/Kabul', N'(UTC+04:30) Kabul', 270)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Alaskan Standard Time', N'America/Anchorage', N'(UTC-09:00) Alaska', -540)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Arab Standard Time', N'Asia/Riyadh', N'(UTC+03:00) Kuwait, Riyadh', 180)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Arabian Standard Time', N'Asia/Dubai', N'(UTC+04:00) Abu Dhabi, Muscat', 240)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Arabic Standard Time', N'Asia/Baghdad', N'(UTC+03:00) Baghdad', 180)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Argentina Standard Time', N'America/Buenos_Aires', N'(UTC-03:00) Buenos Aires', -180)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Atlantic Standard Time', N'America/Halifax', N'(UTC-04:00) Atlantic Time (Canada)', -240)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'AUS Central Standard Time', N'Australia/Darwin', N'(UTC+09:30) Darwin', 570)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'AUS Eastern Standard Time', N'Australia/Sydney', N'(UTC+10:00) Canberra, Melbourne, Sydney', 600)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Azerbaijan Standard Time', N'Asia/Baku', N'(UTC+04:00) Baku', 240)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Azores Standard Time', N'Atlantic/Azores', N'(UTC-01:00) Azores', -60)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Bahia Standard Time', N'America/Bahia', N'(UTC-03:00) Salvador', -180)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Bangladesh Standard Time', N'Asia/Dhaka', N'(UTC+06:00) Dhaka', 360)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Belarus Standard Time', N'Europe/Minsk', N'(UTC+03:00) Minsk', 180)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Canada Central Standard Time', N'America/Regina', N'(UTC-06:00) Saskatchewan', -360)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Cape Verde Standard Time', N'Atlantic/Cape_Verde', N'(UTC-01:00) Cabo Verde Is.', -60)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Caucasus Standard Time', N'Asia/Yerevan', N'(UTC+04:00) Yerevan', 240)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Cen. Australia Standard Time', N'Australia/Adelaide', N'(UTC+09:30) Adelaide', 570)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Central America Standard Time', N'America/Guatemala', N'(UTC-06:00) Central America', -360)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Central Asia Standard Time', N'Asia/Almaty', N'(UTC+06:00) Astana', 360)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Central Brazilian Standard Time', N'America/Cuiaba', N'(UTC-04:00) Cuiaba', -240)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Central Europe Standard Time', N'Europe/Budapest', N'(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague', 60)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Central European Standard Time', N'Europe/Warsaw', N'(UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb', 60)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Central Pacific Standard Time', N'Pacific/Guadalcanal', N'(UTC+11:00) Solomon Is., New Caledonia', 660)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Central Standard Time', N'America/Chicago', N'(UTC-06:00) Central Time (US & Canada)', -360)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Central Standard Time (Mexico)', N'America/Mexico_City', N'(UTC-06:00) Guadalajara, Mexico City, Monterrey', -360)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'China Standard Time', N'Asia/Shanghai', N'(UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi', 480)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Dateline Standard Time', N'Etc/GMT+12', N'(UTC-12:00) International Date Line West', -720)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'E. Africa Standard Time', N'Africa/Nairobi', N'(UTC+03:00) Nairobi', 180)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'E. Australia Standard Time', N'Australia/Brisbane', N'(UTC+10:00) Brisbane', 600)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'E. Europe Standard Time', N'Europe/Chisinau', N'(UTC+02:00) E. Europe', 120)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'E. South America Standard Time', N'America/Sao_Paulo', N'(UTC-03:00) Brasilia', -180)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Eastern Standard Time', N'America/New_York', N'(UTC-05:00) Eastern Time (US & Canada)', -300)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Egypt Standard Time', N'Africa/Cairo', N'(UTC+02:00) Cairo', 120)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Ekaterinburg Standard Time', N'Asia/Yekaterinburg', N'(UTC+05:00) Ekaterinburg (RTZ 4)', 300)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Fiji Standard Time', N'Pacific/Fiji', N'(UTC+12:00) Fiji', 720)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'FLE Standard Time', N'Europe/Kiev', N'(UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius', 120)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Georgian Standard Time', N'Asia/Tbilisi', N'(UTC+04:00) Tbilisi', 240)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'GMT Standard Time', N'Europe/London', N'(UTC) Dublin, Edinburgh, Lisbon, London', 0)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Greenland Standard Time', N'America/Godthab', N'(UTC-03:00) Greenland', -180)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Greenwich Standard Time', N'Atlantic/Reykjavik', N'(UTC) Monrovia, Reykjavik', 0)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'GTB Standard Time', N'Europe/Bucharest', N'(UTC+02:00) Athens, Bucharest', 120)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Hawaiian Standard Time', N'Pacific/Honolulu', N'(UTC-10:00) Hawaii', -600)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'India Standard Time', N'Asia/Calcutta', N'(UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi', 330)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Iran Standard Time', N'Asia/Tehran', N'(UTC+03:30) Tehran', 210)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Israel Standard Time', N'Asia/Jerusalem', N'(UTC+02:00) Jerusalem', 120)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Jordan Standard Time', N'Asia/Amman', N'(UTC+02:00) Amman', 120)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Kaliningrad Standard Time', N'Europe/Kaliningrad', N'(UTC+02:00) Kaliningrad (RTZ 1)', 120)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Kamchatka Standard Time', N'', N'(UTC+12:00) Petropavlovsk-Kamchatsky - Old', 720)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Korea Standard Time', N'Asia/Seoul', N'(UTC+09:00) Seoul', 540)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Libya Standard Time', N'Africa/Tripoli', N'(UTC+02:00) Tripoli', 120)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Line Islands Standard Time', N'Pacific/Kiritimati', N'(UTC+14:00) Kiritimati Island', 840)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Magadan Standard Time', N'Asia/Magadan', N'(UTC+10:00) Magadan', 600)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Mauritius Standard Time', N'Indian/Mauritius', N'(UTC+04:00) Port Louis', 240)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Mid-Atlantic Standard Time', N'', N'(UTC-02:00) Mid-Atlantic - Old', -120)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Middle East Standard Time', N'Asia/Beirut', N'(UTC+02:00) Beirut', 120)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Montevideo Standard Time', N'America/Montevideo', N'(UTC-03:00) Montevideo', -180)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Morocco Standard Time', N'Africa/Casablanca', N'(UTC) Casablanca', 0)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Mountain Standard Time', N'America/Denver', N'(UTC-07:00) Mountain Time (US & Canada)', -420)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Mountain Standard Time (Mexico)', N'America/Chihuahua', N'(UTC-07:00) Chihuahua, La Paz, Mazatlan', -420)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Myanmar Standard Time', N'Asia/Rangoon', N'(UTC+06:30) Yangon (Rangoon)', 390)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'N. Central Asia Standard Time', N'Asia/Novosibirsk', N'(UTC+06:00) Novosibirsk (RTZ 5)', 360)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Namibia Standard Time', N'Africa/Windhoek', N'(UTC+01:00) Windhoek', 60)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Nepal Standard Time', N'Asia/Katmandu', N'(UTC+05:45) Kathmandu', 345)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'New Zealand Standard Time', N'Pacific/Auckland', N'(UTC+12:00) Auckland, Wellington', 720)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Newfoundland Standard Time', N'America/St_Johns', N'(UTC-03:30) Newfoundland', -210)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'North Asia East Standard Time', N'Asia/Irkutsk', N'(UTC+08:00) Irkutsk (RTZ 7)', 480)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'North Asia Standard Time', N'Asia/Krasnoyarsk', N'(UTC+07:00) Krasnoyarsk (RTZ 6)', 420)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Pacific SA Standard Time', N'America/Santiago', N'(UTC-04:00) Santiago', -240)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Pacific Standard Time', N'America/Los_Angeles', N'(UTC-08:00) Pacific Time (US & Canada)', -480)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Pacific Standard Time (Mexico)', N'', N'(UTC-08:00) Baja California', -480)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Pakistan Standard Time', N'Asia/Karachi', N'(UTC+05:00) Islamabad, Karachi', 300)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Paraguay Standard Time', N'America/Asuncion', N'(UTC-04:00) Asuncion', -240)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Romance Standard Time', N'Europe/Paris', N'(UTC+01:00) Brussels, Copenhagen, Madrid, Paris', 60)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Russia Time Zone 10', N'Asia/Srednekolymsk', N'(UTC+11:00) Chokurdakh (RTZ 10)', 660)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Russia Time Zone 11', N'Asia/Kamchatka', N'(UTC+12:00) Anadyr, Petropavlovsk-Kamchatsky (RTZ 11)', 720)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Russia Time Zone 3', N'Europe/Samara', N'(UTC+04:00) Izhevsk, Samara (RTZ 3)', 240)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Russian Standard Time', N'Europe/Moscow', N'(UTC+03:00) Moscow, St. Petersburg, Volgograd (RTZ 2)', 180)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'SA Eastern Standard Time', N'America/Cayenne', N'(UTC-03:00) Cayenne, Fortaleza', -180)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'SA Pacific Standard Time', N'America/Bogota', N'(UTC-05:00) Bogota, Lima, Quito, Rio Branco', -300)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'SA Western Standard Time', N'America/La_Paz', N'(UTC-04:00) Georgetown, La Paz, Manaus, San Juan', -240)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Samoa Standard Time', N'Pacific/Apia', N'(UTC+13:00) Samoa', 780)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'SE Asia Standard Time', N'Asia/Bangkok', N'(UTC+07:00) Bangkok, Hanoi, Jakarta', 420)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Singapore Standard Time', N'Asia/Singapore', N'(UTC+08:00) Kuala Lumpur, Singapore', 480)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'South Africa Standard Time', N'Africa/Johannesburg', N'(UTC+02:00) Harare, Pretoria', 120)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Sri Lanka Standard Time', N'Asia/Colombo', N'(UTC+05:30) Sri Jayawardenepura', 330)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Syria Standard Time', N'Asia/Damascus', N'(UTC+02:00) Damascus', 120)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Taipei Standard Time', N'Asia/Taipei', N'(UTC+08:00) Taipei', 480)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Tasmania Standard Time', N'Australia/Hobart', N'(UTC+10:00) Hobart', 600)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Tokyo Standard Time', N'Asia/Tokyo', N'(UTC+09:00) Osaka, Sapporo, Tokyo', 540)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Tonga Standard Time', N'Pacific/Tongatapu', N'(UTC+13:00) Nuku''alofa', 780)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Turkey Standard Time', N'Europe/Istanbul', N'(UTC+02:00) Istanbul', 120)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Ulaanbaatar Standard Time', N'Asia/Ulaanbaatar', N'(UTC+08:00) Ulaanbaatar', 480)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'US Eastern Standard Time', N'America/Indianapolis', N'(UTC-05:00) Indiana (East)', -300)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'US Mountain Standard Time', N'America/Phoenix', N'(UTC-07:00) Arizona', -420)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'UTC', N'Etc/GMT', N'(UTC) Coordinated Universal Time', 0)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'UTC+12', N'Etc/GMT-12', N'(UTC+12:00) Coordinated Universal Time+12', 720)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'UTC-02', N'Etc/GMT+2', N'(UTC-02:00) Coordinated Universal Time-02', -120)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'UTC-11', N'Etc/GMT+11', N'(UTC-11:00) Coordinated Universal Time-11', -660)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Venezuela Standard Time', N'America/Caracas', N'(UTC-04:30) Caracas', -270)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Vladivostok Standard Time', N'Asia/Vladivostok', N'(UTC+10:00) Vladivostok, Magadan (RTZ 9)', 600)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'W. Australia Standard Time', N'Australia/Perth', N'(UTC+08:00) Perth', 480)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'W. Central Africa Standard Time', N'Africa/Lagos', N'(UTC+01:00) West Central Africa', 60)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'W. Europe Standard Time', N'Europe/Berlin', N'(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna', 60)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'West Asia Standard Time', N'Asia/Tashkent', N'(UTC+05:00) Ashgabat, Tashkent', 300)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'West Pacific Standard Time', N'Pacific/Port_Moresby', N'(UTC+10:00) Guam, Port Moresby', 600)
GO
INSERT [dbo].[TimeZoneInfo] ([TimeZoneId], [LinuxTimeZoneId], [DisplayName], [BaseUtcOffsetMinutes]) VALUES (N'Yakutsk Standard Time', N'Asia/Yakutsk', N'(UTC+09:00) Yakutsk (RTZ 8)', 540)
GO
阅读此文

2016-07-21
数值格式化字符“N”在Windows和Linux/Mac OS里结果不同

凭以往的经验,当我们需要把一个数值格式化为带千分符和2位小数的字符串时,N是个非常方便的格式化字符。

看一段非常简单的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
using System;

namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
decimal d = 1234567.56789m;
Console.WriteLine("{0:N}", d);
}
}
}

毫无疑问,我希望这段代码的输出为1,234,567.57,在 Windows 系统里结果的确如此,但是在 Linux/Mac OS 里,结果却是1,234,567.568,保留了3位小数,我真 (╯‵□′)╯︵┻━┻

我猜这是 Linux/Mac OS 里的标准或者约定?如果真是这样的话,我就比较无奈了,为了让我们的项目无论在什么操作系统下都有一致的输出,要么不使用N,要么在 Windows 和 Linux/Mac OS 下使用不同的格式化字符,比如:

1
2
3
4
5
6
// using System.Runtime.InteropServices;

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
Console.WriteLine("{0:N}", d);
else
Console.WriteLine("{0:#,##0.00}", d);
阅读此文

2016-06-21
在 OS X 里查找并删除找到的文件

闲话休提且说正话,用下面这个命令查找并删除 当前目录和所有子目录里的 .DS_Store 文件:

find . -name .DS_Store -delete

阅读此文

2016-06-15
使用 CommandLineApplication 类创建专业的控制台程序

闲话

在很久很久以前,电脑是命令行/终端/控制台的天下,那屏幕上的光标在行云流水般的键盘敲击下欢快地飞跃着,那一行行的字符输出唰唰唰地滚动着……直到 Windows 95 的出现(那时候我还不知道苹果电脑和它的操作系统),我的鼠标终于不再召灰,开始有了用武之地,然后就是 GUI 的天下……

然而世事就是这样,锦绣繁华之后就开始返璞归真,大鱼大肉太多就向往点粗茶淡饭,开车开久了就怀念起自行车,GUI 充斥的 Windows 的世界里似乎也开始挂起一阵控制台的清风。毕竟,一旦你熟悉了各种命令和参数,敲键盘的速度还是胜过鼠标的,只是现在的人都太懒或者太忙,总是宁愿牺牲效率而不愿意去多记一点东西。

我个人还是很喜欢命令行的,尤其是远程访问一个系统的时候,一个简单的 ssh 命令直接登录到远程 Linux,一个简单的 scp 命令就可以互相传输文件,这种便利、快捷是 Windows 远程桌面所无法比拟的。GUI 也许是 Windows 的设计哲学, 做什么事情都要靠 GUI。 没错,这大大降低了各种操作门槛,但是作为一个程序员,GUI 工具并非总是最佳选择,但除了 GUI 工具,替代选择并不多——直到 .NET Core 的出现。

专业的控制台程序

首先我们要有个标准,怎样才算“专业的控制台程序”?

平常无论是写着玩还是工作需要,我都做过一些控制台程序,在启动参数的传入、解析和执行上都比较随意,类似 MyProgram abc 123 这样,MyProgram 是程序名,abc123 是参数值,内部直接用 args[0]args[1]取得参数值并使用。仅此而已。时间长了,自己都搞不懂每个参数什么意思,参数有哪些有效值,都得查源代码才知道,每个参数的顺序也很重要,颠倒不得。而内部实现上,至少是 Main 方法里是典型的面条式代码。由此可见,我的这些控制台程序,无论是外在,还是内在,都业余得很。

那一个专业的控制台程序应该是什么样的呢?

完善的帮助信息

当你面对一个陌生的命令行程序,或者重新面对你自己2个月之前写的命令行程序,你心里第一反应会是什么呢?让我猜猜,你的第一反应一定是“这货到底怎么用?”(不要告诉我我猜错了,我不相信💢),下一个想法就是这个程序能告诉我用法就好了。一个专业的控制台程序必然会满足你的这个需要——它可以提供完善的帮助信息。比如 git:

git

有了这些帮助信息,我们自然信心倍增,心里有谱多了。

我希望我写的控制台程序也能做到这点!

符合“国际惯例”的调用方式

如果你留意一下 Linux 平台下的一众控制台程序,你会发现他们的参数组织和调用方式十分类似,这种约定俗成的“国际惯例”十分有助于降低熟悉各个控制台程序的学习成本。

我们还是以 git 为例,从上面的截图可以看出,它有非常多的参数可用。虽然都是参数,但根据作用不同,可以分为 command, argument, option 三类。我不是控制台程序达人,对这3类参数的区别与联系还在深入理解、学习中。目前的理解是(以 git 为例):

  • command

    一个复杂的控制台程序可以提供多个子命令,而 command 就代表这些子命令

    比如 cloneinit

  • argument

    是一个 command 需要的参数。

    比如执行 clone 的时候需要指定一个 repository 的地址,这个地址就是一个 argument

  • option

    调整 command 的行为。

    比如对于 clone 可以增加 --verbose 参数使其输出更详尽的信息。

    通常以两个短横线开头后跟参数名,比如 --verbose,对于常用的 option 还会有简写形式,就是一个短横线后跟简写形式的参数名,比如 --verbose 支持简写形式 -v,在帮助里通常以 --verbose, -v 或者 --verbose|-v 的形式说明。

    option 可以有值也可以没有值,有值的时候,其赋值方式不一而足,常见的有用空格的 --branch dev,用等号的(注意等号两边没有空格) --branch=dev,用冒号的(冒号两边没有空格) --branch:dev

对于相对简单的控制台程序,可能只有 argument 和 option 而并不包括 command。

看看那些有名的控制台程序,基本上也都遵循这个套路。在这方面,我不想做个怪胎,所以,我希望我写的控制台程序也能遵照这些“国际惯例”!

易于维护的内部实现

在控制台程序的内部实现上,以前的做法非常简单粗暴,用一堆 if 或者 switch 配合各种 &&|| 成功地做到了一开始只有“上帝”和我明白,1个月后只有“上帝”明白的效果。随着程序参数增多、逻辑越来越复杂,这么搞下去,“上帝”依然可以很潇洒,我会被搞死的。为了不让我变成秃头,为了我可以有更多的时间玩游戏,控制台程序的内部实现必须井井有条、易于维护!

问题来了

了解了一个专业的控制台程序应具备的素质,那么接下来有一个问题萦绕在我心头久久不肯散去……

HOW

作为一个小白,要实现一个有详细说明信息、调用方式符合“国际惯例”、还能优雅地处理各种参数的控制台程序何其困难?而如此套路化的东西难道没有一套成型的东西供参考吗?

人生为何如此艰难

CommandLineApplication

天无绝人之路,一次偶然的邂逅,遇到了它—— CommandLineApplication。如果你用 .NET Core 的话,它可以在你构建专业控制台程序的路上助你一臂之力。

它全名是 Microsoft.Extensions.CommandLineUtils.CommandLineApplication,家住 GitHub 省 aspnet 市 Common 区 src 路 Microsoft.Extensions.CommandLineUtils 大院 CommandLine 室.如果你路盲,这里有个传送门

实践是检验真理的唯一标准 - MathForKids 程序

让我们从头开始,利用 dotnet cli 和 Visual Studio Code 亲自体验一下它到底有多强大。我们将创建一个 MathForKids 程序,它可以根据参数输出一些加减乘除的算式,让孩子算算结果。

MathForKids 程序的功能

  • 它只输出算式,所以我们不要搞得太复杂,不需要什么子命令 command。

    想了解带 command 的用法,可以参考本文最后附上的 CommandLineApplication 的官方测试代码 😛

  • 它在执行的时候需要指定输出的算式是加、减、乘、除还是这4种运算符的组合,因此我们可以设置一个 argument: operator.

    这个 argument 可以允许同时设置多个值。

  • 它可以设置生成的数字的最大值和最小值,因为它们只是调整输出的算式种数字的大小,因此我将其归为 options: minValue, maxValue

    minValue 的默认值是0

    maxValue 的默认值是100

  • 它可以设置生成的算式个数,这也只是调整输出结果,因此我也将其归为 option: count

    count 的默认值是 10

MathForKids 程序的用法

在我们看代码之前,先看看这个程序用起来应该是什么样的。

注意:在本文发布之时,.NET Core 处于 RC 2 阶段,还不支持编译为本地可执行文件。所以目前必须使用 dotnet MathForKids.dll 来运行。图中红色下划线表示输入的命令

首先,它可以提供帮助信息:

MathForKids 帮助信息

从帮助信息中我们可以看到它支持的所有参数,并且支持参数的全写和简写,比如我们可以写 --minValue 也可以简写为 -min

当我们调用它只生成加法和乘法,其它选项默认时:

MathForKids 使用默认 option 调用

注意,我们一次传入了多个参数: 加 乘

当我们设置一些 option 来改变输出结果时:

MathForKids 传入多个 options

注意,这里演示了使用全写和简写添加 option 以及以多种方式赋值(使用冒号、等号和空格)。

以上使用方法看起来是不是有点“专业”的味道了?

Show me the code

为了使用 CommandLineApplication 类,我们需要添加对 Microsoft.Extensions.CommandLineUtils 的引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"version": "1.0.0-*",
"buildOptions": {
"emitEntryPoint": true
},
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.0-rc2-3002702"
},
"Microsoft.Extensions.CommandLineUtils": "1.0.0-rc2-final" //<--- add this dependency
},
"frameworks": {
"netcoreapp1.0": {
"imports": "dnxcore50"
}
}
}

这是 MathForKids 程序的主体部分,说明都在注释里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using System;
using Microsoft.Extensions.CommandLineUtils;

namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
CommandLineApplication app = new CommandLineApplication();
app.HelpOption("--help|-h|-?"); // 使其支持显示帮助信息
app.VersionOption("--version|-v", "1.0.0"); // 使其支持显示版本信息。为了简化起见,直接返回静态的 1.0.0

// 添加 argument,这里我们允许传入这个 argument 的多个值。
CommandArgument argOperator = app.Argument("operator", "算式类型,有效值:加、减、乘、除,可以设置多个类型", multipleValues: true);

// 添加多个 options,注意设置全写和简写的方式,很简单。这应该是基于约定的解析处理方式。
CommandOption optMin = app.Option("--minValue -min <value>", "最小值,默认为0", CommandOptionType.SingleValue);
CommandOption optMax = app.Option("--maxValue -max <value>", "最大值,默认为100", CommandOptionType.SingleValue);
CommandOption optCount = app.Option("--count -c <value>", "生成的算式数量,默认为10", CommandOptionType.SingleValue);

// 传入一个委托方法,当下面的 Execute 执行后会执行我们的委托方法,完成我们需要处理的工作。 委托方法需要返回一个 int,反映执行结果,一如经典的控制台程序需要的那样。
app.OnExecute(() =>
{
return OnAppExecute(argOperator, optMin, optMax, optCount);
});

// 开始执行,把控制台传入的参数直接传递给 CommandLineApplication。
app.Execute(args);
}

private static int OnAppExecute(CommandArgument argOperator, CommandOption optMin, CommandOption optMax, CommandOption optCount)
{
// 此处省略 100 行以示例取代
List<string> operators = argOperator.Values;

string max;
if(optMax.HasValue())
max = optMax.Value();

return 0;
}
}
}

CommandArgumentCommandOption 的使用非常简单,有 HasValueValue 等方法可以判断和取值。

所有代码放在我的 GitHub 里。

官方的测试类也是一个很好的参考资源。

阅读此文

2014-07-29
在输入时自动格式化电话号码

以下代码用来在用户输入时自动格式化电话号码,格式为『nnn-nnn-nnnn』,留在这里备忘。里面的正则表达式是 Google 到的,不得不佩服这个正则表达式的作者的功底。

1
2
3
4
5
6
7
8
9
10
11
12
$('body').on('keydown', 'input[type=tel]', null, function (e) {
var that = $(this);
var tel = that.val();

if (/^\d{4}$/.test(tel) || /^\d{3}-\d{4}$/.test(tel)) {
that.val(tel.replace(/(\d{3})(?!\-)/g, "$1-"));
}

if (tel.length > 12) {
that.val(tel.substr(0, 12));
}
});
阅读此文