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))