1/** @file
2*
3*  Copyright (c) 2016, Hisilicon Limited. All rights reserved.
4*  Copyright (c) 2016, Linaro Limited. All rights reserved.
5*
6*  This program and the accompanying materials
7*  are licensed and made available under the terms and conditions of the BSD License
8*  which accompanies this distribution.  The full text of the license may be found at
9*  http://opensource.org/licenses/bsd-license.php
10*
11*  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
12*  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
13*
14**/
15
16#include <Uefi/UefiBaseType.h>
17#include <Uefi/UefiSpec.h>
18#include <Library/DebugLib.h>
19#include <Library/EfiTimeBaseLib.h>
20
21// Define EPOCH (1970-JANUARY-01) in the Julian Date representation
22#define EPOCH_JULIAN_DATE                               2440588
23
24BOOLEAN
25EFIAPI
26IsLeapYear (
27  IN EFI_TIME *Time
28  )
29{
30  if (Time->Year % 4 == 0) {
31    if (Time->Year % 100 == 0) {
32      if (Time->Year % 400 == 0) {
33        return TRUE;
34      } else {
35        return FALSE;
36      }
37    } else {
38      return TRUE;
39    }
40  } else {
41    return FALSE;
42  }
43}
44
45BOOLEAN
46EFIAPI
47IsDayValid (
48  IN EFI_TIME *Time
49  )
50{
51  INTN DayOfMonth[12] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
52
53  if (Time->Day < 1 ||
54      Time->Day > DayOfMonth[Time->Month - 1] ||
55      (Time->Month == 2 && (!IsLeapYear (Time) && Time->Day > 28))
56     ) {
57    return FALSE;
58  }
59
60  return TRUE;
61}
62
63BOOLEAN
64EFIAPI
65IsTimeValid(
66  IN EFI_TIME *Time
67  )
68{
69  // Check the input parameters are within the range specified by UEFI
70  if ((Time->Year   < 2000) ||
71     (Time->Year   > 2099) ||
72     (Time->Month  < 1   ) ||
73     (Time->Month  > 12  ) ||
74     (!IsDayValid (Time)    ) ||
75     (Time->Hour   > 23  ) ||
76     (Time->Minute > 59  ) ||
77     (Time->Second > 59  ) ||
78     (Time->Nanosecond > 999999999) ||
79     (!((Time->TimeZone == EFI_UNSPECIFIED_TIMEZONE) || ((Time->TimeZone >= -1440) && (Time->TimeZone <= 1440)))) ||
80     (Time->Daylight & (~(EFI_TIME_ADJUST_DAYLIGHT | EFI_TIME_IN_DAYLIGHT)))
81  ) {
82    return FALSE;
83  }
84
85  return TRUE;
86}
87
88/**
89  Converts Epoch seconds (elapsed since 1970 JANUARY 01, 00:00:00 UTC) to EFI_TIME
90 **/
91VOID
92EpochToEfiTime (
93  IN  UINTN     EpochSeconds,
94  OUT EFI_TIME  *Time
95  )
96{
97  UINTN         a;
98  UINTN         b;
99  UINTN         c;
100  UINTN         d;
101  UINTN         g;
102  UINTN         j;
103  UINTN         m;
104  UINTN         y;
105  UINTN         da;
106  UINTN         db;
107  UINTN         dc;
108  UINTN         dg;
109  UINTN         hh;
110  UINTN         mm;
111  UINTN         ss;
112  UINTN         J;
113
114  J  = (EpochSeconds / 86400) + 2440588;
115  j  = J + 32044;
116  g  = j / 146097;
117  dg = j % 146097;
118  c  = (((dg / 36524) + 1) * 3) / 4;
119  dc = dg - (c * 36524);
120  b  = dc / 1461;
121  db = dc % 1461;
122  a  = (((db / 365) + 1) * 3) / 4;
123  da = db - (a * 365);
124  y  = (g * 400) + (c * 100) + (b * 4) + a;
125  m  = (((da * 5) + 308) / 153) - 2;
126  d  = da - (((m + 4) * 153) / 5) + 122;
127
128  Time->Year  = y - 4800 + ((m + 2) / 12);
129  Time->Month = ((m + 2) % 12) + 1;
130  Time->Day   = d + 1;
131
132  ss = EpochSeconds % 60;
133  a  = (EpochSeconds - ss) / 60;
134  mm = a % 60;
135  b = (a - mm) / 60;
136  hh = b % 24;
137
138  Time->Hour        = hh;
139  Time->Minute      = mm;
140  Time->Second      = ss;
141  Time->Nanosecond  = 0;
142
143}
144
145/**
146  Converts EFI_TIME to Epoch seconds (elapsed since 1970 JANUARY 01, 00:00:00 UTC)
147 **/
148UINTN
149EfiTimeToEpoch (
150  IN  EFI_TIME  *Time
151  )
152{
153  UINTN a;
154  UINTN y;
155  UINTN m;
156  UINTN JulianDate;  // Absolute Julian Date representation of the supplied Time
157  UINTN EpochDays;   // Number of days elapsed since EPOCH_JULIAN_DAY
158  UINTN EpochSeconds;
159
160  a = (14 - Time->Month) / 12 ;
161  y = Time->Year + 4800 - a;
162  m = Time->Month + (12*a) - 3;
163
164  JulianDate = Time->Day + ((153*m + 2)/5) + (365*y) + (y/4) - (y/100) + (y/400) - 32045;
165
166  ASSERT (JulianDate >= EPOCH_JULIAN_DATE);
167  EpochDays = JulianDate - EPOCH_JULIAN_DATE;
168
169  EpochSeconds = (EpochDays * SEC_PER_DAY) + ((UINTN)Time->Hour * SEC_PER_HOUR) + (Time->Minute * SEC_PER_MIN) + Time->Second;
170
171  return EpochSeconds;
172}
173