Fuzion Logo
fuzion-lang.dev — The Fuzion Language Portal
JavaScript seems to be disabled. Functionality is limited.

time/date_time.fz


# This file is part of the Fuzion language implementation.
#
# The Fuzion language implementation is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation, version 3 of the License.
#
# The Fuzion language implementation is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License along with The
# Fuzion language implementation.  If not, see <https://www.gnu.org/licenses/>.


# -----------------------------------------------------------------------
#
#  Tokiwa Software GmbH, Germany
#
#  Source code of Fuzion standard library feature time.date_time
#
#  Author: Michael Lill (michael.lill@tokiwa.software)
#
# -----------------------------------------------------------------------

#
# Represents a coordinated universal date and time (UTC) in the gregorian calendar.
#
public date_time(public year, day_in_year, hour, minute, second, nano_second i32) : property.orderable
pre
  debug: (day_in_year ≥ 1  & day_in_year  ≤ days_in_year year)   # leap years have 366 days
  debug: (hour        ≥ 0  & hour         ≤ 23               )
  debug: (minute      ≥ 0  & minute       ≤ 59               )
  debug: (second      ≥ 0  & second       ≤ 60               )   # 60 because of possible leap seconds
  debug: (nano_second ≥ 0  & nano_second  ≤ 1E9              )
is

  # how many days does february have in the given year?
  days_in_february =>
    if is_leap_year year then 29 else 28


  # the days in the months of the year
  # starting at january, february, ..., december
  days_in_months =>
    [31, days_in_february, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]


  # the month of the year
  public month
  post result ≥ 1 & result ≤ 12
  =>
    # Increase the month and add the days in month to
    # the accumulated days. We found our month once
    # the sum of accumulated days and the days in the
    # processed month match or exceed the day_in_year.
    days_in_months
      .reduce (1,0) (r, md ->
        (m, acc_days) := r
        if acc_days+md ≥ day_in_year then abort (m,0) else (m+1,acc_days+md))
      .values.0



  # the day of the month
  public day_of_month
  post result ≥ 1 & result ≤ 31
  =>
    days_in_months
      .reduce day_in_year (d, md -> if d-md ≤ 0 then abort d else d-md)



  # the millisecond of this datetime.
  public milli_second
  post result ≥ 0 & result ≤ 999
  =>
    nano_second / 1E3



  # returns ISO 8601 UTC string, precision millisecond
  # example: 2018-09-14T23:59:59.079Z
  public redef as_string String =>
    "{year.as_string 2 10}-{month.as_string 2 10}-{day_of_month.as_string 2 10}" +
      "T{hour.as_string 2 10}:{minute.as_string 2 10}:{second.as_string 2 10}.{milli_second.as_string 3 10}Z"



  # # NYI
  # infix + (other time.duration) date_time is

  # infix - (other date_time) time.duration is
  # infix - (other time.duration) date_time is

  # as_string(f date_time_format, local/time_zone) String is

  # is_before(other date_time) bool =>
  # is_after(other date_time) bool =>
  # is_between(o1, o2 date_time) bool =>
  # ...

  # 0 = Sunday, 1 = Monday, etc.
  #
  # source: https://c-faq.com/misc/zeller.html
  # original code by: Tomohiko Sakamoto
  #
  public day_of_week i32 =>
    t := [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]
    y := year - (if month < 3 then 1 else 0)
    (y + y/4 - y/100 + y/400 + t[month-1] + day_of_month) % 7

  # type.parse(s String, locale/timezone) date_time is



  # defines equality for date time
  # this is normally used via infix operator =
  public type.equality(a, b date_time.this) bool =>
    a.nano_second = b.nano_second &
     a.year = b.year &
     a.day_in_year = b.day_in_year &
     a.hour = b.hour &
     a.minute = b.minute &
     a.second = b.second



  # defines partial order for date time
  # this is normally used via infix operators <, >, <= etc.
  public type.lteq(a, b date_time.this) bool =>
    [(a.year, b.year),
     (a.day_in_year, b.day_in_year),
     (a.hour, b.hour),
     (a.minute, b.minute),
     (a.second, b.second),
     (a.nano_second, b.nano_second)]
      .reduce true (r, t)->
        if t.values.0 > t.values.1
          abort false
        else
          r


# how many days does the given year have?
public days_in_year(year i32) =>
  if is_leap_year year then 366 else 365



# is the given year a leap year?
is_leap_year(year i32) =>
  (year % 4 = 0 & year % 100 != 0) |
    ((year % 100 = 0) & (year % 400 = 0))