• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

moonbitlang / x / 301

10 Dec 2024 06:19AM UTC coverage: 85.204% (-2.6%) from 87.841%
301

Pull #78

github

web-flow
Merge b830031f4 into 91f0fdf48
Pull Request #78: feat: new package encoding

105 of 161 new or added lines in 3 files covered. (65.22%)

124 existing lines in 29 files now uncovered.

1169 of 1372 relevant lines covered (85.2%)

434.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.45
/time/plain_date.mbt
1
// Copyright 2024 International Digital Economy Academy
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
///| A date without a time zone in the ISO 8601 calendar system.
16
struct PlainDate {
17
  year : Int
18
  month : Int
19
  day : Int
20
}
21

22
///| Creates a PlainDate from the year, month and day.
23
pub fn PlainDate::of(year : Int, month : Int, day : Int) -> PlainDate!Error {
24
  if not(validate_ymd(year, month, day)) {
166✔
25
    fail!(invalid_date_err)
×
26
  }
27
  PlainDate::{ year, month, day }
28
}
29

30
///| Creates a PlainDate from year and ordinal day.
31
pub fn PlainDate::from_year_ord(year : Int, ordinal : Int) -> PlainDate!Error {
32
  let valid = validate_year(year) && validate_ordinal(year, ordinal)
70✔
33
  if not(valid) {
34
    fail!(invalid_date_err)
×
35
  }
36
  let mut month = 1
37
  let mut day = ordinal
38
  while calc_days_in_month(year, month) < day {
213✔
39
    day -= calc_days_in_month(year, month)
154✔
40
    month += 1
41
  }
42
  PlainDate::{ year, month, day }
43
}
44

45
///| Creates a date from the days count since unix epoch.
46
pub fn PlainDate::from_unix_day(unix_day : Int64) -> PlainDate!Error {
47
  let (year, ord) = fixed_days_to_year_ord(
6✔
48
    unix_day + days_zero_year_to_unix_epoch,
49
  )
50
  PlainDate::from_year_ord!(year, ord)
6✔
51
}
52

53
///| Returns the days count since unix epoch of this date.
54
pub fn to_unix_day(self : PlainDate) -> Int64 {
55
  let days = date_to_fixed_days(self.year, self.month, self.day)
43✔
56
  days - days_zero_year_to_unix_epoch
57
}
58

59
///| Creates a PlainDate from a string, like "2008-08-08".
60
pub fn PlainDate::from_string(str : String) -> PlainDate!Error {
61
  // TODO: better parsing implementation.
62
  let mut s = str
15✔
63
  let mut neg = false
64
  if s[0] == '-' {
65
    neg = true
5✔
66
    s = s.substring(start=1)
67
  }
68
  s = if s.length() > 10 { str.substring(end=10) } else { s }
3✔
69
  let ymd = split(s, '-')
70
  if ymd.length() < 3 {
71
    fail!(invalid_date_err)
×
72
  }
73
  let mut y = ymd[0]
74
  let m = ymd[1]
75
  let d = ymd[2]
76
  if y.length() < 4 || m.length() < 2 || d.length() < 2 {
77
    fail!(invalid_date_err)
×
78
  }
79
  if neg {
80
    y = "-" + y
2✔
81
  }
82
  let year = @strconv.parse!(y)
83
  let month = @strconv.parse!(m)
9✔
84
  let day = @strconv.parse!(d)
9✔
85
  PlainDate::of!(year, month, day)
9✔
86
}
87

88
///| Returns a string representing the date.
89
pub fn to_string(self : PlainDate) -> String {
90
  let buf = StringBuilder::new(size_hint=11)
202✔
91
  if self.year < 0 {
92
    buf.write_char('-')
32✔
93
  }
94
  buf.write_string(add_prefix_zero(self.year.abs().to_string(), 4))
95
  buf.write_char('-')
96
  buf.write_string(add_prefix_zero(self.month.to_string(), 2))
97
  buf.write_char('-')
98
  buf.write_string(add_prefix_zero(self.day.to_string(), 2))
99
  buf.to_string()
100
}
101

102
///|
103
pub impl Show for PlainDate with output(self : PlainDate, logger : Logger) {
104
  logger.write_string(self.to_string())
85✔
105
}
106

107
///|
108
pub fn op_equal(self : PlainDate, other : PlainDate) -> Bool {
109
  self.compare(other) == 0
1✔
110
}
111

112
///|
113
pub fn compare(self : PlainDate, other : PlainDate) -> Int {
114
  if self.year > other.year {
7✔
115
    1
1✔
116
  } else if self.year < other.year {
6✔
117
    -1
1✔
118
  } else if self.month > other.month {
5✔
119
    1
1✔
120
  } else if self.month < other.month {
4✔
121
    -1
1✔
122
  } else if self.day > other.day {
3✔
123
    1
1✔
124
  } else if self.day < other.day {
2✔
125
    -1
1✔
126
  } else {
127
    0
1✔
128
  }
129
}
130

131
///| Returns the era of this date.
132
pub fn era(self : PlainDate) -> String {
133
  if self.year >= 1 {
7✔
134
    "CE"
4✔
135
  } else {
136
    "BCE"
3✔
137
  }
138
}
139

140
///| Returns the year of the era of this date.
141
pub fn era_year(self : PlainDate) -> Int {
142
  if self.year <= 0 {
7✔
143
    self.year.abs() + 1
3✔
144
  } else {
145
    self.year
4✔
146
  }
147
}
148

149
///| Returns the number of years relative to a calendar-specific epoch.
150
pub fn year(self : PlainDate) -> Int {
151
  self.year
3✔
152
}
153

154
///| Returns the ordinal number of month in the current year.
155
pub fn month(self : PlainDate) -> Int {
156
  self.month
4✔
157
}
158

159
///| Returns the day of the month.
160
pub fn day(self : PlainDate) -> Int {
161
  self.day
4✔
162
}
163

164
///| Returns the weekday.
165
pub fn weekday(self : PlainDate) -> Weekday {
166
  // Zeller's congruence
167
  // https://en.wikipedia.org/wiki/Zeller%27s_congruence
168
  let mut y = self.year
9✔
169
  let mut m = self.month
170
  let q = self.day
171
  if m < 3 {
172
    m += 12
9✔
173
    y -= 1
174
  }
175
  let k = y % 100
176
  let j = y / 100
177
  let h = (q + 13 * (m + 1) / 5 + k + k / 4 + j / 4 + 5 * j) % 7
178
  let week_day = (h + 5) % 7 + 1
179
  Weekday::new(week_day).unwrap()
180
}
181

182
///| Returns the ordinal day of the year.
183
pub fn ordinal(self : PlainDate) -> Int {
184
  let days = date_to_fixed_days(self.year, self.month, self.day)
16✔
185
  let (_, ordinal) = fixed_days_to_year_ord(days)
186
  ordinal
187
}
188

189
///| Returns the number of days in the week.
190
pub fn PlainDate::days_in_week(_self : PlainDate) -> Int {
191
  7
3✔
192
}
193

194
///| Returns the number of days in the month.
195
pub fn days_in_month(self : PlainDate) -> Int {
196
  let { year, month, .. } = self
17✔
197
  calc_days_in_month(year, month)
198
}
199

200
///| Returns the number of days in the year.
201
pub fn days_in_year(self : PlainDate) -> Int {
202
  calc_days_in_year(self.year)
6✔
203
}
204

205
///| Returns the number of months in the year.
206
pub fn PlainDate::months_in_year(_self : PlainDate) -> Int {
207
  12
3✔
208
}
209

210
///| Checks if the date is in a leap year.
211
pub fn in_leap_year(self : PlainDate) -> Bool {
212
  check_leap_year(self.year)
11✔
213
}
214

215
///| Returns a new date with the specified year.
216
pub fn with_year(self : PlainDate, year : Int) -> PlainDate!Error {
217
  if self.year == year {
17✔
218
    return self
2✔
219
  }
220
  if validate_year(year) {
221
    let new_date = create_from_valid(year, self.month, self.day)
11✔
222
    new_date
223
  } else {
UNCOV
224
    fail!(invalid_date_err)
×
225
  }
226
}
227

228
///| Returns a new date with the specified month.
229
pub fn with_month(self : PlainDate, month : Int) -> PlainDate!Error {
230
  if self.month == month {
11✔
231
    return self
2✔
232
  }
233
  if validate_month(month) {
234
    let new_date = create_from_valid(self.year, month, self.day)
5✔
235
    new_date
236
  } else {
UNCOV
237
    fail!(invalid_date_err)
×
238
  }
239
}
240

241
///| Returns a new date with the specified day of month.
242
pub fn with_day(self : PlainDate, day : Int) -> PlainDate!Error {
243
  if self.day == day {
9✔
244
    return self
2✔
245
  }
246
  if validate_day(self.year, self.month, day) {
247
    let new_date = create_from_valid(self.year, self.month, day)
3✔
248
    new_date
249
  } else {
UNCOV
250
    fail!(invalid_date_err)
×
251
  }
252
}
253

254
///| Returns a new date with the specified ordinal day of year.
255
pub fn with_ordinal(self : PlainDate, ordinal : Int) -> PlainDate!Error {
256
  if self.ordinal() == ordinal {
9✔
UNCOV
257
    return self
×
258
  }
259
  PlainDate::from_year_ord!(self.year, ordinal)
5✔
260
}
261

262
///| Adds specified years to this date, and returns a new date.
263
pub fn add_years(self : PlainDate, years : Int64) -> PlainDate!Error {
264
  if years == 0L {
17✔
265
    return self
2✔
266
  }
267
  let new_year = checked_add_int64!(self.year.to_int64(), years).to_int()
14✔
268
  if validate_year(new_year) {
269
    let new_date = create_from_valid(new_year, self.month, self.day)
11✔
270
    new_date
271
  } else {
UNCOV
272
    fail!(invalid_date_err)
×
273
  }
274
}
275

276
///| Adds specified months to this date, and returns a new date.
277
pub fn add_months(self : PlainDate, months : Int64) -> PlainDate!Error {
278
  if months == 0L {
41✔
279
    return self
6✔
280
  }
281
  let total_months = self.year.to_int64() * 12L + (self.month.to_int64() - 1L)
282
  let new_total_months = checked_add_int64!(total_months, months)
283
  let mut new_year = (new_total_months / 12L).to_int()
33✔
284
  let mut new_month = (new_total_months % 12L + 1L).to_int()
285
  if new_month < 0 {
286
    new_year -= 1
8✔
287
    new_month += 12
288
  }
289
  if validate_year(new_year) {
290
    create_from_valid(new_year, new_month, self.day)
27✔
291
  } else {
UNCOV
292
    fail!(invalid_date_err)
×
293
  }
294
}
295

296
///| Adds specified weeks to this date, and returns a new date.
297
pub fn add_weeks(self : PlainDate, weeks : Int64) -> PlainDate!Error {
298
  if weeks == 0L {
22✔
299
    return self
3✔
300
  }
301
  let days_to_add = checked_mul_int64!(weeks, 7L)
302
  self.add_days!(days_to_add)
13✔
303
}
304

305
///| Adds specified days to this date, and returns a new date.
306
pub fn add_days(self : PlainDate, days : Int64) -> PlainDate!Error {
307
  if days == 0L {
75✔
308
    return self
28✔
309
  }
310
  let fixed_days = date_to_fixed_days(self.year, self.month, self.day)
311
  let new_days = checked_add_int64!(fixed_days, days)
312
  let (year, ordinal) = fixed_days_to_year_ord(new_days)
44✔
313
  PlainDate::from_year_ord!(year, ordinal)
41✔
314
}
315

316
///| Adds a period to this date, and returns a new date.
317
pub fn add_period(self : PlainDate, period : Period) -> PlainDate!Error {
318
  let mut d = self
12✔
319
  d = d
320
    .add_months!(period.to_total_months())
321
    .add_days!(period.days().to_int64())
12✔
322
  d
12✔
323
}
324

325
///| Returns the period between this date and another date.
326
pub fn until(self : PlainDate, end : PlainDate) -> Period!Error {
327
  let start_months = self.year.to_int64() * 12L + self.month.to_int64() - 1L
11✔
328
  let end_months = end.year.to_int64() * 12L + end.month.to_int64() - 1L
329
  let mut total_months = end_months - start_months
330
  let mut days = end.day - self.day
331
  if total_months > 0L && days < 0 {
332
    total_months -= 1L
2✔
333
    let d = self.add_months!(total_months)
334
    let end_days = date_to_fixed_days(end.year, end.month, end.day)
2✔
335
    days = (end_days - date_to_fixed_days(d.year, d.month, d.day)).to_int()
336
  } else if total_months < 0L && days > 0 {
9✔
337
    total_months += 1L
2✔
338
    days -= end.days_in_month()
339
  }
340
  let years = (total_months / 12L).to_int()
341
  let months = (total_months % 12L).to_int()
342
  Period::of(years~, months~, days~)
343
}
344

345
// *****************************
346
// * internal helper functions *
347
// *****************************
348

349
///|
350
let min_year = -9999
351

352
///|
353
let max_year = 9999
354

355
///|
356
let min_month = 1
357

358
///|
359
let max_month = 12
360

361
///|
362
let min_day = 1
363

364
///|
365
let max_day = 31
366

367
///|
368
let min_ordinal = 1
369

370
///|
371
let max_ordinal = 366
372

373
///|
374
let days_per_400_years : Int64 = 400L * 365L + 97L
375

376
///|
377
let days_zero_year_to_unix_epoch : Int64 = 5L * days_per_400_years -
378
  (30L * 365L + 7L)
379

380
///|
381
fn create_from_valid(year : Int, month : Int, day : Int) -> PlainDate {
382
  let mut d = day
59✔
383
  let max_day = calc_days_in_month(year, month)
384
  if d > max_day {
385
    d = max_day
31✔
386
  }
387
  PlainDate::{ year, month, day: d }
388
}
389

390
///|
391
fn check_leap_year(year : Int) -> Bool {
392
  (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
698✔
393
}
394

395
///|
396
fn calc_days_in_month(year : Int, month : Int) -> Int {
397
  let leap = check_leap_year(year)
611✔
398
  match month {
399
    2 => if leap { 29 } else { 28 }
56✔
400
    1 | 3 | 5 | 7 | 8 | 10 | 12 => 31
373✔
401
    _ => 30
85✔
402
  }
403
}
404

405
///|
406
fn calc_days_in_year(year : Int) -> Int {
407
  if check_leap_year(year) {
67✔
408
    366
43✔
409
  } else {
410
    365
24✔
411
  }
412
}
413

414
///|
415
fn date_to_fixed_days(year : Int, month : Int, day : Int) -> Int64 {
416
  let mut days = 0L
116✔
417
  // year days
418
  let y = year.to_int64()
419
  // non leap days
420
  days += 365L * y
421
  // leap days
422
  if y > 0L {
423
    days += (y + 3L) / 4L - (y + 99L) / 100L + (y + 399L) / 400L
93✔
424
  } else {
425
    days += y / 4L - y / 100L + y / 400L
23✔
426
  }
427
  // month days
428
  days += (367L * month.to_int64() - 362L) / 12L
429
  if month > 2 {
430
    days += if check_leap_year(year) { -1L } else { -2L }
4✔
431
  }
432
  days += day.to_int64() - 1L
433
  days
434
}
435

436
///|
437
fn fixed_days_to_year_ord(days : Int64) -> (Int, Int) {
438
  let mut d0 = days
74✔
439
  // adjust to positive days for calculation
440
  let mut year_offset = 0
441
  if d0 < 0L {
442
    let cycles = days / days_per_400_years - 1L
14✔
443
    year_offset = cycles.to_int() * 400
444
    d0 -= cycles * days_per_400_years
445
  }
446
  // calculate year
447
  let n400 = d0 / days_per_400_years
448
  let cycle = d0 % days_per_400_years
449
  let (y, ord) = cycle_to_year_ord(cycle.to_int())
450
  let year = y + n400.to_int() * 400 + year_offset
451
  (year, ord)
452
}
453

454
///|
455
let year_deltas = [
456
  0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7,
457
  7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13,
458
  13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 18,
459
  18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23,
460
  23, 23, 23, 24, 24, 24, 24, 25, 25, 25, // 100
461
   25, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29,
462
  29, 30, 30, 30, 30, 31, 31, 31, 31, 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34,
463
  34, 35, 35, 35, 35, 36, 36, 36, 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39,
464
  39, 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, 42, 43, 43, 43, 43, 44, 44, 44,
465
  44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 48, 48, 48, 48, 49, 49, 49, // 200
466
   49, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53,
467
  53, 54, 54, 54, 54, 55, 55, 55, 55, 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58,
468
  58, 59, 59, 59, 59, 60, 60, 60, 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63,
469
  63, 64, 64, 64, 64, 65, 65, 65, 65, 66, 66, 66, 66, 67, 67, 67, 67, 68, 68, 68,
470
  68, 69, 69, 69, 69, 70, 70, 70, 70, 71, 71, 71, 71, 72, 72, 72, 72, 73, 73, 73, // 300
471
   73, 73, 73, 73, 73, 74, 74, 74, 74, 75, 75, 75, 75, 76, 76, 76, 76, 77, 77, 77,
472
  77, 78, 78, 78, 78, 79, 79, 79, 79, 80, 80, 80, 80, 81, 81, 81, 81, 82, 82, 82,
473
  82, 83, 83, 83, 83, 84, 84, 84, 84, 85, 85, 85, 85, 86, 86, 86, 86, 87, 87, 87,
474
  87, 88, 88, 88, 88, 89, 89, 89, 89, 90, 90, 90, 90, 91, 91, 91, 91, 92, 92, 92,
475
  92, 93, 93, 93, 93, 94, 94, 94, 94, 95, 95, 95, 95, 96, 96, 96, 96, 97, 97, 97,
476
  97, // 400+1
477
]
478

479
///|
480
fn cycle_to_year_ord(cycle : Int) -> (Int, Int) {
481
  let mut year = cycle / 365
74✔
482
  let mut ordinal0 = cycle % 365
483
  let delta = year_deltas[year]
484
  if ordinal0 < delta {
485
    year -= 1
4✔
486
    ordinal0 += 365 - year_deltas[year]
487
  } else {
488
    ordinal0 -= delta
70✔
489
  }
490
  (year, ordinal0 + 1)
491
}
492

493
///|
494
fn validate_ymd(year : Int, month : Int, day : Int) -> Bool {
495
  validate_year(year) && validate_month(month) && validate_day(year, month, day)
166✔
496
}
497

498
///|
499
fn validate_year(year : Int) -> Bool {
500
  year >= min_year && year <= max_year
298✔
501
}
502

503
///|
504
fn validate_month(month : Int) -> Bool {
505
  month >= min_month && month <= max_month
173✔
506
}
507

508
///|
509
fn validate_day(year : Int, month : Int, day : Int) -> Bool {
510
  day >= min_day && day <= max_day && day <= calc_days_in_month(year, month)
171✔
511
}
512

513
///|
514
fn validate_ordinal(year : Int, ordinal : Int) -> Bool {
515
  ordinal >= min_ordinal &&
65✔
516
  ordinal <= max_ordinal &&
517
  ordinal <= calc_days_in_year(year)
518
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc