• Java日期格式化:避免YYYY引发的时间异常


    在编程中,日期格式化是一个常见的任务。使用不同的格式化选项可能会导致一些意外的结果。最近遇到一个问题,就是使用YYYY格式化选项会导致时间异常。

    public static void main(String[] args) throws ParseException {
    
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        SimpleDateFormat sdf2 = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");
       
        String time = "2023-12-31 23:59:59";
        System.out.printf("yyyy-MM-dd HH:mm:ss: %s\n",sdf.format(sdf.parse(time)));
        System.out.printf("YYYY-MM-dd HH:mm:ss: %s\n",sdf2.format(sdf2.parse(time)));
    
        System.out.println("======================================");
        String time2 = "2023-11-31 23:59:59"; //11月没有31号
        System.out.printf("yyyy-MM-dd HH:mm:ss: %s\n",sdf.format(sdf.parse(time2)));
        System.out.printf("YYYY-MM-dd HH:mm:ss: %s\n",sdf2.format(sdf2.parse(time2)));
    
        System.out.println("======================================");
        String time3 = "2023-10-31 23:59:59";
        System.out.printf("yyyy-MM-dd HH:mm:ss: %s\n",sdf.format(sdf.parse(time3)));
        System.out.printf("YYYY-MM-dd HH:mm:ss: %s\n",sdf2.format(sdf2.parse(time3)));
    }
    
    //输出结果
    yyyy-MM-dd HH:mm:ss: 2023-12-31 23:59:59
    YYYY-MM-dd HH:mm:ss: 2023-01-01 23:59:59
    ======================================
    yyyy-MM-dd HH:mm:ss: 2023-12-01 23:59:59
    YYYY-MM-dd HH:mm:ss: 2023-01-01 23:59:59
    ======================================
    yyyy-MM-dd HH:mm:ss: 2023-10-31 23:59:59
    YYYY-MM-dd HH:mm:ss: 2023-01-01 23:59:59
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    网上查阅说大写的YYYY表示一个基于周的年份,它是根据周计算的年份,而不是基于日历的年份。通常情况下,两者的结果是相同的,但在跨年的第一周或最后一周可能会有差异。但这里发现无论怎么调整时间,YYYY-MM-dd HH:mm:ss格式化后一直是2023-01-01 23:59:59,不仅仅是在跨年的时候出现问题,具体原因尚不清楚

    如何避免
    定义通用的格式类,所有使用日期格式的地方都引用这个类,这个类中就定义好yyyy-MM-dd格式即可,这样就不会出现有人手误给大家埋雷了。

    public class LocalDateUtils {
    
        /**
         * 显示年月日时分秒,例如 2023-10-31 09:51:53.
         */
        public static final String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
    
        /**
         * 仅显示年月日,例如 2023-10-31.
         */
        public static final String DATE_PATTERN = "yyyy-MM-dd";
    
        private static final String YEAR = "year";
        private static final String MONTH = "month";
        private static final String WEEK = "week";
        private static final String DAY = "day";
    
        /**
         * 将日期转换为字符串,格式为:yyyy-MM-dd HH:mm:ss
         */
        public static String getLocalDateTimeStr(LocalDateTime localDateTime) {
            return format(localDateTime, DATETIME_PATTERN);
        }
    
        /**
         * 将日期转换为字符串,格式为:yyyy-MM-dd
         */
        public static String getLocalDateStr(LocalDateTime localDateTime) {
            return format(localDateTime, DATE_PATTERN);
        }
    
    
        /**
         * 将字符串转换为日期,格式为:yyyy-MM-dd HH:mm:ss
         */
        public static LocalDateTime parseLocalDateTime(String localDateTimeStr) {
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN);
            return LocalDateTime.parse(localDateTimeStr, dateTimeFormatter);
        }
    
        /**
         * 将字符串转换为日期,格式为:yyyy-MM-dd
         */
        public static LocalDate parseLocalDate(String localDateStr) {
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATE_PATTERN);
            return LocalDate.parse(localDateStr, dateTimeFormatter);
        }
    
    
        /**
         * 将字符串转日期成Long类型的时间戳
         */
        public static Long convertLocalDateTimeToLong(String time) {
            LocalDateTime parse = parse(time, DATETIME_PATTERN);
            return LocalDateTime.from(parse).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
        }
    
        /**
         * 将字符串转日期成Long类型的时间戳
         */
        public static Long convertLocalDateToLong(String time) {
            LocalDateTime parse = parse(time, DATE_PATTERN);
            return LocalDateTime.from(parse).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
        }
    
        public static LocalDateTime parse(String time, String pattern) {
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
            return LocalDateTime.parse(time, dateTimeFormatter);
        }
    
    
        /**
         * 将Long类型的时间戳转换成String 类型的时间格式
         */
        public static String getLocalDateTimeStr(Long time) {
            return format(LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneId.systemDefault()), DATETIME_PATTERN);
        }
    
        /**
         * 将Long类型的时间戳转换成String 类型的时间格式,时间格式为:yyyy-MM-dd
         */
        public static String getLocalDateStr(Long time) {
            return format(LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneId.systemDefault()), DATE_PATTERN);
        }
    
        /**
         * 获取日期时间字符串
         */
        public static String format(TemporalAccessor temporal, String pattern) {
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
            return dateTimeFormatter.format(temporal);
        }
    
    
        /**
         * 取本月第一天
         */
        public static LocalDate firstDayOfThisMonth() {
            LocalDate today = LocalDate.now();
            return today.with(TemporalAdjusters.firstDayOfMonth());
        }
    
        /**
         * 取本月第N天
         */
        public static LocalDate dayOfThisMonth(int n) {
            LocalDate today = LocalDate.now();
            return today.withDayOfMonth(n);
        }
    
        /**
         * 取本月最后一天
         */
        public static LocalDate lastDayOfThisMonth() {
            LocalDate today = LocalDate.now();
            return today.with(TemporalAdjusters.lastDayOfMonth());
        }
    
    
        /**
         * 获取指定日期时间加上指定数量日期时间单位之后的日期时间.
         */
        public static LocalDateTime plus(LocalDateTime localDateTime, int num, ChronoUnit chronoUnit) {
            return localDateTime.plus(num, chronoUnit);
        }
    
        /**
         * 获取指定日期时间减去指定数量日期时间单位之后的日期时间.
         */
        public static LocalDateTime minus(LocalDateTime localDateTime, int num, ChronoUnit chronoUnit) {
            return localDateTime.minus(num, chronoUnit);
        }
    
        /**
         * 根据ChronoUnit计算两个日期时间之间相隔日期时间
         */
        public static long getChronoUnitBetween(LocalDateTime start, LocalDateTime end, ChronoUnit chronoUnit) {
            return Math.abs(start.until(end, chronoUnit));
        }
    
        /**
         * 根据ChronoUnit计算两个日期之间相隔年数或月数或天数
         */
        public static long getChronoUnitBetween(LocalDate start, LocalDate end, ChronoUnit chronoUnit) {
            return Math.abs(start.until(end, chronoUnit));
        }
    
    
        /**
         * 切割日期。按照周期切割成小段日期段。例如: 
    * * @param startDate 开始日期(yyyy-MM-dd) * @param endDate 结束日期(yyyy-MM-dd) * @param period 周期(天,周,月,年) * @return 切割之后的日期集合 *
  • startDate="2023-10-27",endDate="2023-10-31",period="day"
  • *
  • 结果为:[2023-10-27, 2023-10-28, 2023-10-29, 2023-10-03,2023-10-31]

  • *
  • startDate="2023-10-27",endDate="2023-10-31",period="week"
  • *
  • 结果为:[2023-10-27,2023-10-31]

  • *
  • startDate="2023-10-27",endDate="2023-10-31",period="month"
  • *
  • 结果为:[2023-10-27,2023-10-31]

  • *
  • startDate="2023-10-27",endDate="2023-10-31",period="year"
  • *
  • 结果为:[2023-10-27,2023-10-31]

  • */
    public static List<String> listDateStrs(String startDate, String endDate, String period) { List<String> result = new ArrayList<>(); DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATE_PATTERN); LocalDate end = LocalDate.parse(endDate, dateTimeFormatter); LocalDate start = LocalDate.parse(startDate, dateTimeFormatter); LocalDate tmp = start; switch (period) { case DAY: while (start.isBefore(end) || start.isEqual(end)) { result.add(start.toString()); start = start.plusDays(1); } break; case WEEK: while (tmp.isBefore(end) || tmp.isEqual(end)) { if (tmp.plusDays(6).isAfter(end)) { result.add(tmp.toString() + "," + end); } else { result.add(tmp.toString() + "," + tmp.plusDays(6)); } tmp = tmp.plusDays(7); } break; case MONTH: while (tmp.isBefore(end) || tmp.isEqual(end)) { LocalDate lastDayOfMonth = tmp.with(TemporalAdjusters.lastDayOfMonth()); if (lastDayOfMonth.isAfter(end)) { result.add(tmp.toString() + "," + end); } else { result.add(tmp.toString() + "," + lastDayOfMonth); } tmp = lastDayOfMonth.plusDays(1); } break; case YEAR: while (tmp.isBefore(end) || tmp.isEqual(end)) { LocalDate lastDayOfYear = tmp.with(TemporalAdjusters.lastDayOfYear()); if (lastDayOfYear.isAfter(end)) { result.add(tmp.toString() + "," + end); } else { result.add(tmp.toString() + "," + lastDayOfYear); } tmp = lastDayOfYear.plusDays(1); } break; default: break; } return result; } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
  • 相关阅读:
    NIO学习笔记
    进程管理PV信号量购书店题详解
    .NET周刊【3月第1期 2024-03-03】
    浏览器解析URL全部流程
    ESP32-SPI接口bl0942驱动
    MySQL45讲——学习极客时间MySQL实战45讲笔记—— 05 | 深入浅出索引(下)
    Day55 web框架 入门 Django
    A-Level商务模型介绍:波士顿矩阵
    MybatisPlus(5)
    ThreadLocal
  • 原文地址:https://blog.csdn.net/weixin_45817985/article/details/134433867