All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.jlangch.venice.impl.functions.TimeFunctions Maven / Gradle / Ivy

There is a newer version: 1.12.34
Show newest version
/*   __    __         _
 *   \ \  / /__ _ __ (_) ___ ___
 *    \ \/ / _ \ '_ \| |/ __/ _ \
 *     \  /  __/ | | | | (_|  __/
 *      \/ \___|_| |_|_|\___\___|
 *
 *
 * Copyright 2017-2024 Venice
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.jlangch.venice.impl.functions;

import static com.github.jlangch.venice.impl.types.Constants.Nil;
import static com.github.jlangch.venice.impl.types.VncBoolean.True;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAmount;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import com.github.jlangch.venice.VncException;
import com.github.jlangch.venice.impl.types.VncBoolean;
import com.github.jlangch.venice.impl.types.VncFunction;
import com.github.jlangch.venice.impl.types.VncJavaObject;
import com.github.jlangch.venice.impl.types.VncKeyword;
import com.github.jlangch.venice.impl.types.VncLong;
import com.github.jlangch.venice.impl.types.VncString;
import com.github.jlangch.venice.impl.types.VncVal;
import com.github.jlangch.venice.impl.types.collections.VncList;
import com.github.jlangch.venice.impl.types.collections.VncOrderedMap;
import com.github.jlangch.venice.impl.types.collections.VncSequence;
import com.github.jlangch.venice.impl.types.util.Coerce;
import com.github.jlangch.venice.impl.types.util.Types;
import com.github.jlangch.venice.impl.util.ArityExceptions;
import com.github.jlangch.venice.impl.util.SymbolMapBuilder;
import com.github.jlangch.venice.impl.util.reflect.ReflectionAccessor;
import com.github.jlangch.venice.impl.util.time.TimeUtil;



public class TimeFunctions {

    ///////////////////////////////////////////////////////////////////////////
    // Date
    ///////////////////////////////////////////////////////////////////////////

    public static VncFunction date =
        new VncFunction(
                "time/date",
                VncFunction
                    .meta()
                    .arglists("(time/date)", "(time/date x)")
                    .doc(
                        "Creates a new date of type 'java.util.Date'. \n" +
                        "x can be a long representing milliseconds since the epoch, " +
                        "a 'java.time.LocalDate', a 'java.time.LocalDateTime', " +
                        "or a 'java.time.ZonedDateTime'")
                    .examples("(time/date)")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 0, 1);

                if (args.size() == 0) {
                    return new VncJavaObject(new Date());
                }
                else {
                    final VncVal val = args.first();
                    if (Types.isVncLong(val)) {
                        final long millis = ((VncLong)val).getValue();
                        return new VncJavaObject(new Date(millis));
                    }
                    else if (Types.isVncJavaObject(val)) {
                        final Object date = ((VncJavaObject)val).getDelegate();
                        if (date instanceof Date) {
                            return new VncJavaObject(new Date(((Date)date).getTime()));
                        }
                        else if (date instanceof LocalDate) {
                            return new VncJavaObject(TimeUtil.convertLocalDateToDate((LocalDate)date));
                        }
                        else if (date instanceof LocalDateTime) {
                            return new VncJavaObject(TimeUtil.convertLocalDateTimeToDate((LocalDateTime)date));
                        }
                        else if (date instanceof ZonedDateTime) {
                            return new VncJavaObject(TimeUtil.convertZonedDateTimeToDate((ZonedDateTime)date));
                        }
                    }

                    throw new VncException(String.format(
                            "Function 'time/date' does not allow %s as parameter",
                            Types.getType(val)));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };


    public static VncFunction date_Q =
        new VncFunction(
                "time/date?",
                VncFunction
                    .meta()
                    .arglists("(time/date? date)")
                    .doc("Returns true if date is a 'java.util.Date' else false")
                    .examples("(time/date? (time/date))")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);

                return VncBoolean.of(Types.isVncJavaObject(args.first(), Date.class));
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };



    ///////////////////////////////////////////////////////////////////////////
    // LocalDate
    ///////////////////////////////////////////////////////////////////////////

    public static VncFunction local_date =
        new VncFunction(
                "time/local-date",
                VncFunction
                    .meta()
                    .arglists(
                        "(time/local-date)",
                        "(time/local-date year month day)",
                        "(time/local-date date)")
                    .doc("Creates a new local-date. A local-date is represented by 'java.time.LocalDate'")
                    .examples(
                        "(time/local-date)",
                        "(time/local-date 2018 8 1)",
                        "(time/local-date \"2018-08-01\")",
                        "(time/local-date (time/local-date-time 2018 8 1 14 20 10))",
                        "(time/local-date 1375315200000)",
                        "(time/local-date (. :java.util.Date :new))")
                    .seeAlso(
                        "time/local-date-time", "time/zoned-date-time")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 0, 1, 3);

                if (args.size() == 0) {
                    return new VncJavaObject(LocalDate.now());
                }
                else if (args.size() == 1) {
                    final VncVal val = args.first();
                    if (Types.isVncJavaObject(val)) {
                        final Object obj = ((VncJavaObject)val).getDelegate();
                        if (obj instanceof Date) {
                            final long millis = ((Date)obj).getTime();
                            return new VncJavaObject(
                                            Instant.ofEpochMilli(millis)
                                                   .atZone(ZoneId.systemDefault())
                                                   .toLocalDate());
                        }
                        else if (obj instanceof ZonedDateTime) {
                            return new VncJavaObject(((ZonedDateTime)obj).toLocalDate());
                        }
                        else if (obj instanceof LocalDateTime) {
                            return new VncJavaObject(((LocalDateTime)obj).toLocalDate());
                        }
                        else if (obj instanceof LocalDate) {
                            return val;
                        }
                        else {
                            throw new VncException(String.format(
                                    "Function 'time/local-date' does not allow %s as parameters",
                                    Types.getType(val)));
                        }
                    }
                    else if (Types.isVncString(val)) {
                        // ISO local date format "yyyy-mm-dd"
                        final String s = ((VncString)val).getValue();
                        return new VncJavaObject(LocalDate.parse(s));
                    }
                    else if (Types.isVncLong(val)) {
                        final long millis = ((VncLong)val).getValue();
                        return new VncJavaObject(
                                        Instant.ofEpochMilli(millis)
                                               .atZone(ZoneId.systemDefault())
                                               .toLocalDate());
                    }
                    else {
                        throw new VncException(String.format(
                                "Function 'time/local-date' does not allow %s as parameter",
                                Types.getType(val)));
                    }
                }
                else {
                    return new VncJavaObject(
                                LocalDate.of(
                                    Coerce.toVncLong(args.first()).getValue().intValue(),
                                    Coerce.toVncLong(args.second()).getValue().intValue(),
                                    Coerce.toVncLong(args.nth(2)).getValue().intValue()));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction local_date_Q =
        new VncFunction(
                "time/local-date?",
                VncFunction
                    .meta()
                    .arglists("(time/local-date? date)")
                    .doc("Returns true if date is a locale date ('java.time.LocalDate') else false")
                    .examples("(time/local-date? (time/local-date))")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);

                return VncBoolean.of(Types.isVncJavaObject(args.first(), LocalDate.class));
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction local_date_parse =
        new VncFunction(
                "time/local-date-parse",
                VncFunction
                    .meta()
                    .arglists(
                        "(time/local-date-parse str format",
                        "(time/local-date-parse str format locale")
                    .doc(
                        "Parses a local-date.                                            \n\n" +
                        "To parse a large number of dates a pre instantiated formatter   " +
                        "delivers best performance:                                      \n\n" +
                        "```                                                             \n" +
                        "(let [fmt (time/formatter \"yyyy-MM-dd\")]                      \n" +
                        "  (dotimes [n 100] (time/local-date-parse \"2018-12-01\" fmt))) \n" +
                        "```")
                    .examples(
                        "(time/local-date-parse \"2018-12-01\" \"yyyy-MM-dd\")",
                        "(time/local-date-parse \"2018-Dec-01\" \"yyyy-MMM-dd\" :ENGLISH)",
                        "(time/local-date-parse \"2018-12-01\" :iso)")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 2, 3);

                final VncString date = Coerce.toVncString(args.first());
                final VncVal format = args.second();
                final Locale locale = args.size() == 3 ? getLocale(args.nth(2)) : null;

                final DateTimeFormatter formatter = isIsoFormat(format)
                                                      ? DateTimeFormatter.ISO_LOCAL_DATE
                                                      : getDateTimeFormatter(format);

                return new VncJavaObject(LocalDate.parse(date.getValue(), localize(formatter, locale)));
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };



    ///////////////////////////////////////////////////////////////////////////
    // LocalDateTime
    ///////////////////////////////////////////////////////////////////////////

    public static VncFunction local_date_time =
        new VncFunction(
                "time/local-date-time",
                VncFunction
                    .meta()
                    .arglists(
                        "(time/local-date-time)",
                        "(time/local-date-time year month day)",
                        "(time/local-date-time year month day hour minute second)",
                        "(time/local-date-time year month day hour minute second millis)",
                        "(time/local-date-time date)")
                    .doc("Creates a new local-date-time. A local-date-time is represented by 'java.time.LocalDateTime'")
                    .examples(
                        "(time/local-date-time)",
                        "(time/local-date-time 2018 8 1)",
                        "(time/local-date-time 2018 8 1 14 20 10)",
                        "(time/local-date-time 2018 8 1 14 20 10 200)",
                        "(time/local-date-time \"2018-08-01T14:20:10.200\")",
                        "(time/local-date-time (time/local-date 2018 8 1))",
                        "(time/local-date-time 1375315200000)",
                        "(time/local-date-time (. :java.util.Date :new))")
                    .seeAlso(
                        "time/local-date", "time/zoned-date-time")
                     .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 0, 1, 3, 6, 7);

                if (args.size() == 0) {
                    return new VncJavaObject(LocalDateTime.now());
                }
                else if (args.size() == 1) {
                    final VncVal val = args.first();
                    if (Types.isVncJavaObject(val)) {
                        final Object obj = ((VncJavaObject)val).getDelegate();
                        if (obj instanceof Date) {
                            final long millis = ((Date)obj).getTime();
                            return new VncJavaObject(
                                            Instant.ofEpochMilli(millis)
                                                   .atZone(ZoneId.systemDefault())
                                                   .toLocalDateTime());
                        }
                        else if (obj instanceof ZonedDateTime) {
                            return new VncJavaObject(((ZonedDateTime)obj).toLocalDateTime());
                        }
                        else if (obj instanceof LocalDateTime) {
                            return val;
                        }
                        else if (obj instanceof LocalDate) {
                            return new VncJavaObject(((LocalDate)obj).atTime(0, 0, 0));
                        }
                        else if (obj instanceof Instant) {
                            return new VncJavaObject(
                                    LocalDateTime.ofInstant((Instant)obj, ZoneOffset.systemDefault()));
                        }
                        else {
                            throw new VncException(String.format(
                                    "Function 'time/local-date-time' does not allow %s as parameters",
                                    Types.getType(val)));
                        }
                    }
                    else if (Types.isVncString(val)) {
                        // ISO local date format "yyyy-mm-ddThh:MM:ss.SSS"
                        final String s = ((VncString)val).getValue();
                        return new VncJavaObject(LocalDateTime.parse(s));
                    }
                    else if (Types.isVncLong(val)) {
                        final long millis = ((VncLong)val).getValue();
                        return new VncJavaObject(
                                        Instant.ofEpochMilli(millis)
                                               .atZone(ZoneId.systemDefault())
                                               .toLocalDateTime());
                    }
                    else {
                        throw new VncException(String.format(
                                "Function 'time/local-date-time' does not allow %s as parameter",
                                Types.getType(val)));
                    }
                }
                else if (args.size() == 3) {
                    return new VncJavaObject(
                            LocalDateTime.of(
                                Coerce.toVncLong(args.first()).getValue().intValue(),
                                Coerce.toVncLong(args.second()).getValue().intValue(),
                                Coerce.toVncLong(args.nth(2)).getValue().intValue(),
                                0, 0, 0, 0));
                }
                else if (args.size() == 6) {
                    return new VncJavaObject(
                            LocalDateTime.of(
                                Coerce.toVncLong(args.first()).getValue().intValue(),
                                Coerce.toVncLong(args.second()).getValue().intValue(),
                                Coerce.toVncLong(args.nth(2)).getValue().intValue(),
                                Coerce.toVncLong(args.nth(3)).getValue().intValue(),
                                Coerce.toVncLong(args.nth(4)).getValue().intValue(),
                                Coerce.toVncLong(args.nth(5)).getValue().intValue(),
                                0));
                }
                else {
                    return new VncJavaObject(
                            LocalDateTime.of(
                                Coerce.toVncLong(args.first()).getValue().intValue(),
                                Coerce.toVncLong(args.second()).getValue().intValue(),
                                Coerce.toVncLong(args.nth(2)).getValue().intValue(),
                                Coerce.toVncLong(args.nth(3)).getValue().intValue(),
                                Coerce.toVncLong(args.nth(4)).getValue().intValue(),
                                Coerce.toVncLong(args.nth(5)).getValue().intValue(),
                                Coerce.toVncLong(args.nth(6)).getValue().intValue() * 1_000_000));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction local_date_time_Q =
        new VncFunction(
                "time/local-date-time?",
                VncFunction
                    .meta()
                    .arglists("(time/local-date-time? date)")
                    .doc("Returns true if date is a local-date-time  ('java.time.LocalDateTime') else false")
                    .examples("(time/local-date-time? (time/local-date-time))")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);

                return VncBoolean.of(Types.isVncJavaObject(args.first(), LocalDateTime.class));
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction local_date_time_parse =
        new VncFunction(
                "time/local-date-time-parse",
                VncFunction
                    .meta()
                    .arglists(
                            "(time/local-date-time-parse str format",
                            "(time/local-date-time-parse str format locale")
                    .doc(
                        "Parses a local-date-time.                                                     \n\n" +
                        "To parse a large number of dates a pre instantiated formatter                 " +
                        "delivers best performance:                                                    \n\n" +
                        "```                                                                           \n" +
                        "(let [fmt (time/formatter \"yyyy-MM-dd HH:mm:ss\")]                           \n" +
                        "  (dotimes [n 100] (time/local-date-time-parse \"2018-12-01 14:20:01\" fmt))) \n" +
                        "```")
                    .examples(
                        "(time/local-date-time-parse \"2018-08-01 14:20\" \"yyyy-MM-dd HH:mm\")",
                        "(time/local-date-time-parse \"2018-08-01 14:20:01.231\" \"yyyy-MM-dd HH:mm:ss.SSS\")",
                        "(time/local-date-time-parse \"2018-08-01T14:20:01.231\" :iso)")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 2, 3);

                final VncString date = Coerce.toVncString(args.first());
                final VncVal format = args.second();
                final Locale locale = args.size() == 3 ? getLocale(args.nth(2)) : null;

                final DateTimeFormatter formatter = isIsoFormat(format)
                                                        ? DateTimeFormatter.ISO_LOCAL_DATE_TIME
                                                        : getDateTimeFormatter(format);

                return new VncJavaObject(LocalDateTime.parse(date.getValue(), localize(formatter, locale)));
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };



    ///////////////////////////////////////////////////////////////////////////
    // ZonedDateTime
    ///////////////////////////////////////////////////////////////////////////

    public static VncFunction zoned_date_time =
        new VncFunction(
                "time/zoned-date-time",
                VncFunction
                    .meta()
                    .arglists(
                        "(time/zoned-date-time)",
                        "(time/zoned-date-time year month day)",
                        "(time/zoned-date-time year month day hour minute second)",
                        "(time/zoned-date-time year month day hour minute second millis)",
                        "(time/zoned-date-time date)",
                        "(time/zoned-date-time zone-id)",
                        "(time/zoned-date-time zone-id year month day)",
                        "(time/zoned-date-time zone-id year month day hour minute second)",
                        "(time/zoned-date-time zone-id year month day hour minute second millis)",
                        "(time/zoned-date-time zone-id date)")
                    .doc("Creates a new zoned-date-time. A zoned-date-time is represented by 'java.time.ZonedDateTime'")
                    .examples(
                        "(time/zoned-date-time)",
                        "(time/zoned-date-time 2018 8 1)",
                        "(time/zoned-date-time 2018 8 1 14 20 10)",
                        "(time/zoned-date-time 2018 8 1 14 20 10 200)",
                        "(time/zoned-date-time \"2018-08-01T14:20:10.200+01:00\")",
                        "(time/zoned-date-time (time/local-date 2018 8 1))",
                        "(time/zoned-date-time (time/local-date-time 2018 8 1 14 20 10))",
                        "(time/zoned-date-time 1375315200000)",
                        "(time/zoned-date-time (. :java.util.Date :new))",

                        "(time/zoned-date-time \"UTC\")",
                        "(time/zoned-date-time \"UTC\" 2018 8 1)",
                        "(time/zoned-date-time \"UTC\" 2018 8 1 14 20 10)",
                        "(time/zoned-date-time \"UTC\" 2018 8 1 14 20 10 200)",
                        "(time/zoned-date-time \"UTC\" \"2018-08-01T14:20:10.200+01:00\")",
                        "(time/zoned-date-time \"UTC\" (time/local-date 2018 8 1))",
                        "(time/zoned-date-time \"UTC\" (time/local-date-time 2018 8 1 14 20 10))",
                        "(time/zoned-date-time \"UTC\" 1375315200000)",
                        "(time/zoned-date-time \"UTC\" (. :java.util.Date :new))")
                    .seeAlso(
                        "time/local-date", "time/local-date-time")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArities(this, args, 0, 1, 2, 3, 4, 6, 7, 8);

                ZoneId zoneId = null;
                VncList argList = args;
                if (args.size() > 0) {
                    final VncVal val = args.first();
                    if (Types.isVncKeyword(val)) {
                        zoneId = ZoneId.of(((VncKeyword)val).getValue());
                        argList = args.rest();
                    }
                    else if (Types.isVncString(val)) {
                        final String s = ((VncString)val).getValue();
                        if (!s.isEmpty() && !Character.isDigit(s.charAt(0))) {
                            zoneId = ZoneId.of(s);
                            argList = args.rest();
                        }
                    }
                }
                if (argList.size() == 0) {
                    return new VncJavaObject(ZonedDateTime.now(orDefaultZone(zoneId)));
                }
                else if (argList.size() == 1) {
                    final VncVal val = argList.first();
                    if (Types.isVncJavaObject(val)) {
                        final Object obj = ((VncJavaObject)val).getDelegate();
                        if (obj instanceof Date) {
                            final long millis = ((Date)obj).getTime();
                            return new VncJavaObject(
                                            Instant.ofEpochMilli(millis)
                                                   .atZone(orDefaultZone(zoneId)));
                        }
                        else if (obj instanceof ZonedDateTime) {
                            return new VncJavaObject(((ZonedDateTime)obj).withZoneSameInstant(orDefaultZone(zoneId)));
                        }
                        else if (obj instanceof LocalDateTime) {
                            return new VncJavaObject(((LocalDateTime)obj).atZone(orDefaultZone(zoneId)));
                        }
                        else if (obj instanceof LocalDate) {
                            return new VncJavaObject( ((LocalDate)obj).atTime(0, 0, 0).atZone(orDefaultZone(zoneId)));
                        }
                        else {
                            throw new VncException(String.format(
                                    "Function 'time/zoned-date-time' does not allow %s as parameter",
                                    Types.getType(val)));
                        }
                    }
                    else if (Types.isVncString(val)) {
                        // ISO local date format "yyyy-mm-ddThh:MM:ss.SSS"
                        final String s = ((VncString)val).getValue();
                        return new VncJavaObject(ZonedDateTime.parse(
                                                    s,
                                                    zone(DateTimeFormatter.ISO_ZONED_DATE_TIME, zoneId)));
                    }
                    else if (Types.isVncLong(val)) {
                        final long millis = ((VncLong)val).getValue();
                        return new VncJavaObject(
                                        Instant.ofEpochMilli(millis)
                                               .atZone(orDefaultZone(zoneId)));
                    }
                    else {
                        throw new VncException(String.format(
                                "Function 'time/zoned-date-time' does not allow %s as parameter",
                                Types.getType(val)));
                    }
                }
                else if (argList.size() == 3) {
                    return new VncJavaObject(
                            ZonedDateTime.of(
                                Coerce.toVncLong(argList.first()).getValue().intValue(),
                                Coerce.toVncLong(argList.second()).getValue().intValue(),
                                Coerce.toVncLong(argList.nth(2)).getValue().intValue(),
                                0, 0, 0, 0,
                                orDefaultZone(zoneId)));
                }
                else if (argList.size() == 6) {
                    return new VncJavaObject(
                            ZonedDateTime.of(
                                Coerce.toVncLong(argList.first()).getValue().intValue(),
                                Coerce.toVncLong(argList.second()).getValue().intValue(),
                                Coerce.toVncLong(argList.nth(2)).getValue().intValue(),
                                Coerce.toVncLong(argList.nth(3)).getValue().intValue(),
                                Coerce.toVncLong(argList.nth(4)).getValue().intValue(),
                                Coerce.toVncLong(argList.nth(5)).getValue().intValue(),
                                0,
                                orDefaultZone(zoneId)));
                }
                else {
                    return new VncJavaObject(
                            ZonedDateTime.of(
                                Coerce.toVncLong(argList.first()).getValue().intValue(),
                                Coerce.toVncLong(argList.second()).getValue().intValue(),
                                Coerce.toVncLong(argList.nth(2)).getValue().intValue(),
                                Coerce.toVncLong(argList.nth(3)).getValue().intValue(),
                                Coerce.toVncLong(argList.nth(4)).getValue().intValue(),
                                Coerce.toVncLong(argList.nth(5)).getValue().intValue(),
                                Coerce.toVncLong(argList.nth(6)).getValue().intValue() * 1_000_000,
                                orDefaultZone(zoneId)));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction zoned_date_time_Q =
        new VncFunction(
                "time/zoned-date-time?",
                VncFunction
                    .meta()
                    .arglists("(time/zoned-date-time? date)")
                    .doc("Returns true if date is a zoned-date-time ('java.time.ZonedDateTime') else false")
                    .examples("(time/zoned-date-time? (time/zoned-date-time))")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);

                final VncVal val = args.first();
                return VncBoolean.of(Types.isVncJavaObject(val, ZonedDateTime.class));
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction zoned_date_time_parse =
        new VncFunction(
                "time/zoned-date-time-parse",
                VncFunction
                    .meta()
                    .arglists(
                        "(time/zoned-date-time-parse str format",
                        "(time/zoned-date-time-parse str format locale")
                    .doc(
                        "Parses a zoned-date-time.                                                           \n\n" +
                        "To parse a large number of dates a pre instantiated formatter                       " +
                        "delivers best performance:                                                          \n\n" +
                        "```                                                                                 \n" +
                        "(let [fmt (time/formatter \"yyyy-MM-dd'T'HH:mm:ssz\")]                              \n" +
                        "  (dotimes [n 100] (time/zoned-date-time-parse \"2018-12-01T14:20:01+01:00\" fmt))) \n" +
                        "```")
                    .examples(
                        "(time/zoned-date-time-parse \"2018-08-01T14:20:01+01:00\" \"yyyy-MM-dd'T'HH:mm:ssz\")",
                        "(time/zoned-date-time-parse \"2018-08-01T14:20:01.000+01:00\" \"yyyy-MM-dd'T'HH:mm:ss.SSSz\")",
                        "(time/zoned-date-time-parse \"2018-08-01T14:20:01.000+01:00\" :iso)",
                        "(time/zoned-date-time-parse \"2018-08-01 14:20:01.000 +01:00\" \"yyyy-MM-dd' 'HH:mm:ss.SSS' 'z\")")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 2, 3);

                final VncString date = Coerce.toVncString(args.first());
                final VncVal format = args.second();
                final Locale locale = args.size() == 3 ? getLocale(args.nth(2)) : null;

                final DateTimeFormatter formatter = isIsoFormat(format)
                                                        ? DateTimeFormatter.ISO_OFFSET_DATE_TIME
                                                        : getDateTimeFormatter(format);

                return new VncJavaObject(ZonedDateTime.parse(date.getValue(), localize(formatter, locale)));
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };


    ///////////////////////////////////////////////////////////////////////////
    // Unix timestamp. Seconds since Jan 01 1970 (UTC).
    ///////////////////////////////////////////////////////////////////////////

    public static VncFunction unix_timestamp =
        new VncFunction(
                "time/unix-timestamp",
                VncFunction
                    .meta()
                    .arglists(
                        "(time/unix-timestamp)",
                        "(time/unix-timestamp year month day)",
                        "(time/unix-timestamp year month day hour minute second)",
                        "(time/unix-timestamp year month day hour minute second millis)",
                        "(time/unix-timestamp date)")
                    .doc(
                        "Returns a unix timestamp. Seconds since Jan 01 1970 (UTC).\n\n" +
                        "See: [Unix Timestamp](https://www.unixtimestamp.com/)")
                    .examples(
                        "(time/unix-timestamp)",
                        "(time/unix-timestamp 2018 8 1)",
                        "(time/unix-timestamp 2018 8 1 14 20 10)",
                        "(time/unix-timestamp 2018 8 1 14 20 10 200)",
                        "(time/unix-timestamp \"2018-08-01T14:20:10.200\")",
                        "(time/unix-timestamp (time/local-date-time))",
                        "(time/unix-timestamp (time/local-date 2018 8 1))",
                        "(time/unix-timestamp (. :java.util.Date :new))")
                    .seeAlso(
                        "time/unix-timestamp-to-local-date-time",
                        "time/local-date-time",
                        "time/local-date",
                        "time/zoned-date-time")
                     .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 0, 1, 3, 6, 7);

                if (args.size() == 0) {
                    return new VncLong(LocalDateTime.now().toEpochSecond(ZoneOffset.UTC));
                }
                else if (args.size() == 1) {
                    final VncVal val = args.first();
                    if (Types.isVncJavaObject(val)) {
                        final Object obj = ((VncJavaObject)val).getDelegate();
                        if (obj instanceof Date) {
                            final long millis = ((Date)obj).getTime();
                            return new VncLong(
                                            Instant.ofEpochMilli(millis)
                                                   .atZone(ZoneId.systemDefault())
                                                   .toLocalDateTime()
                                                   .toEpochSecond(ZoneOffset.UTC));
                        }
                        else if (obj instanceof ZonedDateTime) {
                            return new VncLong(((ZonedDateTime)obj).toLocalDateTime().toEpochSecond(ZoneOffset.UTC));
                        }
                        else if (obj instanceof LocalDateTime) {
                            return new VncLong(((LocalDateTime)obj).toEpochSecond(ZoneOffset.UTC));
                        }
                        else if (obj instanceof LocalDate) {
                            return new VncLong(((LocalDate)obj).atTime(0,0,0).toEpochSecond(ZoneOffset.UTC));
                        }
                        else if (obj instanceof Instant) {
                            return new VncLong(
                                    ((Instant)obj).getEpochSecond());
                        }
                        else {
                            throw new VncException(String.format(
                                    "Function 'time/unix-timestamp' does not allow %s as parameters",
                                    Types.getType(val)));
                        }
                    }
                    else if (Types.isVncString(val)) {
                        // ISO local date format "yyyy-mm-ddThh:MM:ss.SSS"
                        final String s = ((VncString)val).getValue();
                        return new VncJavaObject(LocalDateTime.parse(s));
                    }
                    else if (Types.isVncLong(val)) {
                        return val;
                    }
                    else {
                        throw new VncException(String.format(
                                "Function 'time/unix-timestamp' does not allow %s as parameter",
                                Types.getType(val)));
                    }
                }
                else if (args.size() == 3) {
                    return new VncLong(
                            LocalDateTime
                                .of(
                                    Coerce.toVncLong(args.first()).getValue().intValue(),
                                    Coerce.toVncLong(args.second()).getValue().intValue(),
                                    Coerce.toVncLong(args.nth(2)).getValue().intValue(),
                                    0, 0, 0, 0)
                                .toEpochSecond(ZoneOffset.UTC));
                }
                else if (args.size() == 6) {
                    return new VncLong(
                            LocalDateTime
                                .of(
                                    Coerce.toVncLong(args.first()).getValue().intValue(),
                                    Coerce.toVncLong(args.second()).getValue().intValue(),
                                    Coerce.toVncLong(args.nth(2)).getValue().intValue(),
                                    Coerce.toVncLong(args.nth(3)).getValue().intValue(),
                                    Coerce.toVncLong(args.nth(4)).getValue().intValue(),
                                    Coerce.toVncLong(args.nth(5)).getValue().intValue(),
                                    0)
                                .toEpochSecond(ZoneOffset.UTC));
                }
                else {
                    return new VncLong(
                            LocalDateTime
                                .of(
                                    Coerce.toVncLong(args.first()).getValue().intValue(),
                                    Coerce.toVncLong(args.second()).getValue().intValue(),
                                    Coerce.toVncLong(args.nth(2)).getValue().intValue(),
                                    Coerce.toVncLong(args.nth(3)).getValue().intValue(),
                                    Coerce.toVncLong(args.nth(4)).getValue().intValue(),
                                    Coerce.toVncLong(args.nth(5)).getValue().intValue(),
                                    Coerce.toVncLong(args.nth(6)).getValue().intValue() * 1_000_000)
                                .toEpochSecond(ZoneOffset.UTC));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };


    public static VncFunction unix_timestamp_to_local_date_time =
        new VncFunction(
                "time/unix-timestamp-to-local-date-time",
                VncFunction
                    .meta()
                    .arglists(
                        "(time/unix-timestamp-to-local-date-time seconds-since-epoch)")
                    .doc(
                        "Converts a unix timestamp (seconds since Jan 01 1970 (UTC)) to a " +
                        "java :LocalDateTime.\n\n" +
                        "See: [Unix Timestamp](https://www.unixtimestamp.com/)")
                    .examples(
                        "(time/unix-timestamp-to-local-date-time (time/unix-timestamp))")
                    .seeAlso(
                        "time/unix-timestamp")
                     .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);

                final long seconds = Coerce.toVncLong(args.first()).toJavaLong();

                final LocalDateTime ts = Instant.ofEpochSecond(seconds)
                                                .atOffset(ZoneOffset.UTC)
                                                .toLocalDateTime();

                return new VncJavaObject(ts);
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };


    ///////////////////////////////////////////////////////////////////////////
    // Compare
    ///////////////////////////////////////////////////////////////////////////

    public static VncFunction after_Q =
        new VncFunction(
                "time/after?",
                VncFunction
                    .meta()
                    .arglists(
                        "(time/after? date1 date2)",
                        "(time/after? date1 date2 & more)")
                    .doc(
                        "Returns true if all dates are ordered from the latest to the earliest " +
                        "(same semantics as `>`)")
                    .examples(
                        "(time/after? (time/local-date 2019 1 1) \n" +
                        "             (time/local-date 2018 1 1))",
                        "(time/after? (time/local-date-time \"2019-01-01T10:00:00.000\") \n" +
                        "             (time/local-date-time \"2018-01-01T10:00:00.000\"))",
                        "(time/after? (time/zoned-date-time \"2019-01-01T10:00:00.000+01:00\") \n" +
                        "             (time/zoned-date-time \"2018-01-01T10:00:00.000+01:00\"))")
                    .seeAlso(
                        "time/before?", "time/not-after?", "time/not-before?")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertMinArity(this, args, 2);

                boolean after = true;

                VncVal d1 = args.first();
                for(VncVal d2 : args.rest()) {
                    final Object date1 = Coerce.toVncJavaObject(d1).getDelegate();
                    final Object date2 = Coerce.toVncJavaObject(d2).getDelegate();

                    if (date1 instanceof ZonedDateTime && date2 instanceof ZonedDateTime) {
                        after = after && ((ZonedDateTime)date1).isAfter((ZonedDateTime)date2);
                    }
                    else if (date1 instanceof LocalDateTime && date2 instanceof LocalDateTime) {
                        after = after && ((LocalDateTime)date1).isAfter((LocalDateTime)date2);
                    }
                    else if (date1 instanceof LocalDate && date2 instanceof LocalDate) {
                        after = after && ((LocalDate)date1).isAfter((LocalDate)date2);
                    }
                    else {
                        throw new VncException(String.format(
                                "Function 'time/after?' does not allow %s %s as date1 / date2 parameter",
                                Types.getType(args.first()),
                                Types.getType(args.second())));
                    }

                    if (!after) break;
                    d1 = d2;
                }

                return VncBoolean.of(after);
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction not_after_Q =
        new VncFunction(
                "time/not-after?",
                VncFunction
                    .meta()
                    .arglists("(time/not-after? date1 date2)")
                    .doc(
                        "Returns true if date1 is not-after date2 else false (same semantics as `<=`)")
                    .examples(
                        "(time/not-after? (time/local-date 2018 1 1) \n" +
                        "                 (time/local-date 2019 1 1))",
                        "(time/not-after? (time/local-date-time \"2018-01-01T10:00:00.000\") \n" +
                        "                 (time/local-date-time \"2019-01-01T10:00:00.000\"))",
                        "(time/not-after? (time/zoned-date-time \"2018-01-01T10:00:00.000+01:00\") \n" +
                        "                 (time/zoned-date-time \"2019-01-01T10:00:00.000+01:00\"))")
                    .seeAlso(
                        "time/after?", "time/before?", "time/not-before?")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 2);

                final Object date1 = Coerce.toVncJavaObject(args.first()).getDelegate();
                final Object date2 = Coerce.toVncJavaObject(args.second()).getDelegate();

                if (date1 instanceof ZonedDateTime && date2 instanceof ZonedDateTime) {
                    return VncBoolean.of(!((ZonedDateTime)date1).isAfter((ZonedDateTime)date2));
                }
                else if (date1 instanceof LocalDateTime && date2 instanceof LocalDateTime) {
                    return VncBoolean.of(!((LocalDateTime)date1).isAfter((LocalDateTime)date2));
                }
                else if (date1 instanceof LocalDate && date2 instanceof LocalDate) {
                    return VncBoolean.of(!((LocalDate)date1).isAfter((LocalDate)date2));
                }
                else {
                    throw new VncException(String.format(
                            "Function 'time/not-after?' does not allow %s %s as date1 / date2 parameter",
                            Types.getType(args.first()),
                            Types.getType(args.second())));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction before_Q =
        new VncFunction(
                "time/before?",
                VncFunction
                    .meta()
                    .arglists(
                        "(time/before? date1 date2)",
                        "(time/before? date1 date2 & more)")
                    .doc(
                        "Returns true if all dates are ordered from the earliest to the latest " +
                        "(same semantics as `<`)")
                    .examples(
                        "(time/before? (time/local-date 2018 1 1) \n" +
                        "              (time/local-date 2019 1 1))",
                        "(time/before? (time/local-date-time \"2018-01-01T10:00:00.000\") \n" +
                        "              (time/local-date-time \"2019-01-01T10:00:00.000\"))",
                        "(time/before? (time/zoned-date-time \"2018-01-01T10:00:00.000+01:00\") \n" +
                        "              (time/zoned-date-time \"2019-01-01T10:00:00.000+01:00\"))")
                    .seeAlso(
                        "time/after?", "time/not-after?", "time/not-before?")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertMinArity(this, args, 2);

                boolean before = true;

                VncVal d1 = args.first();
                for(VncVal d2 : args.rest()) {
                    final Object date1 = Coerce.toVncJavaObject(d1).getDelegate();
                    final Object date2 = Coerce.toVncJavaObject(d2).getDelegate();

                    if (date1 instanceof ZonedDateTime && date2 instanceof ZonedDateTime) {
                        before = before && ((ZonedDateTime)date1).isBefore((ZonedDateTime)date2);
                    }
                    else if (date1 instanceof LocalDateTime && date2 instanceof LocalDateTime) {
                        before = before && ((LocalDateTime)date1).isBefore((LocalDateTime)date2);
                    }
                    else if (date1 instanceof LocalDate && date2 instanceof LocalDate) {
                        before = before && ((LocalDate)date1).isBefore((LocalDate)date2);
                    }
                    else {
                        throw new VncException(String.format(
                                "Function 'time/before?' does not allow %s, %s as date parameter",
                                Types.getType(d1),
                                Types.getType(d2)));
                    }

                    if (!before) break;
                    d1 = d2;
                }

                return VncBoolean.of(before);
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction not_before_Q =
        new VncFunction(
                "time/not-before?",
                VncFunction
                    .meta()
                    .arglists("(time/not-before? date1 date2)")
                    .doc("Returns true if date1 is not-before date2 else false (same semantics as `>=`)")
                    .examples(
                        "(time/not-before? (time/local-date 2019 1 1) \n" +
                        "                  (time/local-date 2019 1 1))",
                        "(time/not-before? (time/local-date-time \"2019-01-01T10:00:00.000\") \n" +
                        "                  (time/local-date-time \"2018-01-01T10:00:00.000\"))",
                        "(time/not-before? (time/zoned-date-time \"2019-01-01T10:00:00.000+01:00\") \n" +
                        "                  (time/zoned-date-time \"2018-01-01T10:00:00.000+01:00\"))")
                    .seeAlso(
                        "time/after?", "time/before?", "time/not-after?")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 2);

                final Object date1 = Coerce.toVncJavaObject(args.first()).getDelegate();
                final Object date2 = Coerce.toVncJavaObject(args.second()).getDelegate();

                if (date1 instanceof ZonedDateTime && date2 instanceof ZonedDateTime) {
                    return VncBoolean.of(!((ZonedDateTime)date1).isBefore((ZonedDateTime)date2));
                }
                else if (date1 instanceof LocalDateTime && date2 instanceof LocalDateTime) {
                    return VncBoolean.of(!((LocalDateTime)date1).isBefore((LocalDateTime)date2));
                }
                else if (date1 instanceof LocalDate && date2 instanceof LocalDate) {
                    return VncBoolean.of(!((LocalDate)date1).isBefore((LocalDate)date2));
                }
                else {
                    throw new VncException(String.format(
                            "Function 'time/not-before?' does not allow %s %s as date1 / date2 parameter",
                            Types.getType(args.first()),
                            Types.getType(args.second())));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };


    ///////////////////////////////////////////////////////////////////////////
    // Plus/Minus
    ///////////////////////////////////////////////////////////////////////////

    public static VncFunction plus =
        new VncFunction(
                "time/plus",
                VncFunction
                    .meta()
                    .arglists(
                        "(time/plus date unit n)",
                        "(time/minus plus temporal)")
                    .doc(
                        "Adds the n units to the date. Units: {:years :months :weeks " +
                        ":days :hours :minutes :seconds :milliseconds}\n\n" +
                        "In the two argument version add a :java.time.Temporal (Period, Duration) " +
                        "to the date.")
                    .examples(
                        "(time/plus (time/local-date) :days 2)",
                        "(time/plus (time/local-date-time) :days 2)",
                        "(time/plus (time/zoned-date-time) :days 2)",
                        "(time/plus (time/local-date) (. :java.time.Period :ofDays 2))",
                        "(time/plus (time/local-date-time) (. :java.time.Period :ofDays 2))",
                        "(time/plus (time/zoned-date-time) (. :java.time.Period :ofDays 2))")
                    .seeAlso(
                        "time/minus")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 2, 3);

                if (args.size() == 2) {
                    final Temporal temporal = Coerce.toVncJavaObject(args.first(), Temporal.class);
                    final TemporalAmount amount = Coerce.toVncJavaObject(args.second(), TemporalAmount.class);
                    return new VncJavaObject(temporal.plus(amount));
                }
                else {
                    final Temporal temporal = Coerce.toVncJavaObject(args.first(), Temporal.class);
                    final ChronoUnit unit = toChronoUnit(Coerce.toVncKeyword(args.second()).getValue());
                    final long n = Coerce.toVncLong(args.nth(2)).getValue();

                    if (unit == null) {
                        throw new VncException(String.format(
                                "Function 'time/plus' invalid time unit %s",
                                Coerce.toVncKeyword(args.second()).getValue()));
                    }

                    return new VncJavaObject(temporal.plus(n, unit));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction minus =
        new VncFunction(
                "time/minus",
                VncFunction
                    .meta()
                    .arglists(
                        "(time/minus date unit n)",
                        "(time/minus date temporal)")
                    .doc(
                        "Subtracts the n units from the date. Units: {:years :months :weeks " +
                        ":days :hours :minutes :seconds :milliseconds}\n\n" +
                        "In the two argument version subtracts a :java.time.Temporal (Period, Duration) " +
                        "from the date.")
                    .examples(
                        "(time/minus (time/local-date) :days 2)",
                        "(time/minus (time/local-date-time) :days 2)",
                        "(time/minus (time/zoned-date-time) :days 2)",
                        "(time/minus (time/local-date) (. :java.time.Period :ofDays 2))",
                        "(time/minus (time/local-date-time) (. :java.time.Period :ofDays 2))",
                        "(time/minus (time/zoned-date-time) (. :java.time.Period :ofDays 2))")
                    .seeAlso(
                        "time/plus")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 2, 3);

                if (args.size() == 2) {
                    final Temporal temporal = Coerce.toVncJavaObject(args.first(), Temporal.class);
                    final TemporalAmount amount = Coerce.toVncJavaObject(args.second(), TemporalAmount.class);
                    return new VncJavaObject(temporal.minus(amount));
                }
                else {
                       final Temporal temporal = Coerce.toVncJavaObject(args.first(), Temporal.class);
                    final ChronoUnit unit = toChronoUnit(Coerce.toVncKeyword(args.second()).getValue());
                    final long n = Coerce.toVncLong(args.nth(2)).getValue();

                    if (unit == null) {
                        throw new VncException(String.format(
                                "Function 'time/minus' invalid time unit %ss",
                                Coerce.toVncKeyword(args.second()).getValue()));
                    }

                    return new VncJavaObject(temporal.minus(n, unit));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };


    public static VncFunction period =
        new VncFunction(
                "time/period",
                VncFunction
                    .meta()
                    .arglists("(time/period from to unit)")
                    .doc(
                        "Returns the period interval of two dates in the specified unit.¶" +
                        "Units: {:years :months :weeks :days :hours :minutes :seconds :milliseconds}")
                    .examples(
                        "(time/period (time/local-date) (time/plus (time/local-date) :days 3) :days)",
                        "(time/period (time/local-date-time) (time/plus (time/local-date-time) :days 3) :days)",
                        "(time/period (time/zoned-date-time) (time/plus (time/zoned-date-time) :days 3) :days)")
                    .seeAlso(
                        "time/local-date", "time/local-date-time", "time/zoned-date-time")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 3);

                final Object from = Coerce.toVncJavaObject(args.first()).getDelegate();
                final Object to = Coerce.toVncJavaObject(args.second()).getDelegate();
                final ChronoUnit unit = toChronoUnit(Coerce.toVncKeyword(args.nth(2)).getValue());

                if (unit == null) {
                    throw new VncException(String.format(
                            "Function 'time/period' invalid time unit %s",
                            Coerce.toVncKeyword(args.second()).getValue()));
                }

                if (from instanceof ZonedDateTime && to instanceof ZonedDateTime) {
                    return new VncLong(unit.between((ZonedDateTime)from, (ZonedDateTime)to));
                }
                else if (from instanceof LocalDateTime && to instanceof LocalDateTime) {
                    return new VncLong(unit.between((LocalDateTime)from, (LocalDateTime)to));
                }
                else if (from instanceof LocalDate && to instanceof LocalDate) {
                    return new VncLong(unit.between((LocalDate)from, (LocalDate)to));
                }
                else {
                    throw new VncException(String.format(
                            "Function 'time/period' does not allow %s %s as from / to parameter",
                            Types.getType(args.first()),
                            Types.getType(args.second())));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };


    ///////////////////////////////////////////////////////////////////////////
    // Fields
    ///////////////////////////////////////////////////////////////////////////

    public static VncFunction year =
        new VncFunction(
                "time/year",
                VncFunction
                    .meta()
                    .arglists("(time/year date)")
                    .doc("Returns the year of the date")
                    .examples(
                        "(time/year (time/local-date))",
                        "(time/year (time/local-date-time))",
                        "(time/year (time/zoned-date-time))")
                    .seeAlso(
                         "time/month",
                         "time/day-of-year",
                         "time/day-of-month", "time/first-day-of-month", "time/last-day-of-month",
                         "time/day-of-week")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);

                final Object date = Coerce.toVncJavaObject(args.first()).getDelegate();

                if (date instanceof ZonedDateTime) {
                    return new VncLong(((ZonedDateTime)date).getYear());
                }
                else if (date instanceof LocalDateTime) {
                    return new VncLong(((LocalDateTime)date).getYear());
                }
                else if (date instanceof LocalDate) {
                    return new VncLong(((LocalDate)date).getYear());
                }
                else {
                    throw new VncException(String.format(
                            "Function 'time/year' does not allow %s as parameter",
                            Types.getType(args.first())));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction month =
        new VncFunction(
                "time/month",
                VncFunction
                    .meta()
                    .arglists("(time/month date)")
                    .doc("Returns the month of the date 1..12")
                    .examples(
                        "(time/month (time/local-date))",
                        "(time/month (time/local-date-time))",
                        "(time/month (time/zoned-date-time))")
                    .seeAlso(
                        "time/year",
                        "time/day-of-year",
                        "time/day-of-month", "time/first-day-of-month", "time/last-day-of-month",
                        "time/day-of-week")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);

                final Object date = Coerce.toVncJavaObject(args.first()).getDelegate();

                if (date instanceof ZonedDateTime) {
                    return new VncLong(((ZonedDateTime)date).getMonth().getValue());
                }
                else if (date instanceof LocalDateTime) {
                    return new VncLong(((LocalDateTime)date).getMonth().getValue());
                }
                else if (date instanceof LocalDate) {
                    return new VncLong(((LocalDate)date).getMonth().getValue());
                }
                else {
                    throw new VncException(String.format(
                            "Function 'time/month' does not allow %s as parameter",
                            Types.getType(args.first())));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction day_of_week =
        new VncFunction(
                "time/day-of-week",
                VncFunction
                    .meta()
                    .arglists("(time/day-of-week date)")
                    .doc("Returns the day of the week (:MONDAY ... :SUNDAY)")
                    .examples(
                        "(time/day-of-week (time/local-date))",
                        "(time/day-of-week (time/local-date-time))",
                        "(time/day-of-week (time/zoned-date-time))")
                    .seeAlso(
                        "time/year", "time/month",
                        "time/day-of-year",
                        "time/day-of-month", "time/first-day-of-month", "time/last-day-of-month")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);

                final Object date = Coerce.toVncJavaObject(args.first()).getDelegate();

                if (date instanceof ZonedDateTime) {
                    return new VncKeyword(((ZonedDateTime)date).getDayOfWeek().name());
                }
                else if (date instanceof LocalDateTime) {
                    return new VncKeyword(((LocalDateTime)date).getDayOfWeek().name());
                }
                else if (date instanceof LocalDate) {
                    return new VncKeyword(((LocalDate)date).getDayOfWeek().name());
                }
                else {
                    throw new VncException(String.format(
                            "Function 'time/day-of-week' does not allow %s as parameter",
                            Types.getType(args.first())));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction day_of_month =
        new VncFunction(
                "time/day-of-month",
                VncFunction
                    .meta()
                    .arglists("(time/day-of-month date)")
                    .doc("Returns the day of the month (1..31)")
                    .examples(
                        "(time/day-of-month (time/local-date))",
                        "(time/day-of-month (time/local-date-time))",
                        "(time/day-of-month (time/zoned-date-time))")
                    .seeAlso(
                        "time/year", "time/month",
                        "time/day-of-year",
                        "time/first-day-of-month", "time/last-day-of-month",
                        "time/day-of-week")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);

                final Object date = Coerce.toVncJavaObject(args.first()).getDelegate();

                if (date instanceof ZonedDateTime) {
                    return new VncLong(((ZonedDateTime)date).getDayOfMonth());
                }
                else if (date instanceof LocalDateTime) {
                    return new VncLong(((LocalDateTime)date).getDayOfMonth());
                }
                else if (date instanceof LocalDate) {
                    return new VncLong(((LocalDate)date).getDayOfMonth());
                }
                else {
                    throw new VncException(String.format(
                            "Function 'time/day-of-month' does not allow %s as parameters",
                            Types.getType(args.first())));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction day_of_year =
        new VncFunction(
                "time/day-of-year",
                VncFunction
                    .meta()
                    .arglists("(time/day-of-year date)")
                    .doc("Returns the day of the year (1..366)")
                    .examples(
                        "(time/day-of-year (time/local-date))",
                        "(time/day-of-year (time/local-date-time))",
                        "(time/day-of-year (time/zoned-date-time))")
                    .seeAlso(
                        "time/year", "time/month",
                        "time/day-of-month", "time/first-day-of-month", "time/last-day-of-month",
                        "time/day-of-week")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);

                final Object date = Coerce.toVncJavaObject(args.first()).getDelegate();

                if (date instanceof ZonedDateTime) {
                    return new VncLong(((ZonedDateTime)date).getDayOfYear());
                }
                else if (date instanceof LocalDateTime) {
                    return new VncLong(((LocalDateTime)date).getDayOfYear());
                }
                else if (date instanceof LocalDate) {
                    return new VncLong(((LocalDate)date).getDayOfYear());
                }
                else {
                    throw new VncException(String.format(
                            "Function 'time/day-of-year' does not allow %s as parameter",
                            Types.getType(args.first())));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction first_day_of_month =
        new VncFunction(
                "time/first-day-of-month",
                VncFunction
                    .meta()
                    .arglists("(time/first-day-of-month date)")
                    .doc("Returns the first day of a month as a local-date.")
                    .examples(
                        "(time/first-day-of-month (time/local-date))",
                        "(time/first-day-of-month (time/local-date-time))",
                        "(time/first-day-of-month (time/zoned-date-time))")
                    .seeAlso(
                        "time/year", "time/month",
                        "time/day-of-year",
                        "time/day-of-month", "time/last-day-of-month",
                        "time/day-of-week")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);

                final Object dt = Coerce.toVncJavaObject(args.first()).getDelegate();

                if (dt instanceof ZonedDateTime) {
                    final LocalDate date = ((ZonedDateTime)dt).toLocalDateTime().toLocalDate();
                    return new VncJavaObject(date.withDayOfMonth(1));
                }
                else if (dt instanceof LocalDateTime) {
                    final LocalDate date = ((LocalDateTime)dt).toLocalDate();
                    return new VncJavaObject(date.withDayOfMonth(1));
                }
                else if (dt instanceof LocalDate) {
                    final LocalDate date = ((LocalDate)dt);
                    return new VncJavaObject(date.withDayOfMonth(1));
                }
                else {
                    throw new VncException(String.format(
                            "Function 'time/first-day-of-month' does not allow %s as parameter",
                            Types.getType(args.first())));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction last_day_of_month =
        new VncFunction(
                "time/last-day-of-month",
                VncFunction
                    .meta()
                    .arglists("(time/last-day-of-month date)")
                    .doc("Returns the last day of a month as a local-date.")
                    .examples(
                        "(time/last-day-of-month (time/local-date))",
                        "(time/last-day-of-month (time/local-date-time))",
                        "(time/last-day-of-month (time/zoned-date-time))")
                    .seeAlso(
                        "time/year", "time/month",
                        "time/day-of-year",
                        "time/day-of-month", "time/first-day-of-month",
                        "time/day-of-week")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);

                final Object dt = Coerce.toVncJavaObject(args.first()).getDelegate();

                if (dt instanceof ZonedDateTime) {
                    final LocalDate date = ((ZonedDateTime)dt).toLocalDateTime().toLocalDate();
                    return new VncJavaObject(date.withDayOfMonth(date.lengthOfMonth()));
                }
                else if (dt instanceof LocalDateTime) {
                    final LocalDate date = ((LocalDateTime)dt).toLocalDate();
                    return new VncJavaObject(date.withDayOfMonth(date.lengthOfMonth()));
                }
                else if (dt instanceof LocalDate) {
                    final LocalDate date = ((LocalDate)dt);
                    return new VncJavaObject(date.withDayOfMonth(date.lengthOfMonth()));
                }
                else {
                    throw new VncException(String.format(
                            "Function 'time/last-day-of-month' does not allow %s as parameter",
                            Types.getType(args.first())));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction hour =
        new VncFunction(
                "time/hour",
                VncFunction
                    .meta()
                    .arglists("(time/hour date)")
                    .doc("Returns the hour of the date 0..23")
                    .examples(
                        "(time/hour (time/local-date))",
                        "(time/hour (time/local-date-time))",
                        "(time/hour (time/zoned-date-time))")
                    .seeAlso(
                         "time/minute", "time/second", "time/milli")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);

                final Object date = Coerce.toVncJavaObject(args.first()).getDelegate();

                if (date instanceof ZonedDateTime) {
                    return new VncLong(((ZonedDateTime)date).getHour());
                }
                else if (date instanceof LocalDateTime) {
                    return new VncLong(((LocalDateTime)date).getHour());
                }
                else if (date instanceof LocalDate) {
                    return new VncLong(0);
                }
                else {
                    throw new VncException(String.format(
                            "Function 'time/hour' does not allow %s as parameter",
                            Types.getType(args.first())));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction minute =
        new VncFunction(
                "time/minute",
                VncFunction
                    .meta()
                    .arglists("(time/minute date)")
                    .doc("Returns the minute of the date 0..59")
                    .examples(
                        "(time/minute (time/local-date))",
                        "(time/minute (time/local-date-time))",
                        "(time/minute (time/zoned-date-time))")
                    .seeAlso(
                        "time/hour", "time/second", "time/milli")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);

                final Object date = Coerce.toVncJavaObject(args.first()).getDelegate();

                if (date instanceof ZonedDateTime) {
                    return new VncLong(((ZonedDateTime)date).getMinute());
                }
                else if (date instanceof LocalDateTime) {
                    return new VncLong(((LocalDateTime)date).getMinute());
                }
                else if (date instanceof LocalDate) {
                    return new VncLong(0);
                }
                else {
                    throw new VncException(String.format(
                            "Function 'time/minute' does not allow %s as parameter",
                            Types.getType(args.first())));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction second =
        new VncFunction(
                "time/second",
                VncFunction
                    .meta()
                    .arglists("(time/second date)")
                    .doc("Returns the second of the date 0..59")
                    .examples(
                        "(time/second (time/local-date))",
                        "(time/second (time/local-date-time))",
                        "(time/second (time/zoned-date-time))")
                    .seeAlso(
                        "time/hour", "time/minute", "time/milli")
                   .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);

                final Object date = Coerce.toVncJavaObject(args.first()).getDelegate();

                if (date instanceof ZonedDateTime) {
                    return new VncLong(((ZonedDateTime)date).getSecond());
                }
                else if (date instanceof LocalDateTime) {
                    return new VncLong(((LocalDateTime)date).getSecond());
                }
                else if (date instanceof LocalDate) {
                    return new VncLong(0);
                }
                else {
                    throw new VncException(String.format(
                            "Function 'time/second' does not allow %s as parameter",
                            Types.getType(args.first())));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction milli =
        new VncFunction(
                "time/milli",
                VncFunction
                    .meta()
                    .arglists("(time/milli date)")
                    .doc("Returns the millis of the date 0..999")
                    .examples(
                        "(time/milli (time/local-date))",
                        "(time/milli (time/local-date-time))",
                        "(time/milli (time/zoned-date-time))")
                    .seeAlso(
                        "time/hour", "time/minute", "time/second")
                   .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);

                final Object date = Coerce.toVncJavaObject(args.first()).getDelegate();

                if (date instanceof ZonedDateTime) {
                    return new VncLong(((ZonedDateTime)date).getNano() / 1_000_000);
                }
                else if (date instanceof LocalDateTime) {
                    return new VncLong(((LocalDateTime)date).getNano() / 1_000_000);
                }
                else if (date instanceof LocalDate) {
                    return new VncLong(0);
                }
                else {
                    throw new VncException(String.format(
                            "Function 'time/milli' does not allow %s as parameter",
                            Types.getType(args.first())));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction length_of_year =
        new VncFunction(
                "time/length-of-year",
                VncFunction
                    .meta()
                    .arglists("(time/length-of-year date)")
                    .doc(
                        "Returns the length of the year represented by this date. \n\n" +
                        "This returns the length of the year in days, either 365 or 366.")
                    .examples(
                        "(time/length-of-year (time/local-date 2000 1 1))",
                        "(time/length-of-year (time/local-date 2001 1 1))",
                        "(time/length-of-year (time/local-date-time))",
                        "(time/length-of-year (time/zoned-date-time))")
                    .seeAlso(
                        "time/length-of-month", "time/leap-year?")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);


                final Object dt = Coerce.toVncJavaObject(args.first()).getDelegate();

                if (dt instanceof ZonedDateTime) {
                    return new VncLong(((ZonedDateTime)dt).toLocalDateTime().toLocalDate().lengthOfYear());
                }
                else if (dt instanceof LocalDateTime) {
                    return new VncLong(((LocalDateTime)dt).toLocalDate().lengthOfYear());
                }
                else if (dt instanceof LocalDate) {
                    return new VncLong(((LocalDate)dt).lengthOfYear());
                }
                else {
                    throw new VncException(String.format(
                            "Function 'time/length-of-year' does not allow %s as parameter",
                            Types.getType(args.first())));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction length_of_month =
        new VncFunction(
                "time/length-of-month",
                VncFunction
                    .meta()
                    .arglists("(time/length-of-month date)")
                    .doc(
                        "Returns the length of the month represented by this date.\n\n" +
                        "This returns the length of the month in days. " +
                        "For example, a date in January would return 31.")
                    .examples(
                        "(time/length-of-month (time/local-date 2000 2 1))",
                        "(time/length-of-month (time/local-date 2001 2 1))",
                        "(time/length-of-month (time/local-date-time))",
                        "(time/length-of-month (time/zoned-date-time))")
                    .seeAlso(
                        "time/length-of-year", "time/leap-year?")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);


                final Object dt = Coerce.toVncJavaObject(args.first()).getDelegate();

                if (dt instanceof ZonedDateTime) {
                    return new VncLong(((ZonedDateTime)dt).toLocalDateTime().toLocalDate().lengthOfMonth());
                }
                else if (dt instanceof LocalDateTime) {
                    return new VncLong(((LocalDateTime)dt).toLocalDate().lengthOfMonth());
                }
                else if (dt instanceof LocalDate) {
                    return new VncLong(((LocalDate)dt).lengthOfMonth());
                }
                else {
                    throw new VncException(String.format(
                            "Function 'time/length-of-month' does not allow %s as parameter",
                            Types.getType(args.first())));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction leap_yearQ =
        new VncFunction(
                "time/leap-year?",
                VncFunction
                    .meta()
                    .arglists("(time/leap-year? date)")
                    .doc("Checks if the year is a leap year.")
                    .examples(
                        "(time/leap-year? 2000)",
                        "(time/leap-year? (time/local-date 2000 1 1))",
                        "(time/leap-year? (time/local-date-time))",
                        "(time/leap-year? (time/zoned-date-time))")
                    .seeAlso(
                        "time/length-of-year", "time/length-of-month")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);


                if (args.first() instanceof VncLong) {
                    return VncBoolean.of(
                            LocalDate.of(Coerce.toVncLong(args.first()).getValue().intValue(), 1, 1)
                                     .isLeapYear());
                }

                final Object dt = Coerce.toVncJavaObject(args.first()).getDelegate();

                if (dt instanceof ZonedDateTime) {
                    return VncBoolean.of(((ZonedDateTime)dt).toLocalDateTime().toLocalDate().isLeapYear());
                }
                else if (dt instanceof LocalDateTime) {
                    return VncBoolean.of(((LocalDateTime)dt).toLocalDate().isLeapYear());
                }
                else if (dt instanceof LocalDate) {
                    return VncBoolean.of(((LocalDate)dt).isLeapYear());
                }
                else {
                    throw new VncException(String.format(
                            "Function 'time/leap-year?' does not allow %s as parameter",
                            Types.getType(args.first())));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };


    ///////////////////////////////////////////////////////////////////////////
    // Miscallenous
    ///////////////////////////////////////////////////////////////////////////

    public static VncFunction with_time =
        new VncFunction(
                "time/with-time",
                VncFunction
                    .meta()
                    .arglists(
                        "(time/with-time date hour minute second)",
                        "(time/with-time date hour minute second millis)")
                    .doc("Sets the time of a date. Returns a new date")
                    .examples(
                        "(time/with-time (time/local-date) 22 00 15 333)",
                        "(time/with-time (time/local-date-time) 22 00 15 333)",
                        "(time/with-time (time/zoned-date-time) 22 00 15 333)")
                    .seeAlso(
                        "time/local-date", "time/local-date-time", "time/zoned-date-time")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 4, 5);

                final Object dt = Coerce.toVncJavaObject(args.first()).getDelegate();

                final int nanos = args.size() == 5 ? Coerce.toVncLong(args.nth(4)).getValue().intValue() * 1_000_000 : 0;

                if (dt instanceof ZonedDateTime) {
                    final ZonedDateTime date = ((ZonedDateTime)dt);
                    return new VncJavaObject(
                                date.withHour(Coerce.toVncLong(args.second()).getValue().intValue())
                                    .withMinute(Coerce.toVncLong(args.nth(2)).getValue().intValue())
                                    .withSecond(Coerce.toVncLong(args.nth(3)).getValue().intValue())
                                    .withNano(nanos));
                }
                else if (dt instanceof LocalDateTime) {
                    return new VncJavaObject(
                                ((LocalDateTime)dt).toLocalDate().atTime(
                                    Coerce.toVncLong(args.second()).getValue().intValue(),
                                    Coerce.toVncLong(args.nth(2)).getValue().intValue(),
                                    Coerce.toVncLong(args.nth(3)).getValue().intValue(),
                                    nanos));
                }
                else if (dt instanceof LocalDate) {
                    return new VncJavaObject(
                            ((LocalDate)dt).atTime(
                                Coerce.toVncLong(args.second()).getValue().intValue(),
                                Coerce.toVncLong(args.nth(2)).getValue().intValue(),
                                Coerce.toVncLong(args.nth(3)).getValue().intValue(),
                                nanos));
                }
                else {
                    throw new VncException(String.format(
                            "Function 'time/with-time' does not allow %s as parameters",
                            Types.getType(args.first())));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction latest =
        new VncFunction(
                "time/latest",
                VncFunction
                    .meta()
                    .arglists("(time/latest coll)")
                    .doc(
                        "Returns the latest date from a collection of dates. " +
                        "All dates must be of equal type. The coll may be empty or nil.")
                    .examples(
                        "(time/latest [(time/local-date 2018 8 1) (time/local-date 2018 8 3)])")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);

                final VncSequence seq = Coerce.toVncSequence(args.first());

                if (seq.isEmpty()) {
                    return Nil;
                }
                else if (seq.size() == 1) {
                    return seq.first();
                }
                else {
                    VncVal latest = seq.first();
                    for(VncVal date : seq.rest()) {
                        if (VncBoolean.isTrue(after_Q.apply(VncList.of(date, latest)))) {
                            latest = date;
                        }
                    }

                    return latest;
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction earliest =
        new VncFunction(
                "time/earliest",
                VncFunction
                    .meta()
                    .arglists("(time/earliest coll)")
                    .doc(
                        "Returns the earliest date from a collection of dates. " +
                        "All dates must be of equal type. The coll may be empty or nil.")
                    .examples(
                        "(time/earliest [(time/local-date 2018 8 4) (time/local-date 2018 8 3)])")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);

                final VncSequence seq = Coerce.toVncSequence(args.first());

                if (seq.isEmpty()) {
                    return Nil;
                }
                else if (seq.size() == 1) {
                    return seq.first();
                }
                else {
                    VncVal latest = seq.first();
                    for(VncVal date : seq.rest()) {
                        if (VncBoolean.isTrue(before_Q.apply(VncList.of(date, latest)))) {
                            latest = date;
                        }
                    }

                    return latest;
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction within_Q =
        new VncFunction(
                "time/within?",
                VncFunction
                    .meta()
                    .arglists("(time/within? date start end)")
                    .doc(
                        "Returns true if the date is after or equal to the start and is before or equal to the end. " +
                        "All three dates must be of the same type. The start and end date may each be nil meaning " +
                        "start is -infinity and end is +infinity. (same semantics as `start <= date <= end`)")
                    .examples(
                        "(time/within? (time/local-date 2018 8 15) \n" +
                        "              (time/local-date 2018 8 10) \n" +
                        "              (time/local-date 2018 8 20))",
                        "(time/within? (time/local-date 2018 8 25) \n" +
                        "              (time/local-date 2018 8 10) \n" +
                        "              (time/local-date 2018 8 20))",
                        "(time/within? (time/local-date 2018 8 20) \n" +
                        "              (time/local-date 2018 8 10) \n" +
                        "              nil)",
                        "(time/within? (time/local-date-time \"2019-01-01T10:00:00.000\") \n" +
                        "              (time/local-date-time \"2010-01-01T10:00:00.000\") \n" +
                        "              (time/local-date-time \"2020-01-01T10:00:00.000\"))",
                        "(time/within? (time/zoned-date-time \"2010-01-01T10:00:00.000+01:00\") \n" +
                        "              (time/zoned-date-time \"2019-01-01T10:00:00.000+01:00\") \n" +
                        "              (time/zoned-date-time \"2020-01-01T10:00:00.000+01:00\"))")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 3);

                final VncVal date = args.first();
                final VncVal start = args.second();
                final VncVal end = args.third();

                if (start == Nil && end == Nil) {
                    return True;
                }
                else if (start != Nil && end != Nil) {
                    return VncBoolean.of(
                            ((VncBoolean.isTrue(not_before_Q.apply(VncList.of(date, start))))
                              && (VncBoolean.isTrue(not_after_Q.apply(VncList.of(date, end))))));
                }
                else if (start != Nil) {
                    return not_before_Q.apply(VncList.of(date, start));
                }
                else {
                    return not_after_Q.apply(VncList.of(date, end));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction zone =
        new VncFunction(
                "time/zone",
                VncFunction
                    .meta()
                    .arglists("(time/zone date)")
                    .doc("Returns the zone of the date")
                    .examples("(time/zone (time/zoned-date-time))")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);

                final Object date = Coerce.toVncJavaObject(args.first()).getDelegate();

                if (date instanceof ZonedDateTime) {
                    return new VncString(((ZonedDateTime)date).getZone().getId());
                }
                else if (date instanceof LocalDateTime) {
                    return Nil;
                }
                else if (date instanceof LocalDate) {
                    return Nil;
                }
                else {
                    throw new VncException(String.format(
                            "Function 'time/zone' does not allow %s as parameter",
                            Types.getType(args.first())));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction zone_offset =
        new VncFunction(
                "time/zone-offset",
                VncFunction
                    .meta()
                    .arglists("(time/zone-offset date)")
                    .doc("Returns the zone-offset of the date in minutes")
                    .examples("(time/zone-offset (time/zoned-date-time))")
                    .seeAlso("time/zoned-date-time")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);

                final Object date = Coerce.toVncJavaObject(args.first()).getDelegate();

                if (date instanceof ZonedDateTime) {
                    return new VncLong(((ZonedDateTime)date).getOffset().getTotalSeconds() / 60);
                }
                else if (date instanceof LocalDateTime) {
                    return Nil;
                }
                else if (date instanceof LocalDate) {
                    return Nil;
                }
                else {
                    throw new VncException(String.format(
                            "Function 'time/zone-offset' does not allow %s as parameter",
                            Types.getType(args.first())));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };



    ///////////////////////////////////////////////////////////////////////////
    // Formatter
    ///////////////////////////////////////////////////////////////////////////

    public static VncFunction formatter =
        new VncFunction(
                "time/formatter",
                VncFunction
                    .meta()
                    .arglists(
                        "(time/formatter format)",
                         "(time/formatter format locale)")
                    .doc("Creates a formatter")
                    .examples(
                        "(time/formatter \"dd-MM-yyyy\")",
                        "(time/formatter \"dd-MM-yyyy\" :en_EN)",
                        "(time/formatter \"dd-MM-yyyy\" \"en_EN\")",
                        "(time/formatter \"yyyy-MM-dd'T'HH:mm:ss.SSSz\")",
                        "(time/formatter :ISO_OFFSET_DATE_TIME)")
                    .seeAlso("time/format")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1, 2);

                // locale
                final Locale locale = args.size() == 2 ? getLocale(args.second()) : null;

                // formatter
                return new VncJavaObject(localize(getDateTimeFormatter(args.first()), locale));
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction format =
        new VncFunction(
                "time/format",
                VncFunction
                    .meta()
                    .arglists(
                        "(time/format date format)",
                        "(time/format date format locale)",
                        "(time/format date formatter)",
                        "(time/format date formatter locale)")
                    .doc(
                        "Formats a date with a format.                                  \n\n" +
                        "To format a large number of dates a pre instantiated formatter " +
                        "delivers best performance:                                     \n\n" +
                        "```                                                            \n" +
                        "(let [fmt (time/formatter \"yyyy-MM-dd'T'HH:mm:ss\")]          \n" +
                        "  (dotimes [n 100] (time/format (time/local-date-time) fmt)))  \n" +
                        "```")
                    .examples(
                        "(time/format (time/local-date) \"dd-MM-yyyy\")",
                        "(time/format (time/local-date) (time/formatter \"dd-MM-yyyy\"))",
                        "(time/format (time/local-date) :iso)",
                        "(time/format (time/local-date-time) \"yyyy-MM-dd'T'HH:mm:ss\")",
                        "(time/format (time/local-date-time) (time/formatter \"yyyy-MM-dd'T'HH:mm:ss\"))",
                        "(time/format (time/local-date-time) :iso)",
                        "(time/format (time/zoned-date-time) \"yyyy-MM-dd'T'HH:mm:ss.SSSz\")",
                        "(time/format (time/zoned-date-time) :iso)",
                        "(time/format (time/zoned-date-time) (time/formatter \"yyyy-MM-dd'T'HH:mm:ss.SSSz\"))")
                    .seeAlso("time/formatter")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 2, 3);

                if (!Types.isVncJavaObject(args.first())) {
                    throw new VncException(String.format(
                            "Function 'time/format' does not allow %s as date parameter",
                            Types.getType(date)));
                }

                // locale
                final Locale locale = args.size() == 3 ? getLocale(args.nth(2)) : null;

                // formatter
                final VncVal format = args.second();

                // format
                final Object date = ((VncJavaObject)args.first()).getDelegate();
                if (date instanceof Date) {
                    final ZonedDateTime dt = Instant.ofEpochMilli(((Date)date).getTime())
                                                    .atZone(ZoneId.systemDefault());

                    final DateTimeFormatter formatter =
                            isIsoFormat(format)
                                ? DateTimeFormatter.ISO_OFFSET_DATE_TIME
                                : localize(getDateTimeFormatter(format), locale);

                    return new VncString(dt.format(formatter));
                }
                else if (date instanceof ZonedDateTime) {
                    final DateTimeFormatter formatter =
                            isIsoFormat(format)
                                ? DateTimeFormatter.ISO_OFFSET_DATE_TIME
                                : localize(getDateTimeFormatter(format), locale);

                    return new VncString(((ZonedDateTime)date).format(formatter));
                }
                else if (date instanceof LocalDateTime) {
                    final DateTimeFormatter formatter =
                            isIsoFormat(format)
                                ? DateTimeFormatter.ISO_LOCAL_DATE_TIME
                                : localize(getDateTimeFormatter(format), locale);

                    return new VncString(((LocalDateTime)date).format(formatter));
                }
                else if (date instanceof LocalDate) {
                    final DateTimeFormatter formatter =
                            isIsoFormat(format)
                                ? DateTimeFormatter.ISO_LOCAL_DATE
                                : localize(getDateTimeFormatter(format), locale);

                    return new VncString(((LocalDate)date).format(formatter));
                }
                else {
                    throw new VncException(String.format(
                            "Function 'time/format' does not allow %s as date parameter",
                            Types.getType(args.first())));
                }
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };



    ///////////////////////////////////////////////////////////////////////////
    // Misc
    ///////////////////////////////////////////////////////////////////////////

    public static VncFunction zone_ids =
        new VncFunction(
                "time/zone-ids",
                VncFunction
                    .meta()
                    .arglists("(time/zone-ids)")
                    .doc("Returns all available zone ids with time offset")
                    .examples("(nfirst (seq (time/zone-ids)) 10)")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 0);


                final List zoneList = new ArrayList<>(ZoneId.getAvailableZoneIds());

                //Get all ZoneIds
                final Map allZoneIds = getAllZoneIds(zoneList);


                //sort map by key
                final LinkedHashMap map = new LinkedHashMap<>();
                allZoneIds
                    .entrySet()
                    .stream()
                    .sorted(Map.Entry.comparingByKey())
                    .forEachOrdered(e -> map.put(
                                            new VncString(e.getKey()),
                                            new VncString(e.getValue())));

                return new VncOrderedMap(map);
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };

    public static VncFunction to_millis =
        new VncFunction(
                "time/to-millis",
                VncFunction
                    .meta()
                    .arglists("(time/to-millis date)")
                    .doc("Converts the passed date to milliseconds since epoch")
                    .examples(
                        "(time/to-millis (time/date))",
                        "(time/to-millis (time/local-date))",
                        "(time/to-millis (time/local-date-time))",
                        "(time/to-millis (time/zoned-date-time))")
                    .build()
        ) {
            @Override
            public VncVal apply(final VncList args) {
                ArityExceptions.assertArity(this, args, 1);

                final VncVal val = args.first();
                if (Types.isVncJavaObject(val)) {
                    final Object date = ((VncJavaObject)val).getDelegate();
                    if (date instanceof Date) {
                        return new VncLong(((Date)date).getTime());
                    }
                    else if (date instanceof LocalDate) {
                        return new VncLong(((LocalDate)date)
                                                .atTime(0, 0, 0)
                                                .atZone(ZoneId.systemDefault())
                                                .toInstant()
                                                .toEpochMilli());
                    }
                    else if (date instanceof LocalDateTime) {
                        return new VncLong(((LocalDateTime)date)
                                                .atZone(ZoneId.systemDefault())
                                                .toInstant()
                                                .toEpochMilli());
                    }
                    else if (date instanceof ZonedDateTime) {
                        return new VncLong(((ZonedDateTime)date).toInstant().toEpochMilli());
                    }
                }

                throw new VncException(String.format(
                            "Function 'time/to-millis' does not allow %s as parameter",
                            Types.getType(val)));
            }

            private static final long serialVersionUID = -1848883965231344442L;
        };



    ///////////////////////////////////////////////////////////////////////////
    // Utils
    ///////////////////////////////////////////////////////////////////////////

    private static Map getAllZoneIds(final List zoneList) {
        final Map result = new HashMap<>();
        final LocalDateTime dt = LocalDateTime.now();

        for (String zoneId : zoneList) {
            final ZoneId zone = ZoneId.of(zoneId);
            final ZonedDateTime zdt = dt.atZone(zone);
            final ZoneOffset zos = zdt.getOffset();

            //replace Z to +00:00
            final String offset = zos.getId().replaceAll("Z", "+00:00");

            result.put(zone.toString(), offset);
        }

        return result;
    }

    private static Locale getLocale(final VncVal locale) {
        if (locale == Nil) {
            return Locale.getDefault();
        }
        if (Types.isVncKeyword(locale)) {
            final String[] e = ((VncKeyword)locale).getValue().split("_");
            switch(e.length) {
                case 0: return Locale.getDefault();
                case 1: return new Locale(e[0]);
                case 2: return new Locale(e[0], e[1]);
                default: return new Locale(e[0], e[1], e[2]);
            }
        }
        if (Types.isVncString(locale)) {
            final String[] e = ((VncString)locale).getValue().split("_");
            switch(e.length) {
                case 0: return Locale.getDefault();
                case 1: return new Locale(e[0]);
                case 2: return new Locale(e[0], e[1]);
                default: return new Locale(e[0], e[1], e[2]);
            }
        }
        else if (Types.isVncJavaObject(locale, Locale.class)) {
            return (Locale)((VncJavaObject)locale).getDelegate();
        }

        throw new VncException(String.format(
                "The type %s does not define a Locale.",
                Types.getType(locale)));
    }

    private static DateTimeFormatter getDateTimeFormatter(final VncVal fmt) {
        if (Types.isVncKeyword(fmt)) {
            return getPredefinedDateTimeFormatter((VncKeyword)fmt);
        }
        else if (Types.isVncString(fmt)) {
            return DateTimeFormatter.ofPattern(((VncString)fmt).getValue());
        }
        else if (Types.isVncJavaObject(fmt, DateTimeFormatter.class)) {
            return (DateTimeFormatter)((VncJavaObject)fmt).getDelegate();
        }
        else {
            throw new VncException(String.format(
                "Function 'time/format' does not allow %s as format parameter.",
                Types.getType(fmt)));
        }
    }

    private static DateTimeFormatter getPredefinedDateTimeFormatter(final VncKeyword fmt) {
        final String fmtName = fmt.getValue();
        try {
            return (DateTimeFormatter)ReflectionAccessor.getStaticField(DateTimeFormatter.class, fmtName).getValue();
        }
        catch(Exception ex) {
            throw new VncException(String.format(
                    "'%s' is not a predefined DateTimeFormatter.",
                    fmtName));
        }
    }

    private static DateTimeFormatter localize(
            final DateTimeFormatter formatter,
            final Locale locale
    ) {
        return locale == null ? formatter : formatter.withLocale(locale);
    }

    private static DateTimeFormatter zone(
            final DateTimeFormatter formatter,
            final ZoneId zoneId
    ) {
        return zoneId == null ? formatter : formatter.withZone(zoneId);
    }

    private static boolean isIsoFormat(final VncVal format) {
        return Types.isVncKeyword(format)
                ? "iso".equalsIgnoreCase(((VncKeyword)format).getValue())
                : false;
    }

    private static ZoneId orDefaultZone(final ZoneId zoneId) {
        return zoneId == null ? ZoneId.systemDefault() : zoneId;
    }

    private static ChronoUnit toChronoUnit(final String unit) {
        switch(unit) {
            case "years":   return ChronoUnit.YEARS;
            case "year":    return ChronoUnit.YEARS;

            case "months":  return ChronoUnit.MONTHS;
            case "month":   return ChronoUnit.MONTHS;

            case "weeks":   return ChronoUnit.WEEKS;
            case "week":    return ChronoUnit.WEEKS;

            case "days":    return ChronoUnit.DAYS;
            case "day":     return ChronoUnit.DAYS;

            case "hours":   return ChronoUnit.HOURS;
            case "hour":    return ChronoUnit.HOURS;

            case "minutes": return ChronoUnit.MINUTES;
            case "minute":  return ChronoUnit.MINUTES;

            case "seconds": return ChronoUnit.SECONDS;
            case "second":  return ChronoUnit.SECONDS;

            case "millis":  return ChronoUnit.MILLIS;
            case "milli":   return ChronoUnit.MILLIS;

            default:        return null;
        }
    }



    ///////////////////////////////////////////////////////////////////////////
    // types_ns is namespace of type functions
    ///////////////////////////////////////////////////////////////////////////

    public static final Map ns =
            new SymbolMapBuilder()
                    .add(date)
                    .add(date_Q)
                    .add(local_date)
                    .add(local_date_Q)
                    .add(local_date_parse)
                    .add(local_date_time)
                    .add(local_date_time_Q)
                    .add(local_date_time_parse)
                    .add(zoned_date_time)
                    .add(zoned_date_time_Q)
                    .add(zoned_date_time_parse)
                    .add(unix_timestamp)
                    .add(unix_timestamp_to_local_date_time)
                    .add(with_time)
                    .add(zone_ids)
                    .add(to_millis)
                    .add(formatter)
                    .add(format)
                    .add(plus)
                    .add(minus)
                    .add(period)
                    .add(year)
                    .add(month)
                    .add(day_of_week)
                    .add(day_of_month)
                    .add(day_of_year)
                    .add(first_day_of_month)
                    .add(last_day_of_month)
                    .add(hour)
                    .add(minute)
                    .add(second)
                    .add(milli)
                    .add(leap_yearQ)
                    .add(length_of_year)
                    .add(length_of_month)
                    .add(zone)
                    .add(zone_offset)
                    .add(after_Q)
                    .add(not_after_Q)
                    .add(before_Q)
                    .add(not_before_Q)
                    .add(earliest)
                    .add(latest)
                    .add(within_Q)
                    .add(within_Q)

                    .toMap();
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy