001// Licensed under the MIT license. See LICENSE file in the project root for full license information.
002
003package de.bytefish.pgbulkinsert.pgsql.utils;
004
005import java.time.LocalDate;
006import java.time.LocalDateTime;
007import java.time.OffsetDateTime;
008import java.time.ZoneOffset;
009import java.time.temporal.ChronoUnit;
010import java.util.concurrent.TimeUnit;
011
012public class TimeStampUtils {
013
014    private TimeStampUtils() {
015
016    }
017
018    private static final LocalDateTime JavaEpoch = LocalDateTime.of(1970, 1, 1, 0, 0, 0);
019
020    private static final LocalDateTime PostgresEpoch = LocalDateTime.of(2000, 1, 1, 0, 0, 0);
021
022    private static final long DaysBetweenJavaAndPostgresEpochs = ChronoUnit.DAYS.between(JavaEpoch, PostgresEpoch);
023
024
025    public static long convertToPostgresTimeStamp(LocalDateTime localDateTime) {
026
027        if(localDateTime == null) {
028            throw new IllegalArgumentException("localDateTime");
029        }
030        // Extract the Time of the Day in Nanoseconds:
031        long timeInNanoseconds = localDateTime
032                .toLocalTime()
033                .toNanoOfDay();
034
035        // Convert the Nanoseconds to Microseconds:
036        long timeInMicroseconds = timeInNanoseconds / 1000;
037
038        // Now Calculate the Postgres Timestamp:
039        if(localDateTime.isBefore(PostgresEpoch)) {
040            long dateInMicroseconds = (localDateTime.toLocalDate().toEpochDay() - DaysBetweenJavaAndPostgresEpochs) * 86400000000L;
041
042            return dateInMicroseconds + timeInMicroseconds;
043        } else {
044            long dateInMicroseconds = (DaysBetweenJavaAndPostgresEpochs - localDateTime.toLocalDate().toEpochDay()) * 86400000000L;
045
046            return -(dateInMicroseconds - timeInMicroseconds);
047        }
048    }
049
050    public static int toPgDays(LocalDate date)
051    {
052        // Adjust TimeZone Offset:
053        LocalDateTime dateTime = date.atStartOfDay();
054        // pg time 0 is 2000-01-01 00:00:00:
055        long secs = toPgSecs(getSecondsSinceJavaEpoch(dateTime));
056        // Needs Days:
057        return (int) TimeUnit.SECONDS.toDays(secs);
058    }
059
060    public static Long toPgSecs(LocalDateTime dateTime) {
061        // pg time 0 is 2000-01-01 00:00:00:
062        long secs = toPgSecs(getSecondsSinceJavaEpoch(dateTime));
063        // Needs Microseconds:
064        return TimeUnit.SECONDS.toMicros(secs);
065    }
066
067    private static long getSecondsSinceJavaEpoch(LocalDateTime localDateTime) {
068        // Adjust TimeZone Offset:
069        OffsetDateTime zdt = localDateTime.atOffset(ZoneOffset.UTC);
070        // Get the Epoch Milliseconds:
071        long milliseconds = zdt.toInstant().toEpochMilli();
072        // Turn into Seconds:
073        return TimeUnit.MILLISECONDS.toSeconds(milliseconds);
074    }
075
076    /**
077     * Converts the given java seconds to postgresql seconds. The conversion is valid for any year 100 BC onwards.
078     *
079     * from /org/postgresql/jdbc2/TimestampUtils.java
080     *
081     * @param seconds Postgresql seconds.
082     * @return Java seconds.
083     */
084    @SuppressWarnings("checkstyle:magicnumber")
085    private static long toPgSecs(final long seconds) {
086        long secs = seconds;
087        // java epoc to postgres epoc
088        secs -= 946684800L;
089
090        // Julian/Greagorian calendar cutoff point
091        if (secs < -13165977600L) { // October 15, 1582 -> October 4, 1582
092            secs -= 86400 * 10;
093            if (secs < -15773356800L) { // 1500-03-01 -> 1500-02-28
094                int years = (int) ((secs + 15773356800L) / -3155823050L);
095                years++;
096                years -= years / 4;
097                secs += years * 86400;
098            }
099        }
100
101        return secs;
102    }
103}