sapphire-lang 0.8.0

Gradually typed scripting language where every value is an object and types are optional, checked at runtime
Documentation
class DateTimeTest < Test {

  # ── Duration ────────────────────────────────────────────────────────────────

  def test_duration_of_hours {
    assert_equal(3, Duration.of_hours(3).hours)
  }

  def test_duration_of_days {
    assert_equal(7, Duration.of_days(7).days)
  }

  def test_duration_negate {
    assert_equal(-5, Duration.of_hours(5).negate.hours)
  }

  def test_duration_add {
    assert_equal(5, Duration.of_hours(3).add(Duration.of_hours(2)).hours)
  }

  def test_duration_of_full {
    d = Duration.of(1, 2, 3, 4, 5, 6, 0)
    assert_equal(1, d.years)
    assert_equal(2, d.months)
    assert_equal(3, d.days)
    assert_equal(4, d.hours)
  }

  # ── Instant ─────────────────────────────────────────────────────────────────

  def test_instant_epoch_seconds {
    assert_equal(3600, Instant.of_epoch_seconds(3600).epoch_seconds)
  }

  def test_instant_now_is_positive {
    assert(Instant.now.epoch_seconds > 0)
  }

  def test_instant_add_sub {
    t = Instant.of_epoch_seconds(0)
    d = Duration.of_hours(1)
    assert_equal(3600, t.add(d).epoch_seconds)
    assert_equal(0, t.add(d).sub(d).epoch_seconds)
  }

  def test_instant_sub_instant_gives_duration {
    a = Instant.of_epoch_seconds(7200)
    b = Instant.of_epoch_seconds(3600)
    assert_equal(1, a.sub(b).hours)
  }

  def test_instant_before_after {
    a = Instant.of_epoch_seconds(0)
    b = Instant.of_epoch_seconds(1)
    assert(a.before?(b))
    assert(b.after?(a))
    assert(!a.after?(b))
  }

  def test_instant_in_timezone {
    z = Instant.of_epoch_seconds(0).in_timezone("UTC")
    assert_equal("UTC", z.timezone)
  }

  # ── Date ────────────────────────────────────────────────────────────────────

  def test_date_of {
    d = Date.of(2024, 3, 15)
    assert_equal(2024, d.year)
    assert_equal(3,    d.month)
    assert_equal(15,   d.day)
  }

  def test_date_day_of_week {
    assert_equal(1, Date.of(2024, 1, 1).day_of_week)
  }

  def test_date_add_days {
    assert_equal(11, Date.of(2024, 1, 1).add(Duration.of_days(10)).day)
  }

  def test_date_sub_days {
    assert_equal(1, Date.of(2024, 1, 11).sub(Duration.of_days(10)).day)
  }

  def test_date_sub_date {
    a = Date.of(2024, 1, 11)
    b = Date.of(2024, 1, 1)
    assert_equal(10, a.sub(b).days)
  }

  def test_date_before_after {
    assert(Date.of(2023, 1, 1).before?(Date.of(2024, 1, 1)))
    assert(Date.of(2024, 1, 1).after?(Date.of(2023, 1, 1)))
  }

  def test_date_next_prev_day {
    assert_equal(2,  Date.of(2024, 1, 31).next_day.month)
    assert_equal(31, Date.of(2024, 2, 1).prev_day.day)
  }

  # ── Time ────────────────────────────────────────────────────────────────────

  def test_time_of {
    t = Time.of(14, 30, 0)
    assert_equal(14, t.hour)
    assert_equal(30, t.minute)
    assert_equal(0,  t.second)
  }

  def test_time_before_after {
    assert(Time.of(8, 0, 0).before?(Time.of(12, 0, 0)))
    assert(Time.of(12, 0, 0).after?(Time.of(8, 0, 0)))
  }

  # ── DateTime ────────────────────────────────────────────────────────────────

  def test_datetime_of {
    dt = DateTime.of(2024, 6, 15, 10, 30, 0)
    assert_equal(2024, dt.year)
    assert_equal(6,    dt.month)
    assert_equal(10,   dt.hour)
    assert_equal(30,   dt.minute)
  }

  def test_datetime_date_time {
    dt = DateTime.of(2024, 6, 15, 10, 30, 0)
    assert_equal(15, dt.date.day)
    assert_equal(30, dt.time.minute)
  }

  def test_datetime_add {
    dt = DateTime.of(2024, 1, 1, 0, 0, 0)
    assert_equal(2, dt.add(Duration.of_days(1)).day)
  }

  # ── ZonedDateTime ────────────────────────────────────────────────────────────

  def test_zoned_now {
    assert(ZonedDateTime.now("UTC").epoch_seconds > 0)
  }

  def test_zoned_from_instant {
    i = Instant.of_epoch_seconds(0)
    z = ZonedDateTime.from_instant(i, "UTC")
    assert_equal("UTC", z.timezone)
    assert_equal(1970, z.year)
  }

  def test_zoned_to_instant_roundtrip {
    i = Instant.of_epoch_seconds(86400)
    i2 = ZonedDateTime.from_instant(i, "UTC").to_instant
    assert_equal(86400, i2.epoch_seconds)
  }

  def test_zoned_epoch_seconds {
    z = ZonedDateTime.now("UTC")
    assert(z.epoch_seconds > 0)
  }

  def test_zoned_with_timezone {
    z = ZonedDateTime.now("UTC").with_timezone("America/Chicago")
    assert_equal("America/Chicago", z.timezone)
  }

  def test_zoned_dst_aware_add {
    z = ZonedDateTime.of_compatible(2024, 3, 10, 1, 30, 0, "America/New_York")
    z2 = z.add(Duration.of_hours(1))
    assert_equal(3, z2.hour)
  }

  def test_zoned_fall_back_before {
    z = ZonedDateTime.of_before(2024, 11, 3, 1, 30, 0, "America/New_York")
    assert(z.to_s.include?("-04:00"))
  }

  def test_zoned_fall_back_after {
    z = ZonedDateTime.of_after(2024, 11, 3, 1, 30, 0, "America/New_York")
    assert(z.to_s.include?("-05:00"))
  }

  def test_zoned_gap_raises {
    assert_raises { ZonedDateTime.of(2024, 3, 10, 2, 30, 0, "America/New_York") }
  }

  def test_zoned_sub_gives_duration {
    a = ZonedDateTime.from_instant(Instant.of_epoch_seconds(7200), "UTC")
    b = ZonedDateTime.from_instant(Instant.of_epoch_seconds(0), "UTC")
    assert_equal(2, a.sub(b).hours)
  }

  def test_zoned_to_utc {
    z = ZonedDateTime.now("America/New_York").to_utc
    assert_equal("UTC", z.timezone)
  }
}